更新
This commit is contained in:
Generated
+475
-423
File diff suppressed because it is too large
Load Diff
@@ -300,6 +300,10 @@ export interface WecomPromotionBatchUpdatePoolsParams {
|
||||
fallback_url?: string
|
||||
status?: 0 | 1
|
||||
automation_config?: Record<string, unknown>
|
||||
member_status?: {
|
||||
member_admin_ids: number[]
|
||||
status: 0 | 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +313,8 @@ export interface WecomPromotionBatchUpdatePoolResult {
|
||||
success: boolean
|
||||
sync_error?: string
|
||||
sync_queued?: boolean
|
||||
member_matched?: number
|
||||
member_updated?: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
@@ -318,6 +324,8 @@ export interface WecomPromotionBatchUpdatePoolsResult {
|
||||
failed: number
|
||||
sync_error_count: number
|
||||
sync_queued_count: number
|
||||
member_matched: number
|
||||
member_updated: number
|
||||
results: WecomPromotionBatchUpdatePoolResult[]
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
<div class="panel-heading panel-heading--table">
|
||||
<div>
|
||||
<h2>明细数据列表</h2>
|
||||
<p>展开部门可查看人员明细;加粉=总进线=区间新增加粉(按员工+客户去重,包含区间内添加后已删客户,剔除继承客户、扫一扫/搜手机号/名片分享添加及区间前已加过的重加)<template v-if="canViewDeletedFans">,(-N)表示加粉总数中已删除</template>;挂号=已支付且实收低于 10 元的订单,预约=有效预约记录;开口率=开口/加粉,挂号率=挂号/加粉,面诊率=面诊/挂号(看挂号后流失),预约率=面诊/预约(看预约后未面诊),面诊接诊率=接诊诊单/面诊,接诊率=接诊诊单/总进线</p>
|
||||
<p>展开部门可查看人员明细;加粉=总进线=区间新增“员工+客户”组合(同一员工的同一客户只计一次,同一客户进入不同员工分别计数;包含区间内添加后已删客户,剔除继承客户、扫一扫/搜手机号/名片分享添加及区间前已存在的相同组合)<template v-if="canViewDeletedFans">,(-N)表示加粉组合中已删除</template>;挂号=已支付且实收低于 10 元的订单,预约=有效预约记录;开口率=开口/加粉,挂号率=挂号/加粉,面诊率=面诊/挂号(看挂号后流失),预约率=面诊/预约(看预约后未面诊),面诊接诊率=接诊诊单/面诊,接诊率=接诊诊单/总进线</p>
|
||||
</div>
|
||||
<span>{{ dashboard.rows.length }} 个顶层节点</span>
|
||||
</div>
|
||||
@@ -193,7 +193,7 @@
|
||||
v-if="hasFans(row.add_fans_count)"
|
||||
type="button"
|
||||
class="fan-count-value fan-detail-trigger fan-detail-trigger--table"
|
||||
title="查看该行加粉客户明细"
|
||||
title="查看该行加粉组合明细"
|
||||
@click.stop="openFansDetail(row)"
|
||||
>
|
||||
{{ formatNumber(row.add_fans_count) }}
|
||||
@@ -349,7 +349,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty :image-size="68" description="当前条件下暂无加粉客户明细" />
|
||||
<el-empty :image-size="68" description="当前条件下暂无加粉组合明细" />
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
@@ -448,7 +448,7 @@ const timeOptions = [
|
||||
{ label: '自定义', value: 'custom' }
|
||||
]
|
||||
const metricCards: Array<{ key: string; label: string; type: MetricType; hint: string }> = [
|
||||
{ key: 'add_fans_count', label: '加粉数', type: 'count', hint: '区间新增加粉(含已删除)' },
|
||||
{ key: 'add_fans_count', label: '加粉数', type: 'count', hint: '区间新增员工+客户组合(不同员工分别计数,含已删除)' },
|
||||
{ key: 'total_open_count', label: '开口数', type: 'count', hint: '来源于个人业绩录入' },
|
||||
{ key: 'interview_count', label: '面诊', type: 'count', hint: '已完成预约' },
|
||||
{ key: 'completed_order_count', label: '接诊诊单', type: 'count', hint: '业务订单,按创建人归属并过滤无效单' },
|
||||
@@ -518,7 +518,7 @@ const rankingKind = computed(() => dashboard.meta.ranking_kind || (
|
||||
))
|
||||
const showRankings = computed(() => rankingKind.value !== 'hidden')
|
||||
const rankingSubject = computed(() => rankingKind.value === 'member' ? '组内成员' : '小组')
|
||||
const fansDetailTitle = computed(() => `${fansDetailEntity.value.name} · 加粉客户明细`)
|
||||
const fansDetailTitle = computed(() => `${fansDetailEntity.value.name} · 加粉组合明细`)
|
||||
const detailRangeText = computed(() => {
|
||||
const startDate = dashboard.meta.start_date || query.start_date || ''
|
||||
const endDate = dashboard.meta.end_date || query.end_date || ''
|
||||
@@ -637,7 +637,7 @@ async function loadFansDetail() {
|
||||
if (requestId !== latestFansDetailRequestId) return
|
||||
fansDetailRows.value = []
|
||||
fansDetailPager.total = 0
|
||||
ElMessage.error(error?.message || '加粉客户明细加载失败')
|
||||
ElMessage.error(error?.message || '加粉组合明细加载失败')
|
||||
} finally {
|
||||
if (requestId === latestFansDetailRequestId) fansDetailLoading.value = false
|
||||
}
|
||||
|
||||
@@ -74,6 +74,16 @@
|
||||
|
||||
<div v-if="overview.pools.length" class="pool-layout">
|
||||
<aside class="pool-sidebar">
|
||||
<div class="pool-select-all">
|
||||
<el-checkbox
|
||||
:model-value="allManageablePoolsSelected"
|
||||
:indeterminate="someManageablePoolsSelected"
|
||||
:disabled="!manageablePoolIds.length"
|
||||
aria-label="全选可管理的分流方案"
|
||||
@change="toggleAllPoolSelection"
|
||||
>全选</el-checkbox>
|
||||
<span>可选 {{ manageablePoolIds.length }} 个</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="pool in overview.pools"
|
||||
:key="pool.id"
|
||||
@@ -488,6 +498,62 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="batch-config-section">
|
||||
<h3>员工上下线</h3>
|
||||
<p class="batch-section-tip">所选员工会在其已加入的所选方案中统一上线或下线;不属于某个方案的员工不会被加入该方案。为保证方案级原子提交,员工上下线需单独批量保存。</p>
|
||||
<div class="batch-field batch-member-status-field" :class="{ 'is-disabled': !batchConfigApply.member_status }">
|
||||
<el-checkbox v-model="batchConfigApply.member_status" :disabled="savingBatchConfig">批量修改员工上线状态</el-checkbox>
|
||||
<div class="batch-member-status-grid">
|
||||
<el-form-item label="目标状态">
|
||||
<el-radio-group v-model="batchConfigForm.member_status" :disabled="!batchConfigApply.member_status || savingBatchConfig">
|
||||
<el-radio-button :value="1">批量上线</el-radio-button>
|
||||
<el-radio-button :value="0">批量下线</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择员工">
|
||||
<el-tree-select
|
||||
v-model="batchConfigForm.member_admin_ids"
|
||||
:data="batchMemberDepartmentTree"
|
||||
:props="memberTreeProps"
|
||||
:default-expanded-keys="batchMemberTreeDefaultExpandedKeys"
|
||||
:filter-node-method="filterMemberTreeNode"
|
||||
node-key="value"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
show-checkbox
|
||||
check-on-click-node
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="3"
|
||||
:multiple-limit="100"
|
||||
:render-after-expand="false"
|
||||
:disabled="!batchConfigApply.member_status || savingBatchConfig"
|
||||
placeholder="按部门勾选,或搜索姓名 / 部门 / 企微 userid"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="member-tree-option" :class="`is-${data.kind}`">
|
||||
<span class="member-tree-option__label">{{ data.label }}</span>
|
||||
<span v-if="data.kind === 'department'" class="member-tree-option__count">{{ data.member_count }} 人</span>
|
||||
<span v-else class="member-tree-option__detail">{{ data.detail }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree-select>
|
||||
<span class="form-tip">勾选部门可全选其下员工;已选 {{ batchConfigForm.member_admin_ids.length }} 名。</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="batchConfigApply.member_status && batchConfigForm.member_status === 0 && batchMemberOfflineBlockedPools.length"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
:title="`${batchMemberOfflineBlockedPools[0].name} 至少需要保留一名当前可用的上线员工`"
|
||||
:description="batchMemberOfflineBlockedPools.length > 1 ? `另有 ${batchMemberOfflineBlockedPools.length - 1} 个方案也会没有可用上线员工,请调整选择。` : '请减少下线员工,或先为该方案上线一名当前可用的员工。'"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="overview.automation_installed" class="batch-config-section automation-batch-section">
|
||||
<h3>自动化设置</h3>
|
||||
<p class="batch-section-tip">先选择要覆盖的分组。未选择的接待、客户或欢迎语设置不会随本次批量操作改变。</p>
|
||||
@@ -514,7 +580,7 @@
|
||||
@busy="(busy) => batchConfigBusy = busy"
|
||||
/>
|
||||
</section>
|
||||
<el-alert v-else type="warning" show-icon :closable="false" title="自动化配置尚未安装" description="本次仍可批量修改验证方式、兜底链接和运行状态;安装自动化数据表后即可批量修改接待、客户和欢迎语设置。" />
|
||||
<el-alert v-else type="warning" show-icon :closable="false" title="自动化配置尚未安装" description="本次仍可批量修改验证方式、兜底链接、运行状态和员工上下线;安装自动化数据表后即可批量修改接待、客户和欢迎语设置。" />
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -655,6 +721,12 @@ interface PromotionMemberOption {
|
||||
dept_names: string[]
|
||||
}
|
||||
|
||||
interface BatchMemberStatusOption extends PromotionMemberOption {
|
||||
pool_count: number
|
||||
online_count: number
|
||||
offline_count: number
|
||||
}
|
||||
|
||||
interface PromotionOperatorOption {
|
||||
id: number
|
||||
name: string
|
||||
@@ -714,8 +786,8 @@ const poolFormScroll = ref<HTMLElement>()
|
||||
const poolFormError = ref('')
|
||||
const automationBusy = ref(false)
|
||||
const accessForm = reactive({ pool_ids: [] as number[], operator_admin_ids: [] as number[], action: 'grant' as 'grant' | 'revoke' })
|
||||
const batchConfigApply = reactive({ skip_verify: false, fallback_url: false, status: false, reception: false, customer: false, welcome: false })
|
||||
const batchConfigForm = reactive({ pool_ids: [] as number[], skip_verify: 0, fallback_url: '', status: 1, automation_config: defaultAutomationConfig() })
|
||||
const batchConfigApply = reactive({ skip_verify: false, fallback_url: false, status: false, member_status: false, reception: false, customer: false, welcome: false })
|
||||
const batchConfigForm = reactive({ pool_ids: [] as number[], skip_verify: 0, fallback_url: '', status: 1, member_admin_ids: [] as number[], member_status: 1 as 0 | 1, automation_config: defaultAutomationConfig() })
|
||||
const memberForm = reactive({ id: 0, name: '', userid: '', daily_limit: 0, status: 1, active_range: [] as string[], remark: '' })
|
||||
const customerStatsLoading = ref(false)
|
||||
const customerStatsLoaded = ref(false)
|
||||
@@ -738,7 +810,84 @@ const selectedPool = computed(() => overview.pools.find((item: any) => Number(it
|
||||
const selectedMemberRules = computed(() => Array.isArray(selectedPool.value?.member_rules) ? selectedPool.value.member_rules : [])
|
||||
const selectedInstallPool = computed(() => overview.pools.find((item: any) => Number(item.id) === selectedInstallPoolId.value))
|
||||
const accessDialogPools = computed(() => overview.pools.filter((item: any) => accessForm.pool_ids.includes(Number(item.id))))
|
||||
const manageablePoolIds = computed(() => overview.pools
|
||||
.filter((pool: any) => pool.can_manage_access)
|
||||
.map((pool: any) => Number(pool.id)))
|
||||
const selectedManageablePoolCount = computed(() => {
|
||||
const selected = new Set(selectedPoolIds.value)
|
||||
return manageablePoolIds.value.filter((id) => selected.has(id)).length
|
||||
})
|
||||
const allManageablePoolsSelected = computed(() => manageablePoolIds.value.length > 0
|
||||
&& selectedManageablePoolCount.value === manageablePoolIds.value.length)
|
||||
const someManageablePoolsSelected = computed(() => selectedManageablePoolCount.value > 0
|
||||
&& selectedManageablePoolCount.value < manageablePoolIds.value.length)
|
||||
const batchConfigPools = computed(() => overview.pools.filter((item: any) => batchConfigForm.pool_ids.includes(Number(item.id))))
|
||||
const batchMemberStatusOptions = computed<BatchMemberStatusOption[]>(() => {
|
||||
const memberOptionById = new Map(overview.member_options.map((member) => [Number(member.id), member]))
|
||||
const grouped = new Map<number, BatchMemberStatusOption & { pool_ids: Set<number> }>()
|
||||
batchConfigPools.value.forEach((pool: any) => {
|
||||
const poolId = Number(pool.id)
|
||||
const memberRules = Array.isArray(pool.member_rules) ? pool.member_rules : []
|
||||
memberRules.forEach((rule: any) => {
|
||||
const adminId = Number(rule.admin_id)
|
||||
if (adminId <= 0) return
|
||||
const member = memberOptionById.get(adminId)
|
||||
const current = grouped.get(adminId) || {
|
||||
id: adminId,
|
||||
name: String(rule.name || member?.name || rule.userid || `员工 ${adminId}`),
|
||||
userid: String(member?.userid || rule.userid || ''),
|
||||
display_dept_id: Number(member?.display_dept_id || 0),
|
||||
dept_ids: Array.isArray(member?.dept_ids) ? member.dept_ids.map(Number).filter((id) => id > 0) : [],
|
||||
dept_names: Array.isArray(member?.dept_names)
|
||||
? member.dept_names.map(String).filter(Boolean)
|
||||
: (Array.isArray(rule.dept_names) ? rule.dept_names.map(String).filter(Boolean) : []),
|
||||
pool_count: 0,
|
||||
online_count: 0,
|
||||
offline_count: 0,
|
||||
pool_ids: new Set<number>()
|
||||
}
|
||||
if (!current.pool_ids.has(poolId)) {
|
||||
current.pool_ids.add(poolId)
|
||||
current.pool_count++
|
||||
if (Number(rule.enabled) === 1) current.online_count++
|
||||
else current.offline_count++
|
||||
}
|
||||
grouped.set(adminId, current)
|
||||
})
|
||||
})
|
||||
return [...grouped.values()]
|
||||
.map(({ pool_ids: _poolIds, ...member }) => member)
|
||||
.sort((left, right) => left.name.localeCompare(right.name, 'zh-CN'))
|
||||
})
|
||||
const batchMemberStatusById = computed(() => new Map(
|
||||
batchMemberStatusOptions.value.map((member) => [member.id, member])
|
||||
))
|
||||
const batchMemberDepartmentTree = computed(() => buildMemberDepartmentTree(
|
||||
overview.department_options,
|
||||
batchMemberStatusOptions.value,
|
||||
{
|
||||
selectableDepartments: true,
|
||||
memberDetail: (member) => {
|
||||
const status = batchMemberStatusById.value.get(member.id)
|
||||
return status
|
||||
? `${status.pool_count} 个方案 · 已上线 ${status.online_count} / 已下线 ${status.offline_count}`
|
||||
: ''
|
||||
}
|
||||
}
|
||||
))
|
||||
const batchMemberTreeDefaultExpandedKeys = computed(() => batchMemberDepartmentTree.value.map((node) => node.value))
|
||||
const batchMemberOfflineBlockedPools = computed(() => {
|
||||
if (!batchConfigApply.member_status || batchConfigForm.member_status !== 0) return []
|
||||
const targetAdminIds = new Set(batchConfigForm.member_admin_ids.map(Number))
|
||||
return batchConfigPools.value.filter((pool: any) => {
|
||||
const rules = Array.isArray(pool.member_rules) ? pool.member_rules : []
|
||||
const targetedEnabled = rules.some((rule: any) => Number(rule.enabled) === 1 && targetAdminIds.has(Number(rule.admin_id)))
|
||||
const remainingAvailable = rules.some((rule: any) => Number(rule.enabled) === 1
|
||||
&& (batchConfigApply.reception || rule.reception_available)
|
||||
&& !targetAdminIds.has(Number(rule.admin_id)))
|
||||
return targetedEnabled && !remainingAvailable
|
||||
})
|
||||
})
|
||||
const batchPrimaryMemberSets = computed(() => batchConfigPools.value.map((pool: any) => new Set<number>(
|
||||
(Array.isArray(pool.member_admin_ids) ? pool.member_admin_ids : []).map(Number)
|
||||
)))
|
||||
@@ -811,6 +960,10 @@ function togglePoolSelection(pool: any, checked: unknown) {
|
||||
selectedPoolIds.value = [...next]
|
||||
}
|
||||
|
||||
function toggleAllPoolSelection(checked: unknown) {
|
||||
selectedPoolIds.value = checked ? [...manageablePoolIds.value] : []
|
||||
}
|
||||
|
||||
function openBatchConfigDialog(poolIds: number[] = selectedPoolIds.value) {
|
||||
const manageableIds = new Set(overview.pools
|
||||
.filter((pool: any) => pool.can_manage_access)
|
||||
@@ -823,6 +976,7 @@ function openBatchConfigDialog(poolIds: number[] = selectedPoolIds.value) {
|
||||
skip_verify: false,
|
||||
fallback_url: false,
|
||||
status: false,
|
||||
member_status: false,
|
||||
reception: false,
|
||||
customer: false,
|
||||
welcome: false
|
||||
@@ -832,6 +986,8 @@ function openBatchConfigDialog(poolIds: number[] = selectedPoolIds.value) {
|
||||
skip_verify: Number(reference?.skip_verify) === 1 ? 1 : 0,
|
||||
fallback_url: String(reference?.fallback_url || ''),
|
||||
status: Number(reference?.status) === 1 ? 1 : 0,
|
||||
member_admin_ids: [],
|
||||
member_status: 1,
|
||||
automation_config: cloneAutomationConfig(reference?.automation_config)
|
||||
})
|
||||
batchConfigError.value = ''
|
||||
@@ -860,9 +1016,23 @@ async function saveBatchConfig() {
|
||||
batchConfigError.value = ''
|
||||
const hasAutomationChange = overview.automation_installed
|
||||
&& (batchConfigApply.reception || batchConfigApply.customer || batchConfigApply.welcome)
|
||||
if (!batchConfigApply.skip_verify && !batchConfigApply.fallback_url && !batchConfigApply.status && !hasAutomationChange) {
|
||||
const hasMemberStatusChange = batchConfigApply.member_status
|
||||
const hasPoolConfigChange = batchConfigApply.skip_verify || batchConfigApply.fallback_url || batchConfigApply.status || hasAutomationChange
|
||||
if (!batchConfigApply.skip_verify && !batchConfigApply.fallback_url && !batchConfigApply.status && !hasMemberStatusChange && !hasAutomationChange) {
|
||||
return setBatchConfigError('请至少勾选一项需要批量修改的配置')
|
||||
}
|
||||
if (hasMemberStatusChange && hasPoolConfigChange) {
|
||||
return setBatchConfigError('员工上下线需要单独批量保存,请取消其他方案配置修改')
|
||||
}
|
||||
if (hasMemberStatusChange && !batchConfigForm.member_admin_ids.length) {
|
||||
return setBatchConfigError('请至少选择一名需要批量上线或下线的员工')
|
||||
}
|
||||
if (hasMemberStatusChange && batchConfigForm.member_admin_ids.length > 100) {
|
||||
return setBatchConfigError('单次最多设置 100 名员工,请减少选择范围')
|
||||
}
|
||||
if (hasMemberStatusChange && batchConfigForm.member_status === 0 && batchMemberOfflineBlockedPools.value.length) {
|
||||
return setBatchConfigError(`${batchMemberOfflineBlockedPools.value[0].name} 至少需要保留一名当前可用的上线员工`)
|
||||
}
|
||||
if (batchConfigApply.fallback_url && batchConfigForm.fallback_url.trim() && !isWebUrl(batchConfigForm.fallback_url.trim())) {
|
||||
return setBatchConfigError('兜底获客助手链接必须为有效的 HTTP/HTTPS 地址')
|
||||
}
|
||||
@@ -889,10 +1059,17 @@ async function saveBatchConfig() {
|
||||
fallback_url?: string
|
||||
status?: 0 | 1
|
||||
automation_config?: Record<string, unknown>
|
||||
member_status?: { member_admin_ids: number[]; status: 0 | 1 }
|
||||
} = {}
|
||||
if (batchConfigApply.skip_verify) changes.skip_verify = batchConfigForm.skip_verify === 1 ? 1 : 0
|
||||
if (batchConfigApply.fallback_url) changes.fallback_url = batchConfigForm.fallback_url.trim()
|
||||
if (batchConfigApply.status) changes.status = batchConfigForm.status === 1 ? 1 : 0
|
||||
if (hasMemberStatusChange) {
|
||||
changes.member_status = {
|
||||
member_admin_ids: [...batchConfigForm.member_admin_ids],
|
||||
status: batchConfigForm.member_status
|
||||
}
|
||||
}
|
||||
if (hasAutomationChange) {
|
||||
const serialized = serializeAutomationConfig(automation) as unknown as Record<string, unknown>
|
||||
const automationPatch: Record<string, unknown> = {}
|
||||
@@ -919,19 +1096,25 @@ async function saveBatchConfig() {
|
||||
batchConfigDialogVisible.value = false
|
||||
selectedPoolIds.value = []
|
||||
await loadOverview()
|
||||
const memberStatusText = batchConfigForm.member_status === 1 ? '上线' : '下线'
|
||||
const memberResultText = hasMemberStatusChange
|
||||
? (result.member_updated > 0
|
||||
? `,${result.member_updated} 条员工规则已${memberStatusText}`
|
||||
: `,所选员工均已处于${memberStatusText}状态`)
|
||||
: ''
|
||||
if (result.failed > 0) {
|
||||
const detail = result.results
|
||||
.filter((item) => !item.success)
|
||||
.slice(0, 2)
|
||||
.map((item) => `${item.name || `方案 ${item.id}`}:${item.error || '保存失败'}`)
|
||||
.join(';')
|
||||
ElMessage.warning(`已更新 ${result.updated} 个方案,${result.failed} 个失败${detail ? `。${detail}` : ''}`)
|
||||
ElMessage.warning(`已更新 ${result.updated} 个方案,${result.failed} 个失败${memberResultText}${detail ? `。${detail}` : ''}`)
|
||||
} else if (result.sync_error_count > 0) {
|
||||
ElMessage.warning(`已更新 ${result.updated} 个方案,其中 ${result.sync_error_count} 个企微范围将在后台自动重试同步`)
|
||||
ElMessage.warning(`已更新 ${result.updated} 个方案${memberResultText},其中 ${result.sync_error_count} 个企微范围将在后台自动重试同步`)
|
||||
} else if (result.sync_queued_count > 0) {
|
||||
ElMessage.success(`已批量更新 ${result.updated} 个分流方案,${result.sync_queued_count} 个企微链接配置将在后台同步`)
|
||||
ElMessage.success(`已批量更新 ${result.updated} 个分流方案${memberResultText},${result.sync_queued_count} 个企微链接配置将在后台同步`)
|
||||
} else {
|
||||
ElMessage.success(`已批量更新 ${result.updated} 个分流方案`)
|
||||
ElMessage.success(`已批量更新 ${result.updated} 个分流方案${memberResultText}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
setBatchConfigError(error?.message || '批量修改分流方案失败')
|
||||
@@ -946,6 +1129,7 @@ function openAccessDialog(poolIds: number[] = selectedPoolIds.value) {
|
||||
.map((pool: any) => Number(pool.id)))
|
||||
const ids = [...new Set(poolIds.map(Number).filter((id) => manageableIds.has(id)))]
|
||||
if (!ids.length) return ElMessage.warning('请先选择可管理的分流方案')
|
||||
if (ids.length > 100) return ElMessage.warning('单次最多设置 100 个分流方案')
|
||||
Object.assign(accessForm, {
|
||||
pool_ids: ids,
|
||||
operator_admin_ids: [],
|
||||
@@ -1286,7 +1470,11 @@ function todayCount(row: any) {
|
||||
|
||||
function buildMemberDepartmentTree(
|
||||
departmentOptions: PromotionDepartmentOption[],
|
||||
memberOptions: PromotionMemberOption[]
|
||||
memberOptions: PromotionMemberOption[],
|
||||
options: {
|
||||
selectableDepartments?: boolean
|
||||
memberDetail?: (member: PromotionMemberOption) => string
|
||||
} = {}
|
||||
): PromotionMemberTreeNode[] {
|
||||
const availableDeptIds = new Set<number>()
|
||||
const collectDeptIds = (departments: PromotionDepartmentOption[]) => {
|
||||
@@ -1331,7 +1519,8 @@ function buildMemberDepartmentTree(
|
||||
const placedMemberIds = new Set<number>()
|
||||
const toMemberNode = (member: PromotionMemberOption): PromotionMemberTreeNode => {
|
||||
const departments = member.dept_names.length ? member.dept_names.join(' / ') : '未分部门'
|
||||
const detail = member.userid ? `${departments} · ${member.userid}` : departments
|
||||
const detail = options.memberDetail?.(member)
|
||||
|| (member.userid ? `${departments} · ${member.userid}` : departments)
|
||||
return {
|
||||
value: member.id,
|
||||
label: member.name,
|
||||
@@ -1366,7 +1555,7 @@ function buildMemberDepartmentTree(
|
||||
value: `dept:${departmentId}`,
|
||||
label: departmentName,
|
||||
kind: 'department',
|
||||
disabled: true,
|
||||
disabled: !options.selectableDepartments,
|
||||
member_count: memberCount,
|
||||
search_text: `${path.join(' ')} ${children.map((child) => child.search_text).join(' ')}`.toLocaleLowerCase(),
|
||||
children
|
||||
@@ -1383,7 +1572,7 @@ function buildMemberDepartmentTree(
|
||||
value: 'dept:unassigned',
|
||||
label: '未分部门',
|
||||
kind: 'department',
|
||||
disabled: true,
|
||||
disabled: !options.selectableDepartments,
|
||||
member_count: children.length,
|
||||
search_text: `未分部门 ${children.map((child) => child.search_text).join(' ')}`.toLocaleLowerCase(),
|
||||
children
|
||||
@@ -1569,6 +1758,7 @@ h1, h2, h3, p { margin: 0; }
|
||||
.selection-count { color: #117f75; font-size: 11px; font-weight: 600; }
|
||||
.pool-layout { display: grid; grid-template-columns: 252px minmax(0, 1fr); height: clamp(430px, calc(100vh - 330px), 720px); min-height: 430px; border: 1px solid var(--line); border-radius: 11px; overflow: hidden; }
|
||||
.pool-sidebar { min-height: 0; padding: 8px; border-right: 1px solid var(--line); overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; background: #f7f9fa; }
|
||||
.pool-select-all { display: flex; position: sticky; z-index: 2; top: 0; align-items: center; justify-content: space-between; min-height: 42px; margin: 0 0 8px; padding: 4px 8px; border-bottom: 1px solid var(--line); background: #f7f9fa; }.pool-select-all :deep(.el-checkbox) { margin-right: 0; }.pool-select-all span { color: #8c98a7; font-size: 10px; }
|
||||
.pool-select-row { display: grid; grid-template-columns: 22px minmax(0, 1fr); align-items: center; gap: 4px; margin-bottom: 5px; }
|
||||
.pool-select-row :deep(.el-checkbox) { justify-content: center; margin-right: 0; }
|
||||
.pool-item { display: grid; grid-template-columns: 9px minmax(0, 1fr) 16px; align-items: center; gap: 9px; width: 100%; min-height: 62px; padding: 10px; border: 1px solid transparent; border-radius: 8px; text-align: left; background: transparent; cursor: pointer; }
|
||||
@@ -1621,10 +1811,11 @@ h1, h2, h3, p { margin: 0; }
|
||||
.batch-pool-summary strong { margin-right: 3px; font-size: 12px; }.batch-pool-summary span { padding: 4px 8px; border-radius: 12px; color: #426078; background: #eaf1f5; font-size: 10px; }.batch-pool-summary small { color: #8491a2; font-size: 10px; }
|
||||
.batch-config-form { margin-top: 16px; }.batch-config-section { margin-bottom: 18px; padding: 16px; border: 1px solid var(--line); border-radius: 9px; background: #fff; }.batch-config-section > h3 { margin: 0 0 14px; color: #303b4d; font-size: 14px; }
|
||||
.batch-field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }.batch-field { padding: 12px; border: 1px solid #dfe6ec; border-radius: 8px; transition: opacity .2s ease, background .2s ease; }.batch-field.is-disabled { opacity: .58; background: #f7f8fa; }.batch-field :deep(.el-form-item) { margin: 10px 0 0; }
|
||||
.batch-member-status-field { padding: 14px; }.batch-member-status-grid { display: grid; grid-template-columns: minmax(190px, .7fr) minmax(0, 1.6fr); gap: 14px; }
|
||||
.batch-section-tip { margin: -6px 0 12px; color: #8491a2; font-size: 11px; line-height: 1.6; }.batch-section-selectors { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }.batch-section-selectors :deep(.el-checkbox) { margin-right: 0; }.automation-batch-section :deep(.automation-note) { margin-top: 14px; }
|
||||
.uploading-save-tip { color: #b88230; font-size: 12px; margin-right: 16px; }
|
||||
.sync-retry-tip { color: #b88230; font-size: 10px; line-height: 1.6; margin-top: 4px; }
|
||||
:global(.promotion-pool-dialog) { max-width: calc(100vw - 32px); margin-top: 5vh; }
|
||||
@media (max-width: 1100px) { .heading-actions { flex-wrap: wrap; justify-content: flex-end; }.metric-grid, .customer-metric-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }.pool-layout { grid-template-columns: 210px minmax(0,1fr); }.pool-toolbar { align-items: flex-start; flex-direction: column; } }
|
||||
@media (max-width: 760px) { .promotion-page { padding: 10px; }.page-header, .section-heading { align-items: flex-start; flex-direction: column; }.section-heading-actions { width: 100%; justify-content: flex-start; }.update-time { display: none; }.metric-grid, .customer-metric-grid, .form-grid, .rule-form-grid, .batch-field-grid { grid-template-columns: 1fr; }.tab-nav { overflow-x: auto; }.tab-nav button { min-width: 112px; }.tab-content { padding: 14px; }.pool-layout { height: auto; min-height: 0; grid-template-columns: 1fr; }.pool-sidebar { display: flex; overflow-x: auto; overflow-y: hidden; border-right: 0; border-bottom: 1px solid var(--line); scrollbar-gutter: auto; }.pool-main { overflow: visible; }.member-table-area { height: 420px; min-height: 320px; flex: none; }.pool-select-row { min-width: 220px; }.pool-item { min-width: 190px; }.callback-list > div { grid-template-columns: 1fr 48px; padding: 8px 0; }.callback-list dt { grid-column: 1 / -1; }.install-pool-select { width: 100%; }.customer-heading-actions { width: 100%; justify-content: flex-end; }.customer-filter-bar :deep(.el-form-item) { width: 100%; margin-right: 0; }.customer-filter-bar :deep(.el-form-item__content), .customer-filter-bar .el-select { width: 100%; }.customer-filter-bar .filter-actions :deep(.el-form-item__content) { justify-content: flex-end; }.customer-pagination { align-items: flex-start; flex-direction: column; }.customer-pagination :deep(.el-pagination) { max-width: 100%; flex-wrap: wrap; justify-content: flex-start; }.access-pool-preview > div { grid-template-columns: 1fr; gap: 3px; } }
|
||||
@media (max-width: 760px) { .promotion-page { padding: 10px; }.page-header, .section-heading { align-items: flex-start; flex-direction: column; }.section-heading-actions { width: 100%; justify-content: flex-start; }.update-time { display: none; }.metric-grid, .customer-metric-grid, .form-grid, .rule-form-grid, .batch-field-grid, .batch-member-status-grid { grid-template-columns: 1fr; }.tab-nav { overflow-x: auto; }.tab-nav button { min-width: 112px; }.tab-content { padding: 14px; }.pool-layout { height: auto; min-height: 0; grid-template-columns: 1fr; }.pool-sidebar { display: flex; overflow-x: auto; overflow-y: hidden; border-right: 0; border-bottom: 1px solid var(--line); scrollbar-gutter: auto; }.pool-select-all { position: static; min-width: 126px; flex: 0 0 126px; margin: 0 6px 0 0; border-right: 1px solid var(--line); border-bottom: 0; }.pool-main { overflow: visible; }.member-table-area { height: 420px; min-height: 320px; flex: none; }.pool-select-row { min-width: 220px; }.pool-item { min-width: 190px; }.callback-list > div { grid-template-columns: 1fr 48px; padding: 8px 0; }.callback-list dt { grid-column: 1 / -1; }.install-pool-select { width: 100%; }.customer-heading-actions { width: 100%; justify-content: flex-end; }.customer-filter-bar :deep(.el-form-item) { width: 100%; margin-right: 0; }.customer-filter-bar :deep(.el-form-item__content), .customer-filter-bar .el-select { width: 100%; }.customer-filter-bar .filter-actions :deep(.el-form-item__content) { justify-content: flex-end; }.customer-pagination { align-items: flex-start; flex-direction: column; }.customer-pagination :deep(.el-pagination) { max-width: 100%; flex-wrap: wrap; justify-content: flex-start; }.access-pool-preview > div { grid-template-columns: 1fr; gap: 3px; } }
|
||||
</style>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user