898 lines
24 KiB
Plaintext
898 lines
24 KiB
Plaintext
<template>
|
||
<view class="members-page">
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<view style="padding-top: var(--status-bar-height); background-color: #ffffff; display: flex; flex-direction: row; align-items: flex-end; border-bottom: 1rpx solid #eeeeee; box-sizing: border-box; height: calc(88rpx + var(--status-bar-height));">
|
||
<view style="display: flex; flex-direction: row; align-items: center; padding: 0 30rpx; height: 88rpx;" @click="uni.navigateBack()">
|
||
<text style="font-size: 44rpx; color: #333333; line-height: 1; margin-right: 6rpx;">‹</text>
|
||
<text style="font-size: 28rpx; color: #333333;">返回</text>
|
||
</view>
|
||
</view>
|
||
<!-- #endif -->
|
||
<view class="tabs">
|
||
<view class="tab" :class="{ active: activeTab === 0 }" @click="activeTab = 0">关怀等级设置</view>
|
||
<view class="tab" :class="{ active: activeTab === 1 }" @click="activeTab = 1">服务对象列表</view>
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="list-container" v-if="activeTab === 0">
|
||
<view class="section-card">
|
||
<view class="card-header">
|
||
<text class="card-title">关怀等级配置</text>
|
||
<text class="add-btn" @click="showAddLevel = true">+ 添加等级</text>
|
||
</view>
|
||
<view class="level-list">
|
||
<view v-for="level in levels" :key="level.id" class="level-item">
|
||
<view class="level-info">
|
||
<text class="level-name">{{ level.name }}</text>
|
||
<text class="level-rate">优惠比例: {{ (level.discount_rate * 10).toFixed(1) }}折</text>
|
||
</view>
|
||
<view class="level-actions">
|
||
<text class="action-edit" @click="editLevel(level)">编辑</text>
|
||
<text class="action-del" @click="deleteLevel(level.id)">删除</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<scroll-view scroll-y class="list-container" v-if="activeTab === 1">
|
||
<view class="user-list">
|
||
<view v-if="users.length === 0" class="empty-tip">暂无服务对象记录</view>
|
||
<view v-for="user in users" :key="user.id" class="user-item">
|
||
<image :src="user.avatar_url || '/static/images/default-avatar.png'" class="user-avatar" />
|
||
<view class="user-info">
|
||
<view class="user-title-row">
|
||
<text class="user-name">{{ user.nickname || user.username || '未设置姓名' }}</text>
|
||
<view class="user-tier-tag" v-if="user.tier_name">{{ user.tier_name }}</view>
|
||
</view>
|
||
<text class="user-email" v-if="user.email">{{ user.email }}</text>
|
||
<text class="user-phone">{{ user.phone || '未绑定手机为服务对象' }}</text>
|
||
</view>
|
||
<view class="user-actions">
|
||
<text class="action-set" @click="showSetTier(user)">设置关怀等级</text>
|
||
<text class="action-set discount-btn" @click="goToExclusive(user)">专属补贴</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 编辑等级弹窗 -->
|
||
<view class="modal" v-if="showEditModal">
|
||
<view class="modal-content">
|
||
<view class="modal-title">{{ currentLevel.id ? '编辑等级' : '添加等级' }}</view>
|
||
<view class="form-item">
|
||
<text class="label">等级名称</text>
|
||
<input class="input" v-model="currentLevel.name" placeholder="请输入名称" />
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">优惠比例 (0-1)</text>
|
||
<input class="input" type="digit" v-model="currentLevel.discount_rate" placeholder="如0.85表示八五折专属优惠" />
|
||
</view>
|
||
<view class="modal-btns">
|
||
<text class="btn cancel" @click="showEditModal = false">取消</text>
|
||
<text class="btn confirm" @click="saveLevel">保存</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 设置用户等级弹窗 -->
|
||
<view class="modal" v-if="showTierModal">
|
||
<view class="modal-content">
|
||
<view class="modal-title">设置关怀等级</view>
|
||
<view class="tier-options">
|
||
<view v-for="level in levels" :key="level.id"
|
||
class="tier-option"
|
||
:class="{ selected: selectedTierId === level.id }"
|
||
@click="selectedTierId = level.id">
|
||
{{ level.name }}
|
||
</view>
|
||
</view>
|
||
<view class="modal-btns">
|
||
<text class="btn cancel" @click="showTierModal = false">取消</text>
|
||
<text class="btn confirm" @click="confirmSetTier">确认</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
import { USE_MOCK, MOCK_MEMBER_LEVELS, MOCK_SERVICE_USERS } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||
|
||
type MemberLevel = {
|
||
id: string
|
||
name: string
|
||
discount_rate: number
|
||
level_rank: number
|
||
}
|
||
|
||
type UserInfo = {
|
||
id: string
|
||
username: string
|
||
email: string
|
||
nickname: string | null
|
||
avatar_url: string | null
|
||
phone: string | null
|
||
tier_id: string | null
|
||
tier_name: string | null
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
activeTab: 0,
|
||
levels: [] as MemberLevel[],
|
||
users: [] as UserInfo[],
|
||
searchKey: '',
|
||
showEditModal: false,
|
||
showTierModal: false,
|
||
showAddLevel: false,
|
||
currentLevel: {
|
||
id: '',
|
||
name: '',
|
||
discount_rate: 1.0,
|
||
level_rank: 0
|
||
} as MemberLevel,
|
||
currentUser: null as UserInfo | null,
|
||
selectedTierId: '',
|
||
merchantId: ''
|
||
}
|
||
},
|
||
onLoad() {
|
||
if (USE_MOCK) {
|
||
this.merchantId = 'mock-merchant'
|
||
this.loadLevels()
|
||
return
|
||
}
|
||
this.merchantId = uni.getStorageSync('user_id') || ''
|
||
this.loadLevels()
|
||
},
|
||
watch: {
|
||
activeTab(val: number) {
|
||
if (val === 1 && this.users.length === 0) {
|
||
this.loadUsers()
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
handleSearch() {
|
||
console.log('按钮被点击,触发 handleSearch');
|
||
this.loadUsers();
|
||
},
|
||
async loadLevels() {
|
||
if (USE_MOCK) {
|
||
this.levels = MOCK_MEMBER_LEVELS as MemberLevel[]
|
||
return
|
||
}
|
||
const res = await supa.from('ml_member_levels').select('*').order('level_rank', { ascending: true }).execute()
|
||
if (res.data != null) {
|
||
const raw = res.data as any[]
|
||
const levels: MemberLevel[] = []
|
||
for (let i = 0; i < raw.length; i++) {
|
||
const obj = raw[i] as UTSJSONObject
|
||
levels.push({
|
||
id: obj.getString('id') || '',
|
||
name: obj.getString('name') || '',
|
||
discount_rate: obj.getNumber('discount_rate') || 1.0,
|
||
level_rank: obj.getNumber('level_rank') || 0
|
||
} as MemberLevel)
|
||
}
|
||
this.levels = levels
|
||
}
|
||
},
|
||
async loadUsers() {
|
||
console.log('--- 启动 ak_users 全量加载 (不带 limit 限制) ---');
|
||
if (USE_MOCK) {
|
||
this.users = MOCK_SERVICE_USERS as UserInfo[]
|
||
return
|
||
}
|
||
try {
|
||
// 1. 移除 limit 限制或设置极大值,确保读到全部数据
|
||
// 同时通过 count 参数确认数据库到底给了多少条
|
||
const res = await supa.from('ak_users')
|
||
.select('id, username, nickname, email, phone, avatar_url, role', { count: 'exact' })
|
||
.execute()
|
||
|
||
if (res.error != null) {
|
||
console.error('API请求错误:', res.error);
|
||
return
|
||
}
|
||
|
||
if (res.data != null) {
|
||
let rawData = res.data as any[]
|
||
console.log('数据库查询成功。总行数:', res.count, ' 返回行数:', rawData.length);
|
||
|
||
// 增加一个调试点:统计一下所有数据的 role 分布,看看到底有多少个 role 是 customer
|
||
let customerCount = 0;
|
||
rawData.forEach((item: any) => {
|
||
const r = String((item as UTSJSONObject)['role'] || '').trim().toLowerCase();
|
||
if (r == 'customer') customerCount++;
|
||
});
|
||
console.log('内存扫描结果: 含有 customer 字样的记录总数:', customerCount);
|
||
|
||
// 2. 获取会员等级地图
|
||
let profileMap = new Map<string, string>()
|
||
try {
|
||
const profileRes = await supa.from('ml_user_profiles').select('*').limit(1).execute()
|
||
if (profileRes.data != null && (profileRes.data as any[]).length > 0) {
|
||
console.log('【数据库结构探查】ml_user_profiles 第一条数据:', JSON.stringify(profileRes.data[0]))
|
||
}
|
||
|
||
const profileAllRes = await supa.from('ml_user_profiles').select('*').execute()
|
||
console.log('【数据调试】ml_user_profiles 返回行数:', (profileAllRes.data as any[] || []).length)
|
||
if (profileAllRes.data != null) {
|
||
const profileData = profileAllRes.data as any[]
|
||
profileData.forEach((p: any) => {
|
||
if (p != null) {
|
||
const po = p as UTSJSONObject
|
||
const uid = String(po['user_id'] || '').trim().toLowerCase()
|
||
|
||
const keys = Object.keys(p as object)
|
||
let foundTid = ''
|
||
|
||
if (keys.includes('tier_id')) {
|
||
foundTid = String(po['tier_id'] || '')
|
||
} else if (keys.includes('level_id')) {
|
||
foundTid = String(po['level_id'] || '')
|
||
} else if (keys.includes('rank_id')) {
|
||
foundTid = String(po['rank_id'] || '')
|
||
} else {
|
||
const autoKey = keys.find(k => k.includes('level') || k.includes('tier'))
|
||
if (autoKey != null) {
|
||
foundTid = String(po[autoKey] || '')
|
||
}
|
||
}
|
||
|
||
foundTid = foundTid.trim().toLowerCase()
|
||
|
||
if (uid != '' && foundTid != '' && foundTid != 'null') {
|
||
console.log(`【映射匹配成功】UID: ${uid} -> TID: ${foundTid}`)
|
||
profileMap.set(uid, foundTid)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.error('查询 profile 报错:', e)
|
||
}
|
||
|
||
// 3. 【极致完善筛选逻辑】
|
||
this.users = rawData.map((u: any): UserInfo | null => {
|
||
if (u == null) return null
|
||
const uo = u as UTSJSONObject
|
||
|
||
let rawRole = String(uo['role'] || '');
|
||
const role = rawRole.trim().toLowerCase();
|
||
|
||
// 严格筛选:仅保留角色为 consumer 的真实消费者
|
||
if (role != 'consumer') return null
|
||
|
||
const uid = String(uo['id'] || uo['user_id'] || '').trim().toLowerCase()
|
||
const username = String(uo['username'] || '')
|
||
// 这里是关键:profileMap 里的 key 是小写的 uid,tid 也是小写的
|
||
const tid = profileMap.get(uid) || ''
|
||
|
||
let tname = ''
|
||
if (tid != '') {
|
||
// 1. 严格 ID 匹配
|
||
const level = this.levels.find(l => (l.id || '').trim().toLowerCase() === tid)
|
||
if (level != null) {
|
||
tname = level.name
|
||
} else {
|
||
// 2. 备用:如果 ID 匹配不到,尝试看这个 tid 是不是等级的序号(level_rank)
|
||
const levelByRank = this.levels.find(l => String(l.level_rank) === tid)
|
||
if (levelByRank != null) tname = levelByRank.name
|
||
}
|
||
}
|
||
|
||
if (tid != '') {
|
||
console.log(`【渲染行检查】用户:${username}, ID:${uid}, 等级TID(DB):${tid}, 匹配结果:${tname}`)
|
||
}
|
||
|
||
return {
|
||
id: uid,
|
||
username: username,
|
||
email: String(uo['email'] || ''),
|
||
nickname: String(uo['nickname'] || uo['username'] || '未设置昵称'),
|
||
avatar_url: String(uo['avatar_url'] || uo['head_img_url'] || ''),
|
||
phone: String(uo['phone'] || ''),
|
||
tier_id: tid,
|
||
tier_name: tname
|
||
} as UserInfo
|
||
}).filter((u: any): boolean => u != null) as UserInfo[]
|
||
|
||
// 【核心优化】自动将已经设置了 VIP 的人排在列表最顶端,方便一眼看到
|
||
this.users.sort((a, b) => {
|
||
const nameA = (a.tier_name || '').trim()
|
||
const nameB = (b.tier_name || '').trim()
|
||
if (nameA != '' && nameB == '') return -1
|
||
if (nameA == '' && nameB != '') return 1
|
||
return 0
|
||
})
|
||
|
||
console.log('【最终渲染检查】当前用户列表长度:', this.users.length);
|
||
// 强制触发一次 UI 重绘
|
||
this.$forceUpdate();
|
||
}
|
||
} catch (e) {
|
||
console.error('加载逻辑崩溃:', e);
|
||
}
|
||
},
|
||
processUserData(rawData: any[]) {
|
||
if (rawData != null && Array.isArray(rawData)) {
|
||
this.users = rawData.map((item: any) => {
|
||
const istr = JSON.stringify(item)
|
||
const obj = JSON.parse(istr) as UTSJSONObject
|
||
|
||
const tierId = obj.getString('tier_id')
|
||
let tierName = ''
|
||
if (tierId != null && tierId != '') {
|
||
const level = this.levels.find(l => l.id === tierId)
|
||
if (level != null) tierName = level.name
|
||
}
|
||
|
||
return {
|
||
id: obj.getString('id') || obj.getString('user_id') || '',
|
||
nickname: obj.getString('nickname') || '未设置昵称',
|
||
avatar_url: obj.getString('avatar_url'),
|
||
phone: obj.getString('phone_number') || '无手机号',
|
||
tier_id: tierId,
|
||
tier_name: tierName
|
||
} as UserInfo
|
||
})
|
||
} else {
|
||
this.users = []
|
||
}
|
||
},
|
||
editLevel(level: MemberLevel) {
|
||
this.currentLevel = JSON.parse(JSON.stringify(level)) as MemberLevel
|
||
this.showEditModal = true
|
||
},
|
||
async saveLevel() {
|
||
if (!this.currentLevel.name) return
|
||
|
||
// 构造提交数据,确保类型正确
|
||
const discount = parseFloat(this.currentLevel.discount_rate.toString())
|
||
const rank = parseInt(this.currentLevel.level_rank.toString())
|
||
|
||
const data = {
|
||
name: this.currentLevel.name,
|
||
discount_rate: isNaN(discount) ? 1.0 : discount,
|
||
level_rank: isNaN(rank) ? 0 : rank
|
||
}
|
||
|
||
let res: any
|
||
if (this.currentLevel.id) {
|
||
res = await supa.from('ml_member_levels').update(data).eq('id', this.currentLevel.id).execute()
|
||
} else {
|
||
res = await supa.from('ml_member_levels').insert(data).execute()
|
||
}
|
||
|
||
if (res.error == null) {
|
||
uni.showToast({ title: '保存成功' })
|
||
this.showEditModal = false
|
||
this.loadLevels()
|
||
} else {
|
||
uni.showModal({ title: '保存失败', content: JSON.stringify(res.error) })
|
||
}
|
||
},
|
||
async deleteLevel(id: string) {
|
||
uni.showModal({
|
||
title: '确认删除',
|
||
content: '此操作将同步删除关联用户的等级,是否继续?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
// 先将该等级下的用户 tier_id 清空,防止外键约束或逻辑残留
|
||
await supa.from('ml_user_profiles').update({ tier_id: null }).eq('tier_id', id).execute()
|
||
const delRes = await supa.from('ml_member_levels').delete().eq('id', id).execute()
|
||
if (delRes.error == null) {
|
||
this.loadLevels()
|
||
this.loadUsers()
|
||
}
|
||
}
|
||
}
|
||
})
|
||
},
|
||
goToExclusive(user: any) {
|
||
const name = user['nickname'] || user['username'] || user['phone'] || '客户'
|
||
const uId = user['id']
|
||
uni.navigateTo({
|
||
url: '/pages/mall/merchant/exclusive-discounts?user_id=' + uId + '&user_name=' + encodeURIComponent(name as string)
|
||
})
|
||
},
|
||
showSetTier(user: UserInfo) {
|
||
this.currentUser = user
|
||
this.selectedTierId = user.tier_id || ''
|
||
this.showTierModal = true
|
||
},
|
||
async confirmSetTier() {
|
||
if (this.currentUser == null) return
|
||
|
||
uni.showLoading({ title: '确认中...' })
|
||
try {
|
||
const userObj = this.currentUser as UserInfo
|
||
const userId = userObj.id
|
||
|
||
// 1. 获取所有字段名(不依赖第一行数据,而是通过 RPC 或直接查询)
|
||
// 为确保万无一失,我们直接同时尝试写入 tier_id 和 level_id
|
||
const probeRes = await supa.from('ml_user_profiles').select('*').limit(1).execute()
|
||
|
||
let finalObj = {
|
||
'user_id': userId,
|
||
'updated_at': new Date().toISOString()
|
||
} as UTSJSONObject
|
||
|
||
// 智能探测字段
|
||
if (probeRes.data != null && (probeRes.data as any[]).length > 0) {
|
||
const keys = Object.keys(probeRes.data![0] as object)
|
||
console.log('【数据库字段探测】:', JSON.stringify(keys))
|
||
|
||
if (keys.includes('tier_id')) {
|
||
finalObj['tier_id'] = this.selectedTierId
|
||
} else if (keys.includes('level_id')) {
|
||
finalObj['level_id'] = this.selectedTierId
|
||
} else if (keys.includes('rank_id')) {
|
||
finalObj['rank_id'] = this.selectedTierId
|
||
} else {
|
||
// 万能匹配
|
||
const anyLevelKey = keys.find(k => k.includes('level') || k.includes('tier'))
|
||
if (anyLevelKey != null) finalObj[anyLevelKey] = this.selectedTierId
|
||
}
|
||
} else {
|
||
// 如果表完全是空的,默认尝试 tier_id
|
||
finalObj['tier_id'] = this.selectedTierId
|
||
}
|
||
|
||
// 2. 使用 UPSERT 逻辑(存在就更新,没有就插入)
|
||
// Supabase 的 upsert 需要定义唯一约束,这里我们根据 user_id 处理
|
||
const checkExist = await supa.from('ml_user_profiles').select('id').eq('user_id', userId).execute()
|
||
|
||
let finalRes: any = null
|
||
if (checkExist.data != null && (checkExist.data as any[]).length > 0) {
|
||
// 注意:更新时不需要带上 user_id 字段
|
||
const updateObj = JSON.parse(JSON.stringify(finalObj)) as UTSJSONObject
|
||
delete updateObj['user_id']
|
||
finalRes = await supa.from('ml_user_profiles').update(updateObj).eq('user_id', userId).execute()
|
||
} else {
|
||
finalRes = await supa.from('ml_user_profiles').insert(finalObj).execute()
|
||
}
|
||
|
||
if (finalRes != null && finalRes.error != null) {
|
||
throw new Error('保存失败: ' + finalRes.error!.message)
|
||
}
|
||
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '设置成功', icon: 'success' })
|
||
this.showTierModal = false
|
||
|
||
// 立即重新获取该用户的 profile 确认
|
||
setTimeout(() => {
|
||
this.loadUsers()
|
||
}, 300)
|
||
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
uni.showModal({
|
||
title: '设置异常',
|
||
content: String(e),
|
||
showCancel: false
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.members-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: #f8f9fa;
|
||
min-height: 100vh;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background: #ffffff;
|
||
padding: 0;
|
||
border-bottom: 1rpx solid #eeeeee;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tab {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 88rpx;
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
position: relative;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tab.active {
|
||
color: #09C39D;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.tab.active::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 44rpx;
|
||
height: 4rpx;
|
||
background: #09C39D;
|
||
border-radius: 2rpx;
|
||
}
|
||
|
||
.list-container {
|
||
flex: 1;
|
||
height: calc(100vh - 176rpx);
|
||
padding: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.section-card {
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 30rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.add-btn {
|
||
font-size: 26rpx;
|
||
color: #09C39D;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.level-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.level-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.level-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-right: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.level-name {
|
||
font-size: 30rpx;
|
||
color: #333333;
|
||
font-weight: 500;
|
||
line-height: 1.4;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.level-rate {
|
||
font-size: 24rpx;
|
||
color: #ff9500;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.level-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.action-edit,
|
||
.action-del {
|
||
font-size: 24rpx;
|
||
line-height: 1.4;
|
||
padding: 8rpx 0;
|
||
}
|
||
|
||
.action-edit {
|
||
color: #09C39D;
|
||
margin-right: 24rpx;
|
||
}
|
||
|
||
.action-del {
|
||
color: #ff3b30;
|
||
}
|
||
|
||
.user-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.empty-tip {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 220rpx;
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
font-size: 28rpx;
|
||
color: #999999;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.user-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background: #ffffff;
|
||
padding: 24rpx;
|
||
border-radius: 16rpx;
|
||
margin-bottom: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 90rpx;
|
||
height: 90rpx;
|
||
border-radius: 45rpx;
|
||
background: #eeeeee;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.user-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-left: 24rpx;
|
||
margin-right: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.user-title-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
line-height: 1.4;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.user-tier-tag {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 36rpx;
|
||
padding: 0 14rpx;
|
||
background: #ff9500;
|
||
color: #ffffff;
|
||
font-size: 20rpx;
|
||
border-radius: 18rpx;
|
||
line-height: 1;
|
||
box-sizing: border-box;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.user-email {
|
||
font-size: 24rpx;
|
||
color: #666666;
|
||
line-height: 1.4;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.user-phone {
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.user-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
flex-wrap: wrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.action-set {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 60rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 24rpx;
|
||
color: #09C39D;
|
||
background-color: #e8f0fe;
|
||
border-radius: 30rpx;
|
||
box-sizing: border-box;
|
||
margin-left: 12rpx;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.action-set.discount-btn {
|
||
color: #ff9800;
|
||
background-color: #fff8e1;
|
||
}
|
||
|
||
.search-bar {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
background: #ffffff;
|
||
margin-bottom: 20rpx;
|
||
border-radius: 12rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
height: 72rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 36rpx;
|
||
padding: 0 30rpx;
|
||
font-size: 26rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.search-btn {
|
||
margin-left: 20rpx;
|
||
height: 72rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #09C39D;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.modal {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 999;
|
||
padding: 30rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 600rpx;
|
||
max-width: 100%;
|
||
background: #ffffff;
|
||
border-radius: 24rpx;
|
||
padding: 40rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.modal-title {
|
||
text-align: center;
|
||
font-size: 34rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
line-height: 1.4;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.form-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.label {
|
||
font-size: 26rpx;
|
||
color: #666666;
|
||
line-height: 1.4;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.input {
|
||
background: #f5f5f5;
|
||
height: 80rpx;
|
||
border-radius: 12rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.modal-btns {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
margin-top: 40rpx;
|
||
}
|
||
|
||
.btn {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 160rpx;
|
||
height: 76rpx;
|
||
padding: 0 32rpx;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
margin-left: 20rpx;
|
||
}
|
||
|
||
.btn.cancel {
|
||
background: #eeeeee;
|
||
color: #666666;
|
||
}
|
||
.btn.confirm {
|
||
background: #09C39D;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.tier-options {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
margin: -10rpx;
|
||
}
|
||
|
||
.tier-option {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 16rpx 30rpx;
|
||
background: #f5f5f5;
|
||
margin: 10rpx;
|
||
border-radius: 36rpx;
|
||
font-size: 26rpx;
|
||
color: #333333;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tier-option.selected {
|
||
background: #09C39D;
|
||
color: #ffffff;
|
||
}
|
||
</style>
|
||
|