feat: realistic face avatar + voice input + ASR endpoint
This commit is contained in:
@@ -17,8 +17,10 @@
|
||||
<text class="tab" :class="{ active: tab === 'overview' }" @click="switchTab('overview')">概览</text>
|
||||
<text class="tab" :class="{ active: tab === 'users' }" @click="switchTab('users')">用户</text>
|
||||
<text class="tab" :class="{ active: tab === 'interviews' }" @click="switchTab('interviews')">面试</text>
|
||||
<text class="tab" :class="{ active: tab === 'resumes' }" @click="switchTab('resumes')">简历</text>
|
||||
<text class="tab" :class="{ active: tab === 'orders' }" @click="switchTab('orders')">订单</text>
|
||||
<text class="tab" :class="{ active: tab === 'pricing' }" @click="switchTab('pricing')">定价</text>
|
||||
<text class="tab" :class="{ active: tab === 'share' }" @click="switchTab('share')">分享</text>
|
||||
<text class="tab" :class="{ active: tab === 'admins' }" @click="switchTab('admins')">管理</text>
|
||||
</view>
|
||||
|
||||
@@ -35,6 +37,17 @@
|
||||
<text class="stat-label">总面试</text>
|
||||
<text class="stat-sub">今日 +{{ overview.todayInterviews }}</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-num">{{ overview.resumeCount ?? 0 }}</text>
|
||||
<text class="stat-label">总简历</text>
|
||||
<text class="stat-sub">付费下载 {{ overview.paidDownloadCount ?? 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="plan-cards">
|
||||
<view class="plan-card" v-for="(cnt, plan) in overview.planBreakdown" :key="plan">
|
||||
<text class="plan-num">{{ cnt }}</text>
|
||||
<text class="plan-label">{{ planNameMap[plan] || plan }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -46,12 +59,22 @@
|
||||
</view>
|
||||
<view class="user-list" v-if="!usersLoading">
|
||||
<view class="user-row" v-for="u in users" :key="u._id">
|
||||
<view class="user-main">
|
||||
<text class="user-phone">{{ u.phone || '--' }}</text>
|
||||
<text class="user-name">{{ u.nickname || '--' }}</text>
|
||||
<text class="user-plan" :class="{ vip: u.plan === 'vip' }">{{ u.plan === 'vip' ? '会员' : '免费' }}</text>
|
||||
<text class="user-remaining">剩{{ u.remaining || 0 }}次</text>
|
||||
<text class="user-vip-btn" v-if="u.plan !== 'vip'" @click="setVip(u._id)">设为会员</text>
|
||||
</view>
|
||||
<view class="user-badges">
|
||||
<text class="user-plan" :class="{ vip: u.plan === 'growth' || u.plan === 'vip' }">{{ u.plan === 'growth' || u.plan === 'vip' ? '会员' : '免费' }}</text>
|
||||
<text class="user-credit">面试:{{ u.interviewCredits ?? 0 }}</text>
|
||||
<text class="user-credit">优化:{{ u.resumeOptimizeCredits ?? 0 }}</text>
|
||||
<text class="user-credit">下载:{{ u.resumeDownloadCredits ?? 0 }}</text>
|
||||
<text class="user-credit share">分享:{{ u.shareCredits ?? 0 }}</text>
|
||||
</view>
|
||||
<view class="user-actions">
|
||||
<text class="user-action-btn" v-if="u.plan !== 'growth' && u.plan !== 'vip'" @click="setVip(u._id)">设为会员</text>
|
||||
<text class="user-action-btn credit" @click="openCreditModal(u)">调整额度</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="load-more" v-if="usersTotal > users.length" @click="loadMoreUsers">加载更多</text>
|
||||
</view>
|
||||
<text class="loading-text" v-if="usersLoading">加载中...</text>
|
||||
@@ -61,15 +84,41 @@
|
||||
<view v-if="tab === 'interviews'" class="section">
|
||||
<view class="iv-list" v-if="!ivLoading">
|
||||
<view class="iv-row" v-for="iv in interviews" :key="iv._id">
|
||||
<text class="iv-pos">{{ iv.position }}</text>
|
||||
<text class="iv-user">{{ iv.userId?.phone || iv.userId?.nickname || '--' }}</text>
|
||||
<text class="iv-status" :class="{ done: iv.status === 'completed' }">{{ iv.status === 'completed' ? '已完成' : '进行中' }}</text>
|
||||
<text class="iv-questions">{{ iv.questionCount || 0 }}题</text>
|
||||
<view class="iv-main">
|
||||
<text class="iv-pos">{{ iv.position }}</text>
|
||||
<text class="iv-user">{{ iv.userId?.phone || iv.userId?.nickname || '--' }}</text>
|
||||
</view>
|
||||
<view class="iv-meta">
|
||||
<text class="iv-status" :class="{ done: iv.status === 'completed' }">{{ iv.status === 'completed' ? '已完成' : '进行中' }}</text>
|
||||
<text class="iv-tag">{{ iv.questionCount || 0 }}题</text>
|
||||
<text class="iv-tag score">得分 {{ iv.totalScore ?? '-' }}</text>
|
||||
<text class="iv-tag filler" v-if="iv.fillerScore != null && iv.fillerScore > 0">语分析 {{ iv.fillerScore }}/{{ iv.fillerDensity ?? '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="loading-text" v-if="ivLoading">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 简历 -->
|
||||
<view v-if="tab === 'resumes'" class="section">
|
||||
<view class="resume-list" v-if="!resumeLoading">
|
||||
<view class="resume-row" v-for="r in resumes" :key="r._id">
|
||||
<view class="resume-main">
|
||||
<text class="resume-title">{{ r.title }}</text>
|
||||
<text class="resume-user">{{ r.userId?.phone || r.userId?.nickname || '--' }}</text>
|
||||
</view>
|
||||
<view class="resume-meta">
|
||||
<text class="resume-tag">v{{ r.version }}</text>
|
||||
<text class="resume-tag" v-if="r.targetPosition">{{ r.targetPosition }}</text>
|
||||
<text class="resume-tag paid" v-if="r.paidDownload">付费下载</text>
|
||||
</view>
|
||||
<text class="resume-time">{{ r.createdAt?.slice(0,10) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="loading-text" v-if="resumeLoading">加载中...</text>
|
||||
<text class="empty-text" v-if="!resumeLoading && resumes.length === 0">暂无简历</text>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<!-- 订单 -->
|
||||
@@ -183,6 +232,60 @@
|
||||
<button class="save-btn" @click="savePricing" :disabled="pricingLoading">保存定价配置</button>
|
||||
<text class="loading-text" v-if="pricingLoading">保存中...</text>
|
||||
</view>
|
||||
<!-- 分享 -->
|
||||
<view v-if="tab === 'share'" class="section">
|
||||
<view class="tabs in-tab">
|
||||
<text class="tab" :class="{ active: shareSubTab === 'records' }" @click="shareSubTab='records';loadShareRecords()">分享记录</text>
|
||||
<text class="tab" :class="{ active: shareSubTab === 'visitors' }" @click="shareSubTab='visitors';loadShareVisitors()">访问记录</text>
|
||||
</view>
|
||||
<view v-if="shareSubTab === 'records'">
|
||||
<view class="share-list" v-if="!shareLoading">
|
||||
<view class="share-row" v-for="r in shareRecords" :key="r.shareCode">
|
||||
<view class="share-main">
|
||||
<text class="share-title">{{ r.title }}</text>
|
||||
<text class="share-meta">{{ r.sharer?.nickname || '--' }} · {{ r.type }}</text>
|
||||
</view>
|
||||
<view class="share-stats">
|
||||
<text>访问 {{ r.visitCount }}</text>
|
||||
<text class="share-credited">有效 {{ r.creditedCount }}</text>
|
||||
</view>
|
||||
<text class="share-time">{{ r.createdAt?.slice(0,16).replace('T',' ') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="loading-text" v-if="shareLoading">加载中...</text>
|
||||
<text class="empty-text" v-if="!shareLoading && shareRecords.length === 0">暂无分享记录</text>
|
||||
</view>
|
||||
<view v-if="shareSubTab === 'visitors'">
|
||||
<view class="share-list" v-if="!shareLoading">
|
||||
<view class="share-row" v-for="(v, i) in shareVisitors" :key="i">
|
||||
<view class="share-main">
|
||||
<text>分享者: {{ v.sharer?.nickname || '--' }}</text>
|
||||
<text class="share-meta">访客: {{ v.visitor?.nickname || '匿名' }}</text>
|
||||
</view>
|
||||
<view class="share-stats">
|
||||
<text class="badge" :class="v.credited ? 'badge-done' : 'badge-pend'">{{ v.credited ? '已积分' : '未积分' }}</text>
|
||||
</view>
|
||||
<text class="share-time">{{ v.createdAt?.slice(0,16).replace('T',' ') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="loading-text" v-if="shareLoading">加载中...</text>
|
||||
<text class="empty-text" v-if="!shareLoading && shareVisitors.length === 0">暂无访问记录</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 额度调整弹窗 -->
|
||||
<view class="modal-mask" v-if="creditModal.show" @click="closeCreditModal">
|
||||
<view class="modal-content" @click.stop>
|
||||
<text class="modal-title">调整 {{ creditModal.user?.nickname || '用户' }} 的额度</text>
|
||||
<view class="cfg-row" v-for="t in creditTypes" :key="t.key">
|
||||
<text>{{ t.label }}</text>
|
||||
<input class="cfg-input" type="digit" v-model.number="t.value" :placeholder="t.key" />
|
||||
</view>
|
||||
<view class="modal-actions">
|
||||
<button class="modal-btn cancel" @click="closeCreditModal">取消</button>
|
||||
<button class="modal-btn confirm" @click="doAdjustCredits">确认调整</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 管理员 -->
|
||||
<view v-if="tab === 'admins'" class="section">
|
||||
<view class="search-bar">
|
||||
@@ -219,16 +322,20 @@ import { api, API_ENDPOINTS } from '../../config'
|
||||
const verified = ref(false)
|
||||
const adminName = ref('')
|
||||
const tab = ref('overview')
|
||||
const shareSubTab = ref('records')
|
||||
const loading = ref(false)
|
||||
const usersLoading = ref(false)
|
||||
const ivLoading = ref(false)
|
||||
const userKeyword = ref('')
|
||||
const usersPage = ref(1)
|
||||
|
||||
const overview = ref({ userCount: 0, interviewCount: 0, todayUsers: 0, todayInterviews: 0 })
|
||||
const overview = ref({ userCount: 0, interviewCount: 0, todayUsers: 0, todayInterviews: 0, resumeCount: 0, paidDownloadCount: 0, planBreakdown: {} })
|
||||
const planNameMap = { free: '免费', growth: '成长', sprint: '冲刺', vip: '会员' }
|
||||
const users = ref([])
|
||||
const usersTotal = ref(0)
|
||||
const interviews = ref([])
|
||||
const resumes = ref([])
|
||||
const resumeLoading = ref(false)
|
||||
const adminKeyword = ref('')
|
||||
const adminList = ref([])
|
||||
const searchResult = ref(null)
|
||||
@@ -261,6 +368,20 @@ const ordersPage = ref(1)
|
||||
const orderLoading = ref(false)
|
||||
const orderFilter = ref('')
|
||||
|
||||
// Share state
|
||||
const shareRecords = ref([])
|
||||
const shareVisitors = ref([])
|
||||
const shareLoading = ref(false)
|
||||
|
||||
// Credit modal
|
||||
const creditModal = ref({ show: false, user: null })
|
||||
const creditTypes = ref([
|
||||
{ key: 'interviewCredits', label: '面试次数', value: 0 },
|
||||
{ key: 'resumeOptimizeCredits', label: '优化次数', value: 0 },
|
||||
{ key: 'resumeDownloadCredits', label: '下载次数', value: 0 },
|
||||
{ key: 'shareCredits', label: '分享积分', value: 0 },
|
||||
])
|
||||
|
||||
const token = () => uni.getStorageSync('token') || ''
|
||||
|
||||
const apiAdmin = (path, opts = {}) => {
|
||||
@@ -300,6 +421,7 @@ const switchTab = (t) => {
|
||||
tab.value = t
|
||||
if (t === 'users' && users.value.length === 0) loadUsers()
|
||||
if (t === 'interviews' && interviews.value.length === 0) loadInterviews()
|
||||
if (t === 'resumes' && resumes.value.length === 0) loadResumes()
|
||||
if (t === 'admins' && adminList.value.length === 0) loadAdmins()
|
||||
if (t === 'pricing') loadPricing()
|
||||
if (t === 'orders') loadOrders()
|
||||
@@ -332,6 +454,15 @@ const loadInterviews = async () => {
|
||||
finally { ivLoading.value = false }
|
||||
}
|
||||
|
||||
const loadResumes = async () => {
|
||||
resumeLoading.value = true
|
||||
try {
|
||||
const res = await apiAdmin('/resumes?page=1&limit=20')
|
||||
if (res.statusCode === 200) resumes.value = res.data.list || []
|
||||
} catch (e) { console.error(e) }
|
||||
finally { resumeLoading.value = false }
|
||||
}
|
||||
|
||||
const loadPricing = async () => {
|
||||
pricingLoading.value = true
|
||||
try {
|
||||
@@ -462,6 +593,54 @@ const setVip = async (targetUserId) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const loadShareRecords = async () => {
|
||||
shareLoading.value = true
|
||||
try {
|
||||
const res = await apiAdmin('/share-records?page=1&limit=50')
|
||||
if (res.statusCode === 200) shareRecords.value = res.data.list || []
|
||||
} catch(e) { console.error(e) }
|
||||
finally { shareLoading.value = false }
|
||||
}
|
||||
|
||||
const loadShareVisitors = async () => {
|
||||
shareLoading.value = true
|
||||
try {
|
||||
const res = await apiAdmin('/share-visitors?page=1&limit=50')
|
||||
if (res.statusCode === 200) shareVisitors.value = res.data.list || []
|
||||
} catch(e) { console.error(e) }
|
||||
finally { shareLoading.value = false }
|
||||
}
|
||||
|
||||
const openCreditModal = (user) => {
|
||||
creditTypes.value = [
|
||||
{ key: 'interviewCredits', label: '面试次数', value: user.interviewCredits ?? 0 },
|
||||
{ key: 'resumeOptimizeCredits', label: '优化次数', value: user.resumeOptimizeCredits ?? 0 },
|
||||
{ key: 'resumeDownloadCredits', label: '下载次数', value: user.resumeDownloadCredits ?? 0 },
|
||||
{ key: 'shareCredits', label: '分享积分', value: user.shareCredits ?? 0 },
|
||||
]
|
||||
creditModal.value = { show: true, user }
|
||||
}
|
||||
|
||||
const closeCreditModal = () => {
|
||||
creditModal.value = { show: false, user: null }
|
||||
}
|
||||
|
||||
const doAdjustCredits = async () => {
|
||||
const userId = creditModal.value.user?._id
|
||||
if (!userId) return
|
||||
try {
|
||||
for (const t of creditTypes.value) {
|
||||
await apiAdmin('/user/credits', {
|
||||
method: 'POST',
|
||||
data: { userId, type: t.key, amount: t.value },
|
||||
})
|
||||
}
|
||||
uni.showToast({ title: '调整成功', icon: 'success' })
|
||||
closeCreditModal()
|
||||
loadUsers()
|
||||
} catch { uni.showToast({ title: '调整失败', icon: 'none' }) }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -481,22 +660,37 @@ const setVip = async (targetUserId) => {
|
||||
.stat-num { font-size: 48rpx; font-weight: 800; color: var(--color-primary); display: block; }
|
||||
.stat-label { font-size: 22rpx; color: var(--color-text-secondary); margin-top: 8rpx; display: block; }
|
||||
.stat-sub { font-size: 20rpx; color: var(--color-success); margin-top: 4rpx; display: block; }
|
||||
.plan-cards { display: flex; gap: 12rpx; margin-top: 16rpx; }
|
||||
.plan-card { flex: 1; background: #FFF; border-radius: var(--radius-sm); padding: 20rpx; text-align: center; box-shadow: var(--shadow-sm); }
|
||||
.plan-num { font-size: 36rpx; font-weight: 700; color: var(--color-primary); display: block; }
|
||||
.plan-label { font-size: 20rpx; color: var(--color-text-secondary); margin-top: 4rpx; display: block; }
|
||||
.search-bar { display: flex; gap: 12rpx; margin-bottom: 16rpx; }
|
||||
.search-input { flex: 1; height: 64rpx; background: #FFF; border: 2rpx solid var(--color-border); border-radius: var(--radius-sm); padding: 0 16rpx; font-size: 24rpx; }
|
||||
.search-btn { height: 64rpx; padding: 0 24rpx; background: var(--color-primary); color: #FFF; border-radius: var(--radius-sm); font-size: 24rpx; border: none; }
|
||||
.user-row { background: #FFF; padding: 20rpx; border-radius: var(--radius-sm); margin-bottom: 8rpx; display: flex; flex-wrap: wrap; gap: 8rpx; }
|
||||
.user-row { background: #FFF; padding: 20rpx; border-radius: var(--radius-sm); margin-bottom: 8rpx; }
|
||||
.user-main { display: flex; gap: 12rpx; margin-bottom: 8rpx; }
|
||||
.user-phone { font-size: 24rpx; font-weight: 600; color: var(--color-text); }
|
||||
.user-name { font-size: 22rpx; color: var(--color-text-secondary); }
|
||||
.user-badges { display: flex; flex-wrap: wrap; gap: 6rpx; margin-bottom: 8rpx; }
|
||||
.user-plan { font-size: 20rpx; background: #EEF2FF; color: var(--color-primary); padding: 2rpx 12rpx; border-radius: var(--radius-round); }
|
||||
.user-remaining { font-size: 20rpx; color: var(--color-text-tertiary); }
|
||||
.user-plan.vip { background: #FEF3C7; color: #D97706; }
|
||||
.user-credit { font-size: 18rpx; background: #F3F4F6; color: var(--color-text-tertiary); padding: 2rpx 10rpx; border-radius: var(--radius-round); }
|
||||
.user-credit.share { background: #FFF7ED; color: #D97706; }
|
||||
.user-actions { display: flex; gap: 12rpx; }
|
||||
.user-action-btn { font-size: 20rpx; color: var(--color-primary); padding: 4rpx 16rpx; border: 2rpx solid var(--color-primary); border-radius: var(--radius-round); }
|
||||
.user-action-btn.credit { color: #D97706; border-color: #D97706; }
|
||||
.loading-text { text-align: center; padding: 40rpx; color: var(--color-text-tertiary); font-size: 24rpx; }
|
||||
.load-more { text-align: center; padding: 20rpx; color: var(--color-primary); font-size: 24rpx; display: block; }
|
||||
.iv-row { background: #FFF; padding: 20rpx; border-radius: var(--radius-sm); margin-bottom: 8rpx; display: flex; flex-wrap: wrap; gap: 8rpx; align-items: center; }
|
||||
.iv-row { background: #FFF; padding: 16rpx; border-radius: var(--radius-sm); margin-bottom: 8rpx; }
|
||||
.iv-main { display: flex; gap: 12rpx; margin-bottom: 6rpx; }
|
||||
.iv-pos { font-size: 24rpx; font-weight: 600; color: var(--color-text); }
|
||||
.iv-user { font-size: 22rpx; color: var(--color-text-secondary); }
|
||||
.iv-meta { display: flex; flex-wrap: wrap; gap: 6rpx; }
|
||||
.iv-status { font-size: 20rpx; background: #FFF7ED; color: var(--color-warning); padding: 2rpx 12rpx; border-radius: var(--radius-round); }
|
||||
.iv-status.done { background: #ECFDF5; color: var(--color-success); }
|
||||
.iv-questions { font-size: 20rpx; color: var(--color-text-tertiary); }
|
||||
.iv-tag { font-size: 18rpx; background: #F3F4F6; color: var(--color-text-tertiary); padding: 2rpx 10rpx; border-radius: var(--radius-round); }
|
||||
.iv-tag.score { background: #EEF2FF; color: var(--color-primary); }
|
||||
.iv-tag.filler { background: #FFF7ED; color: #D97706; }
|
||||
.section-label { font-size: 24rpx; font-weight: 600; color: var(--color-text); margin-bottom: 12rpx; margin-top: 12rpx; }
|
||||
.admin-row { background: #FFF; padding: 20rpx; border-radius: var(--radius-sm); margin-bottom: 8rpx; display: flex; flex-wrap: wrap; gap: 8rpx; align-items: center; }
|
||||
.admin-phone { font-size: 24rpx; font-weight: 600; color: var(--color-text); }
|
||||
@@ -505,6 +699,15 @@ const setVip = async (targetUserId) => {
|
||||
.admin-set-btn.done { color: var(--color-success); border-color: var(--color-success); }
|
||||
.admin-badge { font-size: 18rpx; background: var(--color-primary); color: #FFF; padding: 2rpx 10rpx; border-radius: var(--radius-round); }
|
||||
.empty-text { text-align: center; padding: 20rpx; color: var(--color-text-tertiary); font-size: 22rpx; display: block; }
|
||||
.resume-list { display: flex; flex-direction: column; gap: 8rpx; }
|
||||
.resume-row { background: #FFF; padding: 16rpx; border-radius: var(--radius-sm); display: flex; align-items: center; gap: 12rpx; }
|
||||
.resume-main { flex: 1; display: flex; flex-direction: column; }
|
||||
.resume-title { font-size: 22rpx; font-weight: 600; color: var(--color-text); }
|
||||
.resume-user { font-size: 20rpx; color: var(--color-text-secondary); margin-top: 2rpx; }
|
||||
.resume-meta { display: flex; gap: 6rpx; }
|
||||
.resume-tag { font-size: 18rpx; background: #F3F4F6; color: var(--color-text-tertiary); padding: 2rpx 10rpx; border-radius: var(--radius-round); }
|
||||
.resume-tag.paid { background: #FEF3C7; color: #D97706; }
|
||||
.resume-time { font-size: 18rpx; color: #D1D5DB; white-space: nowrap; }
|
||||
.order-list { display: flex; flex-direction: column; gap: 8rpx; }
|
||||
.order-row { background: #FFF; border-radius: var(--radius-sm); padding: 16rpx; }
|
||||
.order-info { display: flex; justify-content: space-between; margin-bottom: 8rpx; }
|
||||
@@ -527,4 +730,23 @@ const setVip = async (targetUserId) => {
|
||||
.cfg-textarea { width: 100%; height: 160rpx; border: 2rpx solid var(--color-border); border-radius: var(--radius-sm); padding: 12rpx; font-size: 22rpx; margin-top: 8rpx; box-sizing: border-box; }
|
||||
.save-btn { width: 100%; height: 72rpx; background: linear-gradient(135deg, var(--color-gradient-start), var(--color-gradient-mid)); color: #FFF; border-radius: var(--radius-sm); font-size: 26rpx; border: none; margin-top: 12rpx; }
|
||||
.save-btn:disabled { opacity: 0.6; }
|
||||
.in-tab { margin-bottom: 16rpx; }
|
||||
.share-list { display: flex; flex-direction: column; gap: 8rpx; }
|
||||
.share-row { background: #FFF; padding: 16rpx; border-radius: var(--radius-sm); display: flex; align-items: center; gap: 12rpx; }
|
||||
.share-main { flex: 1; display: flex; flex-direction: column; }
|
||||
.share-title { font-size: 22rpx; color: var(--color-text); font-weight: 500; }
|
||||
.share-meta { font-size: 18rpx; color: var(--color-text-tertiary); margin-top: 2rpx; }
|
||||
.share-stats { display: flex; flex-direction: column; align-items: flex-end; font-size: 20rpx; color: var(--color-text-tertiary); }
|
||||
.share-credited { color: var(--color-primary); }
|
||||
.share-time { font-size: 18rpx; color: #D1D5DB; white-space: nowrap; }
|
||||
.badge { font-size: 18rpx; padding: 2rpx 10rpx; border-radius: 6rpx; }
|
||||
.badge-done { background: #ECFDF5; color: #059669; }
|
||||
.badge-pend { background: #FEF3C7; color: #D97706; }
|
||||
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.4); display: flex; align-items: center; justify-content: center; z-index: 999; }
|
||||
.modal-content { background: #FFF; border-radius: var(--radius-lg); padding: 40rpx 32rpx; width: 600rpx; max-width: 90vw; }
|
||||
.modal-title { font-size: 28rpx; font-weight: 600; color: var(--color-text); margin-bottom: 24rpx; }
|
||||
.modal-actions { display: flex; gap: 16rpx; margin-top: 32rpx; }
|
||||
.modal-btn { flex: 1; height: 72rpx; border-radius: var(--radius-sm); font-size: 26rpx; border: none; }
|
||||
.modal-btn.cancel { background: #F3F4F6; color: var(--color-text-secondary); }
|
||||
.modal-btn.confirm { background: var(--color-primary); color: #FFF; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user