refactor: 用户管理界面重构

This commit is contained in:
Ray.Hao
2026-03-11 19:08:34 +08:00
parent 2a83b841f3
commit 7bd0f31734
6 changed files with 141 additions and 308 deletions

1
components.d.ts vendored
View File

@@ -28,6 +28,7 @@ declare module 'vue' {
WdCollapse: typeof import('wot-design-uni/components/wd-collapse/wd-collapse.vue')['default']
WdCollapseItem: typeof import('wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue')['default']
WdConfigProvider: typeof import('wot-design-uni/components/wd-config-provider/wd-config-provider.vue')['default']
WdDivider: typeof import('wot-design-uni/components/wd-divider/wd-divider.vue')['default']
WdDropMenu: typeof import('wot-design-uni/components/wd-drop-menu/wd-drop-menu.vue')['default']
WdDropMenuItem: typeof import('wot-design-uni/components/wd-drop-menu-item/wd-drop-menu-item.vue')['default']
WdFab: typeof import('wot-design-uni/components/wd-fab/wd-fab.vue')['default']

View File

@@ -1,6 +1,6 @@
import request from "@/utils/request";
const DEPT_BASE_URL = "/api/v1/dept";
const DEPT_BASE_URL = "/api/v1/depts";
const DeptAPI = {
/**

View File

@@ -132,8 +132,7 @@
"type": "page",
"name": "user",
"style": {
"navigationBarTitleText": "用户管理",
"backgroundColor": "#f1f5f9"
"navigationBarTitleText": "用户管理"
}
},
{

View File

@@ -1,72 +1,72 @@
<template>
<view class="page page--padding">
<!-- 搜索栏 -->
<view class="filter">
<view class="filter__search">
<wd-search
v-model="queryParams.keywords"
placeholder="搜索用户名/手机号"
hide-cancel
custom-style="border: 1rpx solid rgba(148, 163, 184, 0.24); border-radius: 8rpx;"
@search="handleSearch"
/>
<view class="page page--padding page--pt">
<view>
<wd-search
v-model="queryParams.keywords"
placeholder="搜索用户名/手机号"
hide-cancel
@search="handleSearch"
/>
</view>
<!-- 排序筛选:搜索框→筛选 12rpx -->
<view class="filter-bar" @click="closeOutside">
<view class="flex-1">
<wd-drop-menu>
<wd-drop-menu-item
v-model="sortValue"
title="排序"
:options="sortOptions"
@change="handleSortChange"
/>
</wd-drop-menu>
</view>
<view class="filter__bar flex items-center" @click="closeOutside">
<view class="filter__half flex-1 flex justify-center">
<view class="w-full flex justify-center">
<wd-drop-menu>
<wd-drop-menu-item
v-model="sortValue"
title="排序"
:options="sortOptions"
@change="handleSortChange"
<wd-divider vertical />
<view class="flex-1">
<wd-drop-menu>
<wd-drop-menu-item title="筛选" @open="handleFilterOpen">
<view class="p-4">
<wd-input
v-model="queryParams.keywords"
label="关键字"
placeholder="用户名/昵称/手机号"
/>
</wd-drop-menu>
</view>
</view>
<view class="filter__divider self-stretch" />
<view class="filter__half flex-1 flex justify-center">
<view class="w-full flex justify-center">
<wd-drop-menu>
<wd-drop-menu-item title="筛选" @open="handleFilterOpen">
<view class="p-4">
<wd-input
v-model="queryParams.keywords"
label="关键字"
placeholder="用户名/昵称/手机号"
/>
<cu-date-query v-model="queryParams.createTime" label="创建时间" />
<view class="flex gap-2 mt-4">
<wd-button type="info" block @click="resetUserFilter">重置</wd-button>
<wd-button type="primary" block @click="applyUserFilter">查询</wd-button>
</view>
</view>
</wd-drop-menu-item>
</wd-drop-menu>
</view>
</view>
<cu-date-query v-model="queryParams.createTime" label="创建时间" />
<view class="popup-actions">
<wd-button type="info" plain @click="resetUserFilter">重置</wd-button>
<wd-button type="primary" @click="applyUserFilter">查询</wd-button>
</view>
</view>
</wd-drop-menu-item>
</wd-drop-menu>
</view>
</view>
<!-- 用户列表 -->
<view class="list">
<view
<!-- 用户列表:筛选→列表 16rpx -->
<view class="mt-16rpx">
<wd-card
v-for="item in pageData"
:key="item.id"
class="user-card"
custom-class="list-card"
@click="openUserDialog(item.id)"
>
<!-- 主信息行 -->
<view class="user-card__header">
<view class="flex-start">
<wd-img :src="item.avatar" width="80rpx" height="80rpx" round />
<view class="flex-1 ml-2">
<view class="flex items-center">
<view class="flex-1 ml-16rpx">
<view class="flex-start mt-12rpx">
<text class="font-bold text-32rpx">{{ item.nickname }}</text>
<wd-icon v-if="item.gender === 1" name="gender-male" class="color-#4D80F0 ml-1" />
<wd-icon
v-if="item.gender === 1"
name="gender-male"
color="var(--color-primary)"
class="ml-8rpx"
/>
<wd-icon
v-else-if="item.gender === 2"
name="gender-female"
class="color-#FA4350 ml-1"
color="var(--color-danger)"
class="ml-8rpx"
/>
</view>
<text class="text-24rpx color-text-secondary">
@@ -79,33 +79,29 @@
</view>
<!-- 辅助信息行 -->
<view class="user-card__contact">
<view v-if="item.mobile" class="user-card__meta-item">
<wd-icon name="mobile" size="16" class="user-card__meta-icon" />
<text class="user-card__meta-text">{{ item.mobile }}</text>
<view class="flex gap-24rpx mt-12rpx">
<view v-if="item.mobile" class="flex-start min-w-0">
<wd-icon name="mobile" size="16" class="color-text-secondary" />
<text class="ml-8rpx text-24rpx color-text-secondary truncate">{{ item.mobile }}</text>
</view>
<view v-if="item.email" class="user-card__meta-item">
<wd-icon name="mail" size="16" class="user-card__meta-icon" />
<text class="user-card__meta-text">{{ item.email }}</text>
<view v-if="item.email" class="flex-start min-w-0">
<wd-icon name="mail" size="16" class="color-text-secondary" />
<text class="ml-8rpx text-24rpx color-text-secondary truncate">{{ item.email }}</text>
</view>
</view>
<!-- 元信息行 -->
<view class="user-card__footer">
<view class="user-card__meta-item user-card__meta-item--time">
<text class="user-card__meta-text user-card__meta-text--time">
{{ item.createTime }}
</text>
</view>
<view class="flex-between mt-16rpx">
<text class="text-24rpx color-text-placeholder">{{ item.createTime }}</text>
<view
class="user-card__more"
hover-class="user-card__more--active"
class="w-88rpx h-88rpx flex-center rounded-full"
hover-class="bg-[var(--color-text-placeholder)]/16"
@click.stop="showUserActions(item)"
>
<wd-icon name="more" size="18" />
<wd-icon name="more" size="18" class="color-text-secondary" />
</view>
</view>
</view>
</wd-card>
<wd-loadmore v-if="total > 0" :state="loadMoreState" @reload="fetchUserList" />
<wd-status-tip v-else-if="total === 0" image="search" tip="暂无数据" />
@@ -145,21 +141,20 @@
</wd-cell>
</wd-cell-group>
</wd-form>
<view class="flex gap-2 mt-4">
<wd-button block @click="closeUserDialog">取消</wd-button>
<wd-button type="primary" block :loading="submitting" @click="submitUserForm">
保存
</wd-button>
<view class="popup-actions">
<wd-button type="info" plain @click="closeUserDialog">取消</wd-button>
<wd-button type="primary" :loading="submitting" @click="submitUserForm">保存</wd-button>
</view>
</view>
</wd-popup>
<!-- 浮动新增按钮 -->
<wd-fab
v-if="hasPermission('sys:user:create')"
v-if="hasPermission('sys:user:create') && !dialog.visible"
type="primary"
position="right-bottom"
:expandable="false"
custom-class="fab--small"
@click="openUserDialog()"
/>
</view>
@@ -228,23 +223,23 @@ const handleSortChange = ({ value }: { value: string | number }) => {
queryParams.field = "";
queryParams.direction = "";
}
refreshUserList();
loadUserList();
};
const handleFilterOpen = () => {};
// 搜索触发
const handleSearch = () => refreshUserList();
const handleSearch = () => loadUserList();
// 刷新列表
function refreshUserList() {
// 加载列表
function loadUserList() {
queryParams.pageNum = 1;
fetchUserList();
}
// 应用筛选并刷新
function applyUserFilter() {
closeOutside();
refreshUserList();
loadUserList();
}
// 重置筛选并刷新
function resetUserFilter() {
@@ -275,12 +270,15 @@ function fetchUserList() {
// 打开弹窗(新增/编辑)
async function openUserDialog(id?: number) {
formRef.value?.reset();
Object.assign(formData, initialFormData);
dialog.visible = true;
roleOptions.value = await RoleAPI.getOptions();
deptOptions.value = await DeptAPI.getOptions();
if (id) {
formData.id = id;
const data = await UserAPI.getFormData(id);
Object.assign(formData, data);
Object.assign(formData, data, { id });
}
}
@@ -294,7 +292,7 @@ function submitUserForm() {
.then(() => {
message.show(formData.id ? "修改成功" : "添加成功");
closeUserDialog();
refreshUserList();
loadUserList();
})
.finally(() => {
submitting.value = false;
@@ -331,7 +329,7 @@ function showUserActions(item: UserPageVO) {
if (confirm) {
await UserAPI.deleteByIds(String(item.id));
message.show("删除成功");
refreshUserList();
loadUserList();
}
};
}
@@ -358,7 +356,7 @@ onReachBottom(() => {
}
});
onLoad(() => refreshUserList());
onLoad(() => loadUserList());
</script>
<script lang="ts">
@@ -369,67 +367,9 @@ export default { options: { styleIsolation: "shared" } };
{
"name": "user",
"style": {
"navigationBarTitleText": "用户管理",
"backgroundColor": "#f1f5f9"
"navigationBarTitleText": "用户管理"
}
}
</route>
<style lang="scss" scoped>
.user-card {
padding: 24rpx;
margin-bottom: 24rpx;
background-color: var(--color-bg);
border: 1rpx solid var(--color-border-light, rgba(0, 0, 0, 0.04));
border-radius: 16rpx;
box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.06);
}
.user-card__header {
display: flex;
align-items: center;
}
.user-card__contact {
display: flex;
gap: 24rpx;
margin-top: 16rpx;
}
.user-card__footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16rpx;
}
.user-card__meta-item {
display: flex;
align-items: center;
min-width: 0;
}
.user-card__meta-item--time {
color: var(--color-text-placeholder);
}
.user-card__meta-icon {
flex: 0 0 auto;
color: var(--color-text-secondary);
}
.user-card__meta-text {
margin-left: 8rpx;
overflow: hidden;
font-size: 24rpx;
color: var(--color-text-secondary);
text-overflow: ellipsis;
white-space: nowrap;
}
.user-card__meta-text--time {
margin-left: 0;
color: var(--color-text-placeholder);
}
.user-card__more {
padding: 16rpx;
margin: -16rpx;
color: var(--color-text-secondary);
border-radius: 999rpx;
}
.user-card__more--active {
background: rgba(148, 163, 184, 0.16);
}
</style>
<style lang="scss" scoped></style>

View File

@@ -11,7 +11,15 @@ body,
padding: 0;
margin: 0;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
system-ui,
-apple-system,
BlinkMacSystemFont,
"PingFang SC",
"Hiragino Sans GB",
"Microsoft YaHei",
"Helvetica Neue",
Helvetica,
sans-serif;
font-size: 28rpx;
line-height: 1.5;
color: var(--color-text);
@@ -26,7 +34,7 @@ body,
.page {
min-height: 100vh;
background-color: var(--color-bg-tertiary, #f1f5f9);
background-color: var(--color-bg-page, var(--color-bg-secondary));
}
.page--auto {
@@ -34,190 +42,75 @@ body,
}
.page--padding {
padding: 0 24rpx;
padding: 0 32rpx;
}
.page--pt {
padding-top: 16rpx;
}
// ==========================================================================
// 筛选区域
// 筛选栏
// ==========================================================================
.filter {
&__search {
overflow: hidden;
border-radius: 12rpx;
}
.filter-bar {
display: flex;
align-items: center;
height: 72rpx;
margin-top: 12rpx;
overflow: hidden;
background: var(--color-bg);
border-radius: 12rpx;
&__bar {
display: flex;
align-items: center;
height: 72rpx;
margin-top: 16rpx;
overflow: hidden;
background: #fff;
border-radius: 12rpx;
:deep(.wd-drop-menu) {
flex: 1;
}
:deep(.wd-drop-menu-item) {
flex: 1;
}
}
&__half {
display: flex;
:deep(.wd-drop-menu) {
flex: 1;
min-width: 0;
:deep(.wd-drop-menu) {
width: 100%;
height: 100%;
}
:deep(.wd-drop-menu__menu) {
width: 100%;
height: 100%;
}
:deep(.wd-drop-menu__item) {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
}
}
&__btn {
display: flex;
:deep(.wd-drop-menu-item) {
flex: 1;
align-items: center;
justify-content: center;
height: 100%;
font-size: 26rpx;
color: #475569;
cursor: pointer;
transition: background 0.2s;
&:active {
background: #f8fafc;
}
.wd-icon {
margin-left: 4rpx;
}
}
&__divider {
width: 1px;
height: 32rpx;
background: #e2e8f0;
}
}
// ==========================================================================
// 列表区域
// 列表卡片
// ==========================================================================
.list {
margin-top: 16rpx;
.list-card {
margin-bottom: 12rpx;
&__card {
margin-bottom: 16rpx;
overflow: hidden;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 12rpx;
box-shadow: 0 2rpx 12rpx rgba(15, 23, 42, 0.04);
&:last-child {
margin-bottom: 0;
}
:deep(.wd-card) {
margin: 0 !important;
background: transparent;
border: none;
box-shadow: none;
}
:deep(.wd-card__body) {
padding: 0 !important;
}
}
&__meta {
padding: 8rpx 24rpx 4rpx;
}
&__meta-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 72rpx;
border-bottom: 1px solid #f1f5f9;
}
&__meta-row:last-child {
border-bottom: 0;
}
&__meta-left {
display: flex;
align-items: center;
min-width: 0;
gap: 12rpx;
}
&__meta-label {
font-size: 26rpx;
color: #64748b;
}
&__meta-value {
max-width: 58%;
overflow: hidden;
font-size: 26rpx;
color: #0f172a;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
&__footer {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
color: #64748b;
font-size: 24rpx;
:deep(.wd-card) {
margin: 0 !important;
}
}
// ==========================================================================
// 筛选面板
// 弹窗/面板底部按钮
// ==========================================================================
.filter-panel {
padding: 24rpx;
.popup-actions {
display: flex;
gap: 16rpx;
margin-top: 24rpx;
&__footer {
display: flex;
gap: 24rpx;
margin-top: 24rpx;
:deep(.wd-button) {
flex: 1;
}
:deep(.wd-button) {
flex: 1;
}
}
// ==========================================================================
// 弹窗底部
// 浮动按钮
// ==========================================================================
.popup-footer {
padding: 30rpx;
.fab--small {
width: 80rpx !important;
height: 80rpx !important;
:deep(.wd-fab__trigger) {
width: 80rpx !important;
height: 80rpx !important;
}
:deep(.wd-fab__icon) {
font-size: 36rpx !important;
}
}

View File

@@ -27,7 +27,7 @@ page {
// -----------------------------------------------------------------------
--color-bg: #ffffff;
--color-bg-secondary: #f8fafc;
--color-bg-tertiary: #f1f5f9;
--color-bg-page: #f1f5f9;
// -----------------------------------------------------------------------
// 文字颜色
@@ -89,7 +89,7 @@ page {
// -----------------------------------------------------------------------
--color-bg: #111827;
--color-bg-secondary: #1f2937;
--color-bg-tertiary: #374151;
--color-bg-page: #374151;
// -----------------------------------------------------------------------
// 文字颜色