zhou zhou
昨天 3fdcf1d5e6468c735532e67bde5ff1cdf85bb0c6
rsf-design/src/views/system/role/index.vue
@@ -20,6 +20,7 @@
            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>新增角色</ElButton>
            <ElButton
              v-auth="'delete'"
              type="danger"
              :disabled="selectedRows.length === 0"
              @click="handleBatchDelete"
              v-ripple
@@ -85,13 +86,14 @@
    fetchUpdateRole
  } from '@/api/system-manage'
  import { useTable } from '@/hooks/core/useTable'
  import { useCrudPage } from '@/views/system/common/useCrudPage'
  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
  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 ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
  import { createRoleTableColumns } from './roleTable.columns'
  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
  import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
  import {
    buildRoleDialogModel,
    buildRolePageQueryParams,
@@ -100,7 +102,7 @@
    buildRoleSavePayload,
    buildRoleSearchParams,
    createRoleSearchState,
    getRoleStatusMeta,
    getRolePaginationKey,
    normalizeRoleListRow,
    ROLE_REPORT_STYLE,
    ROLE_REPORT_TITLE,
@@ -111,184 +113,19 @@
  const searchForm = ref(createRoleSearchState())
  const showSearchBar = ref(false)
  const dialogVisible = ref(false)
  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,
    data,
    loading,
    pagination,
    getData,
    replaceSearchParams,
    resetSearchParams,
    handleSizeChange,
    handleCurrentChange,
    refreshData,
    refreshCreate,
    refreshUpdate,
    refreshRemove
  } = useTable({
    core: {
      apiFn: fetchRolePage,
      apiParams: buildRolePageQueryParams(searchForm.value),
      columnsFactory: () => [
        { type: 'selection', width: 52, fixed: 'left' },
        {
          prop: 'name',
          label: '角色名称',
          minWidth: 140,
          showOverflowTooltip: true
        },
        {
          prop: 'code',
          label: '角色编码',
          minWidth: 140,
          showOverflowTooltip: true
        },
        {
          prop: 'memo',
          label: '备注',
          minWidth: 180,
          showOverflowTooltip: true
        },
        {
          prop: 'status',
          label: '状态',
          width: 120,
          formatter: (row) => {
            const statusMeta = getRoleStatusMeta(row.statusBool ?? row.status)
            return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
          }
        },
        {
          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: 120,
          fixed: 'right',
          formatter: (row) =>
            h('div', [
              h(ArtButtonMore, {
                list: [
                  {
                    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',
                    auth: 'edit'
                  },
                  {
                    key: 'delete',
                    label: '删除角色',
                    icon: 'ri:delete-bin-4-line',
                    color: '#f56c6c',
                    auth: 'delete'
                  }
                ],
                onClick: (item) => handleActionClick(item, row)
              })
            ])
        }
      ]
    },
    transform: {
      dataTransformer: (records) => {
        if (!Array.isArray(records)) {
          return []
        }
        return records.map((item) => normalizeRoleListRow(item))
      }
    }
  })
  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) => {
    replaceSearchParams(buildRoleSearchParams(params))
    getData()
  function openScopeDialog(scopeType, row) {
    permissionScopeType.value = scopeType
    currentRoleData.value = buildRoleDialogModel(row)
    permissionDialogVisible.value = true
  }
  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) => {
  function handleActionClick(item, row) {
    switch (item.key) {
      case 'scope-menu':
        openScopeDialog('menu', row)
@@ -313,137 +150,121 @@
    }
  }
  const openScopeDialog = (scopeType, row) => {
    permissionScopeType.value = scopeType
    currentRoleData.value = buildRoleDialogModel(row)
    permissionDialogVisible.value = true
  }
  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
  const {
    columns,
    columnChecks,
    data,
    loading,
    pagination,
    getData,
    replaceSearchParams,
    resetSearchParams,
    handleSizeChange,
    handleCurrentChange,
    refreshData,
    refreshCreate,
    refreshUpdate,
    refreshRemove
  } = useTable({
    core: {
      apiFn: fetchRolePage,
      apiParams: buildRolePageQueryParams(searchForm.value),
      paginationKey: getRolePaginationKey(),
      columnsFactory: () => createRoleTableColumns(handleActionClick)
    },
    transform: {
      dataTransformer: (records) => {
        if (!Array.isArray(records)) {
          return []
        }
        return records.map((item) => normalizeRoleListRow(item))
      }
      await fetchSaveRole(payload)
      ElMessage.success('新增成功')
      dialogVisible.value = false
      currentRoleData.value = buildRoleDialogModel()
      await refreshCreate()
    } catch (error) {
      ElMessage.error(error?.message || '提交失败')
    }
  })
  const {
    dialogVisible,
    dialogType,
    currentRecord: currentRoleData,
    selectedRows,
    handleSelectionChange,
    showDialog,
    handleDialogSubmit,
    handleDelete,
    handleBatchDelete
  } = useCrudPage({
    createEmptyModel: () => buildRoleDialogModel(),
    buildEditModel: (record) => buildRoleDialogModel(record),
    buildSavePayload: (formData) => buildRoleSavePayload(formData),
    saveRequest: fetchSaveRole,
    updateRequest: fetchUpdateRole,
    deleteRequest: fetchDeleteRole,
    entityName: '角色',
    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
    refreshCreate,
    refreshUpdate,
    refreshRemove
  })
  const buildPreviewDialogMeta = (rows) => {
    const now = new Date()
    return {
      reportTitle,
      reportDate: now.toLocaleDateString('zh-CN'),
      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
      count: rows.length
    }
  }
  const handleDelete = async (row) => {
    try {
      await ElMessageBox.confirm(`确定要删除角色「${row.name || row.code || row.id}」吗?`, '删除确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
      await fetchDeleteRole(row.id)
      ElMessage.success('删除成功')
      await refreshRemove()
    } catch (error) {
      if (error !== 'cancel') {
        ElMessage.error(error?.message || '删除失败')
      }
    }
  const resolvePrintRecords = async (payload) => {
    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
        })
    return defaultResponseAdapter(response).records
  }
  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, {
  const {
    previewVisible,
    previewRows,
    previewMeta,
    handlePreviewVisibleChange,
    handleExport,
    handlePrint
  } = usePrintExportPage({
    downloadFileName: 'role.xlsx',
    requestExport: (payload) =>
      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 || '导出失败')
    }
      }),
    resolvePrintRecords,
    buildPreviewRows: (records) => buildRolePrintRows(records),
    buildPreviewMeta: (rows) => buildPreviewDialogMeta(rows)
  })
  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) => {
    replaceSearchParams(buildRoleSearchParams(params))
    getData()
  }
  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 || '打印失败')
    }
  const handleReset = () => {
    Object.assign(searchForm.value, createRoleSearchState())
    resetSearchParams()
  }
</script>