liyu-pc-base 1.0.614 → 1.0.625

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,349 @@
1
+ import { N as e, o as t } from "./init-plugin-1SOa4xzt.js";
2
+ import { t as n } from "./_plugin-vue_export-helper-B3ysoDQm.js";
3
+ import { getApiData as r } from "./api/api-model.js";
4
+ import { exportJsonToExcel as i } from "./utils/excel.js";
5
+ import { Fragment as a, createBlock as o, createCommentVNode as s, createElementBlock as c, createElementVNode as l, createTextVNode as u, createVNode as d, defineComponent as f, onMounted as p, openBlock as m, ref as h, resolveComponent as g, shallowRef as _, toDisplayString as v, unref as y, withCtx as b } from "vue";
6
+ import { message as x } from "ant-design-vue";
7
+ import { DownloadOutlined as S, PlusOutlined as C, UploadOutlined as w } from "@ant-design/icons-vue";
8
+ import { read as T, utils as E } from "xlsx";
9
+ //#region src/components/batchCreateUser/batchCreateUser.vue?vue&type=script&setup=true&lang.ts
10
+ var D = { class: "batch-toolbar" }, O = { class: "batch-tip" }, k = { key: 1 }, A = { key: 1 }, j = "id", M = /*#__PURE__*/ n(/* @__PURE__ */ f({
11
+ __name: "batchCreateUser",
12
+ props: {
13
+ title: {},
14
+ record: {}
15
+ },
16
+ emits: ["cancel", "ok"],
17
+ setup(n, { emit: f }) {
18
+ let M = f, N = h(!0), P = h(!1), F = h([]), I = h(0), L = h([]), R = _([]), z = h(""), B = e(), V = B.currentOrg, H = (() => {
19
+ let e = B.person?.manage_type;
20
+ return e === 2 ? [
21
+ {
22
+ label: "普通用户",
23
+ value: 0
24
+ },
25
+ {
26
+ label: "管理员",
27
+ value: 1
28
+ },
29
+ {
30
+ label: "超级管理员",
31
+ value: 2
32
+ }
33
+ ] : e === 1 ? [{
34
+ label: "普通用户",
35
+ value: 0
36
+ }, {
37
+ label: "管理员",
38
+ value: 1
39
+ }] : [{
40
+ label: "普通用户",
41
+ value: 0
42
+ }];
43
+ })(), U = [
44
+ {
45
+ title: "序号",
46
+ dataIndex: "index",
47
+ width: 70,
48
+ fixed: "left"
49
+ },
50
+ {
51
+ title: "账号",
52
+ dataIndex: "username",
53
+ width: 220
54
+ },
55
+ {
56
+ title: "姓名",
57
+ dataIndex: "realname",
58
+ width: 220
59
+ },
60
+ {
61
+ title: "操作",
62
+ dataIndex: "action",
63
+ width: 140,
64
+ fixed: "right"
65
+ }
66
+ ], W = 0, G = () => `row_${++W}`, K = () => {
67
+ let e = G();
68
+ z.value = e, R.value = [...R.value, {
69
+ id: e,
70
+ username: "",
71
+ realname: ""
72
+ }];
73
+ }, q = (e) => {
74
+ z.value = e.id, R.value = [...R.value];
75
+ }, J = () => {
76
+ z.value = "", R.value = [...R.value];
77
+ }, Y = () => {
78
+ R.value = [];
79
+ }, X = (e) => {
80
+ let t = R.value.findIndex((t) => t.id === e.id);
81
+ t > -1 && R.value.splice(t, 1), R.value = [...R.value];
82
+ }, Z = () => {
83
+ i({
84
+ header: ["账号", "姓名"],
85
+ data: [],
86
+ filename: "批量创建用户导入模板",
87
+ bookType: "xlsx",
88
+ autoWidth: !0,
89
+ percent: h(0)
90
+ });
91
+ }, Q = (e) => {
92
+ let t = new FileReader();
93
+ return t.onload = (e) => {
94
+ try {
95
+ let t = T(new Uint8Array(e.target.result), { type: "array" }), n = t.SheetNames[0], r = t.Sheets[n], i = E.sheet_to_json(r, { header: 1 }), a = (i[0] || []).map((e) => String(e ?? "").trim().toLowerCase()), o = a.findIndex((e) => [
96
+ "账号",
97
+ "用户名",
98
+ "username"
99
+ ].includes(e)), s = a.findIndex((e) => [
100
+ "姓名",
101
+ "名字",
102
+ "realname",
103
+ "name"
104
+ ].includes(e));
105
+ if (o === -1 && s === -1) {
106
+ x.warning("未识别到“账号/姓名”列,请使用下载的模板");
107
+ return;
108
+ }
109
+ let c = i.slice(1).filter((e) => e && e.some((e) => e !== "" && e != null)), l = 0, u = [];
110
+ c.forEach((e) => {
111
+ let t = o > -1 ? String(e[o] ?? "").trim() : "", n = s > -1 ? String(e[s] ?? "").trim() : "";
112
+ !t && !n || (u.push({
113
+ id: G(),
114
+ username: t,
115
+ realname: n
116
+ }), l++);
117
+ }), R.value = [...R.value, ...u], l > 0 ? x.success(`已导入 ${l} 条`) : x.info("文件中没有可导入的数据");
118
+ } catch (e) {
119
+ console.error(e), x.error("解析 Excel 失败");
120
+ }
121
+ }, t.readAsArrayBuffer(e), !1;
122
+ }, $ = async () => {
123
+ if (!F.value || F.value.length === 0) {
124
+ x.error("请至少选择一个组织");
125
+ return;
126
+ }
127
+ if (I.value === null || I.value === void 0) {
128
+ x.error("请选择用户类型");
129
+ return;
130
+ }
131
+ if (R.value.length === 0) {
132
+ x.error("请至少添加一条用户信息");
133
+ return;
134
+ }
135
+ let e = /* @__PURE__ */ new Set();
136
+ for (let t of R.value) {
137
+ let n = (t.username || "").trim(), r = (t.realname || "").trim();
138
+ if (!n) {
139
+ x.error("账号不能为空,请检查表格");
140
+ return;
141
+ }
142
+ if (!r) {
143
+ x.error(`账号 [${n}] 的姓名不能为空`);
144
+ return;
145
+ }
146
+ if (e.has(n)) {
147
+ x.error(`账号 [${n}] 重复,请检查`);
148
+ return;
149
+ }
150
+ e.add(n);
151
+ }
152
+ let n = {
153
+ org_ids: F.value,
154
+ manage_type: I.value,
155
+ user_list: R.value.map((e) => ({
156
+ username: (e.username || "").trim(),
157
+ realname: (e.realname || "").trim()
158
+ })),
159
+ org_id: V?.id
160
+ };
161
+ P.value = !0;
162
+ try {
163
+ await r(n, "/sys/create_batch_user"), x.success("批量创建完成"), t()?.emit("refresh"), M("ok");
164
+ } catch (e) {
165
+ console.error(e);
166
+ } finally {
167
+ P.value = !1;
168
+ }
169
+ }, ee = () => M("cancel");
170
+ return p(async () => {
171
+ try {
172
+ let e = await r({ is_pagination: !1 }, "/org/query_org_for_person_admin"), t = e?.data ?? e ?? [];
173
+ L.value = Array.isArray(t) ? t : [];
174
+ } catch (e) {
175
+ console.error("加载组织列表失败", e);
176
+ }
177
+ }), (e, t) => {
178
+ let n = g("a-select"), r = g("a-form-item"), i = g("a-col"), f = g("a-row"), p = g("a-form"), h = g("a-button"), _ = g("a-upload"), x = g("a-popconfirm"), T = g("a-input"), E = g("a-space"), M = g("s-table"), B = g("a-modal");
179
+ return m(), o(B, {
180
+ title: e.title,
181
+ width: "900px",
182
+ open: N.value,
183
+ bodyStyle: {
184
+ height: "75vh",
185
+ overflowY: "auto",
186
+ padding: "0 24px"
187
+ },
188
+ "confirm-loading": P.value,
189
+ "cancel-text": "取消",
190
+ "ok-text": "确定",
191
+ centered: "",
192
+ "destroy-on-close": "",
193
+ onCancel: ee,
194
+ onOk: $
195
+ }, {
196
+ default: b(() => [
197
+ d(p, {
198
+ layout: "vertical",
199
+ size: "small"
200
+ }, {
201
+ default: b(() => [d(f, { gutter: 16 }, {
202
+ default: b(() => [d(i, { span: 12 }, {
203
+ default: b(() => [d(r, {
204
+ label: "组织",
205
+ required: ""
206
+ }, {
207
+ default: b(() => [d(n, {
208
+ value: F.value,
209
+ "onUpdate:value": t[0] ||= (e) => F.value = e,
210
+ mode: "multiple",
211
+ placeholder: "请选择组织(可多选)",
212
+ options: L.value,
213
+ "field-names": {
214
+ label: "name",
215
+ value: "id"
216
+ },
217
+ "allow-clear": "",
218
+ "show-search": "",
219
+ "option-filter-prop": "name"
220
+ }, null, 8, ["value", "options"])]),
221
+ _: 1
222
+ })]),
223
+ _: 1
224
+ }), d(i, { span: 12 }, {
225
+ default: b(() => [d(r, {
226
+ label: "用户类型",
227
+ required: ""
228
+ }, {
229
+ default: b(() => [d(n, {
230
+ value: I.value,
231
+ "onUpdate:value": t[1] ||= (e) => I.value = e,
232
+ placeholder: "请选择用户类型",
233
+ options: y(H)
234
+ }, null, 8, ["value", "options"])]),
235
+ _: 1
236
+ })]),
237
+ _: 1
238
+ })]),
239
+ _: 1
240
+ })]),
241
+ _: 1
242
+ }),
243
+ l("div", D, [
244
+ d(h, {
245
+ type: "primary",
246
+ onClick: K
247
+ }, {
248
+ default: b(() => [d(y(C)), t[4] ||= u(" 添加 ")]),
249
+ _: 1
250
+ }),
251
+ d(_, {
252
+ "show-upload-list": !1,
253
+ "before-upload": Q,
254
+ accept: ".xlsx,.xls"
255
+ }, {
256
+ default: b(() => [d(h, null, {
257
+ default: b(() => [d(y(w)), t[5] ||= u(" 导入 ")]),
258
+ _: 1
259
+ })]),
260
+ _: 1
261
+ }),
262
+ d(h, { onClick: Z }, {
263
+ default: b(() => [d(y(S)), t[6] ||= u(" 下载导入模板 ")]),
264
+ _: 1
265
+ }),
266
+ R.value.length > 0 ? (m(), o(x, {
267
+ key: 0,
268
+ title: "确认清空所有数据?",
269
+ onConfirm: t[2] ||= (e) => Y()
270
+ }, {
271
+ default: b(() => [d(h, {
272
+ type: "default",
273
+ danger: ""
274
+ }, {
275
+ default: b(() => t[7] ||= [u(" 清空 ")]),
276
+ _: 1
277
+ })]),
278
+ _: 1
279
+ })) : s("", !0),
280
+ l("span", O, "共 " + v(R.value.length) + " 条", 1)
281
+ ]),
282
+ d(M, {
283
+ columns: U,
284
+ "data-source": R.value,
285
+ rowKey: j,
286
+ bordered: !0,
287
+ size: "small",
288
+ pagination: !1,
289
+ scroll: { y: 320 }
290
+ }, {
291
+ bodyCell: b(({ column: e, index: n, record: r }) => [e.dataIndex === "index" ? (m(), c(a, { key: 0 }, [u(v(n + 1), 1)], 64)) : e.dataIndex === "username" ? (m(), c(a, { key: 1 }, [r.id === z.value ? (m(), o(T, {
292
+ key: 0,
293
+ value: r.username,
294
+ "onUpdate:value": (e) => r.username = e,
295
+ placeholder: "请输入账号",
296
+ "allow-clear": ""
297
+ }, null, 8, ["value", "onUpdate:value"])) : (m(), c("span", k, v(r.username), 1))], 64)) : e.dataIndex === "realname" ? (m(), c(a, { key: 2 }, [r.id === z.value ? (m(), o(T, {
298
+ key: 0,
299
+ value: r.realname,
300
+ "onUpdate:value": (e) => r.realname = e,
301
+ placeholder: "请输入姓名",
302
+ "allow-clear": ""
303
+ }, null, 8, ["value", "onUpdate:value"])) : (m(), c("span", A, v(r.realname), 1))], 64)) : e.dataIndex === "action" ? (m(), o(E, { key: 3 }, {
304
+ default: b(() => [r.id === z.value ? (m(), o(h, {
305
+ key: 1,
306
+ type: "link",
307
+ size: "small",
308
+ onClick: t[3] ||= (e) => J()
309
+ }, {
310
+ default: b(() => t[9] ||= [u(" 完成 ")]),
311
+ _: 1
312
+ })) : (m(), o(h, {
313
+ key: 0,
314
+ type: "link",
315
+ size: "small",
316
+ onClick: (e) => q(r)
317
+ }, {
318
+ default: b(() => t[8] ||= [u(" 编辑 ")]),
319
+ _: 2
320
+ }, 1032, ["onClick"])), d(x, {
321
+ title: "确认删除该行?",
322
+ onConfirm: (e) => X(r)
323
+ }, {
324
+ default: b(() => [d(h, {
325
+ type: "link",
326
+ size: "small",
327
+ danger: ""
328
+ }, {
329
+ default: b(() => t[10] ||= [u(" 删除 ")]),
330
+ _: 1
331
+ })]),
332
+ _: 2
333
+ }, 1032, ["onConfirm"])]),
334
+ _: 2
335
+ }, 1024)) : s("", !0)]),
336
+ _: 1
337
+ }, 8, ["data-source"])
338
+ ]),
339
+ _: 1
340
+ }, 8, [
341
+ "title",
342
+ "open",
343
+ "confirm-loading"
344
+ ]);
345
+ };
346
+ }
347
+ }), [["__scopeId", "data-v-aecb1aff"]]);
348
+ //#endregion
349
+ export { M as default };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batchCreateUser-CP_0i4pL.js","names":[],"sources":["../src/components/batchCreateUser/batchCreateUser.vue","../src/components/batchCreateUser/batchCreateUser.vue"],"sourcesContent":["<template>\n <a-modal\n :title=\"title\"\n width=\"900px\"\n :open=\"open\"\n :bodyStyle=\"{ height: '75vh', overflowY: 'auto', padding: '0 24px' }\"\n :confirm-loading=\"loading\"\n cancel-text=\"取消\"\n ok-text=\"确定\"\n centered\n destroy-on-close\n @cancel=\"handleCancel\"\n @ok=\"handleOk\"\n >\n <a-form\n layout=\"vertical\"\n size=\"small\"\n >\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"组织\"\n required\n >\n <a-select\n v-model:value=\"orgIds\"\n mode=\"multiple\"\n placeholder=\"请选择组织(可多选)\"\n :options=\"orgOptions\"\n :field-names=\"{ label: 'name', value: 'id' }\"\n allow-clear\n show-search\n option-filter-prop=\"name\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"用户类型\"\n required\n >\n <a-select\n v-model:value=\"manageType\"\n placeholder=\"请选择用户类型\"\n :options=\"manageTypeOptions\"\n />\n </a-form-item>\n </a-col>\n </a-row>\n </a-form>\n\n <div class=\"batch-toolbar\">\n <a-button\n type=\"primary\"\n @click=\"handleAddRow\"\n >\n <plus-outlined />\n 添加\n </a-button>\n <a-upload\n :show-upload-list=\"false\"\n :before-upload=\"beforeImportUpload\"\n accept=\".xlsx,.xls\"\n >\n <a-button>\n <upload-outlined />\n 导入\n </a-button>\n </a-upload>\n <a-button @click=\"handleDownloadTemplate\">\n <download-outlined />\n 下载导入模板\n </a-button>\n <a-popconfirm\n title=\"确认清空所有数据?\"\n @confirm=\"handleClear()\"\n v-if=\"dataSource.length > 0\"\n >\n <a-button\n type=\"default\"\n danger\n >\n 清空\n </a-button>\n </a-popconfirm>\n <span class=\"batch-tip\">共 {{ dataSource.length }} 条</span>\n </div>\n\n <s-table\n :columns=\"columns\"\n :data-source=\"dataSource\"\n :rowKey=\"rowKey\"\n :bordered=\"true\"\n size=\"small\"\n :pagination=\"false\"\n :scroll=\"{ y: 320 }\"\n >\n <template #bodyCell=\"{ column, index, record }\">\n <template v-if=\"column.dataIndex === 'index'\">\n {{ index + 1 }}\n </template>\n <template v-else-if=\"column.dataIndex === 'username'\">\n <a-input\n v-if=\"record.id === currentEditId\"\n v-model:value=\"record.username\"\n placeholder=\"请输入账号\"\n allow-clear\n />\n <span v-else>{{ record.username }}</span>\n </template>\n <template v-else-if=\"column.dataIndex === 'realname'\">\n <a-input\n v-if=\"record.id === currentEditId\"\n v-model:value=\"record.realname\"\n placeholder=\"请输入姓名\"\n allow-clear\n />\n <span v-else>{{ record.realname }}</span>\n </template>\n <template v-else-if=\"column.dataIndex === 'action'\">\n <a-space>\n <a-button\n type=\"link\"\n size=\"small\"\n v-if=\"record.id !== currentEditId\"\n @click=\"handleEdit(record)\"\n >\n 编辑\n </a-button>\n <a-button\n type=\"link\"\n size=\"small\"\n v-else\n @click=\"handleComplete()\"\n >\n 完成\n </a-button>\n <a-popconfirm\n title=\"确认删除该行?\"\n @confirm=\"handleDelete(record)\"\n >\n <a-button\n type=\"link\"\n size=\"small\"\n danger\n >\n 删除\n </a-button>\n </a-popconfirm>\n </a-space>\n </template>\n </template>\n </s-table>\n </a-modal>\n</template>\n\n<script setup lang=\"ts\">\nimport { DownloadOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons-vue';\nimport { message } from 'ant-design-vue';\nimport { read, utils } from 'xlsx';\nimport type { Ref } from 'vue';\nimport { onMounted, ref, shallowRef } from 'vue';\nimport { event_obj, useUserStore } from 'liyu-pc-base/init-plugin';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport { exportJsonToExcel } from 'liyu-pc-base/utils/excel';\n\ninterface Props {\n title?: string;\n record?: any;\n}\nconst props = defineProps<Props>();\nconst emit = defineEmits(['cancel', 'ok']);\n\nconst open = ref(true);\nconst loading = ref(false);\nconst orgIds = ref<any[]>([]);\nconst manageType = ref<number>(0);\nconst orgOptions = ref<any[]>([]);\nconst dataSource = shallowRef<any[]>([]);\nconst currentEditId = ref<string>('');\n\nconst userStore = useUserStore();\nconst current_org = userStore.currentOrg;\n\n// 用户类型可选项:受当前账号 manage_type 约束(与 permission-user 保持一致)\nconst manageTypeOptions = (() => {\n const curr = userStore.person?.manage_type;\n if (curr === 2) {\n return [\n { label: '普通用户', value: 0 },\n { label: '管理员', value: 1 },\n { label: '超级管理员', value: 2 },\n ];\n } else if (curr === 1) {\n return [\n { label: '普通用户', value: 0 },\n { label: '管理员', value: 1 },\n ];\n }\n return [{ label: '普通用户', value: 0 }];\n})();\n\nconst columns = [\n { title: '序号', dataIndex: 'index', width: 70, fixed: 'left' },\n { title: '账号', dataIndex: 'username', width: 220 },\n { title: '姓名', dataIndex: 'realname', width: 220 },\n { title: '操作', dataIndex: 'action', width: 140, fixed: 'right' },\n];\nconst rowKey = 'id';\n\nlet uid = 0;\nconst genId = () => `row_${++uid}`;\n\nconst handleAddRow = () => {\n const id = genId();\n currentEditId.value = id;\n dataSource.value = [...dataSource.value, { id, username: '', realname: '' }];\n};\n\nconst handleEdit = (record: any) => {\n currentEditId.value = record.id;\n dataSource.value = [...dataSource.value];\n};\nconst handleComplete = () => {\n currentEditId.value = '';\n dataSource.value = [...dataSource.value];\n};\nconst handleClear = () => {\n dataSource.value = [];\n};\n\nconst handleDelete = (record: any) => {\n const idx = dataSource.value.findIndex(r => r.id === record.id);\n if (idx > -1) dataSource.value.splice(idx, 1);\n dataSource.value = [...dataSource.value];\n};\n\n// 下载导入模板(账号 / 姓名 两列)\nconst handleDownloadTemplate = () => {\n const percent: Ref<number> = ref(0);\n exportJsonToExcel({\n header: ['账号', '姓名'],\n data: [],\n filename: '批量创建用户导入模板',\n bookType: 'xlsx',\n autoWidth: true,\n percent,\n } as any);\n};\n\n// 导入 Excel:解析“账号 / 姓名”列,追加到表格\nconst beforeImportUpload = (file: any) => {\n const reader = new FileReader();\n reader.onload = (e: any) => {\n try {\n const data = new Uint8Array(e.target.result);\n const workbook = read(data, { type: 'array' });\n const firstSheet = workbook.SheetNames[0];\n const worksheet = workbook.Sheets[firstSheet];\n const sheetData: any[][] = utils.sheet_to_json(worksheet, { header: 1 });\n const headers = (sheetData[0] || []).map((h: any) =>\n String(h ?? '')\n .trim()\n .toLowerCase(),\n );\n const userCol = headers.findIndex(h => ['账号', '用户名', 'username'].includes(h));\n const nameCol = headers.findIndex(h => ['姓名', '名字', 'realname', 'name'].includes(h));\n if (userCol === -1 && nameCol === -1) {\n message.warning('未识别到“账号/姓名”列,请使用下载的模板');\n return;\n }\n const rows = sheetData\n .slice(1)\n .filter((row: any[]) => row && row.some(c => c !== '' && c != null));\n let added = 0;\n const newRows = [];\n rows.forEach((row: any[]) => {\n const username = userCol > -1 ? String(row[userCol] ?? '').trim() : '';\n const realname = nameCol > -1 ? String(row[nameCol] ?? '').trim() : '';\n if (!username && !realname) return;\n newRows.push({ id: genId(), username, realname });\n added++;\n });\n dataSource.value = [...dataSource.value, ...newRows];\n if (added > 0) message.success(`已导入 ${added} 条`);\n else message.info('文件中没有可导入的数据');\n } catch (err) {\n console.error(err);\n message.error('解析 Excel 失败');\n }\n };\n reader.readAsArrayBuffer(file);\n return false; // 阻止自动上传\n};\n\nconst handleOk = async () => {\n // 前置校验\n if (!orgIds.value || orgIds.value.length === 0) {\n message.error('请至少选择一个组织');\n return;\n }\n if (manageType.value === null || manageType.value === undefined) {\n message.error('请选择用户类型');\n return;\n }\n if (dataSource.value.length === 0) {\n message.error('请至少添加一条用户信息');\n return;\n }\n // 校验账号/姓名非空,且账号不重复\n const seen = new Set<string>();\n for (const row of dataSource.value) {\n const username = (row.username || '').trim();\n const realname = (row.realname || '').trim();\n if (!username) {\n message.error('账号不能为空,请检查表格');\n return;\n }\n if (!realname) {\n message.error(`账号 [${username}] 的姓名不能为空`);\n return;\n }\n if (seen.has(username)) {\n message.error(`账号 [${username}] 重复,请检查`);\n return;\n }\n seen.add(username);\n }\n\n const payload = {\n org_ids: orgIds.value,\n manage_type: manageType.value,\n user_list: dataSource.value.map(r => ({\n username: (r.username || '').trim(),\n realname: (r.realname || '').trim(),\n })),\n org_id: current_org?.id,\n };\n\n loading.value = true;\n try {\n await getApiData(payload, '/sys/create_batch_user');\n message.success(`批量创建完成`);\n event_obj()?.emit('refresh');\n emit('ok');\n } catch (err) {\n console.error(err);\n // 错误提示已由请求拦截器统一处理\n } finally {\n loading.value = false;\n }\n};\n\nconst handleCancel = () => emit('cancel');\n\nonMounted(async () => {\n try {\n const res: any = await getApiData({ is_pagination: false }, '/org/query_org_for_person_admin');\n const list = res?.data ?? res ?? [];\n orgOptions.value = Array.isArray(list) ? list : [];\n } catch (err) {\n console.error('加载组织列表失败', err);\n }\n});\n</script>\n\n<style scoped lang=\"less\">\n.batch-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.batch-tip {\n margin-left: auto;\n color: #999;\n font-size: 12px;\n}\n</style>\n","<template>\n <a-modal\n :title=\"title\"\n width=\"900px\"\n :open=\"open\"\n :bodyStyle=\"{ height: '75vh', overflowY: 'auto', padding: '0 24px' }\"\n :confirm-loading=\"loading\"\n cancel-text=\"取消\"\n ok-text=\"确定\"\n centered\n destroy-on-close\n @cancel=\"handleCancel\"\n @ok=\"handleOk\"\n >\n <a-form\n layout=\"vertical\"\n size=\"small\"\n >\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"组织\"\n required\n >\n <a-select\n v-model:value=\"orgIds\"\n mode=\"multiple\"\n placeholder=\"请选择组织(可多选)\"\n :options=\"orgOptions\"\n :field-names=\"{ label: 'name', value: 'id' }\"\n allow-clear\n show-search\n option-filter-prop=\"name\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"用户类型\"\n required\n >\n <a-select\n v-model:value=\"manageType\"\n placeholder=\"请选择用户类型\"\n :options=\"manageTypeOptions\"\n />\n </a-form-item>\n </a-col>\n </a-row>\n </a-form>\n\n <div class=\"batch-toolbar\">\n <a-button\n type=\"primary\"\n @click=\"handleAddRow\"\n >\n <plus-outlined />\n 添加\n </a-button>\n <a-upload\n :show-upload-list=\"false\"\n :before-upload=\"beforeImportUpload\"\n accept=\".xlsx,.xls\"\n >\n <a-button>\n <upload-outlined />\n 导入\n </a-button>\n </a-upload>\n <a-button @click=\"handleDownloadTemplate\">\n <download-outlined />\n 下载导入模板\n </a-button>\n <a-popconfirm\n title=\"确认清空所有数据?\"\n @confirm=\"handleClear()\"\n v-if=\"dataSource.length > 0\"\n >\n <a-button\n type=\"default\"\n danger\n >\n 清空\n </a-button>\n </a-popconfirm>\n <span class=\"batch-tip\">共 {{ dataSource.length }} 条</span>\n </div>\n\n <s-table\n :columns=\"columns\"\n :data-source=\"dataSource\"\n :rowKey=\"rowKey\"\n :bordered=\"true\"\n size=\"small\"\n :pagination=\"false\"\n :scroll=\"{ y: 320 }\"\n >\n <template #bodyCell=\"{ column, index, record }\">\n <template v-if=\"column.dataIndex === 'index'\">\n {{ index + 1 }}\n </template>\n <template v-else-if=\"column.dataIndex === 'username'\">\n <a-input\n v-if=\"record.id === currentEditId\"\n v-model:value=\"record.username\"\n placeholder=\"请输入账号\"\n allow-clear\n />\n <span v-else>{{ record.username }}</span>\n </template>\n <template v-else-if=\"column.dataIndex === 'realname'\">\n <a-input\n v-if=\"record.id === currentEditId\"\n v-model:value=\"record.realname\"\n placeholder=\"请输入姓名\"\n allow-clear\n />\n <span v-else>{{ record.realname }}</span>\n </template>\n <template v-else-if=\"column.dataIndex === 'action'\">\n <a-space>\n <a-button\n type=\"link\"\n size=\"small\"\n v-if=\"record.id !== currentEditId\"\n @click=\"handleEdit(record)\"\n >\n 编辑\n </a-button>\n <a-button\n type=\"link\"\n size=\"small\"\n v-else\n @click=\"handleComplete()\"\n >\n 完成\n </a-button>\n <a-popconfirm\n title=\"确认删除该行?\"\n @confirm=\"handleDelete(record)\"\n >\n <a-button\n type=\"link\"\n size=\"small\"\n danger\n >\n 删除\n </a-button>\n </a-popconfirm>\n </a-space>\n </template>\n </template>\n </s-table>\n </a-modal>\n</template>\n\n<script setup lang=\"ts\">\nimport { DownloadOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons-vue';\nimport { message } from 'ant-design-vue';\nimport { read, utils } from 'xlsx';\nimport type { Ref } from 'vue';\nimport { onMounted, ref, shallowRef } from 'vue';\nimport { event_obj, useUserStore } from 'liyu-pc-base/init-plugin';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport { exportJsonToExcel } from 'liyu-pc-base/utils/excel';\n\ninterface Props {\n title?: string;\n record?: any;\n}\nconst props = defineProps<Props>();\nconst emit = defineEmits(['cancel', 'ok']);\n\nconst open = ref(true);\nconst loading = ref(false);\nconst orgIds = ref<any[]>([]);\nconst manageType = ref<number>(0);\nconst orgOptions = ref<any[]>([]);\nconst dataSource = shallowRef<any[]>([]);\nconst currentEditId = ref<string>('');\n\nconst userStore = useUserStore();\nconst current_org = userStore.currentOrg;\n\n// 用户类型可选项:受当前账号 manage_type 约束(与 permission-user 保持一致)\nconst manageTypeOptions = (() => {\n const curr = userStore.person?.manage_type;\n if (curr === 2) {\n return [\n { label: '普通用户', value: 0 },\n { label: '管理员', value: 1 },\n { label: '超级管理员', value: 2 },\n ];\n } else if (curr === 1) {\n return [\n { label: '普通用户', value: 0 },\n { label: '管理员', value: 1 },\n ];\n }\n return [{ label: '普通用户', value: 0 }];\n})();\n\nconst columns = [\n { title: '序号', dataIndex: 'index', width: 70, fixed: 'left' },\n { title: '账号', dataIndex: 'username', width: 220 },\n { title: '姓名', dataIndex: 'realname', width: 220 },\n { title: '操作', dataIndex: 'action', width: 140, fixed: 'right' },\n];\nconst rowKey = 'id';\n\nlet uid = 0;\nconst genId = () => `row_${++uid}`;\n\nconst handleAddRow = () => {\n const id = genId();\n currentEditId.value = id;\n dataSource.value = [...dataSource.value, { id, username: '', realname: '' }];\n};\n\nconst handleEdit = (record: any) => {\n currentEditId.value = record.id;\n dataSource.value = [...dataSource.value];\n};\nconst handleComplete = () => {\n currentEditId.value = '';\n dataSource.value = [...dataSource.value];\n};\nconst handleClear = () => {\n dataSource.value = [];\n};\n\nconst handleDelete = (record: any) => {\n const idx = dataSource.value.findIndex(r => r.id === record.id);\n if (idx > -1) dataSource.value.splice(idx, 1);\n dataSource.value = [...dataSource.value];\n};\n\n// 下载导入模板(账号 / 姓名 两列)\nconst handleDownloadTemplate = () => {\n const percent: Ref<number> = ref(0);\n exportJsonToExcel({\n header: ['账号', '姓名'],\n data: [],\n filename: '批量创建用户导入模板',\n bookType: 'xlsx',\n autoWidth: true,\n percent,\n } as any);\n};\n\n// 导入 Excel:解析“账号 / 姓名”列,追加到表格\nconst beforeImportUpload = (file: any) => {\n const reader = new FileReader();\n reader.onload = (e: any) => {\n try {\n const data = new Uint8Array(e.target.result);\n const workbook = read(data, { type: 'array' });\n const firstSheet = workbook.SheetNames[0];\n const worksheet = workbook.Sheets[firstSheet];\n const sheetData: any[][] = utils.sheet_to_json(worksheet, { header: 1 });\n const headers = (sheetData[0] || []).map((h: any) =>\n String(h ?? '')\n .trim()\n .toLowerCase(),\n );\n const userCol = headers.findIndex(h => ['账号', '用户名', 'username'].includes(h));\n const nameCol = headers.findIndex(h => ['姓名', '名字', 'realname', 'name'].includes(h));\n if (userCol === -1 && nameCol === -1) {\n message.warning('未识别到“账号/姓名”列,请使用下载的模板');\n return;\n }\n const rows = sheetData\n .slice(1)\n .filter((row: any[]) => row && row.some(c => c !== '' && c != null));\n let added = 0;\n const newRows = [];\n rows.forEach((row: any[]) => {\n const username = userCol > -1 ? String(row[userCol] ?? '').trim() : '';\n const realname = nameCol > -1 ? String(row[nameCol] ?? '').trim() : '';\n if (!username && !realname) return;\n newRows.push({ id: genId(), username, realname });\n added++;\n });\n dataSource.value = [...dataSource.value, ...newRows];\n if (added > 0) message.success(`已导入 ${added} 条`);\n else message.info('文件中没有可导入的数据');\n } catch (err) {\n console.error(err);\n message.error('解析 Excel 失败');\n }\n };\n reader.readAsArrayBuffer(file);\n return false; // 阻止自动上传\n};\n\nconst handleOk = async () => {\n // 前置校验\n if (!orgIds.value || orgIds.value.length === 0) {\n message.error('请至少选择一个组织');\n return;\n }\n if (manageType.value === null || manageType.value === undefined) {\n message.error('请选择用户类型');\n return;\n }\n if (dataSource.value.length === 0) {\n message.error('请至少添加一条用户信息');\n return;\n }\n // 校验账号/姓名非空,且账号不重复\n const seen = new Set<string>();\n for (const row of dataSource.value) {\n const username = (row.username || '').trim();\n const realname = (row.realname || '').trim();\n if (!username) {\n message.error('账号不能为空,请检查表格');\n return;\n }\n if (!realname) {\n message.error(`账号 [${username}] 的姓名不能为空`);\n return;\n }\n if (seen.has(username)) {\n message.error(`账号 [${username}] 重复,请检查`);\n return;\n }\n seen.add(username);\n }\n\n const payload = {\n org_ids: orgIds.value,\n manage_type: manageType.value,\n user_list: dataSource.value.map(r => ({\n username: (r.username || '').trim(),\n realname: (r.realname || '').trim(),\n })),\n org_id: current_org?.id,\n };\n\n loading.value = true;\n try {\n await getApiData(payload, '/sys/create_batch_user');\n message.success(`批量创建完成`);\n event_obj()?.emit('refresh');\n emit('ok');\n } catch (err) {\n console.error(err);\n // 错误提示已由请求拦截器统一处理\n } finally {\n loading.value = false;\n }\n};\n\nconst handleCancel = () => emit('cancel');\n\nonMounted(async () => {\n try {\n const res: any = await getApiData({ is_pagination: false }, '/org/query_org_for_person_admin');\n const list = res?.data ?? res ?? [];\n orgOptions.value = Array.isArray(list) ? list : [];\n } catch (err) {\n console.error('加载组织列表失败', err);\n }\n});\n</script>\n\n<style scoped lang=\"less\">\n.batch-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.batch-tip {\n margin-left: auto;\n color: #999;\n font-size: 12px;\n}\n</style>\n"],"mappings":"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"}