xsh_1997 17 godzin temu
rodzic
commit
aa68d960cc

+ 336 - 0
doc/_md2docx/convert.js

@@ -0,0 +1,336 @@
1
+/**
2
+ * One-off: convert 企业端操作流程说明.md -> .docx
3
+ */
4
+const fs = require('fs')
5
+const path = require('path')
6
+const {
7
+	Document,
8
+	Packer,
9
+	Paragraph,
10
+	TextRun,
11
+	HeadingLevel,
12
+	Table,
13
+	TableRow,
14
+	TableCell,
15
+	WidthType,
16
+	BorderStyle,
17
+	ImageRun,
18
+	AlignmentType,
19
+	ShadingType
20
+} = require('docx')
21
+
22
+const DOC_DIR = path.resolve(__dirname, '..')
23
+const MD_PATH = path.join(DOC_DIR, '企业端操作流程说明.md')
24
+const OUT_PATH = path.join(DOC_DIR, '企业端操作流程说明.docx')
25
+
26
+function parseInline(text) {
27
+	const runs = []
28
+	const re = /(\*\*[^*]+\*\*|`[^`]+`)/g
29
+	let last = 0
30
+	let m
31
+	while ((m = re.exec(text)) !== null) {
32
+		if (m.index > last) {
33
+			runs.push(new TextRun({ text: text.slice(last, m.index), size: 21 }))
34
+		}
35
+		const token = m[0]
36
+		if (token.startsWith('**')) {
37
+			runs.push(new TextRun({ text: token.slice(2, -2), bold: true, size: 21 }))
38
+		} else {
39
+			runs.push(new TextRun({ text: token.slice(1, -1), font: 'Consolas', size: 18 }))
40
+		}
41
+		last = m.index + token.length
42
+	}
43
+	if (last < text.length) {
44
+		runs.push(new TextRun({ text: text.slice(last), size: 21 }))
45
+	}
46
+	if (!runs.length) runs.push(new TextRun({ text: text || '', size: 21 }))
47
+	return runs
48
+}
49
+
50
+function para(text, opts = {}) {
51
+	return new Paragraph({
52
+		spacing: { after: 120, line: 360 },
53
+		...opts,
54
+		children: typeof text === 'string' ? parseInline(text) : text
55
+	})
56
+}
57
+
58
+function heading(text, level) {
59
+	const map = {
60
+		1: HeadingLevel.HEADING_1,
61
+		2: HeadingLevel.HEADING_2,
62
+		3: HeadingLevel.HEADING_3,
63
+		4: HeadingLevel.HEADING_4
64
+	}
65
+	return new Paragraph({
66
+		heading: map[level] || HeadingLevel.HEADING_2,
67
+		spacing: { before: 240, after: 160 },
68
+		children: [new TextRun({ text, bold: true, size: level === 1 ? 32 : level === 2 ? 28 : 24 })]
69
+	})
70
+}
71
+
72
+function quotePara(text) {
73
+	return new Paragraph({
74
+		spacing: { after: 100 },
75
+		indent: { left: 240 },
76
+		border: {
77
+			left: { style: BorderStyle.SINGLE, size: 18, color: '93C5FD', space: 8 }
78
+		},
79
+		children: parseInline(text.replace(/^>\s?/, ''))
80
+	})
81
+}
82
+
83
+function codePara(line) {
84
+	return new Paragraph({
85
+		spacing: { after: 40 },
86
+		shading: { type: ShadingType.CLEAR, fill: 'F3F4F6' },
87
+		children: [new TextRun({ text: line || ' ', font: 'Consolas', size: 18 })]
88
+	})
89
+}
90
+
91
+function cell(text, opts = {}) {
92
+	const isHeader = !!opts.header
93
+	return new TableCell({
94
+		width: { size: opts.width || 3000, type: WidthType.DXA },
95
+		shading: isHeader ? { type: ShadingType.CLEAR, fill: 'DBEAFE' } : undefined,
96
+		borders: {
97
+			top: { style: BorderStyle.SINGLE, size: 4, color: 'CBD5E1' },
98
+			bottom: { style: BorderStyle.SINGLE, size: 4, color: 'CBD5E1' },
99
+			left: { style: BorderStyle.SINGLE, size: 4, color: 'CBD5E1' },
100
+			right: { style: BorderStyle.SINGLE, size: 4, color: 'CBD5E1' }
101
+		},
102
+		children: [
103
+			new Paragraph({
104
+				spacing: { after: 40 },
105
+				children: parseInline(String(text || '').trim()).map((r) => {
106
+					if (isHeader) {
107
+						return new TextRun({ text: r.text || '', bold: true, size: 20 })
108
+					}
109
+					return r
110
+				})
111
+			})
112
+		]
113
+	})
114
+}
115
+
116
+function parseTable(lines) {
117
+	const rows = lines
118
+		.filter((l) => !/^\|?\s*-+/.test(l.replace(/\|/g, '').trim() === '' ? 'x' : l) && !/^\|[\s\-:|]+\|$/.test(l))
119
+		.filter((l) => l.includes('|'))
120
+		.map((l) =>
121
+			l
122
+				.replace(/^\|/, '')
123
+				.replace(/\|$/, '')
124
+				.split('|')
125
+				.map((c) => c.trim())
126
+		)
127
+		.filter((cols) => !cols.every((c) => /^:?-+:?$/.test(c)))
128
+
129
+	if (!rows.length) return null
130
+	const colCount = Math.max(...rows.map((r) => r.length))
131
+	const width = Math.floor(9000 / colCount)
132
+	return new Table({
133
+		width: { size: 9000, type: WidthType.DXA },
134
+		rows: rows.map((cols, idx) => {
135
+			while (cols.length < colCount) cols.push('')
136
+			return new TableRow({
137
+				children: cols.map((c) => cell(c, { header: idx === 0, width }))
138
+			})
139
+		})
140
+	})
141
+}
142
+
143
+async function main() {
144
+	const md = fs.readFileSync(MD_PATH, 'utf8')
145
+	const lines = md.replace(/\r\n/g, '\n').split('\n')
146
+	const children = []
147
+	let i = 0
148
+
149
+	while (i < lines.length) {
150
+		const line = lines[i]
151
+		const trimmed = line.trim()
152
+
153
+		if (!trimmed) {
154
+			i++
155
+			continue
156
+		}
157
+
158
+		if (trimmed === '---') {
159
+			children.push(
160
+				new Paragraph({
161
+					spacing: { before: 120, after: 120 },
162
+					border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: 'E5E7EB', space: 1 } },
163
+					children: []
164
+				})
165
+			)
166
+			i++
167
+			continue
168
+		}
169
+
170
+		if (trimmed.startsWith('```')) {
171
+			i++
172
+			while (i < lines.length && !lines[i].trim().startsWith('```')) {
173
+				children.push(codePara(lines[i]))
174
+				i++
175
+			}
176
+			i++ // closing ```
177
+			children.push(new Paragraph({ spacing: { after: 120 }, children: [] }))
178
+			continue
179
+		}
180
+
181
+		if (trimmed.startsWith('|')) {
182
+			const tableLines = []
183
+			while (i < lines.length && lines[i].trim().startsWith('|')) {
184
+				tableLines.push(lines[i].trim())
185
+				i++
186
+			}
187
+			const table = parseTable(tableLines)
188
+			if (table) {
189
+				children.push(table)
190
+				children.push(new Paragraph({ spacing: { after: 160 }, children: [] }))
191
+			}
192
+			continue
193
+		}
194
+
195
+		const img = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)/)
196
+		if (img) {
197
+			const imgPath = path.resolve(DOC_DIR, img[2])
198
+			if (fs.existsSync(imgPath)) {
199
+				const buf = fs.readFileSync(imgPath)
200
+				children.push(
201
+					new Paragraph({
202
+						alignment: AlignmentType.CENTER,
203
+						spacing: { before: 160, after: 160 },
204
+						children: [
205
+							new ImageRun({
206
+								data: buf,
207
+								transformation: { width: 420, height: 560 },
208
+								type: 'png'
209
+							})
210
+						]
211
+					})
212
+				)
213
+				children.push(
214
+					new Paragraph({
215
+						alignment: AlignmentType.CENTER,
216
+						spacing: { after: 200 },
217
+						children: [new TextRun({ text: img[1] || '流程图', italics: true, size: 18, color: '6B7280' })]
218
+					})
219
+				)
220
+			} else {
221
+				children.push(para(`[图片缺失] ${img[1]} (${img[2]})`))
222
+			}
223
+			i++
224
+			continue
225
+		}
226
+
227
+		if (trimmed.startsWith('# ')) {
228
+			children.push(heading(trimmed.slice(2), 1))
229
+			i++
230
+			continue
231
+		}
232
+		if (trimmed.startsWith('## ')) {
233
+			children.push(heading(trimmed.slice(3), 2))
234
+			i++
235
+			continue
236
+		}
237
+		if (trimmed.startsWith('### ')) {
238
+			children.push(heading(trimmed.slice(4), 3))
239
+			i++
240
+			continue
241
+		}
242
+		if (trimmed.startsWith('#### ')) {
243
+			children.push(heading(trimmed.slice(5), 4))
244
+			i++
245
+			continue
246
+		}
247
+
248
+		if (trimmed.startsWith('>')) {
249
+			while (i < lines.length && lines[i].trim().startsWith('>')) {
250
+				children.push(quotePara(lines[i].trim()))
251
+				i++
252
+			}
253
+			continue
254
+		}
255
+
256
+		if (/^[-*]\s+/.test(trimmed)) {
257
+			children.push(
258
+				new Paragraph({
259
+					spacing: { after: 80 },
260
+					indent: { left: 360 },
261
+					children: [
262
+						new TextRun({ text: '• ', size: 21 }),
263
+						...parseInline(trimmed.replace(/^[-*]\s+/, ''))
264
+					]
265
+				})
266
+			)
267
+			i++
268
+			continue
269
+		}
270
+
271
+		if (/^\d+\.\s+/.test(trimmed)) {
272
+			const num = trimmed.match(/^(\d+)\.\s+/)[1]
273
+			children.push(
274
+				new Paragraph({
275
+					spacing: { after: 80 },
276
+					indent: { left: 240 },
277
+					children: [
278
+						new TextRun({ text: `${num}. `, size: 21 }),
279
+						...parseInline(trimmed.replace(/^\d+\.\s+/, ''))
280
+					]
281
+				})
282
+			)
283
+			i++
284
+			continue
285
+		}
286
+
287
+		if (trimmed.startsWith('*') && trimmed.endsWith('*') && !trimmed.startsWith('**')) {
288
+			children.push(
289
+				new Paragraph({
290
+					spacing: { after: 100 },
291
+					children: [new TextRun({ text: trimmed.replace(/^\*|\*$/g, ''), italics: true, size: 18, color: '6B7280' })]
292
+				})
293
+			)
294
+			i++
295
+			continue
296
+		}
297
+
298
+		children.push(para(trimmed))
299
+		i++
300
+	}
301
+
302
+	const doc = new Document({
303
+		styles: {
304
+			default: {
305
+				document: {
306
+					styles: [
307
+						{
308
+							id: 'Normal',
309
+							run: { font: 'Microsoft YaHei', size: 21 }
310
+						}
311
+					]
312
+				}
313
+			}
314
+		},
315
+		sections: [
316
+			{
317
+				properties: {
318
+					page: {
319
+						margin: { top: 720, right: 720, bottom: 720, left: 720 }
320
+					}
321
+				},
322
+				children
323
+			}
324
+		]
325
+	})
326
+
327
+	const buffer = await Packer.toBuffer(doc)
328
+	fs.writeFileSync(OUT_PATH, buffer)
329
+	console.log('OK ->', OUT_PATH)
330
+	console.log('bytes', buffer.length)
331
+}
332
+
333
+main().catch((e) => {
334
+	console.error(e)
335
+	process.exit(1)
336
+})

+ 307 - 0
doc/_md2docx/package-lock.json

@@ -0,0 +1,307 @@
1
+{
2
+  "name": "_md2docx",
3
+  "version": "1.0.0",
4
+  "lockfileVersion": 2,
5
+  "requires": true,
6
+  "packages": {
7
+    "": {
8
+      "name": "_md2docx",
9
+      "version": "1.0.0",
10
+      "license": "ISC",
11
+      "dependencies": {
12
+        "docx": "^8.5.0"
13
+      }
14
+    },
15
+    "node_modules/@types/node": {
16
+      "version": "20.19.43",
17
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz",
18
+      "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
19
+      "dependencies": {
20
+        "undici-types": "~6.21.0"
21
+      }
22
+    },
23
+    "node_modules/core-util-is": {
24
+      "version": "1.0.3",
25
+      "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
26
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
27
+    },
28
+    "node_modules/docx": {
29
+      "version": "8.5.0",
30
+      "resolved": "https://registry.npmmirror.com/docx/-/docx-8.5.0.tgz",
31
+      "integrity": "sha512-4SbcbedPXTciySXiSnNNLuJXpvxFe5nqivbiEHXyL8P/w0wx2uW7YXNjnYgjW0e2e6vy+L/tMISU/oAiXCl57Q==",
32
+      "dependencies": {
33
+        "@types/node": "^20.3.1",
34
+        "jszip": "^3.10.1",
35
+        "nanoid": "^5.0.4",
36
+        "xml": "^1.0.1",
37
+        "xml-js": "^1.6.8"
38
+      },
39
+      "engines": {
40
+        "node": ">=10"
41
+      }
42
+    },
43
+    "node_modules/immediate": {
44
+      "version": "3.0.6",
45
+      "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
46
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
47
+    },
48
+    "node_modules/inherits": {
49
+      "version": "2.0.4",
50
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
51
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
52
+    },
53
+    "node_modules/isarray": {
54
+      "version": "1.0.0",
55
+      "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz",
56
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
57
+    },
58
+    "node_modules/jszip": {
59
+      "version": "3.10.1",
60
+      "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
61
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
62
+      "dependencies": {
63
+        "lie": "~3.3.0",
64
+        "pako": "~1.0.2",
65
+        "readable-stream": "~2.3.6",
66
+        "setimmediate": "^1.0.5"
67
+      }
68
+    },
69
+    "node_modules/lie": {
70
+      "version": "3.3.0",
71
+      "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz",
72
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
73
+      "dependencies": {
74
+        "immediate": "~3.0.5"
75
+      }
76
+    },
77
+    "node_modules/nanoid": {
78
+      "version": "5.1.16",
79
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.16.tgz",
80
+      "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==",
81
+      "funding": [
82
+        {
83
+          "type": "github",
84
+          "url": "https://github.com/sponsors/ai"
85
+        }
86
+      ],
87
+      "bin": {
88
+        "nanoid": "bin/nanoid.js"
89
+      },
90
+      "engines": {
91
+        "node": "^18 || >=20"
92
+      }
93
+    },
94
+    "node_modules/pako": {
95
+      "version": "1.0.11",
96
+      "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
97
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
98
+    },
99
+    "node_modules/process-nextick-args": {
100
+      "version": "2.0.1",
101
+      "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
102
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
103
+    },
104
+    "node_modules/readable-stream": {
105
+      "version": "2.3.8",
106
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
107
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
108
+      "dependencies": {
109
+        "core-util-is": "~1.0.0",
110
+        "inherits": "~2.0.3",
111
+        "isarray": "~1.0.0",
112
+        "process-nextick-args": "~2.0.0",
113
+        "safe-buffer": "~5.1.1",
114
+        "string_decoder": "~1.1.1",
115
+        "util-deprecate": "~1.0.1"
116
+      }
117
+    },
118
+    "node_modules/safe-buffer": {
119
+      "version": "5.1.2",
120
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
121
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
122
+    },
123
+    "node_modules/sax": {
124
+      "version": "1.6.1",
125
+      "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz",
126
+      "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
127
+      "engines": {
128
+        "node": ">=11.0.0"
129
+      }
130
+    },
131
+    "node_modules/setimmediate": {
132
+      "version": "1.0.5",
133
+      "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz",
134
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
135
+    },
136
+    "node_modules/string_decoder": {
137
+      "version": "1.1.1",
138
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
139
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
140
+      "dependencies": {
141
+        "safe-buffer": "~5.1.0"
142
+      }
143
+    },
144
+    "node_modules/undici-types": {
145
+      "version": "6.21.0",
146
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
147
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
148
+    },
149
+    "node_modules/util-deprecate": {
150
+      "version": "1.0.2",
151
+      "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
152
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
153
+    },
154
+    "node_modules/xml": {
155
+      "version": "1.0.1",
156
+      "resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz",
157
+      "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="
158
+    },
159
+    "node_modules/xml-js": {
160
+      "version": "1.6.11",
161
+      "resolved": "https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz",
162
+      "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
163
+      "dependencies": {
164
+        "sax": "^1.2.4"
165
+      },
166
+      "bin": {
167
+        "xml-js": "bin/cli.js"
168
+      }
169
+    }
170
+  },
171
+  "dependencies": {
172
+    "@types/node": {
173
+      "version": "20.19.43",
174
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz",
175
+      "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
176
+      "requires": {
177
+        "undici-types": "~6.21.0"
178
+      }
179
+    },
180
+    "core-util-is": {
181
+      "version": "1.0.3",
182
+      "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
183
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
184
+    },
185
+    "docx": {
186
+      "version": "8.5.0",
187
+      "resolved": "https://registry.npmmirror.com/docx/-/docx-8.5.0.tgz",
188
+      "integrity": "sha512-4SbcbedPXTciySXiSnNNLuJXpvxFe5nqivbiEHXyL8P/w0wx2uW7YXNjnYgjW0e2e6vy+L/tMISU/oAiXCl57Q==",
189
+      "requires": {
190
+        "@types/node": "^20.3.1",
191
+        "jszip": "^3.10.1",
192
+        "nanoid": "^5.0.4",
193
+        "xml": "^1.0.1",
194
+        "xml-js": "^1.6.8"
195
+      }
196
+    },
197
+    "immediate": {
198
+      "version": "3.0.6",
199
+      "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
200
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
201
+    },
202
+    "inherits": {
203
+      "version": "2.0.4",
204
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
205
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
206
+    },
207
+    "isarray": {
208
+      "version": "1.0.0",
209
+      "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz",
210
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
211
+    },
212
+    "jszip": {
213
+      "version": "3.10.1",
214
+      "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
215
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
216
+      "requires": {
217
+        "lie": "~3.3.0",
218
+        "pako": "~1.0.2",
219
+        "readable-stream": "~2.3.6",
220
+        "setimmediate": "^1.0.5"
221
+      }
222
+    },
223
+    "lie": {
224
+      "version": "3.3.0",
225
+      "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz",
226
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
227
+      "requires": {
228
+        "immediate": "~3.0.5"
229
+      }
230
+    },
231
+    "nanoid": {
232
+      "version": "5.1.16",
233
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.16.tgz",
234
+      "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="
235
+    },
236
+    "pako": {
237
+      "version": "1.0.11",
238
+      "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
239
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
240
+    },
241
+    "process-nextick-args": {
242
+      "version": "2.0.1",
243
+      "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
244
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
245
+    },
246
+    "readable-stream": {
247
+      "version": "2.3.8",
248
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
249
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
250
+      "requires": {
251
+        "core-util-is": "~1.0.0",
252
+        "inherits": "~2.0.3",
253
+        "isarray": "~1.0.0",
254
+        "process-nextick-args": "~2.0.0",
255
+        "safe-buffer": "~5.1.1",
256
+        "string_decoder": "~1.1.1",
257
+        "util-deprecate": "~1.0.1"
258
+      }
259
+    },
260
+    "safe-buffer": {
261
+      "version": "5.1.2",
262
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
263
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
264
+    },
265
+    "sax": {
266
+      "version": "1.6.1",
267
+      "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.1.tgz",
268
+      "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="
269
+    },
270
+    "setimmediate": {
271
+      "version": "1.0.5",
272
+      "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz",
273
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
274
+    },
275
+    "string_decoder": {
276
+      "version": "1.1.1",
277
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
278
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
279
+      "requires": {
280
+        "safe-buffer": "~5.1.0"
281
+      }
282
+    },
283
+    "undici-types": {
284
+      "version": "6.21.0",
285
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
286
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
287
+    },
288
+    "util-deprecate": {
289
+      "version": "1.0.2",
290
+      "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
291
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
292
+    },
293
+    "xml": {
294
+      "version": "1.0.1",
295
+      "resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz",
296
+      "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="
297
+    },
298
+    "xml-js": {
299
+      "version": "1.6.11",
300
+      "resolved": "https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz",
301
+      "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
302
+      "requires": {
303
+        "sax": "^1.2.4"
304
+      }
305
+    }
306
+  }
307
+}

+ 15 - 0
doc/_md2docx/package.json

@@ -0,0 +1,15 @@
1
+{
2
+  "name": "_md2docx",
3
+  "version": "1.0.0",
4
+  "description": "",
5
+  "main": "index.js",
6
+  "scripts": {
7
+    "test": "echo \"Error: no test specified\" && exit 1"
8
+  },
9
+  "keywords": [],
10
+  "author": "",
11
+  "license": "ISC",
12
+  "dependencies": {
13
+    "docx": "^8.5.0"
14
+  }
15
+}

BIN
doc/md-to-docx-1.6.1.tgz


BIN
doc/screenshots/00-enterprise-operation-flowchart.png


+ 24 - 0
doc/screenshots/README.md

@@ -0,0 +1,24 @@
1
+# 操作流程截图存放目录
2
+
3
+## 已有
4
+
5
+- `00-enterprise-operation-flowchart.png`:总流程图(从登录到办结)
6
+
7
+## 待你补拍
8
+
9
+请按 `企业端操作流程说明.md` 中的清单截取,例如:
10
+
11
+- `01-login-enterprise.png`
12
+- `04-enterprise-home.png`
13
+- `07-workflow-card.png`
14
+- …
15
+
16
+截好后,在 markdown 对应 `【截图 xx】` 下方增加:
17
+
18
+```markdown
19
+![说明](./screenshots/01-login-enterprise.png)
20
+```
21
+
22
+## 可交互流程图
23
+
24
+用浏览器打开上一级目录的 `企业端操作流程图.html`,可查看带分支判断的完整流程图。

+ 171 - 0
doc/企业端操作流程图.html

@@ -0,0 +1,171 @@
1
+<!DOCTYPE html>
2
+<html lang="zh-CN">
3
+<head>
4
+  <meta charset="UTF-8" />
5
+  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+  <title>慧用工 · 企业端操作流程图</title>
7
+  <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
8
+  <style>
9
+    * { box-sizing: border-box; }
10
+    body {
11
+      margin: 0;
12
+      font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
13
+      background: #f5f7fb;
14
+      color: #1f2937;
15
+    }
16
+    header {
17
+      padding: 28px 24px 12px;
18
+      text-align: center;
19
+    }
20
+    header h1 {
21
+      margin: 0 0 8px;
22
+      font-size: 24px;
23
+      font-weight: 700;
24
+    }
25
+    header p {
26
+      margin: 0;
27
+      color: #6b7280;
28
+      font-size: 14px;
29
+    }
30
+    .wrap {
31
+      max-width: 960px;
32
+      margin: 0 auto 48px;
33
+      padding: 0 16px;
34
+    }
35
+    .card {
36
+      background: #fff;
37
+      border-radius: 16px;
38
+      padding: 20px 12px 28px;
39
+      box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
40
+      overflow-x: auto;
41
+    }
42
+    .legend {
43
+      display: flex;
44
+      flex-wrap: wrap;
45
+      gap: 12px 20px;
46
+      justify-content: center;
47
+      margin: 16px 0 8px;
48
+      font-size: 13px;
49
+      color: #4b5563;
50
+    }
51
+    .legend span::before {
52
+      content: "";
53
+      display: inline-block;
54
+      width: 12px;
55
+      height: 12px;
56
+      border-radius: 3px;
57
+      margin-right: 6px;
58
+      vertical-align: -1px;
59
+    }
60
+    .l-step::before { background: #93c5fd; }
61
+    .l-decision::before { background: #fdba74; }
62
+    .l-done::before { background: #86efac; }
63
+    .l-optional::before { background: #ddd6fe; }
64
+    .tip {
65
+      margin-top: 20px;
66
+      padding: 14px 16px;
67
+      background: #eff6ff;
68
+      border-radius: 10px;
69
+      font-size: 13px;
70
+      line-height: 1.7;
71
+      color: #1e40af;
72
+    }
73
+    .mermaid { display: flex; justify-content: center; }
74
+  </style>
75
+</head>
76
+<body>
77
+  <header>
78
+    <h1>慧用工 · 企业端操作流程图</h1>
79
+    <p>从登录开始 → 确认方案 → 办理进度 → 结算开票办结</p>
80
+  </header>
81
+  <div class="wrap">
82
+    <div class="legend">
83
+      <span class="l-step">蓝色:公共步骤</span>
84
+      <span class="l-decision">橙色菱形:判断</span>
85
+      <span style="color:#0f766e"><span style="display:inline-block;width:12px;height:12px;background:#5eead4;border-radius:3px;margin-right:6px;vertical-align:-1px"></span>青绿:路线A 扫码登记</span>
86
+      <span style="color:#c2410c"><span style="display:inline-block;width:12px;height:12px;background:#fdba74;border-radius:3px;margin-right:6px;vertical-align:-1px"></span>橙色:路线B 临时工招聘</span>
87
+      <span class="l-done">绿色:办结</span>
88
+    </div>
89
+    <div class="card">
90
+      <pre class="mermaid">
91
+flowchart TD
92
+  S0([打开微信小程序]) --> S1[进入企业登录页]
93
+  S1 --> S1a{登录方式}
94
+  S1a -->|手机号+验证码| S2[登录成功]
95
+  S1a -->|账号+密码| S2
96
+
97
+  S2 --> H1[直接进入企业工作台 / 对话首页]
98
+  H1 -.->|未认证不拦截| GUEST[「我的」可显示游客/未认证态]
99
+
100
+  H1 --> O1{{可选:与AI助手对话说明用工需求}}
101
+  O1 --> D2[系统生成用工草稿卡片]
102
+  D2 --> D3[打开草稿补全人数/工期/地点/工种]
103
+  D3 --> D4[查看成本方案并确认此方案]
104
+  D4 --> W0[会话中出现「办理进度」卡片]
105
+
106
+  W0 --> P1[企业认证:上传执照/资料<br/>可与人员到位并行,可先跳过]
107
+  W0 --> P2{人员到位方式<br/>二选一,进度条只显示其一}
108
+
109
+  P2 -->|路线A| A0[【路线A · 扫码登记】]
110
+  A0 --> A1[打开「人员登记」]
111
+  A1 --> A2[分享登记二维码给临时工]
112
+  A2 --> A3[工人扫码完成登记]
113
+  A3 --> A4[查看进度至人数到位]
114
+  A4 --> MERGE[人员到位完成]
115
+
116
+  P2 -->|路线B| B0[【路线B · 临时工招聘】]
117
+  B0 --> B1[打开「临时工招聘」]
118
+  B1 --> B2[查看匹配条件与候选人]
119
+  B2 --> B3[按工种勾选人员并确认选择]
120
+  B3 --> B4[招聘完成]
121
+  B4 --> MERGE
122
+
123
+  P1 --> MERGE
124
+  MERGE --> T1[任务发布:填写工作时间后确认发布]
125
+  T1 --> WORK[工人到岗作业]
126
+  WORK --> SET[结算打款:核对结算确认单]
127
+  SET --> SET2[确认并支付]
128
+  SET2 --> INV[开票归档]
129
+  INV --> END([流程办结])
130
+
131
+  MSG[消息待办入口] -.->|结算相关消息| SET
132
+
133
+  classDef step fill:#DBEAFE,stroke:#3B82F6,color:#1E3A8A
134
+  classDef decision fill:#FFEDD5,stroke:#F97316,color:#9A3412
135
+  classDef optional fill:#EDE9FE,stroke:#8B5CF6,color:#5B21B6
136
+  classDef done fill:#DCFCE7,stroke:#22C55E,color:#14532D
137
+  classDef start fill:#F1F5F9,stroke:#64748B,color:#334155
138
+  classDef routeA fill:#CCFBF1,stroke:#0D9488,color:#115E59
139
+  classDef routeB fill:#FFEDD5,stroke:#EA580C,color:#9A3412
140
+
141
+  class S0,END start
142
+  class S1,S2,H1,D2,D3,D4,W0,P1,MERGE,T1,WORK,SET,SET2,INV,MSG,GUEST step
143
+  class S1a,P2 decision
144
+  class O1 optional
145
+  class A0,A1,A2,A3,A4 routeA
146
+  class B0,B1,B2,B3,B4 routeB
147
+      </pre>
148
+    </div>
149
+    <div class="tip">
150
+      <strong>阅读提示:</strong><br />
151
+      1. 企业登录成功后<strong>直接进工作台</strong>,不再判断「是否已企业登记」。<br />
152
+      2. 「企业认证」在确认方案后的办理进度卡中办理,可先跳过;未认证不挡登录进首页。<br />
153
+      3. 人员到位分两条路线:<strong>路线A 扫码登记</strong> / <strong>路线B 临时工招聘</strong>,一笔订单只走其一。<br />
154
+      4. 「任务发布」在人员到位完成后进行:填写工作时间并确认发布。<br />
155
+      5. 结算确认单可通过进度卡或消息待办打开,界面相同。<br />
156
+      6. 若本页图未显示,请用浏览器打开本 HTML(需能访问 jsDelivr CDN)。
157
+    </div>
158
+  </div>
159
+  <script>
160
+    mermaid.initialize({
161
+      startOnLoad: true,
162
+      theme: "base",
163
+      flowchart: { curve: "basis", htmlLabels: true, padding: 12 },
164
+      themeVariables: {
165
+        fontFamily: "PingFang SC, Microsoft YaHei, sans-serif",
166
+        fontSize: "14px"
167
+      }
168
+    });
169
+  </script>
170
+</body>
171
+</html>

+ 338 - 0
doc/企业端操作流程说明.docx

@@ -0,0 +1,338 @@
1
+慧用工 · 企业端操作流程说明(从登录起)
2
+--------------------------------------------------------------------------------
3
+ 适用对象:产品 / 业务领导审阅
4
+--------------------------------------------------------------------------------
5
+ 端:微信小程序 · 企业端
6
+--------------------------------------------------------------------------------
7
+ 范围:从登录 --> 确认方案 --> 办理进度各节点 --> 结算开票办结
8
+--------------------------------------------------------------------------------
9
+ 流程图:见下方总图;也可双击打开同目录 企业端操作流程图.html(浏览器可交互查看)
10
+--------------------------------------------------------------------------------
11
+
12
+一、总流程图(从登录开始)
13
+                                       
14
+                           企业端操作流程图
15
+--------------------------------------------------------------------------------
16
+ 若 Markdown 预览未显示图片,请直接打开:
17
+--------------------------------------------------------------------------------
18
+ - 图片:doc/screenshots/00-enterprise-operation-flowchart.png
19
+--------------------------------------------------------------------------------
20
+ - 可交互图:doc/企业端操作流程图.html(用浏览器打开)
21
+文字版总览(按时间顺序)
22
+打开小程序
23
+   ↓
24
+企业登录(验证码 或 账号密码)
25
+   ↓
26
+登录成功 --> 直接进入企业工作台 / 对话首页
27
+(未认证不拦截;「我的」可显示游客/未认证态)
28
+   ↓
29
+(可选)与 AI 对话说明用工需求 --> 生成用工草稿
30
+   ↓
31
+补全草稿 --> 对比成本方案 -->「确认此方案」
32
+   ↓
33
+出现「办理进度」卡片
34
+   ├── 企业认证(可与人员到位并行;可先跳过)
35
+   └── 人员到位方式(二选一,进度条只显示其一)
36
+         │
37
+         ├────【路线A · 扫码登记】────┐
38
+         │  打开人员登记              │
39
+         │  --> 分享登记二维码给临时工  │
40
+         │  --> 工人扫码完成登记        │
41
+         │  --> 查看进度至人数到位      │
42
+         │                            │
43
+         └────【路线B · 临时工招聘】──┤
44
+            打开临时工招聘            │
45
+            --> 查看匹配条件与候选人    │
46
+            --> 按工种勾选并确认选择    │
47
+            --> 招聘完成                │
48
+                                      ↓
49
+                          两条路线在此汇合
50
+                                      ↓
51
+任务发布(填写工作时间后确认发布)
52
+   ↓
53
+工人到岗作业
54
+   ↓
55
+结算打款(进度卡 或 消息待办)--> 确认并支付
56
+   ↓
57
+开票归档 --> 流程办结
58
+
59
+两条人员到位路线(务必分清)
60
+
61
+
62
+
63
+
64
+A
65
+扫码登记
66
+分享二维码,工人扫码登记,看到人数达标
67
+「人员登记」
68
+B
69
+临时工招聘
70
+在招聘弹层勾选匹配人员并确认选择
71
+「临时工招聘」
72
+
73
+--------------------------------------------------------------------------------
74
+ 一笔订单只会走其中一条;不是两步都要做。
75
+说明:登录不再强制「企业登记」
76
+
77
+
78
+登录后
79
+直接进工作台,不弹「是否已企业登记」门槛
80
+企业认证
81
+在确认方案后的 办理进度卡 里办理(也可先跳过)
82
+游客/未认证
83
+「我的」等处展示未认证说明,不挡登录进首页
84
+
85
+--------------------------------------------------------------------------------
86
+ 旧文档里「登录 --> 是否已企业登记」是过时画法,已删除。
87
+--------------------------------------------------------------------------------
88
+
89
+二、分步操作(按真实点击路径)
90
+第 1 步:打开小程序并进入企业登录
91
+ 1. 微信中打开「慧用工」小程序。
92
+ 2. 进入登录页,确认当前是 「企业登录」(不是「临时工登录」)。
93
+ 3. 若显示临时工登录,点右上角切换到企业登录。
94
+登录方式(二选一)
95
+
96
+
97
+手机号 + 验证码
98
+输入 11 位手机号 --> 获取验证码 --> 输入 6 位验证码 --> 登录
99
+账号 + 密码
100
+切换到密码登录 --> 输入手机号与密码 --> 登录
101
+
102
+登录成功后 直接进入企业工作台(不强制企业登记 / 企业认证)。
103
+【截图 01】企业登录页(标题为「企业登录」)
104
+建议文件名:screenshots/01-login-enterprise.png
105
+--------------------------------------------------------------------------------
106
+
107
+第 2 步:进入企业工作台(对话首页)
108
+ 1. 登录后进入企业工作台。
109
+ 2. 主界面为对话式首页:可与 AI 助手对话,也可查看历史会话中的草稿卡、办理进度卡。
110
+ 3. 底部 / 门户可进入「消息」「我的」等(结算待办常从消息进入)。
111
+ 4. 若尚未完成企业认证,「我的」可能显示未认证/游客态,但不影响先逛首页、先对话。
112
+【截图 02】企业对话首页(含输入框)
113
+建议文件名:02-enterprise-home.png
114
+--------------------------------------------------------------------------------
115
+
116
+第 3 步:对话生成用工草稿(可选但推荐)
117
+ 1. 在首页输入框描述用工需求,例如:
118
+  :: 需要多少人、什么工种
119
+  :: 工作地点、大概工期
120
+ 2. 发送后,系统在会话中生成 「用工草稿」卡片。
121
+ 3. 点击草稿卡片,打开草稿详情。
122
+--------------------------------------------------------------------------------
123
+ 也可从已有会话中打开历史草稿继续编辑;关键是最终要点「确认此方案」。
124
+【截图 03】会话中的用工草稿卡片
125
+建议文件名:03-draft-card.png
126
+--------------------------------------------------------------------------------
127
+
128
+第 4 步:补全草稿并确认方案(产生办理进度卡的前提)
129
+ 1. 在草稿弹层中核对 / 补全:
130
+  :: 工种与人数
131
+  :: 工期、地点等必填项
132
+ 2. 查看 成本对比方案(不同方案费用对比)。
133
+ 3. 选定方案后,点击 「确认此方案」。
134
+ 4. 确认成功后:
135
+  :: 草稿进入已确认状态
136
+  :: 会话中出现 「办理进度」卡片(横向步骤条)
137
+--------------------------------------------------------------------------------
138
+ 领导关注点:没有「确认此方案」,就不会进入后续办理闭环。
139
+【截图 04】草稿弹层 · 成本方案 +「确认此方案」
140
+【截图 05】确认后出现的办理进度卡
141
+建议文件名:04-draft-confirm.png / 05-workflow-card.png
142
+--------------------------------------------------------------------------------
143
+
144
+第 5 步:办理进度卡 · 企业认证
145
+ 1. 在办理进度卡中点到 「企业认证」。
146
+ 2. 若未认证:点「去认证」--> 上传营业执照 / 填写企业与法人信息 --> 提交。
147
+ 3. 认证完成后,本步显示「已完成」,可「查看认证」。
148
+ 4. 允许先「跳过」,后续再补;未认证时「我的」页会提示未认证状态。
149
+【截图 06】企业认证引导面板
150
+【截图 07】企业认证填写/上传弹层
151
+建议文件名:06-cert-panel.png / 07-cert-sheet.png
152
+--------------------------------------------------------------------------------
153
+
154
+第 6 步:办理进度卡 · 人员到位(二选一)
155
+进度条上 只会显示一种:
156
+
157
+
158
+
159
+人员登记
160
+扫码登记来源
161
+分享登记二维码,让工人扫码登记,查看已登记人数
162
+临时工招聘
163
+招聘来源
164
+打开招聘弹层,按工种勾选匹配人员并「确认选择」
165
+
166
+6A 人员登记(扫码分支)
167
+ 1. 点「人员登记」-->「查看 / 分享」。
168
+ 2. 将二维码或小程序分享给临时工。
169
+ 3. 工人扫码完成登记。
170
+ 4. 企业可「查看进度」看到已登记 x / 需求 y。
171
+ 5. 人数到位后,本步「已完成」,为任务发布做准备。
172
+【截图 08】人员登记面板
173
+【截图 09】登记二维码
174
+【截图 10】登记进度(已登记 x/y)
175
+建议文件名:08-registration-panel.png / 09-registration-qr.png / 10-registration-progress.png
176
+6B 临时工招聘(招聘分支)
177
+ 1. 点「临时工招聘」-->「去招聘」。
178
+ 2. 查看搜索条件(岗位、人数、地点、工期;多工种会逗号拼接展示)。
179
+ 3. 按工种展开结果,勾选人员(不超过需求人数)。
180
+ 4. 点「确认选择」提交。
181
+ 5. 完成后可「查看招聘」。
182
+【截图 11】招聘引导面板
183
+【截图 12】招聘弹层(条件 + 按工种折叠结果)
184
+【截图 13】勾选后「确认选择」
185
+建议文件名:11-recruit-panel.png / 12-recruit-sheet.png / 13-recruit-confirm.png
186
+--------------------------------------------------------------------------------
187
+
188
+第 7 步:任务发布
189
+前置条件(系统侧):企业认证完成 + 人员登记/招聘完成 -->「任务发布」进入进行中。
190
+ 1. 在进度卡点「任务发布」-->「去发布」。
191
+ 2. 任务信息:单卡汇总,多工种用逗号拼接,例如 搬运*10人,分拣*5人。
192
+ 3. 工作时间:按工种分别选开始/结束日期与时间。
193
+ 4. 人员须已到位(扫码登记达标,或招聘确认完成);不可缺编发布。
194
+ 5. 点「确认发布」。
195
+ 6. 发布成功后本步「已完成」,可「查看发布」。
196
+【截图 14】任务发布面板(进行中)
197
+【截图 15】任务信息单卡
198
+【截图 16】工作时间(按工种)
199
+【截图 17】发布完成后进度条状态
200
+建议文件名:14-task-panel.png / 15-task-info.png / 16-task-schedule.png / 17-task-done.png
201
+--------------------------------------------------------------------------------
202
+
203
+第 8 步:工人到岗作业(业务等待)
204
+本步主要由工人侧执行,企业端通常等待任务周期结束。
205
+任务结束后,系统进入结算环节。
206
+--------------------------------------------------------------------------------
207
+
208
+第 9 步:结算打款(结算确认单)
209
+ 1. 进度推进到 「结算打款」。
210
+ 2. 入口 A(进度卡):点「查看结算单」。
211
+ 3. 入口 B(消息):打开「消息」--> 点结算相关待办 --> 直达同一结算确认单。
212
+ 4. 核对:企业总支出、按工种人均/到手、资金明细、员工实发。
213
+ 5. 点 「确认并支付」;成功后关闭,进度继续。
214
+【截图 18】结算打款面板
215
+【截图 19】结算确认单(多工种)
216
+【截图 20】消息列表中的结算待办
217
+【截图 21】从消息进入的结算确认单(应与 19 一致)
218
+建议文件名:18-settle-panel.png / 19-settle-sheet.png / 20-msg-settle-todo.png / 21-settle-from-msg.png
219
+--------------------------------------------------------------------------------
220
+
221
+第 10 步:开票归档 --> 办结
222
+ 1. 结算完成后,进度到 「开票归档」。
223
+ 2. 点「去开票」,填写/确认开票信息并提交。
224
+ 3. 完成后可「查看开票」。
225
+ 4. 横向进度条各步均为「已完成」--> 整单流程办结。
226
+【截图 22】开票引导面板
227
+【截图 23】开票弹层
228
+【截图 24】全流程完成后的进度条
229
+建议文件名:22-invoice-panel.png / 23-invoice-sheet.png / 24-workflow-all-done.png
230
+--------------------------------------------------------------------------------
231
+
232
+三、进度状态怎么读(领导一句话版)
233
+
234
+
235
+
236
+待开始
237
+还没轮到
238
+一般点不开办理(登记/招聘前端可先看)
239
+进行中
240
+当前该办
241
+进入对应弹层办理
242
+已完成
243
+本步已办结
244
+可查看,不可重复提交
245
+
246
+并行:确认方案后,「企业认证」与「人员登记/招聘」可同时推进;「任务发布」要等两者都就绪。
247
+--------------------------------------------------------------------------------
248
+
249
+四、消息与「我的」补充
250
+
251
+
252
+消息列表
253
+门户 / 我的进入「消息」
254
+未读消息
255
+点击后标记已读(已读不会重复请求)
256
+结算待办
257
+点开即结算确认单(与进度卡同一界面)
258
+我的 · 未认证
259
+显示未认证说明;认证主入口仍在办理进度卡
260
+
261
+【截图 25】消息列表
262
+建议文件名:25-messages.png
263
+--------------------------------------------------------------------------------
264
+
265
+五、给领导演示的推荐路径
266
+路径 A:招聘分支(演示更快)
267
+ 1. 企业登录 --> 直接进工作台
268
+ 2. 对话生成草稿 --> 确认方案 --> 出现进度卡
269
+ 3. 企业认证(或演示跳过)
270
+ 4. 临时工招聘 --> 确认选择
271
+ 5. 任务发布 --> 填时间 --> 确认发布
272
+ 6. 结算确认单 --> 确认并支付
273
+ 7. 开票归档 --> 办结
274
+路径 B:登记分支(展示扫码)
275
+把第 4 步换成「人员登记 --> 分享二维码 --> 看登记进度」,后续同路径 A。
276
+--------------------------------------------------------------------------------
277
+
278
+六、截图清单(便于打勾)
279
+
280
+
281
+
282
+
283
+00
284
+总流程图(已更新)
285
+screenshots/00-enterprise-operation-flowchart.png
286
+☑
287
+01
288
+企业登录页
289
+01-login-enterprise.png
290
+☐
291
+02
292
+企业对话首页
293
+02-enterprise-home.png
294
+☐
295
+03
296
+草稿卡片
297
+03-draft-card.png
298
+☐
299
+04 - 05
300
+确认方案 / 进度卡
301
+04-draft-confirm.png / 05-workflow-card.png
302
+☐
303
+06 - 07
304
+企业认证
305
+06-cert-panel.png / 07-cert-sheet.png
306
+☐
307
+08 - 10
308
+人员登记
309
+08 - 10-registration-*.png
310
+☐
311
+11 - 13
312
+临时工招聘
313
+11 - 13-recruit-*.png
314
+☐
315
+14 - 17
316
+任务发布
317
+14 - 17-task-*.png
318
+☐
319
+18 - 21
320
+结算
321
+18 - 21-settle-*.png
322
+☐
323
+22 - 24
324
+开票与办结
325
+22 - 24-*.png
326
+☐
327
+25
328
+消息列表
329
+25-messages.png
330
+☐
331
+
332
+截好后在对应 【截图 xx】 下插入:
333
+![说明](./screenshots/xx-xxx.png)
334
+
335
+--------------------------------------------------------------------------------
336
+
337
+文档版本:v4 · 突出扫码登记/临时工招聘双路线;任务发布不可缺编 · 与领导口径一致
338
+相关文件:`doc/企业端操作流程说明.md` · `doc/企业端操作流程图.html` · `doc/screenshots/00-enterprise-operation-flowchart.png`

+ 324 - 0
doc/企业端操作流程说明.md

@@ -0,0 +1,324 @@
1
+# 慧用工 · 企业端操作流程说明(从登录起)
2
+
3
+> **适用对象**:产品 / 业务领导审阅  
4
+> **端**:微信小程序 · 企业端  
5
+> **范围**:从登录 → 确认方案 → 办理进度各节点 → 结算开票办结  
6
+> **流程图**:见下方总图;也可双击打开同目录 `企业端操作流程图.html`(浏览器可交互查看)
7
+
8
+---
9
+
10
+## 一、总流程图(从登录开始)
11
+
12
+![企业端操作流程图](./screenshots/00-enterprise-operation-flowchart.png)
13
+
14
+> 若 Markdown 预览未显示图片,请直接打开:  
15
+> - 图片:`doc/screenshots/00-enterprise-operation-flowchart.png`  
16
+> - 可交互图:`doc/企业端操作流程图.html`(用浏览器打开)
17
+
18
+### 文字版总览(按时间顺序)
19
+
20
+```
21
+打开小程序
22
+   ↓
23
+企业登录(验证码 或 账号密码)
24
+   ↓
25
+登录成功 → 直接进入企业工作台 / 对话首页
26
+(未认证不拦截;「我的」可显示游客/未认证态)
27
+   ↓
28
+(可选)与 AI 对话说明用工需求 → 生成用工草稿
29
+   ↓
30
+补全草稿 → 对比成本方案 →「确认此方案」
31
+   ↓
32
+出现「办理进度」卡片
33
+   ├── 企业认证(可与人员到位并行;可先跳过)
34
+   └── 人员到位方式(二选一,进度条只显示其一)
35
+         │
36
+         ├────【路线A · 扫码登记】────┐
37
+         │  打开人员登记              │
38
+         │  → 分享登记二维码给临时工  │
39
+         │  → 工人扫码完成登记        │
40
+         │  → 查看进度至人数到位      │
41
+         │                            │
42
+         └────【路线B · 临时工招聘】──┤
43
+            打开临时工招聘            │
44
+            → 查看匹配条件与候选人    │
45
+            → 按工种勾选并确认选择    │
46
+            → 招聘完成                │
47
+                                      ↓
48
+                          两条路线在此汇合
49
+                                      ↓
50
+任务发布(填写工作时间后确认发布)
51
+   ↓
52
+工人到岗作业
53
+   ↓
54
+结算打款(进度卡 或 消息待办)→ 确认并支付
55
+   ↓
56
+开票归档 → 流程办结
57
+```
58
+
59
+**两条人员到位路线(务必分清)**
60
+
61
+| 路线 | 名称 | 企业怎么做 | 进度卡上显示 |
62
+|---|---|---|---|
63
+| **A** | 扫码登记 | 分享二维码,工人扫码登记,看到人数达标 | 「人员登记」 |
64
+| **B** | 临时工招聘 | 在招聘弹层勾选匹配人员并确认选择 | 「临时工招聘」 |
65
+
66
+> 一笔订单只会走其中一条;不是两步都要做。
67
+
68
+### 说明:登录不再强制「企业登记」
69
+
70
+| 点 | 当前实现 |
71
+|---|---|
72
+| 登录后 | **直接进工作台**,不弹「是否已企业登记」门槛 |
73
+| 企业认证 | 在确认方案后的 **办理进度卡** 里办理(也可先跳过) |
74
+| 游客/未认证 | 「我的」等处展示未认证说明,**不挡登录进首页** |
75
+
76
+> 旧文档里「登录 → 是否已企业登记」是过时画法,已删除。
77
+
78
+---
79
+
80
+## 二、分步操作(按真实点击路径)
81
+
82
+### 第 1 步:打开小程序并进入企业登录
83
+
84
+1. 微信中打开「慧用工」小程序。  
85
+2. 进入登录页,确认当前是 **「企业登录」**(不是「临时工登录」)。  
86
+3. 若显示临时工登录,点右上角切换到企业登录。
87
+
88
+**登录方式(二选一)**
89
+
90
+| 方式 | 操作 |
91
+|---|---|
92
+| 手机号 + 验证码 | 输入 11 位手机号 → 获取验证码 → 输入 6 位验证码 → 登录 |
93
+| 账号 + 密码 | 切换到密码登录 → 输入手机号与密码 → 登录 |
94
+
95
+登录成功后 **直接进入企业工作台**(不强制企业登记 / 企业认证)。
96
+
97
+**【截图 01】企业登录页(标题为「企业登录」)**  
98
+建议文件名:`screenshots/01-login-enterprise.png`
99
+
100
+---
101
+
102
+### 第 2 步:进入企业工作台(对话首页)
103
+
104
+1. 登录后进入企业工作台。  
105
+2. 主界面为对话式首页:可与 AI 助手对话,也可查看历史会话中的草稿卡、办理进度卡。  
106
+3. 底部 / 门户可进入「消息」「我的」等(结算待办常从消息进入)。  
107
+4. 若尚未完成企业认证,「我的」可能显示未认证/游客态,但不影响先逛首页、先对话。
108
+
109
+**【截图 02】企业对话首页(含输入框)**  
110
+建议文件名:`02-enterprise-home.png`
111
+
112
+---
113
+
114
+### 第 3 步:对话生成用工草稿(可选但推荐)
115
+
116
+1. 在首页输入框描述用工需求,例如:  
117
+   - 需要多少人、什么工种  
118
+   - 工作地点、大概工期  
119
+2. 发送后,系统在会话中生成 **「用工草稿」卡片**。  
120
+3. 点击草稿卡片,打开草稿详情。
121
+
122
+> 也可从已有会话中打开历史草稿继续编辑;关键是最终要点「确认此方案」。
123
+
124
+**【截图 03】会话中的用工草稿卡片**  
125
+建议文件名:`03-draft-card.png`
126
+
127
+---
128
+
129
+### 第 4 步:补全草稿并确认方案(产生办理进度卡的前提)
130
+
131
+1. 在草稿弹层中核对 / 补全:  
132
+   - 工种与人数  
133
+   - 工期、地点等必填项  
134
+2. 查看 **成本对比方案**(不同方案费用对比)。  
135
+3. 选定方案后,点击 **「确认此方案」**。  
136
+4. 确认成功后:  
137
+   - 草稿进入已确认状态  
138
+   - 会话中出现 **「办理进度」卡片**(横向步骤条)
139
+
140
+> **领导关注点**:没有「确认此方案」,就不会进入后续办理闭环。
141
+
142
+**【截图 04】草稿弹层 · 成本方案 +「确认此方案」**  
143
+**【截图 05】确认后出现的办理进度卡**  
144
+建议文件名:`04-draft-confirm.png` / `05-workflow-card.png`
145
+
146
+---
147
+
148
+### 第 5 步:办理进度卡 · 企业认证
149
+
150
+1. 在办理进度卡中点到 **「企业认证」**。  
151
+2. 若未认证:点「去认证」→ 上传营业执照 / 填写企业与法人信息 → 提交。  
152
+3. 认证完成后,本步显示「已完成」,可「查看认证」。  
153
+4. 允许先「跳过」,后续再补;未认证时「我的」页会提示未认证状态。
154
+
155
+**【截图 06】企业认证引导面板**  
156
+**【截图 07】企业认证填写/上传弹层**  
157
+建议文件名:`06-cert-panel.png` / `07-cert-sheet.png`
158
+
159
+---
160
+
161
+### 第 6 步:办理进度卡 · 人员到位(二选一)
162
+
163
+进度条上 **只会显示一种**:
164
+
165
+| 分支 | 何时出现 | 企业要做什么 |
166
+|---|---|---|
167
+| **人员登记** | 扫码登记来源 | 分享登记二维码,让工人扫码登记,查看已登记人数 |
168
+| **临时工招聘** | 招聘来源 | 打开招聘弹层,按工种勾选匹配人员并「确认选择」 |
169
+
170
+#### 6A 人员登记(扫码分支)
171
+
172
+1. 点「人员登记」→「查看 / 分享」。  
173
+2. 将二维码或小程序分享给临时工。  
174
+3. 工人扫码完成登记。  
175
+4. 企业可「查看进度」看到已登记 x / 需求 y。  
176
+5. 人数到位后,本步「已完成」,为任务发布做准备。
177
+
178
+**【截图 08】人员登记面板**  
179
+**【截图 09】登记二维码**  
180
+**【截图 10】登记进度(已登记 x/y)**  
181
+建议文件名:`08-registration-panel.png` / `09-registration-qr.png` / `10-registration-progress.png`
182
+
183
+#### 6B 临时工招聘(招聘分支)
184
+
185
+1. 点「临时工招聘」→「去招聘」。  
186
+2. 查看搜索条件(岗位、人数、地点、工期;多工种会逗号拼接展示)。  
187
+3. 按工种展开结果,勾选人员(不超过需求人数)。  
188
+4. 点「确认选择」提交。  
189
+5. 完成后可「查看招聘」。
190
+
191
+**【截图 11】招聘引导面板**  
192
+**【截图 12】招聘弹层(条件 + 按工种折叠结果)**  
193
+**【截图 13】勾选后「确认选择」**  
194
+建议文件名:`11-recruit-panel.png` / `12-recruit-sheet.png` / `13-recruit-confirm.png`
195
+
196
+---
197
+
198
+### 第 7 步:任务发布
199
+
200
+**前置条件(系统侧)**:企业认证完成 + 人员登记/招聘完成 →「任务发布」进入进行中。
201
+
202
+1. 在进度卡点「任务发布」→「去发布」。  
203
+2. **任务信息**:单卡汇总,多工种用逗号拼接,例如 `搬运*10人,分拣*5人`。  
204
+3. **工作时间**:按工种分别选开始/结束日期与时间。  
205
+4. 人员须已到位(扫码登记达标,或招聘确认完成);**不可缺编发布**。  
206
+5. 点「确认发布」。  
207
+6. 发布成功后本步「已完成」,可「查看发布」。
208
+
209
+**【截图 14】任务发布面板(进行中)**  
210
+**【截图 15】任务信息单卡**  
211
+**【截图 16】工作时间(按工种)**  
212
+**【截图 17】发布完成后进度条状态**  
213
+建议文件名:`14-task-panel.png` / `15-task-info.png` / `16-task-schedule.png` / `17-task-done.png`
214
+
215
+---
216
+
217
+### 第 8 步:工人到岗作业(业务等待)
218
+
219
+本步主要由工人侧执行,企业端通常等待任务周期结束。  
220
+任务结束后,系统进入结算环节。
221
+
222
+---
223
+
224
+### 第 9 步:结算打款(结算确认单)
225
+
226
+1. 进度推进到 **「结算打款」**。  
227
+2. **入口 A(进度卡)**:点「查看结算单」。  
228
+3. **入口 B(消息)**:打开「消息」→ 点结算相关待办 → 直达同一结算确认单。  
229
+4. 核对:企业总支出、按工种人均/到手、资金明细、员工实发。  
230
+5. 点 **「确认并支付」**;成功后关闭,进度继续。
231
+
232
+**【截图 18】结算打款面板**  
233
+**【截图 19】结算确认单(多工种)**  
234
+**【截图 20】消息列表中的结算待办**  
235
+**【截图 21】从消息进入的结算确认单(应与 19 一致)**  
236
+建议文件名:`18-settle-panel.png` / `19-settle-sheet.png` / `20-msg-settle-todo.png` / `21-settle-from-msg.png`
237
+
238
+---
239
+
240
+### 第 10 步:开票归档 → 办结
241
+
242
+1. 结算完成后,进度到 **「开票归档」**。  
243
+2. 点「去开票」,填写/确认开票信息并提交。  
244
+3. 完成后可「查看开票」。  
245
+4. 横向进度条各步均为「已完成」→ **整单流程办结**。
246
+
247
+**【截图 22】开票引导面板**  
248
+**【截图 23】开票弹层**  
249
+**【截图 24】全流程完成后的进度条**  
250
+建议文件名:`22-invoice-panel.png` / `23-invoice-sheet.png` / `24-workflow-all-done.png`
251
+
252
+---
253
+
254
+## 三、进度状态怎么读(领导一句话版)
255
+
256
+| 状态 | 含义 | 企业侧 |
257
+|---|---|---|
258
+| 待开始 | 还没轮到 | 一般点不开办理(登记/招聘前端可先看) |
259
+| 进行中 | 当前该办 | 进入对应弹层办理 |
260
+| 已完成 | 本步已办结 | 可查看,不可重复提交 |
261
+
262
+**并行**:确认方案后,「企业认证」与「人员登记/招聘」可同时推进;「任务发布」要等两者都就绪。
263
+
264
+---
265
+
266
+## 四、消息与「我的」补充
267
+
268
+| 场景 | 操作说明 |
269
+|---|---|
270
+| 消息列表 | 门户 / 我的进入「消息」 |
271
+| 未读消息 | 点击后标记已读(已读不会重复请求) |
272
+| 结算待办 | 点开即结算确认单(与进度卡同一界面) |
273
+| 我的 · 未认证 | 显示未认证说明;认证主入口仍在办理进度卡 |
274
+
275
+**【截图 25】消息列表**  
276
+建议文件名:`25-messages.png`
277
+
278
+---
279
+
280
+## 五、给领导演示的推荐路径
281
+
282
+### 路径 A:招聘分支(演示更快)
283
+
284
+1. 企业登录 → 直接进工作台  
285
+2. 对话生成草稿 → 确认方案 → 出现进度卡  
286
+3. 企业认证(或演示跳过)  
287
+4. 临时工招聘 → 确认选择  
288
+5. 任务发布 → 填时间 → 确认发布  
289
+6. 结算确认单 → 确认并支付  
290
+7. 开票归档 → 办结  
291
+
292
+### 路径 B:登记分支(展示扫码)
293
+
294
+把第 4 步换成「人员登记 → 分享二维码 → 看登记进度」,后续同路径 A。
295
+
296
+---
297
+
298
+## 六、截图清单(便于打勾)
299
+
300
+| 编号 | 内容 | 建议文件名 | 已截 |
301
+|---|---|---|---|
302
+| 00 | 总流程图(已更新) | `screenshots/00-enterprise-operation-flowchart.png` | ☑ |
303
+| 01 | 企业登录页 | `01-login-enterprise.png` | ☐ |
304
+| 02 | 企业对话首页 | `02-enterprise-home.png` | ☐ |
305
+| 03 | 草稿卡片 | `03-draft-card.png` | ☐ |
306
+| 04–05 | 确认方案 / 进度卡 | `04-draft-confirm.png` / `05-workflow-card.png` | ☐ |
307
+| 06–07 | 企业认证 | `06-cert-panel.png` / `07-cert-sheet.png` | ☐ |
308
+| 08–10 | 人员登记 | `08`–`10-registration-*.png` | ☐ |
309
+| 11–13 | 临时工招聘 | `11`–`13-recruit-*.png` | ☐ |
310
+| 14–17 | 任务发布 | `14`–`17-task-*.png` | ☐ |
311
+| 18–21 | 结算 | `18`–`21-settle-*.png` | ☐ |
312
+| 22–24 | 开票与办结 | `22`–`24-*.png` | ☐ |
313
+| 25 | 消息列表 | `25-messages.png` | ☐ |
314
+
315
+截好后在对应 `【截图 xx】` 下插入:
316
+
317
+```markdown
318
+![说明](./screenshots/xx-xxx.png)
319
+```
320
+
321
+---
322
+
323
+*文档版本:v4 · 突出扫码登记/临时工招聘双路线;任务发布不可缺编 · 与领导口径一致*  
324
+*相关文件:`doc/企业端操作流程说明.md` · `doc/企业端操作流程图.html` · `doc/screenshots/00-enterprise-operation-flowchart.png`*

+ 3 - 2
huimv-employment/app/common/config.js

@@ -1,6 +1,7 @@
1 1
 /** 小程序直连 API(体验版/开发版统一;有正式 HTTPS 域名后再改 env) */
2
-const MP_API = 'http://192.168.1.6:8081/api/v1/mp'
3
-// const MP_API = 'https://img.ifarmcloud.com/deploymentApi/api/v1/mp'
2
+// const MP_API = 'http://192.168.1.6:8081/api/v1/mp'
3
+const MP_API = 'https://img.ifarmcloud.com/deploymentApi/api/v1/mp'
4
+// const MP_API = 'http://115.238.57.190:5200/api/v1/mp'
4 5
 
5 6
 /** H5 开发走 devServer 代理,避免浏览器跨域 */
6 7
 const H5_DEV_PROXY_API = '/api/v1/mp'