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/user/index.vue | 444 ++++++++++++++++++++++++++++++++++++++-----------------
1 files changed, 307 insertions(+), 137 deletions(-)
diff --git a/rsf-design/src/views/system/user/index.vue b/rsf-design/src/views/system/user/index.vue
index bf169fd..634f8c9 100644
--- a/rsf-design/src/views/system/user/index.vue
+++ b/rsf-design/src/views/system/user/index.vue
@@ -1,81 +1,104 @@
<!-- 鐢ㄦ埛绠$悊椤甸潰 -->
-<!-- art-full-height 鑷姩璁$畻鍑洪〉闈㈠墿浣欓珮搴� -->
-<!-- art-table-card 涓�涓鍚堢郴缁熸牱寮忕殑 class锛屽悓鏃惰嚜鍔ㄦ拺婊″墿浣欓珮搴� -->
-<!-- 鏇村 useTable 浣跨敤绀轰緥璇风Щ姝ヨ嚦 鍔熻兘绀轰緥 涓嬮潰鐨勯珮绾ц〃鏍肩ず渚嬫垨鑰呮煡鐪嬪畼鏂规枃妗� -->
-<!-- useTable 鏂囨。锛歨ttps://www.artd.pro/docs/zh/guide/hooks/use-table.html -->
<template>
<div class="user-page art-full-height">
- <!-- 鎼滅储鏍� -->
- <UserSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams"></UserSearch>
+ <UserSearch
+ v-model="searchForm"
+ :dept-tree-options="deptTreeOptions"
+ :role-options="roleOptions"
+ @search="handleSearch"
+ @reset="handleReset"
+ />
<ElCard class="art-table-card">
- <!-- 琛ㄦ牸澶撮儴 -->
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
<ElSpace wrap>
- <ElButton @click="showDialog('add')" v-ripple>鏂板鐢ㄦ埛</ElButton>
+ <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鐢ㄦ埛</ElButton>
</ElSpace>
</template>
</ArtTableHeader>
- <!-- 琛ㄦ牸 -->
<ArtTable
:loading="loading"
:data="data"
:columns="columns"
:pagination="pagination"
- @selection-change="handleSelectionChange"
@pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange"
- >
- </ArtTable>
+ />
- <!-- 鐢ㄦ埛寮圭獥 -->
<UserDialog
v-model:visible="dialogVisible"
:type="dialogType"
:user-data="currentUserData"
+ :role-options="roleOptions"
+ :dept-tree-options="deptTreeOptions"
@submit="handleDialogSubmit"
+ />
+
+ <UserDetailDrawer
+ v-model:visible="detailDrawerVisible"
+ :loading="detailLoading"
+ :user-data="detailUserData"
/>
</ElCard>
</div>
</template>
<script setup>
+ import request from '@/utils/http'
+ import {
+ fetchDeleteUser,
+ fetchGetDeptTree,
+ fetchGetRoleOptions,
+ fetchGetUserDetail,
+ fetchResetUserPassword,
+ fetchSaveUser,
+ fetchUpdateUser,
+ fetchUpdateUserStatus
+ } from '@/api/system-manage'
+ import { useTable } from '@/hooks/core/useTable'
+ import { useAuth } from '@/hooks/core/useAuth'
+ import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+ import { ElMessage, ElMessageBox, ElSwitch, ElTag } from 'element-plus'
import UserSearch from './modules/user-search.vue'
import UserDialog from './modules/user-dialog.vue'
+ import UserDetailDrawer from './modules/user-detail-drawer.vue'
+ import {
+ buildUserDialogModel,
+ buildUserPageQueryParams,
+ buildUserSavePayload,
+ buildUserSearchParams,
+ createUserSearchState,
+ getUserStatusMeta,
+ mergeUserDetailRecord,
+ normalizeDeptTreeOptions,
+ normalizeRoleOptions,
+ normalizeUserListRow,
+ formatUserRoleNames
+ } from './userPage.helpers'
- import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
- import { ACCOUNT_TABLE_DATA } from '@/mock/temp/formData'
- import { useTable } from '@/hooks/core/useTable'
- import { fetchGetUserList } from '@/api/system-manage'
- import { ElTag, ElMessageBox, ElImage } from 'element-plus'
defineOptions({ name: 'User' })
+
+ const searchForm = ref(createUserSearchState())
const dialogType = ref('add')
const dialogVisible = ref(false)
- const currentUserData = ref({})
- const selectedRows = ref([])
- const searchForm = ref({
- userName: void 0,
- userGender: void 0,
- userPhone: void 0,
- userEmail: void 0,
- status: '1'
- })
- const USER_STATUS_CONFIG = {
- 1: { type: 'success', text: '鍦ㄧ嚎' },
- 2: { type: 'info', text: '绂荤嚎' },
- 3: { type: 'warning', text: '寮傚父' },
- 4: { type: 'danger', text: '娉ㄩ攢' }
+ const currentUserData = ref(buildUserDialogModel())
+ const detailDrawerVisible = ref(false)
+ const detailLoading = ref(false)
+ const detailUserData = ref({})
+ const roleOptions = ref([])
+ const deptTreeOptions = ref([])
+ const RESET_PASSWORD = '123456'
+ const { hasAuth } = useAuth()
+
+ const fetchUserPage = (params = {}) => {
+ return request.post({
+ url: '/user/page',
+ params: buildUserPageQueryParams(params)
+ })
}
- const getUserStatusConfig = (status) => {
- return (
- USER_STATUS_CONFIG[status] || {
- type: 'info',
- text: '鏈煡'
- }
- )
- }
+
const {
columns,
columnChecks,
@@ -87,136 +110,283 @@
resetSearchParams,
handleSizeChange,
handleCurrentChange,
- refreshData
+ refreshData,
+ refreshCreate,
+ refreshUpdate,
+ refreshRemove
} = useTable({
- // 鏍稿績閰嶇疆
core: {
- apiFn: fetchGetUserList,
- apiParams: {
- current: 1,
- size: 20,
- ...searchForm.value
- },
- // 鑷畾涔夊垎椤靛瓧娈垫槧灏勶紝鏈缃椂灏嗕娇鐢ㄥ叏灞�閰嶇疆 tableConfig.ts 涓殑 paginationKey
- // paginationKey: {
- // current: 'pageNum',
- // size: 'pageSize'
- // },
+ apiFn: fetchUserPage,
+ apiParams: buildUserPageQueryParams(searchForm.value),
columnsFactory: () => [
- { type: 'selection' },
- // 鍕鹃�夊垪
- { type: 'index', width: 60, label: '搴忓彿' },
- // 搴忓彿
{
- prop: 'userInfo',
+ prop: 'username',
label: '鐢ㄦ埛鍚�',
- width: 280,
- // visible: false, // 榛樿鏄惁鏄剧ず鍒�
+ minWidth: 140,
+ showOverflowTooltip: true
+ },
+ {
+ prop: 'nickname',
+ label: '鏄电О',
+ minWidth: 120,
+ showOverflowTooltip: true
+ },
+ {
+ prop: 'deptLabel',
+ label: '閮ㄩ棬',
+ minWidth: 140,
+ showOverflowTooltip: true
+ },
+ {
+ prop: 'phone',
+ label: '鎵嬫満鍙�',
+ minWidth: 130
+ },
+ {
+ prop: 'email',
+ label: '閭',
+ minWidth: 180,
+ showOverflowTooltip: true
+ },
+ {
+ prop: 'roleNames',
+ label: '瑙掕壊',
+ minWidth: 180,
+ showOverflowTooltip: true,
+ formatter: (row) => formatUserRoleNames(row.roles) || row.roleNames || '-'
+ },
+ {
+ prop: 'status',
+ label: '鐘舵��',
+ width: 180,
formatter: (row) => {
- return h('div', { class: 'user flex-c' }, [
- h(ElImage, {
- class: 'size-9.5 rounded-md',
- src: row.avatar,
- previewSrcList: [row.avatar],
- // 鍥剧墖棰勮鏄惁鎻掑叆鑷� body 鍏冪礌涓婏紝鐢ㄤ簬瑙e喅琛ㄦ牸鍐呴儴鍥剧墖棰勮鏍峰紡寮傚父
- previewTeleported: true
+ const statusMeta = getUserStatusMeta(row.statusBool ?? row.status)
+ if (!hasAuth('edit')) {
+ return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+ }
+ return h('div', { class: 'flex items-center gap-2' }, [
+ h(ElSwitch, {
+ modelValue: row.statusBool ?? statusMeta.bool,
+ loading: row._statusLoading,
+ 'onUpdate:modelValue': (value) => handleStatusChange(row, value)
}),
- h('div', { class: 'ml-2' }, [
- h('p', { class: 'user-name' }, row.userName),
- h('p', { class: 'email' }, row.userEmail)
- ])
+ h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
])
}
},
{
- prop: 'userGender',
- label: '鎬у埆',
+ prop: 'updateTimeText',
+ label: '鏇存柊鏃堕棿',
+ minWidth: 180,
sortable: true,
- formatter: (row) => row.userGender
- },
- { prop: 'userPhone', label: '鎵嬫満鍙�' },
- {
- prop: 'status',
- label: '鐘舵��',
- formatter: (row) => {
- const statusConfig = getUserStatusConfig(row.status)
- return h(ElTag, { type: statusConfig.type }, () => statusConfig.text)
- }
+ formatter: (row) => row.updateTimeText || '-'
},
{
- prop: 'createTime',
- label: '鍒涘缓鏃ユ湡',
- sortable: true
+ prop: 'createTimeText',
+ label: '鍒涘缓鏃堕棿',
+ minWidth: 180,
+ sortable: true,
+ formatter: (row) => row.createTimeText || '-'
},
{
prop: 'operation',
label: '鎿嶄綔',
- width: 120,
+ width: 220,
fixed: 'right',
- // 鍥哄畾鍒�
- formatter: (row) =>
- h('div', [
- h(ArtButtonTable, {
- type: 'edit',
- onClick: () => showDialog('edit', row)
- }),
- h(ArtButtonTable, {
- type: 'delete',
- onClick: () => deleteUser(row)
- })
- ])
+ formatter: (row) => {
+ const buttons = []
+
+ if (hasAuth('query')) {
+ buttons.push(
+ h(ArtButtonTable, {
+ type: 'view',
+ onClick: () => openDetail(row)
+ })
+ )
+ }
+
+ if (hasAuth('edit')) {
+ buttons.push(
+ h(ArtButtonTable, {
+ type: 'edit',
+ onClick: () => openEditDialog(row)
+ }),
+ h(ArtButtonTable, {
+ icon: 'ri:key-2-line',
+ iconClass: 'bg-warning/12 text-warning',
+ onClick: () => handleResetPassword(row)
+ })
+ )
+ }
+
+ if (hasAuth('delete')) {
+ buttons.push(
+ h(ArtButtonTable, {
+ type: 'delete',
+ onClick: () => handleDelete(row)
+ })
+ )
+ }
+
+ return h('div', buttons)
+ }
}
]
},
- // 鏁版嵁澶勭悊
transform: {
- // 鏁版嵁杞崲鍣� - 鏇挎崲澶村儚
dataTransformer: (records) => {
if (!Array.isArray(records)) {
- console.warn('鏁版嵁杞崲鍣�: 鏈熸湜鏁扮粍绫诲瀷锛屽疄闄呮敹鍒�:', typeof records)
return []
}
- return records.map((item, index) => {
- return {
- ...item,
- avatar: ACCOUNT_TABLE_DATA[index % ACCOUNT_TABLE_DATA.length].avatar
- }
- })
+ return records.map((item) => normalizeUserListRow(item))
}
}
})
- const handleSearch = (params) => {
- replaceSearchParams(params)
- getData()
- }
- const showDialog = (type, row) => {
- console.log('鎵撳紑寮圭獥:', { type, row })
- dialogType.value = type
- currentUserData.value = row || {}
- nextTick(() => {
- dialogVisible.value = true
- })
- }
- const deleteUser = (row) => {
- console.log('鍒犻櫎鐢ㄦ埛:', row)
- ElMessageBox.confirm(`纭畾瑕佹敞閿�璇ョ敤鎴峰悧锛焋, '娉ㄩ攢鐢ㄦ埛', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'error'
- }).then(() => {
- ElMessage.success('娉ㄩ攢鎴愬姛')
- })
- }
- const handleDialogSubmit = async () => {
+
+ const loadLookups = async () => {
try {
- dialogVisible.value = false
- currentUserData.value = {}
+ const [roles, depts] = await Promise.all([fetchGetRoleOptions({}), fetchGetDeptTree({})])
+ roleOptions.value = normalizeRoleOptions(roles)
+ deptTreeOptions.value = normalizeDeptTreeOptions(depts)
} catch (error) {
- console.error('鎻愪氦澶辫触:', error)
+ console.error('鍔犺浇鐢ㄦ埛椤靛瓧鍏稿け璐�', error)
}
}
- const handleSelectionChange = (selection) => {
- selectedRows.value = selection
- console.log('閫変腑琛屾暟鎹�:', selectedRows.value)
+
+ onMounted(() => {
+ loadLookups()
+ })
+
+ const handleSearch = (params) => {
+ replaceSearchParams(buildUserSearchParams(params))
+ getData()
+ }
+
+ const handleReset = () => {
+ Object.assign(searchForm.value, createUserSearchState())
+ resetSearchParams()
+ }
+
+ const showDialog = (type, row) => {
+ dialogType.value = type
+ currentUserData.value = type === 'edit' ? buildUserDialogModel(row) : buildUserDialogModel()
+ dialogVisible.value = true
+ }
+
+ const loadUserDetail = async (id) => {
+ detailLoading.value = true
+ try {
+ return await fetchGetUserDetail(id)
+ } finally {
+ detailLoading.value = false
+ }
+ }
+
+ const openEditDialog = async (row) => {
+ try {
+ const detail = await loadUserDetail(row.id)
+ currentUserData.value = buildUserDialogModel(mergeUserDetailRecord(detail, row))
+ dialogType.value = 'edit'
+ dialogVisible.value = true
+ } catch (error) {
+ ElMessage.error(error?.message || '鑾峰彇鐢ㄦ埛璇︽儏澶辫触')
+ }
+ }
+
+ const openDetail = async (row) => {
+ detailDrawerVisible.value = true
+ try {
+ detailUserData.value = mergeUserDetailRecord(await loadUserDetail(row.id), row)
+ } catch (error) {
+ detailDrawerVisible.value = false
+ detailUserData.value = {}
+ ElMessage.error(error?.message || '鑾峰彇鐢ㄦ埛璇︽儏澶辫触')
+ }
+ }
+
+ const handleDialogSubmit = async (formData) => {
+ const payload = buildUserSavePayload(formData)
+ try {
+ if (dialogType.value === 'edit') {
+ await fetchUpdateUser(payload)
+ ElMessage.success('淇敼鎴愬姛')
+ dialogVisible.value = false
+ currentUserData.value = buildUserDialogModel()
+ await refreshUpdate()
+ return
+ }
+ await fetchSaveUser(payload)
+ ElMessage.success('鏂板鎴愬姛')
+ dialogVisible.value = false
+ currentUserData.value = buildUserDialogModel()
+ await refreshCreate()
+ } catch (error) {
+ ElMessage.error(error?.message || '鎻愪氦澶辫触')
+ }
+ }
+
+ const handleDelete = async (row) => {
+ try {
+ await ElMessageBox.confirm(`纭畾瑕佸垹闄ょ敤鎴枫��${row.username || row.nickname || row.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ await fetchDeleteUser(row.id)
+ ElMessage.success('鍒犻櫎鎴愬姛')
+ await refreshRemove()
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+ }
+ }
+ }
+
+ const handleResetPassword = async (row) => {
+ try {
+ await ElMessageBox.confirm(
+ `纭畾灏嗙敤鎴枫��${row.username || row.nickname || row.id}銆嶇殑瀵嗙爜閲嶇疆涓� ${RESET_PASSWORD} 鍚楋紵`,
+ '閲嶇疆瀵嗙爜',
+ {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }
+ )
+ await fetchResetUserPassword({
+ id: row.id,
+ password: RESET_PASSWORD
+ })
+ ElMessage.success(`瀵嗙爜宸查噸缃负 ${RESET_PASSWORD}`)
+ await refreshUpdate()
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error(error?.message || '閲嶇疆瀵嗙爜澶辫触')
+ }
+ }
+ }
+
+ const handleStatusChange = async (row, checked) => {
+ const previousStatus = row.status
+ const previousStatusBool = row.statusBool
+ const nextStatus = checked ? 1 : 0
+ row._statusLoading = true
+ row.status = nextStatus
+ row.statusBool = checked
+
+ try {
+ await fetchUpdateUserStatus({
+ id: row.id,
+ status: nextStatus
+ })
+ ElMessage.success('鐘舵�佸凡鏇存柊')
+ await refreshUpdate()
+ } catch (error) {
+ row.status = previousStatus
+ row.statusBool = previousStatusBool
+ ElMessage.error(error?.message || '鐘舵�佹洿鏂板け璐�')
+ } finally {
+ row._statusLoading = false
+ }
}
</script>
--
Gitblit v1.9.1