441 lines
11 KiB
Plaintext
441 lines
11 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>
|
|
|
|
<!-- 分页 -->
|
|
<CommonPagination
|
|
v-if="filteredLabels.length > 0"
|
|
:total="filteredLabels.length"
|
|
:loading="false"
|
|
:currentPage="currentPage"
|
|
:pageSize="pageSize"
|
|
:pageSizeOptionLabels="pageSizeOptionLabels"
|
|
:pageSizeIndex="pageSizeIndex"
|
|
:visiblePages="visiblePages"
|
|
:totalPage="totalPage"
|
|
:jumpPageInput="jumpPageInput"
|
|
@page-size-change="handlePageSizeChange"
|
|
@page-change="handlePageChange"
|
|
@update:jumpPageInput="(val : string) => { jumpPageInput = val }"
|
|
@jump-page="handleJumpPage"
|
|
/>
|
|
</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'
|
|
import CommonPagination from '@/components/CommonPagination/CommonPagination.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) }
|
|
}
|
|
|
|
// 分页适配状态
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(15)
|
|
let jumpPageInput = ''
|
|
const pageSizeOptions = [10, 15, 20, 30, 50]
|
|
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
|
|
const pageSizeIndex = computed(() => {
|
|
const idx = pageSizeOptions.indexOf(pageSize.value)
|
|
return idx >= 0 ? idx : 0
|
|
})
|
|
const totalPage = computed(() => Math.max(1, Math.ceil(filteredLabels.value.length / pageSize.value)))
|
|
const visiblePages = computed(() => {
|
|
const total = totalPage.value
|
|
const cur = currentPage.value
|
|
if (total <= 7) return Array.from({ length: total }, (_: any, i: number) => i + 1)
|
|
if (cur <= 4) return [1, 2, 3, 4, 5, -1, total]
|
|
if (cur >= total - 3) return [1, -1, total - 4, total - 3, total - 2, total - 1, total]
|
|
return [1, -1, cur - 1, cur, cur + 1, -1, total]
|
|
})
|
|
const handlePageChange = (p: number) => { currentPage.value = p }
|
|
const handlePageSizeChange = (e: any) => {
|
|
const idx = Number(e.detail.value)
|
|
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
|
|
currentPage.value = 1
|
|
}
|
|
const handleJumpPage = () => {
|
|
const p = parseInt(jumpPageInput)
|
|
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
|
|
}
|
|
</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; }
|
|
|
|
/* 分页区域已迁至 CommonPagination 组件 */
|
|
|
|
/* 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>
|