| | |
| | | v-show="showSearchBar" |
| | | v-model="searchForm" |
| | | @search="handleSearch" |
| | | @reset="resetSearchParams" |
| | | ></RoleSearch> |
| | | @reset="handleReset" |
| | | /> |
| | | |
| | | <ElCard class="art-table-card" :style="{ 'margin-top': showSearchBar ? '12px' : '0' }"> |
| | | <ArtTableHeader |
| | |
| | | > |
| | | <template #left> |
| | | <ElSpace wrap> |
| | | <ElButton @click="showDialog('add')" v-ripple>新增角色</ElButton> |
| | | <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>新增角色</ElButton> |
| | | <ElButton |
| | | v-auth="'delete'" |
| | | :disabled="selectedRows.length === 0" |
| | | @click="handleBatchDelete" |
| | | v-ripple |
| | | > |
| | | 批量删除 |
| | | </ElButton> |
| | | <span v-auth="'query'" class="inline-flex"> |
| | | <ListExportPrint |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :selected-rows="selectedRows" |
| | | :query-params="reportQueryParams" |
| | | :columns="roleReportColumns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="resolvedPreviewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </span> |
| | | </ElSpace> |
| | | </template> |
| | | </ArtTableHeader> |
| | | |
| | | <!-- 表格 --> |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="data" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination:size-change="handleSizeChange" |
| | | @pagination:current-change="handleCurrentChange" |
| | | > |
| | | </ArtTable> |
| | | /> |
| | | </ElCard> |
| | | |
| | | <!-- 角色编辑弹窗 --> |
| | | <RoleEditDialog |
| | | v-model="dialogVisible" |
| | | v-model:visible="dialogVisible" |
| | | :dialog-type="dialogType" |
| | | :role-data="currentRoleData" |
| | | @success="refreshData" |
| | | @submit="handleDialogSubmit" |
| | | /> |
| | | |
| | | <!-- 菜单权限弹窗 --> |
| | | <RolePermissionDialog |
| | | v-model="permissionDialog" |
| | | v-model:visible="permissionDialogVisible" |
| | | :role-data="currentRoleData" |
| | | :scope-type="permissionScopeType" |
| | | @success="refreshData" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { |
| | | fetchExportRoleReport, |
| | | fetchDeleteRole, |
| | | fetchGetRoleMany, |
| | | fetchRolePrintPage, |
| | | fetchRolePage, |
| | | fetchSaveRole, |
| | | fetchUpdateRole |
| | | } from '@/api/system-manage' |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import RoleSearch from './modules/role-search.vue' |
| | | import RoleEditDialog from './modules/role-edit-dialog.vue' |
| | | |
| | | import RolePermissionDialog from './modules/role-permission-dialog.vue' |
| | | |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import { fetchGetRoleList } from '@/api/system-manage' |
| | | import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue' |
| | | import { ElTag, ElMessageBox } from 'element-plus' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { ElMessage, ElMessageBox, ElTag } from 'element-plus' |
| | | import { |
| | | buildRoleDialogModel, |
| | | buildRolePageQueryParams, |
| | | buildRolePrintRows, |
| | | buildRoleReportMeta, |
| | | buildRoleSavePayload, |
| | | buildRoleSearchParams, |
| | | createRoleSearchState, |
| | | getRoleStatusMeta, |
| | | normalizeRoleListRow, |
| | | ROLE_REPORT_STYLE, |
| | | ROLE_REPORT_TITLE, |
| | | resolveRoleReportColumns |
| | | } from './rolePage.helpers' |
| | | |
| | | defineOptions({ name: 'Role' }) |
| | | const searchForm = ref({ |
| | | roleName: void 0, |
| | | roleCode: void 0, |
| | | description: void 0, |
| | | enabled: void 0, |
| | | daterange: void 0 |
| | | }) |
| | | |
| | | const searchForm = ref(createRoleSearchState()) |
| | | const showSearchBar = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const permissionDialog = ref(false) |
| | | const currentRoleData = ref(void 0) |
| | | const dialogType = ref('add') |
| | | const currentRoleData = ref(buildRoleDialogModel()) |
| | | const permissionDialogVisible = ref(false) |
| | | const permissionScopeType = ref('menu') |
| | | const selectedRows = ref([]) |
| | | const previewVisible = ref(false) |
| | | const previewRows = ref([]) |
| | | const previewMeta = ref({}) |
| | | const previewToken = ref(0) |
| | | const activePrintToken = ref(0) |
| | | const userStore = useUserStore() |
| | | const reportTitle = ROLE_REPORT_TITLE |
| | | const reportQueryParams = computed(() => buildRoleSearchParams(searchForm.value)) |
| | | |
| | | const { |
| | | columns, |
| | | columnChecks, |
| | |
| | | resetSearchParams, |
| | | handleSizeChange, |
| | | handleCurrentChange, |
| | | refreshData |
| | | refreshData, |
| | | refreshCreate, |
| | | refreshUpdate, |
| | | refreshRemove |
| | | } = useTable({ |
| | | // 核心配置 |
| | | core: { |
| | | apiFn: fetchGetRoleList, |
| | | apiParams: { |
| | | current: 1, |
| | | size: 20 |
| | | }, |
| | | // 排除 apiParams 中的属性 |
| | | excludeParams: ['daterange'], |
| | | apiFn: fetchRolePage, |
| | | apiParams: buildRolePageQueryParams(searchForm.value), |
| | | columnsFactory: () => [ |
| | | { type: 'selection', width: 52, fixed: 'left' }, |
| | | { |
| | | prop: 'roleId', |
| | | label: '角色ID', |
| | | width: 100 |
| | | }, |
| | | { |
| | | prop: 'roleName', |
| | | prop: 'name', |
| | | label: '角色名称', |
| | | minWidth: 120 |
| | | }, |
| | | { |
| | | prop: 'roleCode', |
| | | label: '角色编码', |
| | | minWidth: 120 |
| | | }, |
| | | { |
| | | prop: 'description', |
| | | label: '角色描述', |
| | | minWidth: 150, |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'enabled', |
| | | label: '角色状态', |
| | | width: 100, |
| | | prop: 'code', |
| | | label: '角色编码', |
| | | minWidth: 140, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'memo', |
| | | label: '备注', |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'status', |
| | | label: '状态', |
| | | width: 120, |
| | | formatter: (row) => { |
| | | const statusConfig = row.enabled |
| | | ? { type: 'success', text: '启用' } |
| | | : { type: 'warning', text: '禁用' } |
| | | return h(ElTag, { type: statusConfig.type }, () => statusConfig.text) |
| | | const statusMeta = getRoleStatusMeta(row.statusBool ?? row.status) |
| | | return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text) |
| | | } |
| | | }, |
| | | { |
| | | prop: 'createTime', |
| | | label: '创建日期', |
| | | width: 180, |
| | | sortable: true |
| | | prop: 'updateTimeText', |
| | | label: '更新时间', |
| | | minWidth: 180, |
| | | sortable: true, |
| | | formatter: (row) => row.updateTimeText || '-' |
| | | }, |
| | | { |
| | | prop: 'createTimeText', |
| | | label: '创建时间', |
| | | minWidth: 180, |
| | | sortable: true, |
| | | formatter: (row) => row.createTimeText || '-' |
| | | }, |
| | | { |
| | | prop: 'operation', |
| | | label: '操作', |
| | | width: 80, |
| | | width: 120, |
| | | fixed: 'right', |
| | | formatter: (row) => |
| | | h('div', [ |
| | | h(ArtButtonMore, { |
| | | list: [ |
| | | { |
| | | key: 'permission', |
| | | label: '菜单权限', |
| | | icon: 'ri:user-3-line' |
| | | key: 'scope-menu', |
| | | label: '网页权限', |
| | | icon: 'ri:layout-2-line', |
| | | auth: 'edit' |
| | | }, |
| | | { |
| | | key: 'scope-pda', |
| | | label: 'PDA权限', |
| | | icon: 'ri:smartphone-line', |
| | | auth: 'edit' |
| | | }, |
| | | { |
| | | key: 'scope-matnr', |
| | | label: '物料权限', |
| | | icon: 'ri:archive-line', |
| | | auth: 'edit' |
| | | }, |
| | | { |
| | | key: 'scope-warehouse', |
| | | label: '仓库权限', |
| | | icon: 'ri:store-2-line', |
| | | auth: 'edit' |
| | | }, |
| | | { |
| | | key: 'edit', |
| | | label: '编辑角色', |
| | | icon: 'ri:edit-2-line' |
| | | icon: 'ri:edit-2-line', |
| | | auth: 'edit' |
| | | }, |
| | | { |
| | | key: 'delete', |
| | | label: '删除角色', |
| | | icon: 'ri:delete-bin-4-line', |
| | | color: '#f56c6c' |
| | | color: '#f56c6c', |
| | | auth: 'delete' |
| | | } |
| | | ], |
| | | onClick: (item) => buttonMoreClick(item, row) |
| | | onClick: (item) => handleActionClick(item, row) |
| | | }) |
| | | ]) |
| | | } |
| | | ] |
| | | }, |
| | | transform: { |
| | | dataTransformer: (records) => { |
| | | if (!Array.isArray(records)) { |
| | | return [] |
| | | } |
| | | return records.map((item) => normalizeRoleListRow(item)) |
| | | } |
| | | } |
| | | }) |
| | | const dialogType = ref('add') |
| | | const showDialog = (type, row) => { |
| | | dialogVisible.value = true |
| | | dialogType.value = type |
| | | currentRoleData.value = row |
| | | } |
| | | |
| | | const roleReportColumns = computed(() => resolveRoleReportColumns(columns.value)) |
| | | const resolvedPreviewMeta = computed(() => |
| | | buildRoleReportMeta({ |
| | | previewMeta: previewMeta.value, |
| | | count: previewRows.value.length, |
| | | titleAlign: ROLE_REPORT_STYLE.titleAlign, |
| | | titleLevel: ROLE_REPORT_STYLE.titleLevel |
| | | }) |
| | | ) |
| | | |
| | | const handleSearch = (params) => { |
| | | const { daterange, ...filtersParams } = params |
| | | const [startTime, endTime] = Array.isArray(daterange) ? daterange : [null, null] |
| | | replaceSearchParams({ ...filtersParams, startTime, endTime }) |
| | | replaceSearchParams(buildRoleSearchParams(params)) |
| | | getData() |
| | | } |
| | | const buttonMoreClick = (item, row) => { |
| | | |
| | | const handleReset = () => { |
| | | Object.assign(searchForm.value, createRoleSearchState()) |
| | | resetSearchParams() |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = Array.isArray(rows) ? rows : [] |
| | | } |
| | | |
| | | const handlePreviewVisibleChange = (visible) => { |
| | | previewVisible.value = Boolean(visible) |
| | | if (!visible) { |
| | | activePrintToken.value = 0 |
| | | } |
| | | } |
| | | |
| | | const showDialog = (type, row) => { |
| | | dialogType.value = type |
| | | currentRoleData.value = type === 'edit' ? buildRoleDialogModel(row) : buildRoleDialogModel() |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleActionClick = (item, row) => { |
| | | switch (item.key) { |
| | | case 'permission': |
| | | showPermissionDialog(row) |
| | | case 'scope-menu': |
| | | openScopeDialog('menu', row) |
| | | break |
| | | case 'scope-pda': |
| | | openScopeDialog('pda', row) |
| | | break |
| | | case 'scope-matnr': |
| | | openScopeDialog('matnr', row) |
| | | break |
| | | case 'scope-warehouse': |
| | | openScopeDialog('warehouse', row) |
| | | break |
| | | case 'edit': |
| | | showDialog('edit', row) |
| | | break |
| | | case 'delete': |
| | | deleteRole(row) |
| | | handleDelete(row) |
| | | break |
| | | default: |
| | | break |
| | | } |
| | | } |
| | | const showPermissionDialog = (row) => { |
| | | permissionDialog.value = true |
| | | currentRoleData.value = row |
| | | |
| | | const openScopeDialog = (scopeType, row) => { |
| | | permissionScopeType.value = scopeType |
| | | currentRoleData.value = buildRoleDialogModel(row) |
| | | permissionDialogVisible.value = true |
| | | } |
| | | const deleteRole = (row) => { |
| | | ElMessageBox.confirm(`确定删除角色"${row.roleName}"吗?此操作不可恢复!`, '删除确认', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }) |
| | | .then(() => { |
| | | ElMessage.success('删除成功') |
| | | refreshData() |
| | | |
| | | const handleDialogSubmit = async (formData) => { |
| | | const payload = buildRoleSavePayload(formData) |
| | | try { |
| | | if (dialogType.value === 'edit') { |
| | | await fetchUpdateRole(payload) |
| | | ElMessage.success('修改成功') |
| | | dialogVisible.value = false |
| | | currentRoleData.value = buildRoleDialogModel() |
| | | await refreshUpdate() |
| | | return |
| | | } |
| | | await fetchSaveRole(payload) |
| | | ElMessage.success('新增成功') |
| | | dialogVisible.value = false |
| | | currentRoleData.value = buildRoleDialogModel() |
| | | await refreshCreate() |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || '提交失败') |
| | | } |
| | | } |
| | | |
| | | const handleDelete = async (row) => { |
| | | try { |
| | | await ElMessageBox.confirm(`确定要删除角色「${row.name || row.code || row.id}」吗?`, '删除确认', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.info('已取消删除') |
| | | await fetchDeleteRole(row.id) |
| | | ElMessage.success('删除成功') |
| | | await refreshRemove() |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | ElMessage.error(error?.message || '删除失败') |
| | | } |
| | | } |
| | | } |
| | | |
| | | const handleBatchDelete = async () => { |
| | | if (!selectedRows.value.length) return |
| | | const ids = selectedRows.value.map((item) => item.id).filter((id) => id !== void 0 && id !== null) |
| | | if (!ids.length) return |
| | | |
| | | try { |
| | | await ElMessageBox.confirm(`确定要批量删除选中的 ${ids.length} 个角色吗?`, '批量删除确认', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }) |
| | | await fetchDeleteRole(ids.join(',')) |
| | | ElMessage.success('批量删除成功') |
| | | selectedRows.value = [] |
| | | await refreshRemove() |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | ElMessage.error(error?.message || '批量删除失败') |
| | | } |
| | | } |
| | | } |
| | | |
| | | const handleExport = async (payload) => { |
| | | try { |
| | | const response = await fetchExportRoleReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }) |
| | | if (!response.ok) { |
| | | throw new Error(`导出失败 (${response.status})`) |
| | | } |
| | | const blob = await response.blob() |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = 'role.xlsx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | link.remove() |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | ElMessage.success('导出成功') |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || '导出失败') |
| | | } |
| | | } |
| | | |
| | | const handlePrint = async (payload) => { |
| | | const token = previewToken.value + 1 |
| | | previewToken.value = token |
| | | activePrintToken.value = token |
| | | previewVisible.value = false |
| | | previewRows.value = [] |
| | | previewMeta.value = {} |
| | | |
| | | try { |
| | | const response = Array.isArray(payload?.ids) && payload.ids.length > 0 |
| | | ? await fetchGetRoleMany(payload.ids) |
| | | : await fetchRolePrintPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20 |
| | | }) |
| | | if (activePrintToken.value !== token) { |
| | | return |
| | | } |
| | | const records = defaultResponseAdapter(response).records |
| | | if (activePrintToken.value !== token) { |
| | | return |
| | | } |
| | | |
| | | const rows = buildRolePrintRows(records) |
| | | const now = new Date() |
| | | previewRows.value = rows |
| | | previewMeta.value = { |
| | | reportTitle, |
| | | reportDate: now.toLocaleDateString('zh-CN'), |
| | | printedAt: now.toLocaleString('zh-CN', { hour12: false }), |
| | | operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', |
| | | count: rows.length |
| | | } |
| | | handlePreviewVisibleChange(true) |
| | | } catch (error) { |
| | | if (activePrintToken.value !== token) { |
| | | return |
| | | } |
| | | ElMessage.error(error?.message || '打印失败') |
| | | } |
| | | } |
| | | </script> |