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

600 lines
12 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
页面开发示例代码
展示项目中页面组件的编写规范和最佳实践
-->
<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>