Files
medical-mall/pages/mall/admin/user/level/index.uvue
2026-02-06 12:06:33 +08:00

524 lines
16 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-user-level">
<view class="content-body">
<!-- 椤堕儴杩囨护鏍?-->
<view class="filter-card border-shadow">
<view class="filter-item">
<text class="label-txt">绛夌骇鐘舵€?</text>
<view class="select-mock">
<text class="select-val">璇烽€夋嫨</text>
<text class="arrow-down">鈻?/text>
</view>
</view>
<view class="filter-item">
<text class="label-txt">绛夌骇鍚嶇О:</text>
<input class="search-input" placeholder="璇疯緭鍏ョ瓑绾у悕绉? v-model="filterName" />
</view>
<view class="btn-query" @click="handleQuery">
<text class="query-txt">鏌ヨ</text>
</view>
</view>
<!-- 涓昏鍐呭鍖哄煙 -->
<view class="table-card border-shadow">
<view class="card-header">
<view class="btn-primary-blue" @click="handleAdd">
<text class="btn-txt">娣诲姞鐢ㄦ埛绛夌骇</text>
</view>
</view>
<!-- 鏁版嵁琛ㄦ牸 -->
<view class="table-container">
<view class="table-header-row">
<view class="th" style="width: 80px;">ID</view>
<view class="th" style="width: 120px;">绛夌骇鍥炬爣</view>
<view class="th" style="width: 150px;">绛夌骇鑳屾櫙鍥?/view>
<view class="th" style="flex: 1;">绛夌骇鍚嶇О</view>
<view class="th" style="width: 100px;">绛夌骇</view>
<view class="th" style="width: 120px;">浜彈鎶樻墸</view>
<view class="th" style="width: 150px;">缁忛獙鍊艰姹?/view>
<view class="th" style="width: 120px;">鏄惁鏄剧ず</view>
<view class="th" style="width: 150px;">鎿嶄綔</view>
</view>
<view class="table-body">
<view v-for="(item, index) in levelList" :key="item.id" class="table-row">
<view class="td" style="width: 80px;"><text class="td-txt">{{ item.id }}</text></view>
<view class="td" style="width: 120px;">
<view class="icon-circle" :style="{ backgroundColor: item.iconBg }">
<text class="icon-symbol">{{ item.iconSymbol }}</text>
</view>
</view>
<view class="td" style="width: 150px;">
<view class="bg-thumb" :style="{ background: item.bgGradient }"></view>
</view>
<view class="td" style="flex: 1;"><text class="td-txt">{{ item.name }}</text></view>
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.level }}</text></view>
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.discount }}</text></view>
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.experience }}</text></view>
<view class="td" style="width: 120px;">
<view :class="['switch-box', item.isShow ? 'active' : '']" @click="toggleShow(index)">
<view class="switch-dot"></view>
</view>
</view>
<view class="td" style="width: 150px;">
<view class="op-links">
<text class="op-link" @click="handleEdit(item)">缂栬緫</text>
<text class="op-split">|</text>
<text class="op-link text-danger">鍒犻櫎</text>
</view>
</view>
</view>
</view>
</view>
<!-- 鍒嗛〉 -->
<view class="pagination-footer">
<view class="page-total">
<text class="total-txt">鍏?{{ total }} 鏉?/text>
</view>
<view class="page-select">
<text class="page-val">15鏉?椤?鈻?/text>
</view>
<view class="page-btns">
<text class="p-btn disabled"><</text>
<text class="p-btn active">1</text>
<text class="p-btn">></text>
</view>
<view class="page-jump">
<text class="jump-txt">鍓嶅線</text>
<input class="jump-input" placeholder="1" />
<text class="jump-txt">椤?/text>
</view>
</view>
</view>
</view>
<!-- 鎶藉眽寮圭獥 (Add/Edit) -->
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
<view class="drawer-header">
<text class="title-txt">{{ isEdit ? '缂栬緫鐢ㄦ埛绛夌骇' : '娣诲姞鐢ㄦ埛绛夌骇' }}</text>
<text class="close-btn" @click="closeDrawer">脳</text>
</view>
<scroll-view class="drawer-body" :scroll-y="true">
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鍚嶇О:</text></view>
<input class="input-base" v-model="form.name" placeholder="璇疯緭鍏ョ瓑绾у悕绉? />
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鏉冮噸:</text></view>
<input class="input-base" type="number" v-model="form.level" placeholder="绛夌骇鏉冮噸瓒婂ぇ绛夌骇瓒婇珮" />
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鍥炬爣:</text></view>
<view class="upload-placeholder">
<text class="up-ic">+</text>
<text class="up-txt">涓婁紶鍥炬爣</text>
</view>
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">绛夌骇鑳屾櫙鍥?</text></view>
<view class="upload-placeholder bg-up">
<text class="up-ic">+</text>
<text class="up-txt">涓婁紶鑳屾櫙鍥?/text>
</view>
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">浜彈鎶樻墸(%):</text></view>
<input class="input-base" type="number" v-model="form.discount" placeholder="璇疯緭鍏ユ姌鎵紝濡傦細95" />
</view>
<view class="form-item">
<view class="label-box"><text class="required">*</text><text class="label-txt">缁忛獙鍊艰姹?</text></view>
<input class="input-base" type="number" v-model="form.experience" placeholder="璇疯緭鍏ョ粡楠屽€艰姹? />
</view>
<view class="form-item">
<view class="label-box"><text class="label-txt">鏄惁鏄剧ず:</text></view>
<view :class="['switch-box', form.isShow ? 'active' : '']" @click="form.isShow = !form.isShow">
<view class="switch-dot"></view>
</view>
</view>
</scroll-view>
<view class="drawer-footer">
<view class="btn-cancel" @click="closeDrawer"><text class="btn-cancel-txt">鍙栨秷</text></view>
<view class="btn-save" @click="handleSave"><text class="btn-save-txt">鎻愪氦</text></view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
interface LevelItem {
id: number
name: string
level: number
iconBg: string
iconSymbol: string
bgGradient: string
discount: number
experience: number
isShow: boolean
}
const filterName = ref('')
const total = ref(5)
const levelList = ref<LevelItem[]>([
{ id: 1, name: 'V1', level: 1, iconBg: '#fdf6ec', iconSymbol: '馃憫', bgGradient: 'linear-gradient(to bottom right, #f5e6d3, #e8d5bc)', discount: 99, experience: 500, isShow: true },
{ id: 2, name: 'V2', level: 2, iconBg: '#ecf5ff', iconSymbol: '馃拵', bgGradient: 'linear-gradient(to bottom right, #d3e9f5, #bcd9e8)', discount: 97, experience: 1000, isShow: true },
{ id: 3, name: 'V3', level: 3, iconBg: '#f4f4f5', iconSymbol: '猸?, bgGradient: 'linear-gradient(to bottom right, #e3e3e3, #cbcbcb)', discount: 95, experience: 3000, isShow: true },
{ id: 4, name: 'V4', level: 4, iconBg: '#fef0f0', iconSymbol: '馃憫', bgGradient: 'linear-gradient(to bottom right, #f5dfd3, #e8c6bc)', discount: 93, experience: 8000, isShow: true },
{ id: 5, name: 'V5', level: 5, iconBg: '#f0f9eb', iconSymbol: '馃挔', bgGradient: 'linear-gradient(to bottom right, #d3e1f5, #bccce8)', discount: 70, experience: 15000, isShow: true }
])
const showDrawer = ref(false)
const isClosing = ref(false)
const isEdit = ref(false)
const form = reactive({
name: '',
level: 1,
discount: 100,
experience: 0,
isShow: true
})
const handleQuery = () => { console.log('Querying...') }
const handleAdd = () => {
isEdit.value = false
form.name = ''
form.level = levelList.value.length + 1
form.discount = 100
form.experience = 0
form.isShow = true
showDrawer.value = true
isClosing.value = false
}
const handleEdit = (item: LevelItem) => {
isEdit.value = true
form.name = item.name
form.level = item.level
form.discount = item.discount
form.experience = item.experience
form.isShow = item.isShow
showDrawer.value = true
isClosing.value = false
}
const closeDrawer = () => {
isClosing.value = true
setTimeout(() => {
showDrawer.value = false
isClosing.value = false
}, 300)
}
const toggleShow = (index: number) => {
levelList.value[index].isShow = !levelList.value[index].isShow
}
const handleSave = () => {
console.log('Saving...', form)
closeDrawer()
}
</script>
<style scoped lang="scss">
.admin-user-level {
background-color: #f0f2f5;
min-height: 100vh;
padding: 24px;
}
.border-shadow {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.content-body {
display: flex;
flex-direction: column;
gap: 20px;
}
/* 杩囨护鏍?*/
.filter-card {
padding: 24px;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: 24px;
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.label-txt { font-size: 14px; color: #606266; }
.select-mock {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 12px;
cursor: pointer;
}
.select-val { font-size: 14px; color: #c0c4cc; }
.arrow-down { font-size: 10px; color: #c0c4cc; }
.search-input {
width: 200px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.btn-query {
background-color: #2d8cf0;
padding: 0 20px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
cursor: pointer;
}
.query-txt { color: #fff; font-size: 14px; }
/* 琛ㄦ牸鍖哄煙 */
.table-card {
background-color: #fff;
display: flex;
flex-direction: column;
}
.card-header { padding: 20px; }
.btn-primary-blue {
background-color: #2d8cf0;
padding: 8px 16px;
border-radius: 4px;
display: inline-flex;
cursor: pointer;
}
.btn-txt { color: #fff; font-size: 14px; }
.table-container { padding: 0 20px; }
.table-header-row {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 10px;
font-size: 14px;
color: #515a6e;
font-weight: bold;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
}
.td {
padding: 12px 10px;
display: flex;
align-items: center;
}
.td-txt { font-size: 14px; color: #515a6e; }
/* 鍥炬爣鍜岃儗鏅瑙?*/
.icon-circle {
width: 36px;
height: 36px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #eee;
}
.icon-symbol { font-size: 18px; }
.bg-thumb {
width: 60px;
height: 40px;
border-radius: 4px;
border: 1px solid #eee;
}
/* 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: #2d8cf0; }
.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); }
.op-links { display: flex; flex-direction: row; align-items: center; }
.op-link { color: #2d8cf0; font-size: 14px; cursor: pointer; margin: 0 5px; }
.op-split { color: #e8eaec; margin: 0 5px; }
.text-danger { color: #ed4014; }
/* 鍒嗛〉 */
.pagination-footer {
padding: 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.total-txt { font-size: 14px; color: #606266; }
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
.page-btns { display: flex; flex-direction: row; gap: 8px; }
.p-btn {
width: 32px;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
.jump-txt { font-size: 14px; color: #606266; }
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
/* Drawer Styles */
.drawer-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
z-index: 2000;
display: flex;
justify-content: flex-end;
}
.drawer-content {
width: 500px;
height: 100%;
display: flex;
flex-direction: column;
animation: slideIn 0.3s ease-out;
}
.drawer-header {
padding: 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.title-txt { font-size: 16px; font-weight: bold; color: #333; }
.close-btn { font-size: 24px; color: #999; cursor: pointer; }
.drawer-body { flex: 1; padding: 24px; }
.form-item { margin-bottom: 24px; }
.label-box { display: flex; flex-direction: row; align-items: center; margin-bottom: 10px; }
.required { color: #ed4014; margin-right: 4px; }
.label-txt { font-size: 14px; color: #333; }
.input-base {
width: 100%;
height: 40px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 12px;
font-size: 14px;
}
.upload-placeholder {
width: 80px;
height: 80px;
border: 1px dashed #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fbfbfb;
cursor: pointer;
}
.bg-up { width: 150px; height: 80px; }
.up-ic { font-size: 24px; color: #c0c4cc; }
.up-txt { font-size: 12px; color: #c0c4cc; margin-top: 4px; }
.drawer-footer {
padding: 20px;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: flex-end;
gap: 12px;
}
.btn-cancel, .btn-save { padding: 8px 20px; border-radius: 4px; cursor: pointer; }
.btn-cancel { border: 1px solid #dcdfe6; }
.btn-save { background-color: #2d8cf0; }
.btn-cancel-txt { color: #666; font-size: 14px; }
.btn-save-txt { color: #fff; font-size: 14px; }
/* Animations */
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
.slide-out { animation: slideOut 0.3s ease-in forwards; }
@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }
.mask-fade-out { animation: fadeOut 0.3s ease-in forwards; }
@keyframes fadeOut { from { background-color: rgba(0, 0, 0, 0.4); } to { background-color: rgba(0, 0, 0, 0); } }
</style>