index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <template>
  2. <div class="sub-account-management-container">
  3. <ProTable
  4. ref="proTable"
  5. :columns="columns"
  6. :request-api="getTableList"
  7. :init-param="initParam"
  8. :data-callback="dataCallback"
  9. row-key="userId"
  10. >
  11. <!-- 表格 header 按钮 -->
  12. <template #tableHeader="scope">
  13. <div class="table-header-btn">
  14. <el-button type="primary" :icon="Plus" @click="handleCreateSubAccount"> 新建账号 </el-button>
  15. <el-button :icon="Delete" :disabled="!scope.isSelected" @click="handleBatchDelete"> 批量删除 </el-button>
  16. </div>
  17. </template>
  18. <template #tableHeaderRight>
  19. <el-link type="primary" :underline="false" @click="handleOperationLog"> 历史操作记录 </el-link>
  20. </template>
  21. <!-- 可操作权限列 -->
  22. <template #permissionCount="scope">
  23. <span>{{ scope.row.permissionCount || 0 }}个</span>
  24. </template>
  25. <!-- 操作列 -->
  26. <template #operation="scope">
  27. <el-button link type="primary" @click="handleView(scope.row)"> 查看 </el-button>
  28. <el-button link type="danger" @click="handleDelete(scope.row)"> 删除 </el-button>
  29. </template>
  30. </ProTable>
  31. <!-- 删除确认对话框 -->
  32. <el-dialog v-model="deleteDialogVisible" title="删除确认" width="400px" :close-on-click-modal="false">
  33. <div class="delete-content">
  34. <p v-if="deleteType === 'single'">确定要删除子账号"{{ currentAccount?.accountName }}"吗?</p>
  35. <p v-else>确定要删除选中的 {{ selectedAccounts.length }} 个子账号吗?</p>
  36. <p class="delete-warning">此操作不可恢复!</p>
  37. </div>
  38. <template #footer>
  39. <div class="dialog-footer">
  40. <el-button @click="deleteDialogVisible = false"> 取消 </el-button>
  41. <el-button type="danger" @click="handleConfirmDelete" :loading="deleteLoading"> 确定删除 </el-button>
  42. </div>
  43. </template>
  44. </el-dialog>
  45. </div>
  46. </template>
  47. <script setup lang="tsx" name="subAccountManagement">
  48. import { ref, reactive, onMounted, computed } from "vue";
  49. import { useRouter } from "vue-router";
  50. import { ElMessage } from "element-plus";
  51. import { Plus, Delete } from "@element-plus/icons-vue";
  52. import ProTable from "@/components/ProTable/index.vue";
  53. import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
  54. import {
  55. querySubAccounts,
  56. getAllNormalRoles,
  57. type RoleItem,
  58. removeRole,
  59. batchDeleteSubAccounts
  60. } from "@/api/modules/accountRoleManagement";
  61. import { localGet } from "@/utils";
  62. // 子账号数据类型
  63. interface SubAccountItem {
  64. userId: number;
  65. accountName: string;
  66. phone: string;
  67. roleId: number;
  68. roleName: string;
  69. permissionCount: number;
  70. }
  71. // 路由
  72. const router = useRouter();
  73. // ProTable 实例
  74. const proTable = ref<ProTableInstance>();
  75. // 角色列表(用于搜索下拉框)
  76. const roleList = ref<RoleItem[]>([]);
  77. // 选中的账号
  78. const selectedAccounts = ref<SubAccountItem[]>([]);
  79. // 删除相关
  80. const deleteDialogVisible = ref(false);
  81. const deleteLoading = ref(false);
  82. const deleteType = ref<"single" | "batch">("single");
  83. const currentAccount = ref<SubAccountItem | null>(null);
  84. // 获取 storeId
  85. const getStoreId = (): string => {
  86. return localGet("createdId") || localGet("geeker-user")?.userInfo?.storeId || "";
  87. };
  88. // 加载角色列表(用于搜索下拉框)
  89. const loadRoleList = async () => {
  90. try {
  91. const storeId = getStoreId();
  92. if (!storeId) return;
  93. const res = await getAllNormalRoles({ storeId: Number(storeId) });
  94. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  95. if (code === 200) {
  96. roleList.value = (res.data || []).map((role: any) => ({
  97. ...role,
  98. id: role.roleId || role.id,
  99. roleId: role.roleId || role.id
  100. }));
  101. }
  102. } catch (error) {
  103. console.error("获取角色列表失败:", error);
  104. }
  105. };
  106. // 表格列配置
  107. const columns = reactive<ColumnProps<SubAccountItem>[]>([
  108. {
  109. type: "selection",
  110. fixed: "left",
  111. width: 55
  112. },
  113. {
  114. prop: "accountName",
  115. label: "账号名称",
  116. search: {
  117. el: "input",
  118. props: { placeholder: "请输入账号名称" }
  119. }
  120. },
  121. {
  122. prop: "phone",
  123. label: "手机号",
  124. search: {
  125. el: "input",
  126. props: { placeholder: "请输入手机号" }
  127. }
  128. },
  129. {
  130. prop: "roleName",
  131. label: "角色",
  132. search: {
  133. el: "select",
  134. props: { placeholder: "请选择角色", clearable: true },
  135. key: "roleName"
  136. },
  137. enum: computed(() => {
  138. return roleList.value.map(role => ({
  139. label: role.roleName,
  140. value: role.roleName
  141. }));
  142. }),
  143. fieldNames: { label: "label", value: "value" }
  144. },
  145. {
  146. prop: "permissionCount",
  147. label: "可操作权限"
  148. },
  149. {
  150. prop: "operation",
  151. label: "操作",
  152. fixed: "right",
  153. width: 180
  154. }
  155. ]);
  156. // 初始化参数
  157. const initParam = reactive({
  158. storeId: getStoreId()
  159. });
  160. // 数据回调处理
  161. const dataCallback = (data: any) => {
  162. // 如果返回的是数组,转换为分页格式
  163. if (Array.isArray(data)) {
  164. return {
  165. list: data,
  166. total: data.length
  167. };
  168. }
  169. // 如果返回的是分页对象
  170. if (data.records) {
  171. return {
  172. list: data.records || [],
  173. total: data.total || 0
  174. };
  175. }
  176. // 默认返回
  177. return {
  178. list: [],
  179. total: 0
  180. };
  181. };
  182. // 请求接口
  183. const getTableList = (params: any) => {
  184. const storeId = getStoreId();
  185. if (!storeId) {
  186. ElMessage.warning("缺少店铺ID");
  187. return Promise.resolve({ code: 200, data: [], msg: "缺少店铺ID" });
  188. }
  189. return querySubAccounts(Number(storeId), params.accountName || "", params.phone || "", params.roleName || "");
  190. };
  191. // 创建子账号
  192. const handleCreateSubAccount = () => {
  193. router.push("/accountRoleManagement/subAccountManagement/create");
  194. };
  195. // 查看
  196. const handleView = (row: SubAccountItem) => {
  197. router.push({
  198. path: "/accountRoleManagement/subAccountManagement/create",
  199. query: {
  200. id: row.userId.toString(),
  201. accountName: row.accountName,
  202. roleId: row.roleId,
  203. type: "view"
  204. }
  205. });
  206. };
  207. // 编辑
  208. const handleEdit = (row: SubAccountItem) => {
  209. router.push({
  210. path: "/accountRoleManagement/subAccountManagement/create",
  211. query: {
  212. id: row.id,
  213. roleId: row.roleId,
  214. userId: row.userId,
  215. accountName: row.accountName,
  216. type: "edit"
  217. }
  218. });
  219. };
  220. // 删除单个账号
  221. const handleDelete = (row: SubAccountItem) => {
  222. currentAccount.value = row;
  223. deleteType.value = "single";
  224. deleteDialogVisible.value = true;
  225. };
  226. // 批量删除
  227. const handleBatchDelete = () => {
  228. const selectedList = proTable.value?.selectedList || [];
  229. if (selectedList.length === 0) {
  230. ElMessage.warning("请选择要删除的子账号");
  231. return;
  232. }
  233. selectedAccounts.value = selectedList as SubAccountItem[];
  234. deleteType.value = "batch";
  235. deleteDialogVisible.value = true;
  236. };
  237. // 确认删除
  238. const handleConfirmDelete = async () => {
  239. deleteLoading.value = true;
  240. try {
  241. if (deleteType.value === "single" && currentAccount.value) {
  242. const res = await removeRole(currentAccount.value.roleId, getStoreId(), currentAccount.value.userId);
  243. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  244. if (code === 200) {
  245. ElMessage.success("删除成功");
  246. } else {
  247. ElMessage.error("删除失败,请重试");
  248. }
  249. } else {
  250. // 批量删除
  251. const res = await batchDeleteSubAccounts(
  252. getStoreId(),
  253. selectedAccounts.value.map(item => item.userId)
  254. );
  255. const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
  256. if (code === 200) {
  257. ElMessage.success("删除成功");
  258. } else {
  259. ElMessage.error("删除失败,请重试");
  260. }
  261. }
  262. // 刷新表格
  263. proTable.value?.getTableList();
  264. deleteDialogVisible.value = false;
  265. currentAccount.value = null;
  266. selectedAccounts.value = [];
  267. } catch (error) {
  268. ElMessage.error("删除失败,请重试");
  269. } finally {
  270. deleteLoading.value = false;
  271. }
  272. };
  273. // 跳转到历史操作记录
  274. const handleOperationLog = () => {
  275. router.push("/accountRoleManagement/subAccountManagement/operationLog");
  276. };
  277. // 初始化
  278. onMounted(() => {
  279. loadRoleList();
  280. });
  281. </script>
  282. <style lang="scss" scoped>
  283. .sub-account-management-container {
  284. min-height: calc(100vh - 84px);
  285. padding: 20px;
  286. background: #ffffff;
  287. }
  288. .table-header-btn {
  289. display: flex;
  290. gap: 12px;
  291. align-items: center;
  292. }
  293. // 删除对话框内容
  294. .delete-content {
  295. p {
  296. margin: 0 0 12px;
  297. font-size: 14px;
  298. color: var(--el-text-color-primary);
  299. &:last-child {
  300. margin-bottom: 0;
  301. }
  302. }
  303. .delete-warning {
  304. font-weight: 500;
  305. color: #e6a23c;
  306. }
  307. }
  308. // 对话框底部
  309. .dialog-footer {
  310. display: flex;
  311. gap: 12px;
  312. justify-content: flex-end;
  313. }
  314. // 修复表格为空时没有高度的问题
  315. :deep(.el-table) {
  316. min-height: 400px;
  317. .el-table__empty-block {
  318. position: relative;
  319. display: flex;
  320. align-items: center;
  321. justify-content: center;
  322. width: 100%;
  323. min-height: 400px;
  324. }
  325. }
  326. </style>