This commit is contained in:
Your Name
2026-09-09 14:04:09 +08:00
parent 9be2e18ea6
commit 083fd857e8
7 changed files with 462 additions and 46 deletions
+6
View File
@@ -20,6 +20,12 @@
部署后,对已经出现“待移出”的方案点击“同步成员范围”;只有显示“企微成员范围已确认同步”,才能认为当前计划已经由企业微信回读确认。批量重新保存也会主动同步各方案。后台分钟任务仍用于上限、有效期和失败补偿。
## 追加:直接批量同步成员范围
列表上方新增“批量同步成员范围”。左侧勾选或全选方案后可直接同步当前配置,无需再次保存方案。单次最多 100 个方案,最多 2 个请求同时执行;展示逐项结果及完成进度。同步未确认或受阻的方案保留勾选,可再次批量同步或单项重试。共享操作人可以勾选有操作权限的方案,访问授权和批量配置仍沿用管理权限。
新增 6 项回归,前端合计 13 项通过(包括实际 SFC、模板和 SCSS 编译),详见 [批量同步回归记录](batch-sync-tests.md)。浏览器用三个虚构方案验证批量同步,两项成功、一项模拟失败时,结果准确且仅保留失败方案勾选。全量类型检查已再次执行,仍因其他模块错误未通过;本次获客助手页面及 API 无类型报错。此次追加未重新执行全量生产构建,未部署或调用真实企微。
## 验证记录
见 [前端验证](frontend.md) 与 [隔离数据库验证](backend-tests.md)。所有测试使用虚构数据,未向真实企业微信发送请求。
@@ -0,0 +1,34 @@
# 批量同步成员范围回归验证
验证日期:2026-09-09。
修改文件:`admin/tests/wecom-promotion-member-sync.test.cjs`。新增 6 个用例,保留原有 7 个用例。
## 覆盖行为
- 批量操作固定启动时的方案列表,去重后同步;同一时间最多 2 个请求。
- 每个请求完成后更新进度,全部请求完成才刷新概览;概览刷新完成后才清理忙碌标记和进度。
- 批量同步只调用成员范围同步接口,不调用方案配置、成员规则、操作权限、客户同步或成员上下线接口。
- 完全成功时清空勾选并提示确认成功;结果使用 `sync_only`,不会声称本次进行了本地保存。
- 混合同步成功、请求超时、待确认、缺少官方链接和无操作权限时,逐项保留准确结果;不对受阻方案发起请求,保留未确认方案勾选,汇总为警告。
- 批量运行期间阻止重复批量同步、单方案同步和成员切换;勾选及全选操作无效。
- 单方案同步、成员切换或批量配置保存期间不能启动批量同步。
- 空选择和超过 100 个唯一方案时警告且不发请求;100 个唯一方案加重复 ID 可以正常完成。
- 共享操作人能够勾选并同步有操作权限的方案;访问设置和批量配置仍仅接受有管理权限的方案;权限变化后清理不可选项。
## 执行结果
`D:/web/zyt/admin` 执行:
```text
node --test tests/wecom-promotion-member-sync.test.cjs
tests 13
pass 13
fail 0
cancelled 0
skipped 0
```
首次运行 12 个通过,1 个因测试对“选择/勾选”的文案要求过窄而失败;放宽该同义表达断言后全部通过,无需修改实现。
测试直接编译实际页面的 Vue SFC 脚本、模板和 SCSS,并以模拟 API 执行业务方法。未连接真实网络、数据库或企业微信;页面视觉和浏览器交互由主任务另外验证。
@@ -0,0 +1,109 @@
src/components/chat-dialog/ChatMessageItem.vue(29,7): error TS7022: 'attrs' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
src/components/editor/index.vue(29,44): error TS7016: Could not find a declaration file for module '@wangeditor/editor-for-vue'. 'D:/web/zyt/admin/node_modules/.pnpm/@wangeditor+editor-for-vue@_d49ef1161b4f4b880c450fdbfe3a0001/node_modules/@wangeditor/editor-for-vue/dist/index.esm.js' implicitly has an 'any' type.
There are types at 'D:/web/zyt/admin/node_modules/@wangeditor/editor-for-vue/dist/src/index.d.ts', but this result could not be resolved when respecting package.json "exports". The '@wangeditor/editor-for-vue' library may need to update its package.json or typings.
src/utils/call-local-recorder.ts(335,26): error TS2339: Property 'captureStream' does not exist on type 'HTMLVideoElement'.
src/views/asset/user/index.vue(230,20): error TS2339: Property 'remark' does not exist on type 'never'.
src/views/consumer/prescription/index.vue(1150,42): error TS2322: Type '(value: string[]) => void' is not assignable to type '(value: CascaderValue | null | undefined) => any'.
Types of parameters 'value' and 'value' are incompatible.
Type 'CascaderValue | null | undefined' is not assignable to type 'string[]'.
Type 'undefined' is not assignable to type 'string[]'.
src/views/consumer/prescription/index.vue(1952,9): error TS2322: Type '{ name: any; code: any; children: any; }[]' is not assignable to type 'never[]'.
Type '{ name: any; code: any; children: any; }' is not assignable to type 'never'.
src/views/consumer/prescription/index.vue(1958,13): error TS2322: Type '{ name: string; children: { name: string; children: { name: string; }[]; }[]; }' is not assignable to type 'never'.
src/views/consumer/prescription/index.vue(1986,13): error TS2322: Type '{ name: string; children: { name: string; children: { name: string; }[]; }[]; }' is not assignable to type 'never'.
src/views/consumer/prescription/index.vue(2001,13): error TS2322: Type '{ name: string; children: { name: string; children: { name: string; }[]; }[]; }' is not assignable to type 'never'.
src/views/consumer/prescription/index.vue(2016,13): error TS2322: Type '{ name: string; children: { name: string; children: { name: string; }[]; }[]; }' is not assignable to type 'never'.
src/views/consumer/prescription/index.vue(2256,32): error TS2304: Cannot find name 'searchPatientsAPI'.
src/views/consumer/prescription/order_list.vue(253,26): error TS2322: Type 'unknown[]' is not assignable to type 'TreeNodeData[]'.
Type 'unknown' is not assignable to type 'TreeNodeData'.
src/views/consumer/prescription/order_list.vue(983,45): error TS2322: Type 'string | number | undefined' is not assignable to type 'number | null | undefined'.
Type 'string' is not assignable to type 'number'.
src/views/consumer/prescription/order_list.vue(1181,41): error TS2322: Type 'string | number | undefined' is not assignable to type 'number | null | undefined'.
Type 'string' is not assignable to type 'number'.
src/views/consumer/prescription/order_list.vue(1939,68): error TS7006: Parameter 'r' implicitly has an 'any' type.
src/views/consumer/prescription/order_list.vue(2379,9): error TS2322: Type '{ value: any; label: any; code: any; children: any; }[]' is not assignable to type 'never[]'.
Type '{ value: any; label: any; code: any; children: any; }' is not assignable to type 'never'.
src/views/consumer/prescription/order_list.vue(3775,13): error TS2322: Type 'string | number | undefined' is not assignable to type 'number | undefined'.
Type 'string' is not assignable to type 'number'.
src/views/consumer/prescription/order_list_h5.vue(271,26): error TS2322: Type 'unknown[]' is not assignable to type 'TreeNodeData[]'.
Type 'unknown' is not assignable to type 'TreeNodeData'.
src/views/consumer/prescription/order_list_h5.vue(566,47): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; }'.
src/views/consumer/prescription/order_list_h5.vue(576,58): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; }'.
src/views/consumer/prescription/order_list_h5.vue(586,47): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; }'.
src/views/consumer/prescription/order_list_h5.vue(596,59): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; }'.
src/views/consumer/prescription/order_list_h5.vue(611,46): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown; }'.
src/views/consumer/prescription/order_list_h5.vue(621,53): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; diagnosis_id?: number | undefined; pay_order_ids?: number[] | undefined; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; diagnosis_id?: number | undefined; pay_order_ids?: number[] | undefined; }'.
src/views/consumer/prescription/order_list_h5.vue(631,53): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; }'.
src/views/consumer/prescription/order_list_h5.vue(641,53): error TS2345: Argument of type 'Record<string, any>' is not assignable to parameter of type '{ id: number; }'.
Property 'id' is missing in type 'Record<string, any>' but required in type '{ id: number; }'.
src/views/consumer/prescription/order_list_h5.vue(1550,45): error TS2322: Type 'string | number | undefined' is not assignable to type 'number | null | undefined'.
Type 'string' is not assignable to type 'number'.
src/views/consumer/prescription/order_list_h5.vue(1738,41): error TS2322: Type 'string | number | undefined' is not assignable to type 'number | null | undefined'.
Type 'string' is not assignable to type 'number'.
src/views/consumer/prescription/order_list_h5.vue(2509,68): error TS7006: Parameter 'r' implicitly has an 'any' type.
src/views/consumer/prescription/order_list_h5.vue(2848,9): error TS2322: Type '{ value: any; label: any; code: any; children: any; }[]' is not assignable to type 'never[]'.
Type '{ value: any; label: any; code: any; children: any; }' is not assignable to type 'never'.
src/views/consumer/prescription/order_list_h5.vue(4694,13): error TS2322: Type 'string | number | undefined' is not assignable to type 'number | undefined'.
Type 'string' is not assignable to type 'number'.
src/views/decoration/component/tabbar/pc/attr.vue(10,18): error TS2345: Argument of type '{ modelValue: any; }' is not assignable to parameter of type '{ readonly modelValue: any[]; readonly max?: number | undefined; readonly min?: number | undefined; readonly itemData: any; readonly "onUpdate:modelValue"?: ((value: any) => any) | undefined; } & VNodeProps & AllowedComponentProps & ComponentCustomProps & Record<...>'.
Property 'itemData' is missing in type '{ modelValue: any; }' but required in type '{ readonly modelValue: any[]; readonly max?: number | undefined; readonly min?: number | undefined; readonly itemData: any; readonly "onUpdate:modelValue"?: ((value: any) => any) | undefined; }'.
src/views/decoration/component/tabbar/pc/attr.vue(13,18): error TS2345: Argument of type '{ modelValue: any; }' is not assignable to parameter of type '{ readonly modelValue: any[]; readonly max?: number | undefined; readonly min?: number | undefined; readonly itemData: any; readonly "onUpdate:modelValue"?: ((value: any) => any) | undefined; } & VNodeProps & AllowedComponentProps & ComponentCustomProps & Record<...>'.
Property 'itemData' is missing in type '{ modelValue: any; }' but required in type '{ readonly modelValue: any[]; readonly max?: number | undefined; readonly min?: number | undefined; readonly itemData: any; readonly "onUpdate:modelValue"?: ((value: any) => any) | undefined; }'.
src/views/decoration/component/widgets/middle-banner/content.vue(6,33): error TS2339: Property 'height' does not exist on type '{}'.
src/views/doctor/dept-tongji.vue(131,31): error TS7006: Parameter 'depts' implicitly has an 'any' type.
src/views/doctor/dept-tongji.vue(132,17): error TS7034: Variable 'result' implicitly has type 'any[]' in some locations where its type cannot be determined.
src/views/doctor/dept-tongji.vue(133,27): error TS7006: Parameter 'dept' implicitly has an 'any' type.
src/views/doctor/dept-tongji.vue(136,30): error TS7005: Variable 'result' implicitly has an 'any[]' type.
src/views/doctor/dept-tongji.vue(139,20): error TS7005: Variable 'result' implicitly has an 'any[]' type.
src/views/doctor/tongji.vue(94,58): error TS2322: Type 'string' is not assignable to type '"success" | "warning" | "info" | "primary" | "danger" | undefined'.
src/views/doctor/tongji.vue(125,58): error TS2322: Type 'string' is not assignable to type '"success" | "warning" | "info" | "primary" | "danger" | undefined'.
src/views/doctor/tongji.vue(230,48): error TS2769: No overload matches this call.
Overload 1 of 3, '(callbackfn: (previousValue: unknown, currentValue: unknown, currentIndex: number, array: unknown[]) => unknown, initialValue: unknown): unknown', gave the following error.
Argument of type '(total: number, count: number) => number' is not assignable to parameter of type '(previousValue: unknown, currentValue: unknown, currentIndex: number, array: unknown[]) => unknown'.
Types of parameters 'total' and 'previousValue' are incompatible.
Type 'unknown' is not assignable to type 'number'.
Overload 2 of 3, '(callbackfn: (previousValue: number, currentValue: unknown, currentIndex: number, array: unknown[]) => number, initialValue: number): number', gave the following error.
Argument of type '(total: number, count: number) => number' is not assignable to parameter of type '(previousValue: number, currentValue: unknown, currentIndex: number, array: unknown[]) => number'.
Types of parameters 'count' and 'currentValue' are incompatible.
Type 'unknown' is not assignable to type 'number'.
src/views/doctor/tongji.vue(236,51): error TS2769: No overload matches this call.
Overload 1 of 3, '(callbackfn: (previousValue: unknown, currentValue: unknown, currentIndex: number, array: unknown[]) => unknown, initialValue: unknown): unknown', gave the following error.
Argument of type '(total: number, count: number) => number' is not assignable to parameter of type '(previousValue: unknown, currentValue: unknown, currentIndex: number, array: unknown[]) => unknown'.
Types of parameters 'total' and 'previousValue' are incompatible.
Type 'unknown' is not assignable to type 'number'.
Overload 2 of 3, '(callbackfn: (previousValue: number, currentValue: unknown, currentIndex: number, array: unknown[]) => number, initialValue: number): number', gave the following error.
Argument of type '(total: number, count: number) => number' is not assignable to parameter of type '(previousValue: number, currentValue: unknown, currentIndex: number, array: unknown[]) => number'.
Types of parameters 'count' and 'currentValue' are incompatible.
Type 'unknown' is not assignable to type 'number'.
src/views/fans/h5.vue(43,47): error TS2322: Type '"" | "info" | "danger"' is not assignable to type '"success" | "warning" | "info" | "primary" | "danger" | undefined'.
Type '""' is not assignable to type '"success" | "warning" | "info" | "primary" | "danger" | undefined'.
src/views/fans/h5.vue(167,51): error TS2322: Type 'string' is not assignable to type '"success" | "warning" | "info" | "primary" | "danger" | undefined'.
src/views/fans/index.vue(158,34): error TS2322: Type 'string' is not assignable to type '"success" | "warning" | "info" | "primary" | "danger" | undefined'.
src/views/message/notice/index.vue(12,44): error TS2322: Type '(opts?: { silent?: boolean; }) => Promise<any>' is not assignable to type '(name: TabPaneName) => any'.
Types of parameters 'opts' and 'name' are incompatible.
Type 'TabPaneName' is not assignable to type '{ silent?: boolean | undefined; } | undefined'.
Type 'string' has no properties in common with type '{ silent?: boolean | undefined; }'.
src/views/order/index.vue(446,33): error TS2367: This comparison appears to be unintentional because the types '"supplement"' and '"normal"' have no overlap.
src/views/order/index.vue(1542,13): error TS2367: This comparison appears to be unintentional because the types '"supplement"' and '"normal"' have no overlap.
src/views/organization/department/edit.vue(20,29): error TS2353: Object literal may only specify known properties, and 'value' does not exist in type 'TreeOptionProps'.
src/views/permission/admin/edit.vue(44,29): error TS2353: Object literal may only specify known properties, and 'value' does not exist in type 'TreeOptionProps'.
src/views/tcm/appointment/list.vue(90,26): error TS2322: Type 'unknown[]' is not assignable to type 'TreeNodeData[]'.
Type 'unknown' is not assignable to type 'TreeNodeData'.
src/views/tcm/diagnosis/add.vue(744,49): error TS2349: This expression is not callable.
Type 'String' has no call signatures.
src/views/tcm/diagnosis/add.vue(772,45): error TS2349: This expression is not callable.
Type 'String' has no call signatures.
src/views/tcm/diagnosis/components/BloodRecordList.vue(416,5): error TS2322: Type 'string' is not assignable to type 'number'.
src/views/tcm/diagnosis/components/RecordingVideoPlayer.vue(145,54): error TS2554: Expected 0 arguments, but got 1.
src/views/tcm/diagnosis/components/RecordingVideoPlayer.vue(146,50): error TS2554: Expected 0 arguments, but got 1.
src/views/tcm/diagnosis/components/RecordingVideoPlayer.vue(147,47): error TS2554: Expected 0 arguments, but got 1.
src/views/tcm/diagnosis/components/RecordingVideoPlayer.vue(148,47): error TS2554: Expected 0 arguments, but got 1.
src/views/tcm/diagnosis/components/RecordingVideoPlayer.vue(154,26): error TS2554: Expected 0 arguments, but got 1.
src/views/workbench/index.vue(541,18): error TS7006: Parameter 'd' implicitly has an 'any' type.
+11 -4
View File
@@ -21,20 +21,27 @@ const overview = {
dispatch_sync: { status: 1, desired_version: 2, applied_version: 1, last_error: '' }
}]
}
for (const id of [100, 101]) {
const pool = structuredClone(overview.pools[0])
Object.assign(pool, { id, name: `批量验证方案 ${id}`, public_key: `test-only-${id}` })
overview.pools.push(pool)
}
overview.summary.pool_count = overview.summary.online_links = overview.pools.length
const fixture = `const overview = ${JSON.stringify(overview)};
async function invoke(name, params) {
if (name === 'wecomPromotionOverview') return structuredClone(overview);
if (name === 'wecomPromotionSyncMemberRange') {
const pool = overview.pools[0];
if (new URLSearchParams(location.search).get('mode') === 'failure') {
const pool = overview.pools.find(item => item.id === params.pool_id);
const mode = new URLSearchParams(location.search).get('mode');
if (mode === 'failure' || (mode === 'mixed' && pool.id === 100)) {
pool.dispatch_sync.status = 3; pool.dispatch_sync.last_error = '模拟企微网络错误';
pool.member_rules.forEach(row => {row.sync_status = 3; row.sync_error = '模拟企微网络错误';});
return {pool_id:99,sync_status:'failed',sync_error:'模拟企微网络错误',sync_queued:true};
return {pool_id:pool.id,sync_status:'failed',sync_error:'模拟企微网络错误',sync_queued:true};
}
pool.official_link.range_userids = ['test_xuke']; pool.official_link.last_sync_time = Math.floor(Date.now()/1000);
pool.dispatch_sync = {status:0,desired_version:2,applied_version:2,last_error:''};
pool.member_rules.forEach(row => {row.is_in_remote_range = row.enabled === 1; row.sync_status = 0;});
return {pool_id:99,sync_status:'synced',sync_error:'',sync_queued:false,range_userids:['test_xuke'],range_department_ids:[]};
return {pool_id:pool.id,sync_status:'synced',sync_error:'',sync_queued:false,range_userids:['test_xuke'],range_department_ids:[]};
}
throw new Error('隔离页面未实现该操作:' + name);
}