check-electricity-work-flow-nodes.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. /**
  2. * 临时用电作业票审批流校验
  3. *
  4. * 用法:node scripts/check-electricity-work-flow-nodes.js
  5. *
  6. * 校验内容:
  7. * 1) 动火 / 受限空间 / 高处作业 / 吊装作业的既有实现文件未被改动
  8. * 2) 临时用电 9 个后续环节的 code / name / 字段与源系统一致
  9. * 3) 环节匹配:按 code / 别名 / 名称 / 双向包含都能命中,且不串台
  10. * 4) 默认安全措施清单与源系统 formData 逐条一致
  11. * 5) 「风险辨识」与「危害辨识」取值 key 不冲突(源系统两者表单相同)
  12. * 6) 临时用电运行时辅助(回显 / payload / 流程变量)的取值口径正确
  13. * 7) 组件模板与脚本一致:模板里调用的方法必须都已在组件中定义
  14. * (漏定义会在渲染到该分支时抛 "is not a function",整个环节区域渲染不出来)
  15. * 8) detail.vue 的作业类型分发与「自动切到后续环节 tab」的时机正确
  16. *
  17. * 源系统依据:
  18. * https://www.acwlkj.com/IntellSecurity-web/flow/getFlowDefineListByEnterprise.do
  19. * -> 流程定义 HO_PERMIT_ELECTRICITY(id 3766,名称「危险作业-临时用电作业」)
  20. * https://www.acwlkj.com/IntellSecurity-web/flow/getFlowNodeDefineByFlowid.do?flow_id=3766
  21. * -> 节点定义(共 10 个节点,1 级为作业申请)
  22. */
  23. const fs = require('fs');
  24. const os = require('os');
  25. const path = require('path');
  26. const { execFileSync } = require('child_process');
  27. const babel = require('@babel/core');
  28. const Module = require('module');
  29. const ROOT = path.resolve(__dirname, '..');
  30. const SRC = path.join(
  31. ROOT,
  32. 'src/views/bpm/handleTask/components/dangerousWorkTicket'
  33. );
  34. const OUT = path.join(os.tmpdir(), 'kd-aiot-check-electricity');
  35. // 其它作业类型的既有实现文件:临时用电实现不得改动它们
  36. const EXISTING_FILES = [
  37. 'fireFlowNodes.js',
  38. 'fireFlowTask.js',
  39. 'confinedSpaceFlowNodes.js',
  40. 'confinedSpaceFlowTask.js',
  41. 'heightFlowNodes.js',
  42. 'heightFlowTask.js',
  43. 'hoistingFlowNodes.js',
  44. 'hoistingFlowTask.js',
  45. 'components/fireTicketDetail.vue',
  46. 'components/flowNodeForm.vue',
  47. 'components/confinedSpaceFlow.vue',
  48. 'components/heightFlow.vue',
  49. 'components/hoistingFlow.vue'
  50. ];
  51. let failed = 0;
  52. function check(name, cond, extra) {
  53. if (cond) {
  54. console.log(' PASS ' + name);
  55. } else {
  56. failed++;
  57. console.log(
  58. ' FAIL ' + name + (extra ? ' -> ' + JSON.stringify(extra) : '')
  59. );
  60. }
  61. }
  62. // 编译 ESM -> CJS
  63. // electricityFlowTask.js 引用了 fireFlowTask.js(只读复用),
  64. // fireFlowTask.js 又引用了 fireFlowNodes.js,一并编译进来
  65. fs.mkdirSync(OUT, { recursive: true });
  66. [
  67. 'electricityFlowNodes.js',
  68. 'electricityFlowTask.js',
  69. 'fireFlowNodes.js',
  70. 'fireFlowTask.js'
  71. ].forEach((file) => {
  72. const code = fs.readFileSync(path.join(SRC, file), 'utf8');
  73. const res = babel.transformSync(code, {
  74. filename: file,
  75. babelrc: false,
  76. configFile: false,
  77. plugins: [require.resolve('@babel/plugin-transform-modules-commonjs')]
  78. });
  79. fs.writeFileSync(path.join(OUT, file), res.code);
  80. });
  81. // 临时用电运行时辅助会引用 @/api/*,这里用桩模块代替,避免校验脚本依赖后端接口
  82. const STUB = path.join(OUT, 'stub.js');
  83. fs.writeFileSync(
  84. STUB,
  85. 'module.exports = {\n' +
  86. ' getTaskListByProcessInstanceId: function () { return Promise.resolve([]); },\n' +
  87. ' getProcessInstance: function () { return Promise.resolve({}); }\n' +
  88. '};\n'
  89. );
  90. const originalResolve = Module._resolveFilename;
  91. Module._resolveFilename = function (request, ...args) {
  92. if (request.startsWith('@/')) {
  93. return STUB;
  94. }
  95. return originalResolve.call(this, request, ...args);
  96. };
  97. const es = require(path.join(OUT, 'electricityFlowNodes.js'));
  98. const est = require(path.join(OUT, 'electricityFlowTask.js'));
  99. console.log('\n[1] 其它作业类型的既有实现未被改动');
  100. EXISTING_FILES.forEach((file) => {
  101. let changed = true;
  102. try {
  103. execFileSync('git', ['diff', '--quiet', 'HEAD', '--', file], {
  104. cwd: path.join(SRC, '..'),
  105. stdio: 'ignore'
  106. });
  107. changed = false;
  108. } catch (e) {
  109. changed = true;
  110. }
  111. check(file + ' 与 HEAD 一致', !changed);
  112. });
  113. console.log(
  114. '\n[2] 临时用电环节(源系统 HO_PERMIT_ELECTRICITY 共 10 节点,1 级为作业申请模板)'
  115. );
  116. const expected = [
  117. ['HO_PERMIT_ELECTRICITY_TWOTH', '风险辨识'],
  118. ['HO_PERMIT_ELECTRICITY_THREETH', '气体分析'],
  119. ['HO_PERMIT_ELECTRICITY_FOURTH', '危害辨识'],
  120. ['HO_PERMIT_ELECTRICITY_FIVETH', '现场安全措施确认'],
  121. ['HO_PERMIT_ELECTRICITY_SIXTH', '安全交底'],
  122. ['HO_PERMIT_ELECTRICITY_SEVENTH', '作业负责人意见'],
  123. ['HO_PERMIT_ELECTRICITY_EIGHTTH', '用电单位意见'],
  124. ['HO_PERMIT_ELECTRICITY_NINETH', '作业实施'],
  125. ['HO_PERMIT_ELECTRICITY_TENTH', '完工验收']
  126. ];
  127. check('环节数量为 9', es.ELECTRICITY_FLOW_NODES.length === 9, {
  128. len: es.ELECTRICITY_FLOW_NODES.length
  129. });
  130. expected.forEach(([code, name], i) => {
  131. const node = es.ELECTRICITY_FLOW_NODES[i];
  132. check(
  133. '第' + (i + 2) + '级 ' + code + ' / ' + name,
  134. node.code === code && node.name === name,
  135. { code: node.code, name: node.name }
  136. );
  137. });
  138. console.log('\n[3] 与源系统 formJson 一致性');
  139. const nodeOf = (k) => es.ELECTRICITY_FLOW_NODES.find((n) => n.key === k);
  140. const colsOf = (k, fk) => {
  141. const node = nodeOf(k);
  142. const field = node.fields.find((f) => f.key === fk);
  143. return (field.columns || []).map((c) => c.label);
  144. };
  145. check(
  146. '气体分析:表名「气体分析记录表」',
  147. nodeOf('gas_analysis').fields[0].label === '气体分析记录表'
  148. );
  149. check(
  150. '气体分析:4 列且顺序与源系统一致(分析时间/分析点/分析结果/分析人)',
  151. JSON.stringify(colsOf('gas_analysis', 'gas_analysis_list')) ===
  152. JSON.stringify(['分析时间', '分析点', '分析结果', '分析人']),
  153. colsOf('gas_analysis', 'gas_analysis_list')
  154. );
  155. check(
  156. '气体分析:分析时间为日期类型、其余为多行文本(与源系统一致)',
  157. nodeOf('gas_analysis').fields[0].columns[0].type === 'date' &&
  158. nodeOf('gas_analysis').fields[0].columns[1].type === 'textarea' &&
  159. nodeOf('gas_analysis').fields[0].columns[2].type === 'textarea' &&
  160. nodeOf('gas_analysis').fields[0].columns[3].type === 'textarea'
  161. );
  162. check(
  163. '危害辨识:多选 + 单行文本说明(源系统该节点表单复制自风险辨识)',
  164. nodeOf('hazard').fields[0].type === 'checkbox' &&
  165. nodeOf('hazard').fields[0].options.length === 20 &&
  166. nodeOf('hazard').fields[1].type === 'text'
  167. );
  168. check(
  169. '风险辨识:多选 20 项 + 单行文本说明(源系统 checkbox + input)',
  170. nodeOf('risk').fields[0].type === 'checkbox' &&
  171. nodeOf('risk').fields[0].options.length === 20 &&
  172. nodeOf('risk').fields[0].options[0].label === '物体打击' &&
  173. nodeOf('risk').fields[0].options[19].label === '其它伤害' &&
  174. nodeOf('risk').fields[1].type === 'text'
  175. );
  176. check(
  177. '风险辨识:说明字段别名不与多选框标签冲突',
  178. !nodeOf('risk').fields[1].alias.includes('风险辨识结果')
  179. );
  180. check(
  181. '现场安全措施确认:表名「安全措施确认表」,3 列(安全措施/是否确认/确认人)',
  182. nodeOf('measure').fields[0].label === '安全措施确认表' &&
  183. JSON.stringify(colsOf('measure', 'measure_list')) ===
  184. JSON.stringify(['安全措施', '是否确认', '确认人'])
  185. );
  186. check(
  187. '现场安全措施确认:编制人签字 + 确认人签字',
  188. JSON.stringify(nodeOf('measure').fields.map((f) => f.label)) ===
  189. JSON.stringify(['安全措施确认表', '编制人签字', '确认人签字'])
  190. );
  191. check(
  192. '是否确认选项为 不涉及 / 已确认',
  193. JSON.stringify(
  194. nodeOf('measure').fields[0].columns[1].options.map((o) => o.label)
  195. ) === JSON.stringify(['不涉及', '已确认'])
  196. );
  197. check(
  198. '安全交底:2 个签字(安全交底人签字 / 接受交底人签字)',
  199. JSON.stringify(nodeOf('brief').fields.map((f) => f.label)) ===
  200. JSON.stringify(['安全交底人签字', '接受交底人签字'])
  201. );
  202. check(
  203. '作业负责人意见:意见 + 签字',
  204. nodeOf('leader_opinion').fields.length === 2 &&
  205. nodeOf('leader_opinion').fields[0].type === 'textarea' &&
  206. nodeOf('leader_opinion').fields[1].type === 'sign'
  207. );
  208. check(
  209. '用电单位意见:意见 + 签字(不是受限空间的「所在单位意见」)',
  210. nodeOf('electric_unit_opinion').fields.length === 2 &&
  211. nodeOf('electric_unit_opinion').fields[0].type === 'textarea' &&
  212. nodeOf('electric_unit_opinion').fields[1].type === 'sign' &&
  213. !es.ELECTRICITY_FLOW_NODES.some((n) => n.name === '所在单位意见')
  214. );
  215. check(
  216. '作业实施:开始时间 + 结束时间 + 事中记录表 + 可燃气体分析记录表',
  217. JSON.stringify(nodeOf('execute').fields.map((f) => f.label)) ===
  218. JSON.stringify([
  219. '作业实施开始时间',
  220. '作业实施结束时间',
  221. '事中记录表',
  222. '可燃气体分析记录表'
  223. ])
  224. );
  225. check(
  226. '事中记录表 3 列(时间/记录内容/记录人)',
  227. JSON.stringify(colsOf('execute', 'during_record_list')) ===
  228. JSON.stringify(['时间', '记录内容', '记录人'])
  229. );
  230. check(
  231. '可燃气体分析记录表 4 列(分析时间/分析点/分析结果/分析人)',
  232. JSON.stringify(colsOf('execute', 'gas_record_list')) ===
  233. JSON.stringify(['分析时间', '分析点', '分析结果', '分析人'])
  234. );
  235. check(
  236. '完工验收:意见 + 签字',
  237. nodeOf('acceptance').fields.length === 2 &&
  238. nodeOf('acceptance').fields[0].label === '意见' &&
  239. nodeOf('acceptance').fields[1].type === 'sign'
  240. );
  241. check(
  242. '安全措施默认清单 13 条与源系统 formData 逐条一致',
  243. es.DEFAULT_ELECTRICITY_MEASURES.length === 13 &&
  244. es.DEFAULT_ELECTRICITY_MEASURES[0] === '作业人员持有电工作业操作证' &&
  245. es.DEFAULT_ELECTRICITY_MEASURES[2] ===
  246. '上级开关已断电、加锁,并挂安全警示标牌' &&
  247. es.DEFAULT_ELECTRICITY_MEASURES[9] ===
  248. '临时用电设施已装配漏电保护器,移动工具、手持工具已采取防漏电的安全措施(一机一闸一保护)' &&
  249. es.DEFAULT_ELECTRICITY_MEASURES[12] ===
  250. '作业场所已进行气体检测且符合作业安全要求'
  251. );
  252. check(
  253. '默认行挂在 measure_list 字段上(defaultRows)',
  254. Array.isArray(nodeOf('measure').fields[0].defaultRows) &&
  255. nodeOf('measure').fields[0].defaultRows.length === 13 &&
  256. nodeOf('measure').fields[0].defaultRows[0].measure_content ===
  257. es.DEFAULT_ELECTRICITY_MEASURES[0]
  258. );
  259. console.log('\n[4] 环节匹配(多级匹配:code / 别名 / 名称 / 双向包含)');
  260. const m = es.matchElectricityFlowNode('HO_PERMIT_ELECTRICITY_THREETH');
  261. check('按任务定义 key 精确命中', m && m.key === 'gas_analysis', {
  262. key: m && m.key
  263. });
  264. check(
  265. '按节点名称命中',
  266. (es.matchElectricityFlowNode('现场安全措施确认') || {}).key === 'measure'
  267. );
  268. check(
  269. '按别名命中',
  270. (es.matchElectricityFlowNode('气体检测') || {}).key === 'gas_analysis'
  271. );
  272. check(
  273. '按平台自定义 key 反向包含命中',
  274. (es.matchElectricityFlowNode('el_gas_analysis') || {}).key === 'gas_analysis'
  275. );
  276. check(
  277. '流程定义 key / 一级作业申请 key 不误命中后续环节',
  278. es.matchElectricityFlowNode('HO_PERMIT_ELECTRICITY') === null &&
  279. es.matchElectricityFlowNode('HO_PERMIT_ELECTRICITY_ONETH') === null
  280. );
  281. check('空标识返回 null', es.matchElectricityFlowNode('', null) === null);
  282. check(
  283. '「危害辨识」不会被「风险辨识」抢走',
  284. (es.matchElectricityFlowNode('危害辨识') || {}).key === 'hazard' &&
  285. (es.matchElectricityFlowNode('风险辨识') || {}).key === 'risk'
  286. );
  287. check(
  288. '「危险辨识」归到风险辨识节点',
  289. (es.matchElectricityFlowNode('危险辨识') || {}).key === 'risk'
  290. );
  291. console.log('\n[5] 风险辨识 / 危害辨识 取值 key 不冲突');
  292. const riskNode = nodeOf('risk');
  293. const hazardNode = nodeOf('hazard');
  294. const riskKeys = riskNode.fields.map((f) => f.key);
  295. const hazardKeys = hazardNode.fields.map((f) => f.key);
  296. check(
  297. '两个环节的字段 key 无交集',
  298. riskKeys.every((k) => !hazardKeys.includes(k)),
  299. { riskKeys, hazardKeys }
  300. );
  301. check(
  302. '危害辨识使用 hazard_ 前缀',
  303. hazardKeys.every((k) => k.startsWith('hazard_')),
  304. hazardKeys
  305. );
  306. const hazardAlias = hazardNode.fields.flatMap((f) => f.alias || []);
  307. check(
  308. '危害辨识的别名不会命中风险辨识的取值 key',
  309. !hazardAlias.includes('risk_result') &&
  310. !hazardAlias.includes('risk_result_desc')
  311. );
  312. console.log('\n[6] 运行时辅助(回显 / payload / 流程变量)');
  313. const measureNode = nodeOf('measure');
  314. const source = {
  315. measure_list: [
  316. { measure_content: 'x', measure_confirm: '2', measure_confirmer: '张三' }
  317. ],
  318. measure_preparer_sign: '/tmp/a.png'
  319. };
  320. const loaded = est.loadNodeValues(measureNode, [source]);
  321. check(
  322. '回显:按自身 key 取到已保存内容',
  323. loaded.measure_list.length === 1 &&
  324. loaded.measure_preparer_sign === '/tmp/a.png'
  325. );
  326. check(
  327. '回显:未保存的字段为 undefined',
  328. loaded.measure_confirmer_sign === undefined
  329. );
  330. const payload = est.buildNodePayload(measureNode, {
  331. measure_list: [{ measure_content: 'x' }],
  332. measure_preparer_sign: '/tmp/a.png'
  333. });
  334. check(
  335. 'payload:保留原始类型(数组不转字符串)',
  336. Array.isArray(payload.measure_list) && payload.measure_list.length === 1
  337. );
  338. check(
  339. 'payload:空值不写入',
  340. !('measure_confirmer_sign' in payload) && !('measure_confirm' in payload)
  341. );
  342. const vars = est.buildNodeVariables(measureNode, {
  343. measure_list: [{ measure_content: 'x' }],
  344. measure_preparer_sign: '/tmp/a.png'
  345. });
  346. check(
  347. '流程变量:子表转 JSON 字符串、其余转字符串',
  348. typeof vars.measure_list === 'string' &&
  349. vars.measure_preparer_sign === '/tmp/a.png'
  350. );
  351. const riskVars = est.buildNodeVariables(riskNode, {
  352. risk_result: ['5', '8'],
  353. risk_result_desc: '触电、火灾'
  354. });
  355. check(
  356. '流程变量:多选值用逗号连接',
  357. riskVars.risk_result === '5,8' && riskVars.risk_result_desc === '触电、火灾'
  358. );
  359. const hazardVars = est.buildNodeVariables(hazardNode, {
  360. hazard_result: ['5'],
  361. hazard_result_desc: '触电'
  362. });
  363. check(
  364. '流程变量:风险辨识与危害辨识各自独立',
  365. riskVars.risk_result === '5,8' && hazardVars.hazard_result === '5'
  366. );
  367. console.log('\n[7] 组件模板与脚本一致性(模板调用的方法必须已在组件中定义)');
  368. const compiler = require('vue-template-compiler');
  369. const parser = require('@babel/parser');
  370. /**
  371. * 收集组件脚本里已定义的成员名(props / data / computed / methods)
  372. * @param {string} script script 源码
  373. * @returns {Set<string>} 成员名集合
  374. */
  375. function collectComponentMembers(script) {
  376. const names = new Set();
  377. const ast = parser.parse(script, {
  378. sourceType: 'module',
  379. plugins: [
  380. 'optionalChaining',
  381. 'objectRestSpread',
  382. 'nullishCoalescingOperator'
  383. ]
  384. });
  385. let component = null;
  386. for (const node of ast.program.body) {
  387. if (node.type === 'ExportDefaultDeclaration') {
  388. component = node.declaration;
  389. }
  390. }
  391. const collectObjectKeys = (obj) => {
  392. ((obj && obj.properties) || []).forEach((prop) => {
  393. if (!prop.key) {
  394. return;
  395. }
  396. names.add(prop.key.name || prop.key.value);
  397. });
  398. };
  399. ((component && component.properties) || []).forEach((prop) => {
  400. if (!prop.key) {
  401. return;
  402. }
  403. const key = prop.key.name;
  404. const value = prop.value;
  405. if (key === 'props' || key === 'methods' || key === 'computed') {
  406. collectObjectKeys(value);
  407. return;
  408. }
  409. if (key === 'data') {
  410. // data() { return { ... } }
  411. const body = value && value.body;
  412. if (!body) {
  413. return;
  414. }
  415. const ret = body.body.find(
  416. (stmt) => stmt.type === 'ReturnStatement' && stmt.argument
  417. );
  418. if (ret) {
  419. collectObjectKeys(ret.argument);
  420. }
  421. }
  422. });
  423. return names;
  424. }
  425. /**
  426. * 取模板里被调用的成员名
  427. *
  428. * 注意:Vue 2 编译后的 render 用 with(this) 解析标识符,
  429. * 方法调用编译成裸函数名(如 getRows(field)),不带 vm. 前缀,
  430. * 因此这里直接扫描模板表达式,而不是扫描编译产物。
  431. *
  432. * @param {string} template 模板源码
  433. * @returns {object} { calls, errors }
  434. */
  435. function collectTemplateCalls(template) {
  436. const calls = new Set();
  437. // 1) 指令 / 事件绑定属性值: :xxx="expr"、v-xxx="expr"、v-xxx:arg="expr"、@xxx="expr"
  438. // 2) 插值 {{ expr }}
  439. const attrRe = /(?:[:@][\w.-]+|v-[\w-]+(?::[\w.-]+)?)\s*=\s*"([^"]*)"/g;
  440. const interpRe = /\{\{([\s\S]*?)\}\}/g;
  441. const exprs = [];
  442. let m;
  443. while ((m = attrRe.exec(template)) !== null) {
  444. exprs.push(m[1]);
  445. }
  446. while ((m = interpRe.exec(template)) !== null) {
  447. exprs.push(m[1]);
  448. }
  449. // 全局函数与 Vue 内部函数,不属于组件成员
  450. const globals = new Set([
  451. 'String',
  452. 'Number',
  453. 'Boolean',
  454. 'Array',
  455. 'Object',
  456. 'JSON',
  457. 'Math',
  458. 'Date',
  459. 'parseInt',
  460. 'parseFloat',
  461. 'isNaN',
  462. 'require'
  463. ]);
  464. // 标识符后紧跟 "(" 即为函数调用;排除属性访问(xxx.method())与内部变量
  465. const callRe = /(^|[^.\w$])([A-Za-z_$][\w$]*)\s*\(/g;
  466. exprs.forEach((expr) => {
  467. let hit;
  468. callRe.lastIndex = 0;
  469. while ((hit = callRe.exec(expr)) !== null) {
  470. const name = hit[2];
  471. if (name.startsWith('$') || name.startsWith('_') || globals.has(name)) {
  472. continue;
  473. }
  474. calls.add(name);
  475. }
  476. });
  477. return { calls, errors: compiler.compile(template).errors || [] };
  478. }
  479. const sfcPath = path.join(SRC, 'components/electricityFlow.vue');
  480. const sfcRaw = fs.readFileSync(sfcPath, 'utf8');
  481. const sfc = compiler.parseComponent(sfcRaw);
  482. const templateCheck = collectTemplateCalls(sfc.template.content);
  483. const members = collectComponentMembers(sfc.script.content);
  484. check(
  485. 'electricityFlow.vue 模板编译无错误',
  486. templateCheck.errors.length === 0,
  487. templateCheck.errors
  488. );
  489. const missing = [...templateCheck.calls].filter((name) => !members.has(name));
  490. check(
  491. `模板调用的 ${templateCheck.calls.size} 个方法均已定义`,
  492. missing.length === 0,
  493. missing
  494. );
  495. check(
  496. '模板确实调用了 getConfirmColumn / confirmAll(子表「全部确认」按钮)',
  497. templateCheck.calls.has('getConfirmColumn') &&
  498. templateCheck.calls.has('confirmAll')
  499. );
  500. check(
  501. '未识别到环节时展示当前任务名/ key(便于排查流程模型配置)',
  502. templateCheck.calls.has('taskLabel') || /taskLabel/.test(sfcRaw)
  503. );
  504. check(
  505. '子表日期列渲染为日期时间选择器(el-date-picker)',
  506. /<el-date-picker[^>]*column\.type === 'date'/.test(sfcRaw)
  507. );
  508. check(
  509. '组件引用的是临时用电作业自己的节点/任务文件',
  510. /from '\.\.\/electricityFlowNodes'/.test(sfc.script.content) &&
  511. /from '\.\.\/electricityFlowTask'/.test(sfc.script.content) &&
  512. !/from '\.\.\/heightFlow/.test(sfc.script.content) &&
  513. !/from '\.\.\/hoistingFlow/.test(sfc.script.content) &&
  514. !/from '\.\.\/confinedSpaceFlow/.test(sfc.script.content)
  515. );
  516. check(
  517. '组件样式与受限空间实现保持同一套类名(scoped,不影响其它组件)',
  518. /current-node-box/.test(sfcRaw) &&
  519. /all-nodes-box/.test(sfcRaw) &&
  520. /node-form-header/.test(sfcRaw)
  521. );
  522. console.log('\n[8] detail.vue:五类作业的分支互斥且都已接入');
  523. const detailSrc = fs.readFileSync(path.join(SRC, 'detail.vue'), 'utf8');
  524. check(
  525. 'detail.vue:按作业类型分发到各自的后续环节组件',
  526. /isConfinedSpaceWork[\s\S]{0,200}getConfinedTableValue/.test(detailSrc) &&
  527. /isHeightWork[\s\S]{0,200}getHeightTableValue/.test(detailSrc) &&
  528. /isHeightWork[\s\S]{0,200}saveHeight/.test(detailSrc) &&
  529. /isHoistingWork[\s\S]{0,200}getHoistingTableValue/.test(detailSrc) &&
  530. /isHoistingWork[\s\S]{0,200}saveHoisting/.test(detailSrc) &&
  531. /isElectricityWork[\s\S]{0,200}getElectricityTableValue/.test(detailSrc) &&
  532. /isElectricityWork[\s\S]{0,200}saveElectricity/.test(detailSrc)
  533. );
  534. check(
  535. 'detail.vue:临时用电作业的 tab 与组件已注册',
  536. /name="flowElectricity"/.test(detailSrc) &&
  537. /<electricity-flow/.test(detailSrc) &&
  538. /import ElectricityFlow from '\.\/components\/electricityFlow\.vue'/.test(
  539. detailSrc
  540. )
  541. );
  542. check(
  543. 'detail.vue:作业票模板已包含临时用电作业(templates.js key = electricity)',
  544. /key:\s*'electricity'/.test(
  545. fs.readFileSync(path.join(SRC, 'templates.js'), 'utf8')
  546. )
  547. );
  548. check(
  549. 'detail.vue:默认 tab 是「作业票信息」(不再自动切到「后续环节」)',
  550. /activeTab:\s*'info'/.test(detailSrc) &&
  551. !/switchToFlowTab/.test(detailSrc)
  552. );
  553. console.log('\n' + (failed ? 'FAILED: ' + failed + ' 项' : 'ALL PASSED'));
  554. process.exit(failed ? 1 : 0);