From e9283ffe6822b12ec5dd2ccf4dc13a369b227a61 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期一, 30 三月 2026 08:32:06 +0800
Subject: [PATCH] chore: sync rsf-design from isolated worktree

---
 rsf-design/src/views/system/role/index.vue |  428 +++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 332 insertions(+), 96 deletions(-)

diff --git a/rsf-design/src/views/system/role/index.vue b/rsf-design/src/views/system/role/index.vue
index d6a11ca..b6787aa 100644
--- a/rsf-design/src/views/system/role/index.vue
+++ b/rsf-design/src/views/system/role/index.vue
@@ -5,8 +5,8 @@
       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
@@ -17,62 +17,115 @@
       >
         <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,
@@ -84,130 +137,313 @@
     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>

--
Gitblit v1.9.1