464 lines
10 KiB
Plaintext
464 lines
10 KiB
Plaintext
<template>
|
|
<view class="admin-main">
|
|
<view class="label-layout">
|
|
<!-- 左侧标签组 -->
|
|
<view class="label-group-aside">
|
|
<view class="aside-header" @click="openGroupModal()">
|
|
<text class="btn-group-add">+ 添加分组</text>
|
|
</view>
|
|
<scroll-view class="aside-list" :scroll-y="true">
|
|
<view
|
|
v-for="(group, gIndex) in groups"
|
|
:key="gIndex"
|
|
class="group-item"
|
|
:class="activeGroupIndex === gIndex ? 'active' : ''"
|
|
@click="activeGroupIndex = gIndex"
|
|
>
|
|
<view class="group-left">
|
|
<image src="/static/logo.png" class="folder-icon" />
|
|
<text class="group-name">{{ group.name }}</text>
|
|
</view>
|
|
<view class="group-ops">
|
|
<text class="op-more">...</text>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
|
|
<!-- 右侧标签列表 -->
|
|
<view class="label-content-main">
|
|
<view class="table-card-full">
|
|
<view class="table-toolbar">
|
|
<button class="btn-add-label" @click="openLabelDrawer()">添加标签</button>
|
|
</view>
|
|
|
|
<view class="table-header-row">
|
|
<text class="th-cell flex-1">ID</text>
|
|
<text class="th-cell flex-3">标签名称</text>
|
|
<text class="th-cell flex-3">分类名称</text>
|
|
<text class="th-cell flex-2 text-center">状态</text>
|
|
<text class="th-cell flex-2 text-center">移动端展示</text>
|
|
<text class="th-cell flex-2 text-center">操作</text>
|
|
</view>
|
|
|
|
<view class="table-body-scroll">
|
|
<view v-if="filteredLabels.length === 0" class="empty-box">
|
|
<text class="empty-text">该分组下暂无标签</text>
|
|
</view>
|
|
<view v-for="(label, lIndex) in filteredLabels" :key="lIndex" class="table-row-line">
|
|
<text class="td-cell flex-1 color-9">{{ label.id }}</text>
|
|
<view class="td-cell flex-3">
|
|
<text class="label-tag-box">{{ label.name }}</text>
|
|
</view>
|
|
<text class="td-cell flex-3">{{ groups[activeGroupIndex]?.name }}</text>
|
|
<view class="td-cell flex-2 row-center">
|
|
<StatusSwitch v-model="label.status" />
|
|
</view>
|
|
<view class="td-cell flex-2 row-center">
|
|
<StatusSwitch v-model="label.showInMobile" activeText="显示" inactiveText="隐藏" />
|
|
</view>
|
|
<view class="td-cell flex-2 row-center">
|
|
<text class="btn-op-blue" @click="openLabelDrawer(label)">修改</text>
|
|
<view class="v-line"></view>
|
|
<text class="btn-op-red" @click="deleteLabel(label)">删除</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 分页模拟 -->
|
|
<view class="table-pagination">
|
|
<text class="page-total">共 {{ filteredLabels.length }} 条</text>
|
|
<view class="page-size-selector">
|
|
<text>15条/页</text>
|
|
<text class="arrow-down">v</text>
|
|
</view>
|
|
<view class="page-numbers">
|
|
<text class="page-btn active">1</text>
|
|
</view>
|
|
<view class="page-jump">
|
|
<text>前往</text>
|
|
<input class="jump-input" :value="'1'" />
|
|
<text>页</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 标签抽屉 (右侧展示) -->
|
|
<view class="drawer-mask" v-if="showDrawerMask" @click="closeLabelDrawer">
|
|
<view class="drawer-content" @click.stop="" :class="{ 'drawer-show': showDrawer }">
|
|
<view class="drawer-header">
|
|
<text class="drawer-title">添加标签</text>
|
|
<text class="drawer-close" @click="closeLabelDrawer"></text>
|
|
</view>
|
|
<view class="drawer-body">
|
|
<view class="form-item">
|
|
<text class="form-label">标签名称:</text>
|
|
<input class="drawer-input" v-model="labelForm.name" placeholder="请输入标签名称" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">所属分组:</text>
|
|
<text class="form-value">{{ groups[activeGroupIndex]?.name }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="drawer-footer">
|
|
<button class="btn-footer-cancel" @click="closeLabelDrawer">取消</button>
|
|
<button class="btn-footer-submit" @click="closeLabelDrawer">确定</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import { ref, reactive, computed } from 'vue'
|
|
import StatusSwitch from '@/components/StatusSwitch.uvue'
|
|
|
|
interface Label {
|
|
id: number;
|
|
name: string;
|
|
groupId: number;
|
|
status: boolean;
|
|
showInMobile: boolean;
|
|
}
|
|
|
|
interface Group {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
const groups = reactive<Group[]>([
|
|
{ id: 0, name: '全部' } as Group,
|
|
{ id: 1, name: '商务礼品专题' } as Group,
|
|
{ id: 2, name: '员工福利' } as Group,
|
|
{ id: 3, name: '主题' } as Group
|
|
])
|
|
|
|
const labels = reactive<Label[]>([
|
|
{ id: 1, name: '外事礼品', groupId: 1, status: true, showInMobile: true } as Label,
|
|
{ id: 2, name: '会议庆典', groupId: 1, status: true, showInMobile: true } as Label,
|
|
{ id: 3, name: '入职纪念', groupId: 2, status: true, showInMobile: true } as Label,
|
|
{ id: 4, name: '员工激励', groupId: 2, status: true, showInMobile: true } as Label,
|
|
{ id: 5, name: '员工生日', groupId: 2, status: true, showInMobile: true } as Label,
|
|
{ id: 6, name: '三八妇女节', groupId: 3, status: true, showInMobile: true } as Label,
|
|
{ id: 7, name: '新春快乐', groupId: 3, status: true, showInMobile: true } as Label
|
|
])
|
|
|
|
const activeGroupIndex = ref(0)
|
|
|
|
const filteredLabels = computed((): Label[] => {
|
|
const activeGroup = groups[activeGroupIndex.value]
|
|
if (activeGroupIndex.value === 0) return labels
|
|
return labels.filter((l: Label): boolean => l.groupId === activeGroup.id)
|
|
})
|
|
|
|
// Drawer logic
|
|
const showDrawerMask = ref(false)
|
|
const showDrawer = ref(false)
|
|
const labelForm = reactive({ name: '' })
|
|
|
|
function openLabelDrawer(label: Label | null = null) {
|
|
if (label) { labelForm.name = label.name } else { labelForm.name = '' }
|
|
showDrawerMask.value = true
|
|
setTimeout(() => {
|
|
showDrawer.value = true
|
|
}, 50)
|
|
}
|
|
|
|
function closeLabelDrawer() {
|
|
showDrawer.value = false
|
|
setTimeout(() => {
|
|
showDrawerMask.value = false
|
|
}, 300)
|
|
}
|
|
|
|
function openGroupModal() {
|
|
uni.showToast({ title: '添加分组功能已模拟', icon: 'none' })
|
|
}
|
|
|
|
function deleteLabel(label: Label) {
|
|
const idx = labels.indexOf(label)
|
|
if (idx > -1) { labels.splice(idx, 1) }
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.admin-main {
|
|
padding: 0;
|
|
background-color: transparent;
|
|
min-height: auto;
|
|
}
|
|
|
|
.label-layout {
|
|
display: flex;
|
|
flex-direction: row;
|
|
height: 100%;
|
|
gap: 20px;
|
|
}
|
|
|
|
/* 左侧 */
|
|
.label-group-aside {
|
|
width: 200px;
|
|
background-color: #fff;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.aside-header {
|
|
padding: 16px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn-group-add { font-size: 14px; color: #999; }
|
|
|
|
.aside-list { flex: 1; }
|
|
|
|
.group-item {
|
|
padding: 12px 16px;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.group-item.active {
|
|
background-color: #e6f7ff;
|
|
color: #1890ff;
|
|
border-left-color: #1890ff;
|
|
}
|
|
|
|
.group-left {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.folder-icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.group-name { font-size: 14px; }
|
|
.op-more { color: #ccc; }
|
|
|
|
/* 右侧 */
|
|
.label-content-main {
|
|
flex: 1;
|
|
background-color: #fff;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.table-card-full {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
padding: 24px;
|
|
}
|
|
|
|
.table-toolbar { margin-bottom: 20px; }
|
|
|
|
.btn-add-label {
|
|
background-color: #1890ff;
|
|
color: #fff;
|
|
height: 32px;
|
|
line-height: 32px;
|
|
padding: 0 16px;
|
|
font-size: 14px;
|
|
border-radius: 4px;
|
|
border: none;
|
|
margin: 0;
|
|
}
|
|
|
|
.table-header-row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
background-color: #fafafa;
|
|
height: 44px;
|
|
align-items: center;
|
|
border: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.th-cell { font-size: 14px; font-weight: bold; padding: 0 12px; }
|
|
|
|
.table-body-scroll { flex: 1; overflow-y: auto; }
|
|
|
|
.table-row-line {
|
|
display: flex;
|
|
flex-direction: row;
|
|
min-height: 54px;
|
|
align-items: center;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
border-left: 1px solid #f0f0f0;
|
|
border-right: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.td-cell { padding: 0 12px; font-size: 14px; color: #666; }
|
|
.color-9 { color: #999; }
|
|
|
|
.label-tag-box {
|
|
background-color: #f0f9eb;
|
|
color: #67c23a;
|
|
padding: 4px 10px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.btn-op-blue { color: #1890ff; font-size: 14px; cursor: pointer; }
|
|
.btn-op-red { color: #ff4d4f; font-size: 14px; cursor: pointer; }
|
|
.v-line { width: 1px; height: 12px; background-color: #eee; margin: 0 10px; }
|
|
|
|
/* 分页 */
|
|
.table-pagination {
|
|
padding-top: 20px;
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.page-total { font-size: 13px; color: #666; margin-right: 12px; }
|
|
|
|
.page-size-selector {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
border: 1px solid #dcdfe6;
|
|
padding: 0 8px;
|
|
height: 28px;
|
|
border-radius: 4px;
|
|
margin-right: 12px;
|
|
}
|
|
|
|
.page-size-selector text { font-size: 12px; }
|
|
.arrow-down { margin-left: 5px; color: #999; }
|
|
|
|
.page-numbers { display: flex; flex-direction: row; margin-right: 12px; }
|
|
|
|
.page-btn {
|
|
width: 28px;
|
|
height: 28px;
|
|
line-height: 28px;
|
|
text-align: center;
|
|
border: 1px solid #dcdfe6;
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
margin: 0 2px;
|
|
}
|
|
|
|
.page-btn.active { background-color: #1890ff; color: #fff; border-color: #1890ff; }
|
|
|
|
.page-jump {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
font-size: 13px;
|
|
color: #666;
|
|
}
|
|
|
|
.jump-input {
|
|
width: 40px;
|
|
height: 28px;
|
|
border: 1px solid #dcdfe6;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
margin: 0 8px;
|
|
}
|
|
|
|
/* Drawer styles */
|
|
.drawer-mask {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 1000;
|
|
background-color: rgba(0,0,0,0.5);
|
|
}
|
|
|
|
.drawer-content {
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 50%;
|
|
background-color: #fff;
|
|
transform: translateX(100%);
|
|
transition: transform 0.3s ease-in-out;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.drawer-show {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.drawer-header {
|
|
padding: 20px 24px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.drawer-title { font-size: 16px; font-weight: bold; }
|
|
.drawer-close { font-size: 20px; color: #999; cursor: pointer; }
|
|
|
|
.drawer-body { flex: 1; padding: 24px; }
|
|
|
|
.form-item {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.form-label { width: 100px; font-size: 14px; color: #666; }
|
|
.form-value { font-size: 14px; color: #333; }
|
|
|
|
.drawer-input {
|
|
flex: 1;
|
|
height: 36px;
|
|
border: 1px solid #dcdfe6;
|
|
border-radius: 4px;
|
|
padding: 0 12px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.drawer-footer {
|
|
padding: 16px 24px;
|
|
border-top: 1px solid #f0f0f0;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.btn-footer-cancel, .btn-footer-submit {
|
|
height: 32px;
|
|
line-height: 32px;
|
|
padding: 0 20px;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
margin-left: 12px;
|
|
}
|
|
|
|
.btn-footer-cancel { background-color: #fff; border: 1px solid #dcdfe6; color: #666; }
|
|
.btn-footer-submit { background-color: #1890ff; color: #fff; border: none; }
|
|
|
|
.empty-box { padding: 40px 0; text-align: center; }
|
|
.empty-text { font-size: 13px; color: #999; }
|
|
|
|
.row-center { display: flex; flex-direction: row; justify-content: center; align-items: center; }
|
|
.flex-1 { flex: 1; }
|
|
.flex-2 { flex: 2; }
|
|
.flex-3 { flex: 3; }
|
|
</style>
|