<script lang="ts" setup>
|
import type { WxCpMemberApi } from '#/api/wxcp/member';
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
import { Alert, Radio, Select, Spin } from 'ant-design-vue';
|
|
import {
|
getWxCpDepartmentList,
|
getWxCpMemberList,
|
} from '#/api/wxcp/member';
|
|
import { PUSH_SCOPE_OPTIONS, PUSH_SCOPE } from '../data';
|
|
defineOptions({ name: 'WxCpPushScopePicker' });
|
|
interface Props {
|
/** 推送范围:0全员 1指定部门 2指定成员 */
|
scope?: number;
|
/** 推送目标,逗号分隔;范围是全员时无意义 */
|
target?: string;
|
disabled?: boolean;
|
}
|
|
const props = withDefaults(defineProps<Props>(), {
|
scope: PUSH_SCOPE.ALL,
|
target: '',
|
disabled: false,
|
});
|
|
const emit = defineEmits<{
|
'update:scope': [value: number];
|
'update:target': [value: string];
|
}>();
|
|
/** 通讯录数据:只在首次需要时拉取,避免每次打开弹窗都打企业微信接口 */
|
const departments = ref<WxCpMemberApi.Department[]>([]);
|
const members = ref<WxCpMemberApi.Member[]>([]);
|
const loading = ref(false);
|
const loaded = ref(false);
|
const loadError = ref('');
|
|
async function ensureContacts() {
|
if (loaded.value || loading.value) {
|
return;
|
}
|
loading.value = true;
|
loadError.value = '';
|
try {
|
const [deptList, memberList] = await Promise.all([
|
getWxCpDepartmentList(),
|
getWxCpMemberList({ fetchChild: true }),
|
]);
|
departments.value = deptList ?? [];
|
members.value = memberList ?? [];
|
loaded.value = true;
|
} catch (error: unknown) {
|
loadError.value =
|
(error as Error)?.message || '加载企业微信通讯录失败,请稍后重试';
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
// 选到需要通讯录的范围时才加载
|
watch(
|
() => props.scope,
|
(scope) => {
|
if (scope === PUSH_SCOPE.PARTY || scope === PUSH_SCOPE.USER) {
|
ensureContacts();
|
}
|
},
|
);
|
|
onMounted(() => {
|
if (props.scope === PUSH_SCOPE.PARTY || props.scope === PUSH_SCOPE.USER) {
|
ensureContacts();
|
}
|
});
|
|
const deptOptions = computed(() =>
|
departments.value.map((d) => ({ label: d.name || `部门${d.id}`, value: d.id })),
|
);
|
|
const memberOptions = computed(() =>
|
members.value.map((m) => ({
|
label: m.name ? `${m.name}(${m.userId})` : m.userId,
|
value: m.userId,
|
})),
|
);
|
|
/** 目标字符串 <-> 数组 */
|
const selected = computed<string[] | number[]>({
|
get() {
|
const raw = (props.target || '').trim();
|
if (!raw) {
|
return [];
|
}
|
const parts = raw
|
.split(/[,,]/)
|
.map((s) => s.trim())
|
.filter(Boolean);
|
// 部门用数字,成员用字符串(成员账号是 LiuYi 这种)
|
return props.scope === PUSH_SCOPE.PARTY
|
? parts.map((p) => Number(p)).filter((n) => Number.isFinite(n))
|
: parts;
|
},
|
set(value) {
|
emit('update:target', (value as (number | string)[]).join(','));
|
},
|
});
|
|
function handleScopeChange(e: unknown) {
|
const value = Number((e as { target?: { value?: number } })?.target?.value);
|
emit('update:scope', Number.isFinite(value) ? value : PUSH_SCOPE.ALL);
|
// 切换范围时清掉旧目标,避免把成员账号当成部门ID提交
|
emit('update:target', '');
|
}
|
</script>
|
|
<template>
|
<div>
|
<Radio.Group
|
:value="scope"
|
:disabled="disabled"
|
button-style="solid"
|
@change="handleScopeChange"
|
>
|
<Radio.Button
|
v-for="opt in PUSH_SCOPE_OPTIONS"
|
:key="opt.value"
|
:value="opt.value"
|
>
|
{{ opt.label }}
|
</Radio.Button>
|
</Radio.Group>
|
|
<div v-if="scope !== PUSH_SCOPE.ALL" class="mt-3">
|
<Spin :spinning="loading">
|
<Alert
|
v-if="loadError"
|
:message="loadError"
|
type="warning"
|
show-icon
|
class="mb-2"
|
/>
|
<Select
|
v-model:value="selected"
|
:options="scope === PUSH_SCOPE.PARTY ? deptOptions : memberOptions"
|
:disabled="disabled || !!loadError"
|
:mode="scope === PUSH_SCOPE.PARTY ? 'multiple' : 'multiple'"
|
:placeholder="
|
scope === PUSH_SCOPE.PARTY
|
? '请选择部门,可多选'
|
: '请选择成员,可多选'
|
"
|
option-filter-prop="label"
|
show-search
|
allow-clear
|
class="w-full"
|
/>
|
</Spin>
|
<div class="mt-2 text-xs text-gray-400">
|
{{
|
scope === PUSH_SCOPE.PARTY
|
? '推送给所选部门下的全部成员'
|
: '推送给所选成员,账号来自企业微信通讯录'
|
}}
|
</div>
|
</div>
|
</div>
|
</template>
|