1026 lines
25 KiB
Plaintext
1026 lines
25 KiB
Plaintext
<template>
|
||
<AdminLayout :currentPage="currentPage">
|
||
<view class="page">
|
||
<view class="header">
|
||
<text class="title">{{ title }}</text>
|
||
<text class="sub-title">管理商城注册用户信息</text>
|
||
</view>
|
||
|
||
<!-- 筛选区域 -->
|
||
<view class="filter-section">
|
||
<view class="filter-row">
|
||
<view class="filter-item">
|
||
<text class="filter-label">搜索</text>
|
||
<input
|
||
v-model="searchText"
|
||
class="filter-input"
|
||
placeholder="用户名/邮箱/真实姓名"
|
||
@confirm="handleSearch"
|
||
/>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">角色</text>
|
||
<picker
|
||
:value="roleIndex"
|
||
:range="roleOptions"
|
||
range-key="label"
|
||
@change="onRoleChange"
|
||
>
|
||
<view class="picker">
|
||
{{ roleOptions[roleIndex]?.label || '全部' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">状态</text>
|
||
<picker
|
||
:value="statusIndex"
|
||
:range="statusOptions"
|
||
range-key="label"
|
||
@change="onStatusChange"
|
||
>
|
||
<view class="picker">
|
||
{{ statusOptions[statusIndex]?.label || '全部' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">会员</text>
|
||
<picker
|
||
:value="memberIndex"
|
||
:range="memberOptions"
|
||
range-key="label"
|
||
@change="onMemberChange"
|
||
>
|
||
<view class="picker">
|
||
{{ memberOptions[memberIndex]?.label || '全部' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="filter-actions">
|
||
<button class="btn btn-primary" @click="handleSearch">搜索</button>
|
||
<button class="btn btn-secondary" @click="handleReset">重置</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户列表 -->
|
||
<view class="table-section">
|
||
<view class="table-header">
|
||
<text class="table-title">用户列表</text>
|
||
<text class="table-count">共 {{ total }} 条</text>
|
||
</view>
|
||
|
||
<view class="table-container">
|
||
<view class="table">
|
||
<!-- 表头 -->
|
||
<view class="table-row header">
|
||
<view class="table-cell">用户信息</view>
|
||
<view class="table-cell">角色</view>
|
||
<view class="table-cell">状态</view>
|
||
<view class="table-cell">会员信息</view>
|
||
<view class="table-cell">余额</view>
|
||
<view class="table-cell">注册时间</view>
|
||
<view class="table-cell">操作</view>
|
||
</view>
|
||
|
||
<!-- 表格数据 -->
|
||
<view
|
||
v-for="user in userList"
|
||
:key="user.id"
|
||
class="table-row"
|
||
@click="handleViewUser(user)"
|
||
>
|
||
<view class="table-cell user-info">
|
||
<view class="user-avatar">
|
||
<image
|
||
v-if="user.avatar_url"
|
||
:src="user.avatar_url"
|
||
class="avatar-img"
|
||
/>
|
||
<view v-else class="avatar-placeholder">
|
||
{{ user.username?.charAt(0)?.toUpperCase() || 'U' }}
|
||
</view>
|
||
</view>
|
||
<view class="user-details">
|
||
<text class="username">{{ user.username || '-' }}</text>
|
||
<text class="email">{{ user.email || '-' }}</text>
|
||
<text v-if="user.real_name" class="real-name">{{ user.real_name }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="table-cell">
|
||
<view class="role-tag" :class="getRoleClass(user.role)">
|
||
{{ getRoleLabel(user.role) }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="table-cell">
|
||
<view class="status-tag" :class="getStatusClass(user.profile_status)">
|
||
{{ getStatusLabel(user.profile_status) }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="table-cell member-info">
|
||
<view v-if="user.is_member" class="member-active">
|
||
<text class="member-plan">{{ user.member_plan_name || '会员' }}</text>
|
||
<text v-if="user.member_end_date" class="member-end">
|
||
到期: {{ formatDate(user.member_end_date) }}
|
||
</text>
|
||
</view>
|
||
<view v-else class="member-none">
|
||
<text class="non-member">非会员</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="table-cell">
|
||
<text class="balance">¥{{ user.balance?.toFixed(2) || '0.00' }}</text>
|
||
</view>
|
||
|
||
<view class="table-cell">
|
||
<text class="date">{{ formatDate(user.created_at) }}</text>
|
||
</view>
|
||
|
||
<view class="table-cell actions">
|
||
<button class="btn-small btn-view" @click.stop="handleViewUser(user)">
|
||
查看
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分页 -->
|
||
<view v-if="hasMore || page > 1" class="pagination">
|
||
<button
|
||
class="btn btn-secondary"
|
||
:disabled="page <= 1"
|
||
@click="handlePrevPage"
|
||
>
|
||
上一页
|
||
</button>
|
||
<text class="page-info">第 {{ page }} 页</text>
|
||
<button
|
||
class="btn btn-secondary"
|
||
:disabled="!hasMore"
|
||
@click="handleNextPage"
|
||
>
|
||
下一页
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户详情弹窗 -->
|
||
<view v-if="showUserDetail" class="modal-overlay" @click="closeUserDetail">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">用户详情</text>
|
||
<button class="btn-close" @click="closeUserDetail">×</button>
|
||
</view>
|
||
|
||
<view v-if="userDetail" class="modal-body">
|
||
<!-- 基本信息 -->
|
||
<view class="detail-section">
|
||
<text class="section-title">基本信息</text>
|
||
<view class="detail-grid">
|
||
<view class="detail-item">
|
||
<text class="detail-label">用户名</text>
|
||
<text class="detail-value">{{ userDetail.username || '-' }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">邮箱</text>
|
||
<text class="detail-value">{{ userDetail.email || '-' }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">角色</text>
|
||
<text class="detail-value">{{ getRoleLabel(userDetail.role) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">状态</text>
|
||
<text class="detail-value">{{ getStatusLabel(userDetail.profile_status) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">真实姓名</text>
|
||
<text class="detail-value">{{ userDetail.real_name || '-' }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">信用分数</text>
|
||
<text class="detail-value">{{ userDetail.credit_score || '-' }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">注册时间</text>
|
||
<text class="detail-value">{{ formatDate(userDetail.created_at) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">余额</text>
|
||
<text class="detail-value">¥{{ userDetail.balance?.toFixed(2) || '0.00' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 会员信息 -->
|
||
<view v-if="userDetail.is_member && userDetail.member_info" class="detail-section">
|
||
<text class="section-title">会员信息</text>
|
||
<view class="member-detail">
|
||
<view class="detail-item">
|
||
<text class="detail-label">方案名称</text>
|
||
<text class="detail-value">{{ userDetail.member_info.plan_name }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">方案编码</text>
|
||
<text class="detail-value">{{ userDetail.member_info.plan_code }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">计费周期</text>
|
||
<text class="detail-value">{{ getBillingPeriodLabel(userDetail.member_info.billing_period) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">价格</text>
|
||
<text class="detail-value">¥{{ userDetail.member_info.price?.toFixed(2) || '0.00' }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">状态</text>
|
||
<text class="detail-value">{{ getMemberStatusLabel(userDetail.member_info.status) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">开始时间</text>
|
||
<text class="detail-value">{{ formatDate(userDetail.member_info.start_date) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">到期时间</text>
|
||
<text class="detail-value">{{ userDetail.member_info.end_date ? formatDate(userDetail.member_info.end_date) : '永久' }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">自动续费</text>
|
||
<text class="detail-value">{{ userDetail.member_info.auto_renew ? '是' : '否' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 地址信息 -->
|
||
<view v-if="userDetail.addresses && userDetail.addresses.length > 0" class="detail-section">
|
||
<text class="section-title">地址信息</text>
|
||
<view class="address-list">
|
||
<view
|
||
v-for="address in userDetail.addresses"
|
||
:key="address.id"
|
||
class="address-item"
|
||
>
|
||
<view class="address-header">
|
||
<text class="address-name">{{ address.receiver_name }}</text>
|
||
<text class="address-phone">{{ address.receiver_phone }}</text>
|
||
<view v-if="address.is_default" class="default-tag">默认</view>
|
||
</view>
|
||
<view class="address-detail">
|
||
<text>{{ address.province }} {{ address.city }} {{ address.district }} {{ address.address_detail }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<button class="btn btn-secondary" @click="closeUserDetail">关闭</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</AdminLayout>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted } from 'vue'
|
||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||
import AdminUserService, { type AdminUserItem, type AdminUserDetail } from '@/services/admin/AdminUserService.uts'
|
||
|
||
const currentPage = ref<string>('user-list')
|
||
const title = ref<string>('用户管理')
|
||
|
||
// 筛选相关
|
||
const searchText = ref<string>('')
|
||
const roleIndex = ref<number>(0)
|
||
const statusIndex = ref<number>(0)
|
||
const memberIndex = ref<number>(0)
|
||
|
||
const roleOptions = ref([
|
||
{ label: '全部', value: null },
|
||
{ label: '消费者', value: 'customer' },
|
||
{ label: '商家', value: 'merchant' },
|
||
{ label: '配送员', value: 'delivery' },
|
||
{ label: '客服', value: 'service' },
|
||
{ label: '管理员', value: 'admin' },
|
||
{ label: '数据分析', value: 'analytics' }
|
||
])
|
||
|
||
const statusOptions = ref([
|
||
{ label: '全部', value: null },
|
||
{ label: '正常', value: 1 },
|
||
{ label: '冻结', value: 2 },
|
||
{ label: '注销', value: 3 },
|
||
{ label: '待审核', value: 4 }
|
||
])
|
||
|
||
const memberOptions = ref([
|
||
{ label: '全部', value: null },
|
||
{ label: '会员', value: true },
|
||
{ label: '非会员', value: false }
|
||
])
|
||
|
||
// 列表数据
|
||
const userList = ref<AdminUserItem[]>([])
|
||
const total = ref<number>(0)
|
||
const page = ref<number>(1)
|
||
const limit = ref<number>(20)
|
||
const hasMore = ref<boolean>(false)
|
||
const loading = ref<boolean>(false)
|
||
|
||
// 详情弹窗
|
||
const showUserDetail = ref<boolean>(false)
|
||
const userDetail = ref<AdminUserDetail | null>(null)
|
||
|
||
// 加载用户列表
|
||
async function loadUserList() {
|
||
if (loading.value) return
|
||
|
||
loading.value = true
|
||
try {
|
||
const params = {
|
||
page: page.value,
|
||
limit: limit.value,
|
||
search: searchText.value || null,
|
||
role: roleOptions.value[roleIndex.value]?.value,
|
||
status: statusOptions.value[statusIndex.value]?.value,
|
||
is_member: memberOptions.value[memberIndex.value]?.value
|
||
}
|
||
|
||
const response = await AdminUserService.getUserList(params)
|
||
userList.value = response.items
|
||
total.value = response.total
|
||
hasMore.value = response.has_more
|
||
} catch (error) {
|
||
console.error('加载用户列表失败:', error)
|
||
uni.showToast({
|
||
title: '加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索
|
||
function handleSearch() {
|
||
page.value = 1
|
||
loadUserList()
|
||
}
|
||
|
||
// 重置筛选
|
||
function handleReset() {
|
||
searchText.value = ''
|
||
roleIndex.value = 0
|
||
statusIndex.value = 0
|
||
memberIndex.value = 0
|
||
page.value = 1
|
||
loadUserList()
|
||
}
|
||
|
||
// 分页
|
||
function handlePrevPage() {
|
||
if (page.value > 1) {
|
||
page.value--
|
||
loadUserList()
|
||
}
|
||
}
|
||
|
||
function handleNextPage() {
|
||
if (hasMore.value) {
|
||
page.value++
|
||
loadUserList()
|
||
}
|
||
}
|
||
|
||
// 查看用户详情
|
||
async function handleViewUser(user: AdminUserItem) {
|
||
try {
|
||
const detail = await AdminUserService.getUserDetail(user.id)
|
||
userDetail.value = detail
|
||
showUserDetail.value = true
|
||
} catch (error) {
|
||
console.error('获取用户详情失败:', error)
|
||
uni.showToast({
|
||
title: '获取详情失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 关闭详情弹窗
|
||
function closeUserDetail() {
|
||
showUserDetail.value = false
|
||
userDetail.value = null
|
||
}
|
||
|
||
// 筛选器变更
|
||
function onRoleChange(e: any) {
|
||
roleIndex.value = e.detail.value
|
||
}
|
||
|
||
function onStatusChange(e: any) {
|
||
statusIndex.value = e.detail.value
|
||
}
|
||
|
||
function onMemberChange(e: any) {
|
||
memberIndex.value = e.detail.value
|
||
}
|
||
|
||
// 工具函数
|
||
function formatDate(dateStr: string): string {
|
||
if (!dateStr) return '-'
|
||
const date = new Date(dateStr)
|
||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
||
}
|
||
|
||
function getRoleLabel(role: string): string {
|
||
const roleMap = {
|
||
'customer': '消费者',
|
||
'merchant': '商家',
|
||
'delivery': '配送员',
|
||
'service': '客服',
|
||
'admin': '管理员',
|
||
'analytics': '数据分析'
|
||
}
|
||
return roleMap[role] || role
|
||
}
|
||
|
||
function getRoleClass(role: string): string {
|
||
const classMap = {
|
||
'customer': 'role-customer',
|
||
'merchant': 'role-merchant',
|
||
'delivery': 'role-delivery',
|
||
'service': 'role-service',
|
||
'admin': 'role-admin',
|
||
'analytics': 'role-analytics'
|
||
}
|
||
return classMap[role] || 'role-default'
|
||
}
|
||
|
||
function getStatusLabel(status: number): string {
|
||
const statusMap = {
|
||
1: '正常',
|
||
2: '冻结',
|
||
3: '注销',
|
||
4: '待审核'
|
||
}
|
||
return statusMap[status] || '未知'
|
||
}
|
||
|
||
function getStatusClass(status: number): string {
|
||
const classMap = {
|
||
1: 'status-normal',
|
||
2: 'status-frozen',
|
||
3: 'status-canceled',
|
||
4: 'status-pending'
|
||
}
|
||
return classMap[status] || 'status-default'
|
||
}
|
||
|
||
function getBillingPeriodLabel(period: string): string {
|
||
const periodMap = {
|
||
'monthly': '月付',
|
||
'yearly': '年付'
|
||
}
|
||
return periodMap[period] || period
|
||
}
|
||
|
||
function getMemberStatusLabel(status: string): string {
|
||
const statusMap = {
|
||
'trial': '试用',
|
||
'active': '活跃',
|
||
'past_due': '逾期',
|
||
'canceled': '已取消',
|
||
'expired': '已过期'
|
||
}
|
||
return statusMap[status] || status
|
||
}
|
||
|
||
// 页面加载时获取数据
|
||
onMounted(() => {
|
||
loadUserList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@import '@/uni.scss';
|
||
|
||
.page {
|
||
padding: $space-lg;
|
||
background-color: $background-page;
|
||
}
|
||
|
||
.header {
|
||
padding: $space-lg;
|
||
border-radius: $radius;
|
||
background: $background-primary;
|
||
box-shadow: $shadow-xs;
|
||
margin-bottom: $space-lg;
|
||
}
|
||
|
||
.title {
|
||
font-size: $font-size-lg;
|
||
font-weight: $font-weight-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.sub-title {
|
||
margin-top: $space-xs;
|
||
font-size: $font-size-md;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
// 筛选区域
|
||
.filter-section {
|
||
background: $background-primary;
|
||
border-radius: $radius;
|
||
padding: $space-lg;
|
||
margin-bottom: $space-lg;
|
||
box-shadow: $shadow-xs;
|
||
}
|
||
|
||
.filter-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: $space-md;
|
||
align-items: end;
|
||
}
|
||
|
||
.filter-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: $space-xs;
|
||
min-width: 160px;
|
||
}
|
||
|
||
.filter-label {
|
||
font-size: $font-size-sm;
|
||
color: $text-secondary;
|
||
font-weight: $font-weight-medium;
|
||
}
|
||
|
||
.filter-input {
|
||
padding: $space-sm;
|
||
border: 1px solid $border;
|
||
border-radius: $radius-sm;
|
||
font-size: $font-size-md;
|
||
background: $background-primary;
|
||
|
||
&:focus {
|
||
border-color: $primary;
|
||
outline: none;
|
||
}
|
||
}
|
||
|
||
.picker {
|
||
padding: $space-sm;
|
||
border: 1px solid $border;
|
||
border-radius: $radius-sm;
|
||
font-size: $font-size-md;
|
||
background: $background-primary;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.filter-actions {
|
||
display: flex;
|
||
gap: $space-sm;
|
||
}
|
||
|
||
.btn {
|
||
padding: $space-sm $space-md;
|
||
border-radius: $radius-sm;
|
||
font-size: $font-size-md;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
|
||
&.btn-primary {
|
||
background: $primary;
|
||
color: white;
|
||
|
||
&:hover {
|
||
background: darken($primary, 10%);
|
||
}
|
||
}
|
||
|
||
&.btn-secondary {
|
||
background: $background-secondary;
|
||
color: $text-primary;
|
||
border: 1px solid $border;
|
||
|
||
&:hover {
|
||
background: darken($background-secondary, 5%);
|
||
}
|
||
}
|
||
|
||
&.btn-small {
|
||
padding: $space-xs $space-sm;
|
||
font-size: $font-size-sm;
|
||
}
|
||
|
||
&.btn-view {
|
||
background: $primary;
|
||
color: white;
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
|
||
// 表格区域
|
||
.table-section {
|
||
background: $background-primary;
|
||
border-radius: $radius;
|
||
box-shadow: $shadow-xs;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.table-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: $space-lg;
|
||
border-bottom: 1px solid $border;
|
||
}
|
||
|
||
.table-title {
|
||
font-size: $font-size-lg;
|
||
font-weight: $font-weight-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.table-count {
|
||
font-size: $font-size-sm;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.table-container {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.table {
|
||
width: 100%;
|
||
min-width: 800px;
|
||
}
|
||
|
||
.table-row {
|
||
display: flex;
|
||
border-bottom: 1px solid $border;
|
||
|
||
&.header {
|
||
background: $background-secondary;
|
||
font-weight: $font-weight-medium;
|
||
}
|
||
|
||
&:hover:not(.header) {
|
||
background: $background-hover;
|
||
}
|
||
}
|
||
|
||
.table-cell {
|
||
flex: 1;
|
||
padding: $space-md;
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 120px;
|
||
word-break: break-word;
|
||
}
|
||
|
||
// 用户信息单元格
|
||
.user-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $space-sm;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.avatar-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.avatar-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: $primary;
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: $font-size-md;
|
||
font-weight: $font-weight-bold;
|
||
}
|
||
|
||
.user-details {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.username {
|
||
font-weight: $font-weight-medium;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.email {
|
||
font-size: $font-size-sm;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.real-name {
|
||
font-size: $font-size-sm;
|
||
color: $primary;
|
||
}
|
||
|
||
// 标签样式
|
||
.role-tag, .status-tag {
|
||
padding: 2px 8px;
|
||
border-radius: $radius-sm;
|
||
font-size: $font-size-xs;
|
||
font-weight: $font-weight-medium;
|
||
}
|
||
|
||
.role-customer { background: #e6f7ff; color: #1890ff; }
|
||
.role-merchant { background: #f6ffed; color: #52c41a; }
|
||
.role-delivery { background: #fff2e8; color: #fa8c16; }
|
||
.role-service { background: #f9f0ff; color: #722ed1; }
|
||
.role-admin { background: #fff1f0; color: #ff4d4f; }
|
||
.role-analytics { background: #e6fffb; color: #13c2c2; }
|
||
|
||
.status-normal { background: #f6ffed; color: #52c41a; }
|
||
.status-frozen { background: #fff2e8; color: #fa8c16; }
|
||
.status-canceled { background: #fff1f0; color: #ff4d4f; }
|
||
.status-pending { background: #f0f5ff; color: #1890ff; }
|
||
|
||
// 会员信息
|
||
.member-info {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 2px;
|
||
}
|
||
|
||
.member-active {
|
||
.member-plan {
|
||
font-weight: $font-weight-medium;
|
||
color: $primary;
|
||
}
|
||
|
||
.member-end {
|
||
font-size: $font-size-xs;
|
||
color: $text-secondary;
|
||
}
|
||
}
|
||
|
||
.member-none {
|
||
.non-member {
|
||
color: $text-secondary;
|
||
font-size: $font-size-sm;
|
||
}
|
||
}
|
||
|
||
.balance {
|
||
font-weight: $font-weight-medium;
|
||
color: $success;
|
||
}
|
||
|
||
.date {
|
||
font-size: $font-size-sm;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.actions {
|
||
justify-content: center;
|
||
}
|
||
|
||
// 分页
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: $space-md;
|
||
padding: $space-lg;
|
||
border-top: 1px solid $border;
|
||
}
|
||
|
||
.page-info {
|
||
font-size: $font-size-sm;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
// 弹窗
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.modal-content {
|
||
background: $background-primary;
|
||
border-radius: $radius;
|
||
width: 90%;
|
||
max-width: 800px;
|
||
max-height: 80vh;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: $space-lg;
|
||
border-bottom: 1px solid $border;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: $font-size-lg;
|
||
font-weight: $font-weight-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.btn-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: $font-size-xl;
|
||
color: $text-secondary;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&:hover {
|
||
color: $text-primary;
|
||
}
|
||
}
|
||
|
||
.modal-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: $space-lg;
|
||
}
|
||
|
||
.detail-section {
|
||
margin-bottom: $space-xl;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.section-title {
|
||
font-size: $font-size-lg;
|
||
font-weight: $font-weight-bold;
|
||
color: $text-primary;
|
||
margin-bottom: $space-md;
|
||
padding-bottom: $space-sm;
|
||
border-bottom: 1px solid $border;
|
||
}
|
||
|
||
.detail-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: $space-md;
|
||
}
|
||
|
||
.detail-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: $space-sm 0;
|
||
}
|
||
|
||
.detail-label {
|
||
font-size: $font-size-sm;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.detail-value {
|
||
font-size: $font-size-sm;
|
||
color: $text-primary;
|
||
font-weight: $font-weight-medium;
|
||
}
|
||
|
||
.member-detail, .address-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: $space-md;
|
||
}
|
||
|
||
.address-item {
|
||
padding: $space-md;
|
||
border: 1px solid $border;
|
||
border-radius: $radius-sm;
|
||
}
|
||
|
||
.address-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $space-sm;
|
||
margin-bottom: $space-xs;
|
||
}
|
||
|
||
.address-name {
|
||
font-weight: $font-weight-medium;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.address-phone {
|
||
font-size: $font-size-sm;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.default-tag {
|
||
background: $primary;
|
||
color: white;
|
||
padding: 2px 6px;
|
||
border-radius: $radius-xs;
|
||
font-size: $font-size-xs;
|
||
}
|
||
|
||
.address-detail {
|
||
font-size: $font-size-sm;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.modal-footer {
|
||
padding: $space-lg;
|
||
border-top: 1px solid $border;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
// 响应式
|
||
@media screen and (max-width: 768px) {
|
||
.page {
|
||
padding: $space-md;
|
||
}
|
||
|
||
.filter-row {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.filter-item {
|
||
min-width: auto;
|
||
}
|
||
|
||
.filter-actions {
|
||
justify-content: stretch;
|
||
|
||
.btn {
|
||
flex: 1;
|
||
}
|
||
}
|
||
|
||
.table-row {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
|
||
.table-cell {
|
||
min-width: auto;
|
||
border-bottom: 1px solid $border-light;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
.modal-content {
|
||
width: 95%;
|
||
max-height: 90vh;
|
||
}
|
||
|
||
.detail-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style> |