create.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <template>
  2. <div class="sub-account-form-container">
  3. <!-- 头部:返回按钮和标题 -->
  4. <div class="header-section">
  5. <el-button :icon="ArrowLeft" text @click="handleBack"> 返回 </el-button>
  6. </div>
  7. <!-- 表单内容 -->
  8. <div class="form-content">
  9. <el-form ref="subAccountFormRef" :model="subAccountForm" :rules="subAccountFormRules" label-width="120px">
  10. <!-- 基本信息 -->
  11. <div class="form-section">
  12. <h3 class="section-title">基本信息</h3>
  13. <el-form-item label="账号名称" prop="accountName">
  14. <el-input v-model="subAccountForm.accountName" placeholder="请输入" maxlength="50" clearable :disabled="isView" />
  15. </el-form-item>
  16. <el-form-item label="手机号" prop="phone">
  17. <el-input v-model="subAccountForm.phone" placeholder="请输入" maxlength="11" clearable :disabled="isView" />
  18. </el-form-item>
  19. </div>
  20. <!-- 权限 -->
  21. <div class="form-section">
  22. <h3 class="section-title">权限</h3>
  23. <el-form-item label="选择角色" prop="roleId">
  24. <el-select
  25. v-model="subAccountForm.roleId"
  26. placeholder="请选择角色"
  27. clearable
  28. style="width: 100%"
  29. :disabled="isView"
  30. @change="handleRoleChange"
  31. >
  32. <el-option
  33. v-for="role in roleList"
  34. :key="role.roleId || role.id"
  35. :label="role.roleName"
  36. :value="role.roleId || role.id"
  37. />
  38. </el-select>
  39. </el-form-item>
  40. <!-- 权限展示:与角色管理相同的树状图,仅禁用展示 -->
  41. <el-form-item label="权限" v-if="subAccountForm.roleId && permissionTreeData.length > 0">
  42. <div class="permission-tree-container">
  43. <el-tree
  44. ref="permissionTreeRef"
  45. :data="permissionTreeData"
  46. :props="treeProps"
  47. show-checkbox
  48. node-key="id"
  49. :default-expand-all="true"
  50. :default-checked-keys="defaultCheckedKeys"
  51. :expand-on-click-node="false"
  52. >
  53. <template #default="{ data }">
  54. <span class="tree-node-label">
  55. <span>{{ data.label }}</span>
  56. </span>
  57. </template>
  58. </el-tree>
  59. </div>
  60. </el-form-item>
  61. </div>
  62. </el-form>
  63. </div>
  64. <!-- 底部按钮 -->
  65. <div class="form-footer">
  66. <el-button @click="handleCancel">
  67. {{ isView ? "返回" : "取消" }}
  68. </el-button>
  69. <el-button v-if="!isView" type="primary" @click="handleSave" :loading="saveLoading"> 确定 </el-button>
  70. </div>
  71. </div>
  72. </template>
  73. <script setup lang="ts" name="subAccountCreate">
  74. import { ref, reactive, onMounted, computed, nextTick } from "vue";
  75. import { useRouter, useRoute } from "vue-router";
  76. import { ElMessage, type FormInstance, type FormRules } from "element-plus";
  77. import { ArrowLeft } from "@element-plus/icons-vue";
  78. import {
  79. getAllNormalRoles,
  80. getRolePermissionTable,
  81. type RolePermissionItem,
  82. type RoleItem,
  83. createAccountAndAssignRole,
  84. getSubAccountDetail,
  85. updateSubAccount
  86. } from "@/api/modules/accountRoleManagement";
  87. import { localGet } from "@/utils";
  88. // 路由
  89. const router = useRouter();
  90. const route = useRoute();
  91. // 判断是编辑、查看还是创建
  92. const accountId = computed(() => (route.params.id as string) || (route.query.id as string) || undefined);
  93. const isEdit = computed(() => !!accountId.value && route.query.type !== "view");
  94. const isView = computed(() => !!accountId.value && route.query.type === "view");
  95. // 表单引用
  96. const subAccountFormRef = ref<FormInstance>();
  97. // 表单数据
  98. const subAccountForm = reactive({
  99. accountName: "",
  100. phone: "",
  101. roleId: undefined as number | undefined,
  102. userId: undefined as number | undefined
  103. });
  104. // 表单验证规则
  105. const subAccountFormRules: FormRules = {
  106. accountName: [{ required: true, message: "请输入账号名称", trigger: "blur" }],
  107. phone: [
  108. { required: true, message: "请输入手机号", trigger: "blur" },
  109. { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号", trigger: "blur" }
  110. ],
  111. roleId: [{ required: true, message: "请选择角色", trigger: "change" }]
  112. };
  113. // 角色列表
  114. const roleList = ref<RoleItem[]>([]);
  115. // 树形组件配置(disabled 用于只读展示)
  116. const treeProps = {
  117. children: "children",
  118. label: "label",
  119. disabled: "disabled"
  120. };
  121. // 权限树数据(与角色管理同结构,节点全部 disabled)
  122. const permissionTreeData = ref<any[]>([]);
  123. const permissionTreeRef = ref<any>();
  124. const defaultCheckedKeys = ref<number[]>([]);
  125. // 保存加载状态
  126. const saveLoading = ref(false);
  127. // 加载角色列表
  128. const loadRoleList = async () => {
  129. try {
  130. const storeId = localGet("createdId") || "";
  131. const res = await getAllNormalRoles({ storeId });
  132. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  133. if (code === 200) {
  134. // 处理角色数据,确保有 id 或 roleId 字段
  135. const roles = (res.data || []).map((role: any) => ({
  136. ...role,
  137. // 如果后端返回的是 roleId,也确保 id 字段存在
  138. id: role.roleId || role.id,
  139. // 如果后端返回的是 id,也确保 roleId 字段存在
  140. roleId: role.roleId || role.id
  141. }));
  142. roleList.value = roles;
  143. } else {
  144. ElMessage.error(res.msg || "获取角色列表失败");
  145. }
  146. } catch (error) {
  147. console.error("获取角色列表失败:", error);
  148. ElMessage.error("获取角色列表失败,请重试");
  149. }
  150. };
  151. // 将角色权限(仅已选中的)转成树结构,不包含未选中的节点
  152. const transformRolePermissionsToTree = (list: RolePermissionItem[]): { tree: any[]; allIds: number[] } => {
  153. const level1Map = new Map<string, any>();
  154. const level2Map = new Map<string, any>();
  155. const level3Map = new Map<string, any>();
  156. const allIds: number[] = [];
  157. let idCounter = 1;
  158. for (const item of list || []) {
  159. if (!item.level1Permission) continue;
  160. let level1 = level1Map.get(item.level1Permission);
  161. if (!level1) {
  162. level1 = { id: idCounter++, label: item.level1Permission, disabled: true, children: [] };
  163. allIds.push(level1.id);
  164. level1Map.set(item.level1Permission, level1);
  165. }
  166. if (item.level2Permission) {
  167. const k2 = `${item.level1Permission}-${item.level2Permission}`;
  168. let level2 = level2Map.get(k2);
  169. if (!level2) {
  170. level2 = { id: idCounter++, label: item.level2Permission, disabled: true, children: [] };
  171. allIds.push(level2.id);
  172. level2Map.set(k2, level2);
  173. level1.children.push(level2);
  174. }
  175. if (item.level3Permission?.trim()) {
  176. const names = item.level3Permission
  177. .split(/\s+/)
  178. .map(s => s.trim())
  179. .filter(Boolean);
  180. for (const name of names) {
  181. const k3 = `${k2}-${name}`;
  182. if (level3Map.has(k3)) continue;
  183. const level3 = { id: idCounter++, label: name, disabled: true };
  184. allIds.push(level3.id);
  185. level3Map.set(k3, level3);
  186. level2.children.push(level3);
  187. }
  188. }
  189. }
  190. }
  191. return { tree: Array.from(level1Map.values()), allIds };
  192. };
  193. // 加载当前角色权限:只展示已选中的权限树(不展示未选中的)
  194. const loadPermissionList = async () => {
  195. if (!subAccountForm.roleId) {
  196. permissionTreeData.value = [];
  197. defaultCheckedKeys.value = [];
  198. return;
  199. }
  200. try {
  201. const roleId = Number(subAccountForm.roleId);
  202. if (!roleId || isNaN(roleId)) {
  203. permissionTreeData.value = [];
  204. defaultCheckedKeys.value = [];
  205. return;
  206. }
  207. const res = await getRolePermissionTable({ roleId });
  208. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  209. if (code === 200 && res.data) {
  210. const { tree, allIds } = transformRolePermissionsToTree(res.data);
  211. permissionTreeData.value = tree;
  212. defaultCheckedKeys.value = allIds;
  213. nextTick(() => {
  214. permissionTreeRef.value?.setCheckedKeys(allIds);
  215. });
  216. } else {
  217. ElMessage.error(res.msg || "获取权限列表失败");
  218. permissionTreeData.value = [];
  219. defaultCheckedKeys.value = [];
  220. }
  221. } catch (error) {
  222. console.error("获取权限列表失败:", error);
  223. ElMessage.error("获取权限列表失败,请重试");
  224. permissionTreeData.value = [];
  225. defaultCheckedKeys.value = [];
  226. }
  227. };
  228. // 初始化数据
  229. onMounted(() => {
  230. loadRoleList();
  231. if (isEdit.value || isView.value) {
  232. loadSubAccountData();
  233. }
  234. });
  235. // (编辑或查看时调用详情接口获取子账号数据)
  236. const loadSubAccountData = async () => {
  237. if (!accountId.value) return;
  238. try {
  239. const storeId = localGet("createdId") || localGet("geeker-user")?.userInfo?.storeId || "";
  240. const userId = route.query.userId;
  241. const roleIdNew = Number(route.query.roleId);
  242. const res = await getSubAccountDetail(Number(storeId), userId, roleIdNew);
  243. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  244. if (code === 200 && res.data) {
  245. // 处理返回数据:可能是数组或对象
  246. let accountData: any;
  247. accountData = res.data;
  248. // 填充表单数据
  249. subAccountForm.accountName = accountData.accountName || "";
  250. subAccountForm.phone = accountData.phone || "";
  251. subAccountForm.roleId = accountData.roleId;
  252. subAccountForm.userId = accountData.userId || Number(accountId.value);
  253. // 按角色加载权限树勾选(权限由角色决定,仅展示)
  254. if (subAccountForm.roleId) {
  255. await loadPermissionList();
  256. }
  257. } else {
  258. ElMessage.error(res.msg || "获取子账号详情失败");
  259. }
  260. } catch (error) {
  261. console.error("获取子账号详情失败:", error);
  262. ElMessage.error("获取子账号详情失败,请重试");
  263. }
  264. };
  265. // 返回
  266. const handleBack = () => {
  267. router.back();
  268. };
  269. // 取消
  270. const handleCancel = () => {
  271. router.back();
  272. };
  273. // 保存
  274. const handleSave = async () => {
  275. if (!subAccountFormRef.value) return;
  276. await subAccountFormRef.value.validate(async valid => {
  277. if (!valid) return;
  278. saveLoading.value = true;
  279. try {
  280. const createdId = localGet("createdId") || localGet("geeker-user")?.userInfo?.storeId || "";
  281. const storeId = Number(createdId) || 0;
  282. if (isEdit.value) {
  283. // 编辑模式:调用更新接口
  284. if (!subAccountForm.userId) {
  285. ElMessage.error("缺少用户ID,无法更新");
  286. return;
  287. }
  288. const updateAccountDto = {
  289. accountName: subAccountForm.accountName,
  290. phone: subAccountForm.phone,
  291. roleId: route.query.roleId || 0,
  292. storeId: storeId,
  293. userId: route.query.userId,
  294. id: accountId.value
  295. };
  296. const res = await updateSubAccount(updateAccountDto);
  297. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  298. if (code == 200) {
  299. ElMessage.success("编辑成功");
  300. setTimeout(() => {
  301. router.push("/accountRoleManagement/subAccountManagement");
  302. }, 1000);
  303. }
  304. } else {
  305. // 创建模式:调用创建接口
  306. const createAccountDto = {
  307. accountName: subAccountForm.accountName,
  308. phone: subAccountForm.phone,
  309. roleId: subAccountForm.roleId,
  310. storeId: storeId
  311. };
  312. const res = await createAccountAndAssignRole(createAccountDto);
  313. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  314. if (code === 200) {
  315. ElMessage.success("创建成功后,员工可以使用手机号与验证码进行登录");
  316. setTimeout(() => {
  317. router.push("/accountRoleManagement/subAccountManagement");
  318. }, 1000);
  319. }
  320. }
  321. } catch (error) {
  322. console.error("保存失败:", error);
  323. } finally {
  324. saveLoading.value = false;
  325. }
  326. });
  327. };
  328. // 处理角色选择变化
  329. const handleRoleChange = (value: number | null | undefined) => {
  330. subAccountForm.roleId = value as number | undefined;
  331. permissionTreeData.value = [];
  332. defaultCheckedKeys.value = [];
  333. if (value) {
  334. loadPermissionList();
  335. } else {
  336. nextTick(() => permissionTreeRef.value?.setCheckedKeys([]));
  337. }
  338. };
  339. </script>
  340. <style lang="scss" scoped>
  341. .sub-account-form-container {
  342. min-height: calc(100vh - 84px);
  343. padding: 20px;
  344. background: #ffffff;
  345. }
  346. // 头部区域
  347. .header-section {
  348. padding-bottom: 16px;
  349. margin-bottom: 24px;
  350. border-bottom: 1px solid #e4e7ed;
  351. }
  352. // 表单内容
  353. .form-content {
  354. max-width: 800px;
  355. padding: 24px 0;
  356. .form-section {
  357. margin-bottom: 32px;
  358. .section-title {
  359. margin: 0 0 24px;
  360. font-size: 16px;
  361. font-weight: 600;
  362. color: var(--el-text-color-primary);
  363. }
  364. }
  365. :deep(.el-form-item) {
  366. margin-bottom: 24px;
  367. .el-form-item__label {
  368. font-weight: 500;
  369. color: var(--el-text-color-regular);
  370. }
  371. }
  372. // 权限树(与角色管理同款样式,仅禁用展示)
  373. .permission-tree-container {
  374. width: 100%;
  375. min-height: 200px;
  376. max-height: 500px;
  377. padding: 16px;
  378. overflow-y: auto;
  379. background: #f5f7fa;
  380. border: 1px solid #e4e7ed;
  381. border-radius: 4px;
  382. :deep(.el-tree) {
  383. background: transparent;
  384. .el-tree-node {
  385. .el-tree-node__content {
  386. height: 40px;
  387. padding: 0 12px;
  388. margin-bottom: 2px;
  389. border-radius: 4px;
  390. transition: background-color 0.2s;
  391. &:hover {
  392. background-color: #ecf5ff;
  393. }
  394. .tree-node-label {
  395. display: flex;
  396. flex: 1;
  397. align-items: center;
  398. font-size: 14px;
  399. color: var(--el-text-color-primary);
  400. }
  401. }
  402. .el-checkbox {
  403. margin-right: 8px;
  404. }
  405. .el-tree-node__children {
  406. padding-left: 20px;
  407. }
  408. }
  409. .el-tree-node__expand-icon {
  410. margin-right: 6px;
  411. font-size: 14px;
  412. color: #909399;
  413. transform: translateX(-5px);
  414. }
  415. .el-tree-node__expand-icon.is-leaf {
  416. display: none;
  417. }
  418. }
  419. }
  420. }
  421. // 底部按钮
  422. .form-footer {
  423. display: flex;
  424. gap: 12px;
  425. justify-content: center;
  426. padding: 24px 0;
  427. margin-top: 24px;
  428. border-top: 1px solid #e4e7ed;
  429. }
  430. </style>