shop-wx/doc/examples/page-example.vue

600 lines
12 KiB
Vue
Raw Normal View History

<!--
页面开发示例代码
展示项目中页面组件的编写规范和最佳实践
-->
<script lang="ts" setup>
/**
* 导入顺序规范
* 1. Vue 框架相关
* 2. 第三方库
* 3. 工具函数
* 4. API 接口
* 5. 状态管理
* 6. 本地组件
*/
import { ref, reactive, computed, onMounted, onShow, watch } from 'vue';
import { getUserList, getRoleList, createUser, updateUser, deleteUser, type User } from '@/api/user';
import { useUserStore } from '@/store/user';
import { debounce } from '@/utils/common';
import UserForm from './components/UserForm.vue';
// ✅ 页面配置(使用 definePage 宏)
definePage({
style: {
navigationBarTitleText: '用户管理',
// 可以添加其他页面配置
},
});
/**
* 响应式数据定义
*/
// 加载状态
const loading = ref(false);
// 用户列表
const userList = ref<User[]>([]);
// 搜索参数
const searchParams = reactive({
keyword: '',
status: '',
});
// 分页参数
const pagination = reactive({
current: 1,
size: 10,
total: 0,
});
// 选中的用户列表
const selectedUsers = ref<number[]>([]);
// 是否显示表单弹窗
const showUserForm = ref(false);
// 表单模式add/edit
const formMode = ref<'add' | 'edit'>('add');
// 当前编辑的用户
const currentUser = ref<User | null>(null);
// 角色列表(用于用户表单)
const roleList = ref([]);
/**
* 计算属性
*/
// 表格选中状态
const isAllSelected = computed(() => {
return userList.value.length > 0 && selectedUsers.value.length === userList.value.length;
});
const isIndeterminate = computed(() => {
return selectedUsers.value.length > 0 && selectedUsers.value.length < userList.value.length;
});
/**
* 方法定义
*/
// ✅ 初始化数据
/**
* 初始化页面数据
*/
const initData = async () => {
await Promise.all([getUserData(), getRoleData()]);
};
// ✅ 获取用户列表
/**
* 获取用户列表数据
*/
const getUserData = async () => {
loading.value = true;
try {
const result = await getUserList({
...searchParams,
page: pagination.current,
size: pagination.size,
});
userList.value = result.data;
pagination.total = result.total;
// 清空选中状态
selectedUsers.value = [];
} catch (error) {
console.error('获取用户列表失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none',
});
} finally {
loading.value = false;
}
};
// ✅ 获取角色列表
/**
* 获取角色列表用于用户表单
*/
const getRoleData = async () => {
try {
const result = await getRoleList();
roleList.value = result;
} catch (error) {
console.error('获取角色列表失败:', error);
}
};
// ✅ 防抖搜索
/**
* 防抖搜索处理
*/
const handleSearch = debounce(() => {
pagination.current = 1; // 重置页码
getUserData();
}, 500);
// ✅ 搜索框输入
/**
* 搜索框输入处理
* @param value 输入值
*/
const handleSearchInput = (value: string) => {
searchParams.keyword = value;
handleSearch();
};
// ✅ 清空搜索
/**
* 清空搜索条件
*/
const handleSearchClear = () => {
searchParams.keyword = '';
handleSearch();
};
// ✅ 分页改变
/**
* 分页改变处理
* @param page 页码
*/
const handlePageChange = (page: number) => {
pagination.current = page;
getUserData();
};
// ✅ 添加用户
/**
* 打开添加用户弹窗
*/
const handleAddUser = () => {
formMode.value = 'add';
currentUser.value = null;
showUserForm.value = true;
};
// ✅ 编辑用户
/**
* 打开编辑用户弹窗
* @param user 用户信息
*/
const handleEditUser = (user: User) => {
formMode.value = 'edit';
currentUser.value = { ...user };
showUserForm.value = true;
};
// ✅ 删除用户
/**
* 删除用户确认
* @param user 用户信息
*/
const handleDeleteUser = (user: User) => {
uni.showModal({
title: '确认删除',
content: `确定要删除用户"${user.nickname}"吗?`,
success: async (res) => {
if (res.confirm) {
await confirmDelete(user.id);
}
},
});
};
// ✅ 确认删除
/**
* 确认删除用户
* @param id 用户ID
*/
const confirmDelete = async (id: number) => {
try {
await deleteUser(id);
uni.showToast({
title: '删除成功',
icon: 'success',
});
// 刷新列表
getUserData();
} catch (error) {
console.error('删除用户失败:', error);
uni.showToast({
title: '删除失败',
icon: 'none',
});
}
};
// ✅ 批量删除
/**
* 批量删除用户
*/
const handleBatchDelete = () => {
if (selectedUsers.value.length === 0) {
uni.showToast({
title: '请选择要删除的用户',
icon: 'none',
});
return;
}
uni.showModal({
title: '确认删除',
content: `确定要删除选中的 ${selectedUsers.value.length} 个用户吗?`,
success: async (res) => {
if (res.confirm) {
try {
// 这里应该调用批量删除 API
// await batchDeleteUsers(selectedUsers.value);
uni.showToast({
title: '批量删除成功',
icon: 'success',
});
getUserData();
} catch (error) {
console.error('批量删除失败:', error);
uni.showToast({
title: '删除失败',
icon: 'none',
});
}
}
},
});
};
// ✅ 全选/取消全选
/**
* 表格全选切换
* @param checked 是否选中
*/
const handleSelectAll = (checked: boolean) => {
if (checked) {
selectedUsers.value = userList.value.map((user) => user.id);
} else {
selectedUsers.value = [];
}
};
// ✅ 单选切换
/**
* 单个用户选择切换
* @param user 用户信息
*/
const handleSelectItem = (user: User) => {
const index = selectedUsers.value.indexOf(user.id);
if (index === -1) {
selectedUsers.value.push(user.id);
} else {
selectedUsers.value.splice(index, 1);
}
};
// ✅ 表单提交成功
/**
* 用户表单提交成功回调
*/
const handleFormSuccess = () => {
showUserForm.value = false;
currentUser.value = null;
// 刷新列表
getUserData();
};
// ✅ 状态切换
/**
* 切换用户状态
* @param user 用户信息
*/
const handleToggleStatus = async (user: User) => {
try {
await updateUser({
id: user.id,
status: user.status === 0 ? 1 : 0,
});
uni.showToast({
title: '状态更新成功',
icon: 'success',
});
// 刷新列表
getUserData();
} catch (error) {
console.error('更新状态失败:', error);
uni.showToast({
title: '操作失败',
icon: 'none',
});
}
};
/**
* 生命周期钩子
*/
onMounted(() => {
initData();
});
onShow(() => {
// 页面显示时刷新数据(可选)
});
</script>
<template>
<view class="user-manage-page">
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input">
<input
v-model="searchParams.keyword"
placeholder="请输入用户名或昵称"
@input="handleSearchInput"
@clear="handleSearchClear"
/>
</view>
<picker
:value="searchParams.status"
@change="handleStatusChange"
:range="[{label:'全部', value:''}, {label:'正常', value:0}, {label:'禁用', value:1}]"
range-key="label"
>
<view class="status-picker">
<text>{{ searchParams.status === '' ? '全部' : searchParams.status === 0 ? '正常' : '禁用' }}</text>
<text class="arrow"></text>
</view>
</picker>
</view>
<!-- 操作栏 -->
<view class="action-bar">
<button class="add-btn" @click="handleAddUser">添加用户</button>
<button
class="delete-btn"
:disabled="selectedUsers.length === 0"
@click="handleBatchDelete"
>
批量删除 ({{ selectedUsers.length }})
</button>
</view>
<!-- 用户表格 -->
<view class="user-table">
<view class="table-header">
<checkbox
:checked="isAllSelected"
:indeterminate="isIndeterminate"
@tap="handleSelectAll"
/>
<text class="th">用户名</text>
<text class="th">昵称</text>
<text class="th">状态</text>
<text class="th">操作</text>
</view>
<view class="table-body" v-if="!loading && userList.length > 0">
<view
v-for="user in userList"
:key="user.id"
class="table-row"
>
<checkbox
:checked="selectedUsers.includes(user.id)"
@tap="handleSelectItem(user)"
/>
<text class="td">{{ user.username }}</text>
<text class="td">{{ user.nickname }}</text>
<view class="td">
<text
class="status-badge"
:class="{ 'status-active': user.status === 0 }"
@tap="handleToggleStatus(user)"
>
{{ user.status === 0 ? '正常' : '禁用' }}
</text>
</view>
<view class="td action-buttons">
<button class="edit-btn" @tap="handleEditUser(user)">编辑</button>
<button class="delete-btn" @tap="handleDeleteUser(user)">删除</button>
</view>
</view>
</view>
<!-- 加载中 -->
<view class="loading-container" v-if="loading">
<text>加载中...</text>
</view>
<!-- 空数据 -->
<view class="empty-container" v-if="!loading && userList.length === 0">
<text>暂无数据</text>
</view>
</view>
<!-- 分页 -->
<view class="pagination" v-if="pagination.total > 0">
<text> {{ pagination.total }} 条记录</text>
<!-- 这里可以使用 uni-app 的分页组件 -->
</view>
<!-- 用户表单弹窗 -->
<user-form
v-model="showUserForm"
:mode="formMode"
:user="currentUser"
:role-list="roleList"
@success="handleFormSuccess"
/>
</view>
</template>
<style scoped lang="scss">
.user-manage-page {
padding: 20rpx;
}
/* 搜索栏 */
.search-bar {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
.search-input {
flex: 1;
padding: 10rpx 20rpx;
background: #f5f5f5;
border-radius: 8rpx;
}
.status-picker {
display: flex;
align-items: center;
gap: 10rpx;
padding: 10rpx 20rpx;
background: #f5f5f5;
border-radius: 8rpx;
.arrow {
font-size: 20rpx;
color: #999;
}
}
}
/* 操作栏 */
.action-bar {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
.add-btn,
.delete-btn {
padding: 20rpx 40rpx;
border-radius: 8rpx;
font-size: 28rpx;
}
.add-btn {
background: #4a90ff;
color: white;
}
.delete-btn {
background: #f56c6c;
color: white;
&[disabled] {
background: #ccc;
}
}
}
/* 表格 */
.user-table {
background: white;
border-radius: 8rpx;
overflow: hidden;
}
.table-header {
display: flex;
align-items: center;
padding: 20rpx;
background: #f5f5f5;
font-weight: 600;
.th {
flex: 1;
margin-left: 20rpx;
}
}
.table-row {
display: flex;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.td {
flex: 1;
margin-left: 20rpx;
}
.status-badge {
padding: 8rpx 16rpx;
border-radius: 4rpx;
font-size: 24rpx;
background: #f56c6c;
color: white;
&.status-active {
background: #67c23a;
}
}
.action-buttons {
display: flex;
gap: 10rpx;
button {
padding: 8rpx 16rpx;
border-radius: 4rpx;
font-size: 24rpx;
&.edit-btn {
background: #4a90ff;
color: white;
}
&.delete-btn {
background: #f56c6c;
color: white;
}
}
}
}
/* 空状态 */
.loading-container,
.empty-container {
display: flex;
justify-content: center;
align-items: center;
padding: 100rpx;
color: #999;
}
/* 分页 */
.pagination {
margin-top: 20rpx;
text-align: center;
color: #666;
}
</style>