Files
medical-mall/pages/mall/admin/user/grouping/index.uvue

677 lines
14 KiB
Plaintext
Raw 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.
<template>
<view class="user-group-page">
<view class="content-card">
<!-- 操作按钮行 -->
<view class="action-bar">
<view class="filter-item">
<input class="filter-input" v-model="searchText" placeholder="搜索分组名称/备注" @confirm="handleSearch" />
</view>
<button class="btn primary small" @click="onAddGroup">添加分组</button>
<button class="btn ghost small" @click="handleSearch">查询</button>
<button class="btn ghost small" @click="handleReset">重置</button>
</view>
<!-- 分组列表表格 -->
<view class="table-container">
<!-- 表头 -->
<view class="table-header">
<view class="col col-id"><text>ID</text></view>
<view class="col col-name"><text>分组名称</text></view>
<view class="col col-remark"><text>备注</text></view>
<view class="col col-status"><text>状态</text></view>
<view class="col col-ops"><text>操作</text></view>
</view>
<!-- 表格内容 -->
<view class="table-body">
<view v-if="loading" class="table-loading"><text>加载中...</text></view>
<view v-else-if="groupList.length === 0" class="table-empty"><text>暂无数据</text></view>
<view v-else v-for="(group, index) in groupList" :key="group.id" class="table-row">
<view class="col col-id"><text>{{ group.id }}</text></view>
<view class="col col-name"><text>{{ group.name }}</text></view>
<view class="col col-remark"><text>{{ group.remark || '-' }}</text></view>
<view class="col col-status">
<view :class="['switch-box', group.status === 1 ? 'active' : '']" @click="onToggleStatus(index)">
<view class="switch-dot"></view>
</view>
</view>
<view class="col col-ops">
<text class="op-link" @click="onEditGroup(group)">修改</text>
<view class="op-divider">|</view>
<text class="op-link text-danger" @click="onDeleteGroup(group.id)">删除</text>
</view>
</view>
</view>
</view>
<!-- 分页区域 -->
<view class="pagination-row">
<text class="total-text">共 {{ total }} 条</text>
<view class="page-btns">
<view :class="['page-btn', page <= 1 ? 'disabled' : '']" @click="prevPage"><text></text></view>
<view class="page-btn active"><text>{{ page }}</text></view>
<view :class="['page-btn', page >= totalPages ? 'disabled' : '']" @click="nextPage"><text></text></view>
</view>
<view class="page-jump">
<text>前往</text>
<input class="jump-input" v-model="jumpPage" @confirm="goToJumpPage" />
<text>页</text>
</view>
</view>
</view>
<!-- 添加/修改分组弹窗 -->
<view class="modal-mask" v-if="showModal" @click="closeModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ modalTitle }}</text>
<text class="modal-close" @click="closeModal">×</text>
</view>
<view class="modal-body">
<view class="form-item">
<view class="label-box">
<text class="required">*</text>
<text class="label">分组名称:</text>
</view>
<input
class="form-input"
v-model="formData.name"
placeholder="请输入分组名称"
/>
</view>
<view class="form-item" style="margin-top: 20px;">
<view class="label-box">
<text class="label">备注说明:</text>
</view>
<textarea
class="form-textarea"
v-model="formData.remark"
placeholder="请输入备注说明"
/>
</view>
<view class="form-item" style="margin-top: 20px;">
<view class="label-box">
<text class="label">状态:</text>
</view>
<view :class="['switch-box', formData.status === 1 ? 'active' : '']" @click="formData.status = (formData.status === 1 ? 0 : 1)">
<view class="switch-dot"></view>
</view>
</view>
</view>
<view class="modal-footer">
<button class="btn ghost" @click="closeModal">取消</button>
<button class="btn primary" @click="submitForm">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, computed, onMounted } from 'vue'
import {
fetchAdminUserGroupPage,
saveAdminUserGroup,
deleteAdminUserGroup,
setAdminUserGroupStatus,
type AdminUserGroup
} from '@/services/admin/userGroupService.uts'
const searchText = ref('')
const total = ref(0)
const groupList = ref<AdminUserGroup[]>([])
const loading = ref(false)
const page = ref(1)
const pageSize = ref(15)
const jumpPage = ref('')
const totalPages = computed((): number => {
if (pageSize.value <= 0) return 1
const pages = Math.ceil(total.value / pageSize.value)
return pages <= 0 ? 1 : pages
})
const loadGroupList = async () => {
loading.value = true
try {
const res = await fetchAdminUserGroupPage(page.value, pageSize.value, {
search: searchText.value || null,
status: null,
includeDeleted: false
})
groupList.value = res.items
total.value = res.total
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
}
}
onMounted(() => {
loadGroupList()
})
// 弹窗状态
const showModal = ref(false)
const isEdit = ref(false)
const modalTitle = computed(() => isEdit.value ? '修改分组' : '添加分组')
// 表单数据
const formData = reactive({
id: '',
name: '',
remark: '',
status: 1
})
// 搜索与重置
function handleSearch() {
page.value = 1
loadGroupList()
}
function handleReset() {
searchText.value = ''
page.value = 1
loadGroupList()
}
// 分页控制
function prevPage() {
if (page.value > 1) {
page.value--
loadGroupList()
}
}
function nextPage() {
if (page.value < totalPages.value) {
page.value++
loadGroupList()
}
}
function goToJumpPage() {
const targetPage = parseInt(jumpPage.value)
if (!isNaN(targetPage) && targetPage >= 1 && targetPage <= totalPages.value) {
page.value = targetPage
loadGroupList()
jumpPage.value = ''
} else {
uni.showToast({ title: '请输入有效的页码', icon: 'none' })
}
}
// 状态切换
async function onToggleStatus(index: number) {
const group = groupList.value[index]
const targetStatus = group.status === 1 ? 0 : 1
try {
const ok = await setAdminUserGroupStatus(group.id, targetStatus)
if (ok) {
group.status = targetStatus
uni.showToast({ title: '状态更新成功' })
}
} catch (e) {
uni.showToast({ title: '状态更新失败', icon: 'none' })
}
}
// 添加分组
function onAddGroup() {
isEdit.value = false
formData.id = ''
formData.name = ''
formData.remark = ''
formData.status = 1
showModal.value = true
}
// 修改分组
function onEditGroup(group: AdminUserGroup) {
isEdit.value = true
formData.id = group.id
formData.name = group.name
formData.remark = group.remark || ''
formData.status = group.status
showModal.value = true
}
// 删除分组
function onDeleteGroup(id: string) {
uni.showModal({
title: '提示',
content: '确定要删除该分组吗?',
success: async (res) => {
if (res.confirm) {
try {
const ok = await deleteAdminUserGroup(id)
if (ok) {
uni.showToast({ title: '删除成功' })
loadGroupList()
}
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
}
})
}
// 关闭弹窗
function closeModal() {
showModal.value = false
}
// 提交表单
async function submitForm() {
if (!formData.name) {
uni.showToast({ title: '请输入分组名称', icon: 'none' })
return
}
try {
const resId = await saveAdminUserGroup({
id: isEdit.value ? formData.id : null,
name: formData.name,
remark: formData.remark,
status: formData.status
})
if (resId != null) {
uni.showToast({ title: isEdit.value ? '修改成功' : '添加成功' })
closeModal()
loadGroupList()
}
} catch (e) {
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
</script>
<style scoped lang="scss">
.user-group-page {
padding: 16px;
background-color: #f0f2f5;
min-height: 100vh;
}
.content-card {
background: #fff;
border-radius: 4px;
padding: 24px;
}
.action-bar {
margin-bottom: 20px;
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.filter-input {
width: 200px;
height: 32px;
padding: 0 12px;
border: 1px solid #d9d9d9;
border-radius: 2px;
font-size: 14px;
background-color: #fff;
}
/* 按钮样式 */
.btn {
height: 32px;
line-height: 32px;
padding: 0 15px;
font-size: 14px;
border-radius: 2px;
border: 1px solid #d9d9d9;
background: #fff;
color: #666;
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: auto;
margin: 0;
}
.btn.primary {
background-color: #1890ff;
border-color: #1890ff;
color: #fff;
}
.btn.ghost {
background-color: #fff;
border-color: #d9d9d9;
color: #666;
}
.btn.small {
height: 32px;
padding: 0 12px;
}
/* 表格样式 */
.table-container {
border: 1px solid #f0f0f0;
border-radius: 2px;
overflow: hidden;
}
.table-header {
display: flex;
flex-direction: row;
background-color: #f8faff;
border-bottom: 1px solid #f0f0f0;
}
.table-header .col {
padding: 12px 16px;
font-weight: 500;
color: #333;
font-size: 14px;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
}
.table-row:last-child {
border-bottom: none;
}
.table-row .col {
padding: 12px 16px;
color: #666;
font-size: 14px;
display: flex;
flex-direction: row;
align-items: center;
}
.col-id {
width: 100px;
}
.col-name {
flex: 1;
}
.col-ops {
width: 150px;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.col-remark {
flex: 1;
}
.col-status {
width: 120px;
display: flex;
justify-content: center;
}
.table-loading,
.table-empty {
padding: 24px;
text-align: center;
color: #999;
font-size: 14px;
}
.text-danger {
color: #ff4d4f;
}
.op-link {
color: #1890ff;
cursor: pointer;
font-size: 14px;
}
.op-link:hover {
text-decoration: underline;
}
.op-divider {
margin: 0 8px;
color: #1890ff;
font-size: 12px;
opacity: 0.5;
}
/* Switch 开关 */
.switch-box {
width: 44px;
height: 22px;
background-color: #dcdfe6;
border-radius: 11px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
}
.switch-box.active {
background-color: #1890ff;
}
.switch-dot {
width: 18px;
height: 18px;
background-color: #fff;
border-radius: 9px;
position: absolute;
top: 2px;
left: 2px;
transition: transform 0.3s;
}
.switch-box.active .switch-dot {
transform: translateX(22px);
}
/* 分页样式 */
.pagination-row {
margin-top: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
color: #666;
font-size: 14px;
}
.total-text {
margin-right: 16px;
}
.page-size-selector {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 8px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 2px;
margin-right: 16px;
.arrow {
font-size: 10px;
margin-left: 8px;
color: #999;
}
}
.page-btns {
display: flex;
flex-direction: row;
margin-right: 16px;
}
.page-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #d9d9d9;
border-radius: 2px;
margin-right: 8px;
cursor: pointer;
&.active {
background: #1890ff;
border-color: #1890ff;
color: #fff;
}
&.disabled {
color: #ccc;
cursor: not-allowed;
}
}
.page-jump {
display: flex;
flex-direction: row;
align-items: center;
.jump-input {
width: 48px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 2px;
margin: 0 8px;
text-align: center;
font-size: 14px;
}
}
/* 弹窗样式 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.modal-content {
width: 520px;
background: #fff;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.modal-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 16px;
font-weight: 500;
color: #333;
}
.modal-close {
font-size: 20px;
color: #999;
cursor: pointer;
&:hover {
color: #666;
}
}
.modal-body {
padding: 40px 24px;
}
.form-item {
display: flex;
align-items: center;
}
.label-box {
width: 100px;
display: flex;
justify-content: flex-end;
align-items: center;
}
.required {
color: #ff4d4f;
margin-right: 4px;
}
.label {
color: #333;
font-size: 14px;
}
.form-input {
flex: 1;
height: 32px;
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 2px;
font-size: 14px;
&:focus {
border-color: #40a9ff;
outline: none;
}
}
.form-textarea {
flex: 1;
height: 80px;
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 2px;
font-size: 14px;
&:focus {
border-color: #40a9ff;
outline: none;
}
}
.modal-footer {
padding: 10px 16px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: flex-end;
gap: 8px;
}
</style>