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

523 lines
15 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="admin-page">
<view class="admin-sections">
<!-- 筛选面板 -->
<view class="admin-card filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">用户搜索:</text>
<view class="input-group">
<view class="compact-select">
<text>请选择</text>
<text class="arrow">▼</text>
</view>
<input class="filter-input" placeholder="请输入用户" />
</view>
</view>
<view class="filter-item">
<text class="label">用户等级:</text>
<view class="filter-select">
<text class="select-placeholder">请选择用户等级</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-item">
<text class="label">用户分组:</text>
<view class="filter-select">
<text class="select-placeholder">请选择用户分组</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">搜索</button>
<button class="btn" @click="onReset">重置</button>
<text class="expand-btn">展开 </text>
</view>
</view>
</view>
<!-- 内容卡片 -->
<view class="admin-card content-card">
<!-- 平台切换 Tabs -->
<view class="tabs-row">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: activeTab === index }"
@click="activeTab = index"
>
<text>{{ tab }}</text>
</view>
</view>
<!-- 操作按钮行 -->
<view class="action-bar">
<button class="btn primary small" @click="onAddUser">添加用户</button>
<button class="btn ghost small">发送优惠券</button>
<button class="btn ghost small">发送图文消息</button>
<button class="btn ghost small">批量设置分组</button>
<button class="btn ghost small">批量设置标签</button>
<button class="btn ghost small">导出</button>
</view>
<!-- 用户列表表格 -->
<view class="table-container">
<!-- 表头 -->
<view class="table-header">
<view class="col col-check"><checkbox :checked="isAllChecked" /></view>
<view class="col col-expand"></view>
<view class="col col-id"><text>用户ID</text></view>
<view class="col col-avatar"><text>头像</text></view>
<view class="col col-name"><text>姓名</text></view>
<view class="col col-member"><text>付费会员</text></view>
<view class="col col-level"><text>用户等级</text></view>
<view class="col col-group"><text>分组</text></view>
<view class="col col-spread"><text>分销等级</text></view>
<view class="col col-phone"><text>手机号</text></view>
<view class="col col-type"><text>用户类型</text></view>
<view class="col col-balance sortable">
<text>余额</text>
<text class="sort-icon">↕</text>
</view>
<view class="col col-ops"><text>操作</text></view>
</view>
<!-- 表格内容 -->
<view class="table-body">
<view v-for="user in userList" :key="user.id" class="table-row"
:style="{ zIndex: activeDropdownId === user.id ? 1000 : 1 }"
>
<view class="col col-check"><checkbox :checked="user.checked" /></view>
<view class="col col-expand"><text class="expand-arrow"></text></view>
<view class="col col-id"><text>{{ user.id }}</text></view>
<view class="col col-avatar">
<image class="avatar-img" :src="user.avatar" mode="aspectFill" />
</view>
<view class="col col-name">
<text class="name-text">{{ user.nickname }}</text>
</view>
<view class="col col-member">
<text :class="user.isMember === '是' ? 'status-yes' : 'status-no'">{{ user.isMember }}</text>
</view>
<view class="col col-level"><text>{{ user.level }}</text></view>
<view class="col col-group"><text>{{ user.group }}</text></view>
<view class="col col-spread"><text>{{ user.spreadLevel }}</text></view>
<view class="col col-phone"><text>{{ user.phone }}</text></view>
<view class="col col-type"><text>{{ user.userType }}</text></view>
<view class="col col-balance"><text>{{ user.balance }}</text></view>
<view class="col col-ops">
<text class="op-link" @click.stop="onDetail(user)">详情</text>
<view class="op-divider">|</view>
<view class="more-hover-container"
@mouseover="activeDropdownId = user.id"
@mouseleave="activeDropdownId = null"
@click.stop="activeDropdownId = (activeDropdownId === user.id ? null : user.id)"
>
<view class="more-trigger pointer">
<text class="op-link">更多</text>
<text class="arrow"></text>
</view>
<view class="dropdown-list-box" v-if="activeDropdownId === user.id">
<view class="dropdown-arrow-top"></view>
<view class="dropdown-menu-list">
<text class="menu-item" @click.stop="uni.showToast({title:'修改余额', icon:'none'})">修改余额</text>
<text class="menu-item" @click.stop="uni.showToast({title:'修改积分', icon:'none'})">修改积分</text>
<text class="menu-item" @click.stop="uni.showToast({title:'赠送会员', icon:'none'})">赠送会员</text>
<text class="menu-item" @click.stop="uni.showToast({title:'设置分组', icon:'none'})">设置分组</text>
<text class="menu-item" @click.stop="uni.showToast({title:'设置标签', icon:'none'})">设置标签</text>
<text class="menu-item" @click.stop="uni.showToast({title:'修改上级推广人', icon:'none'})">修改上级推广人</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 分页区域 (模拟) -->
<view class="pagination">
<text class="page-info">共 80834 条数据</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const activeTab = ref(0)
const tabs = ['全部', '微信公众号', '微信小程序', 'H5', 'PC', 'APP']
const isAllChecked = ref(false)
const activeDropdownId = ref<string | null>(null)
const userList = ref([
{ id: '77414', avatar: '/static/logo.png', nickname: '199****0268', isMember: '否', level: '无', group: '无', spreadLevel: '', phone: '199****0268', userType: '公众号', balance: '88888.00', checked: false },
{ id: '75311', avatar: '/static/logo.png', nickname: 'wljbhg', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100002.00', checked: false },
{ id: '75305', avatar: '/static/logo.png', nickname: '相见欢', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75296', avatar: '/static/logo.png', nickname: '..', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75293', avatar: '/static/logo.png', nickname: '钟(钏)华', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75289', avatar: '/static/logo.png', nickname: '小二上酒', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75257', avatar: '/static/logo.png', nickname: '5+7', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75226', avatar: '/static/logo.png', nickname: '慢步前行', isMember: '是', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false },
{ id: '75211', avatar: '/static/logo.png', nickname: '难得糊涂', isMember: '否', level: '无', group: 'A类客户', spreadLevel: '', phone: '', userType: '公众号', balance: '100000.00', checked: false }
])
function onSearch() {
uni.showToast({ title: '搜索中...', icon: 'none' })
}
function onReset() {
uni.showToast({ title: '已重置', icon: 'none' })
}
function onAddUser() {
uni.showToast({ title: '添加用户', icon: 'none' })
}
function onDetail(user: any) {
uni.showToast({ title: '查看用户: ' + user.id, icon: 'none' })
}
</script>
<style scoped lang="scss">
/* 筛选卡片 */
.filter-card {
}
.filter-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 24px;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
}
.label {
font-size: 14px;
color: #333;
width: 70px;
}
.input-group {
display: flex;
flex-direction: row;
border: 1px solid #d9d9d9;
border-radius: 2px;
height: 32px;
width: 260px;
}
.compact-select {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
border-right: 1px solid #d9d9d9;
background: #fafafa;
text { font-size: 14px; color: #666; }
.arrow { font-size: 10px; margin-left: 4px; color: #bfbfbf; }
}
.filter-input {
flex: 1;
height: 30px;
padding: 0 12px;
font-size: 14px;
}
.filter-select {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border: 1px solid #d9d9d9;
border-radius: 2px;
height: 32px;
width: 220px;
padding: 0 12px;
background: #fff;
}
.select-placeholder { font-size: 14px; color: #bfbfbf; }
.arrow { font-size: 10px; color: #bfbfbf; }
.filter-btns {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.btn {
height: 32px;
padding: 0 16px;
font-size: 14px;
border-radius: 2px;
border: 1px solid #d9d9d9;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin: 0;
}
.btn.primary {
background: #2f54eb;
border-color: #2f54eb;
color: #fff;
}
.btn.ghost {
color: #2f54eb;
border-color: #2f54eb;
background: #fff;
}
.btn.small {
height: 28px;
padding: 0 12px;
font-size: 13px;
}
.expand-btn {
font-size: 14px;
color: #2f54eb;
cursor: pointer;
}
/* 内容卡片 */
.content-card {
padding: 0;
overflow: visible; /* 必须 visible 以显示下拉菜单 */
}
/* Tabs */
.tabs-row {
display: flex;
flex-direction: row;
padding: 0 24px;
border-bottom: 1px solid #f0f0f0;
}
.tab-item {
padding: 16px 20px;
cursor: pointer;
position: relative;
text { font-size: 15px; color: #666; }
&.active {
text { color: #2f54eb; font-weight: 500; }
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: #2f54eb;
}
}
}
/* 操作栏 */
.action-bar {
padding: 16px 24px;
display: flex;
flex-direction: row;
gap: 12px;
}
/* 表格 */
.table-container {
padding: 0 24px 24px;
overflow: visible;
}
.table-header {
display: flex;
flex-direction: row;
background: #f8faff;
border-bottom: 1px solid #f0f0f0;
padding: 12px 0;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
padding: 16px 0;
align-items: center;
position: relative;
overflow: visible;
&:hover {
background: #fafafa;
}
}
.col {
padding: 0 8px;
display: flex;
align-items: center;
font-size: 14px;
color: #333;
}
.col-check { width: 40px; justify-content: center; }
.col-expand { width: 30px; justify-content: center; }
.col-id { width: 80px; }
.col-avatar { width: 80px; justify-content: center; }
.col-name { width: 140px; }
.col-member { width: 90px; justify-content: center; }
.col-level { width: 90px; justify-content: center; }
.col-group { width: 110px; justify-content: center; }
.col-spread { width: 110px; justify-content: center; }
.col-phone { width: 130px; }
.col-type { width: 90px; }
.col-balance { width: 110px; }
.col-ops {
display: flex;
flex-direction: row;
width: 130px;
justify-content: flex-end;
padding-right: 16px;
align-items: center;
}
.more-hover-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 32px;
min-width: 46px; /* 增加点击和悬停范围 */
padding: 0 4px;
cursor: pointer;
}
.more-trigger {
display: flex;
flex-direction: row;
align-items: center;
gap: 2px;
pointer-events: none; /* 确保事件冒泡到 container */
.arrow { font-size: 10px; color: #2f54eb; margin-left: 2px; }
}
.dropdown-list-box {
position: absolute;
top: 30px;
right: -5px; /* 稍微向左移动一点 */
width: 140px; /* 增加宽度以容纳长文字 */
padding-top: 10px; /* 增加缓冲区防止鼠标移出 */
z-index: 9999;
}
.dropdown-arrow-top {
position: absolute;
top: 2px;
right: 25px;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
}
.dropdown-menu-list {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
padding: 6px 0;
}
.menu-item {
padding: 10px 16px;
font-size: 14px;
color: #606266;
text-align: center;
line-height: 1.4;
&:hover {
background-color: #f5f7fa;
color: #2f54eb;
}
}
.danger-item {
&:hover {
color: #ff4d4f !important;
}
}
.pointer { cursor: pointer; }
.table-header .col {
color: #5c5c5c;
font-weight: 500;
}
.sort-icon {
font-size: 12px;
margin-left: 4px;
color: #bfbfbf;
}
.expand-arrow {
color: #bfbfbf;
font-size: 18px;
}
.avatar-img {
width: 40px;
height: 40px;
border-radius: 4px;
background: #f5f5f5;
}
.name-text {
font-weight: 400;
}
.status-yes { color: #52c41a; }
.status-no { color: #ff4d4f; }
.op-link {
color: #2f54eb;
cursor: pointer;
font-size: 14px;
}
.op-divider {
color: #e8e8e8;
font-size: 12px;
margin: 0 8px;
}
.pagination {
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.page-info {
font-size: 14px;
color: #999;
}
</style>