Files
medical-mall/pages/mall/admin/cms/article/list.uvue
2026-02-03 21:35:57 +08:00

383 lines
19 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-cms-article">
<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">{{ filterCategory }}</text>
<text class="arrow-down">▼</text>
</view>
</view>
<view class="filter-item">
<text class="label-txt">文章搜索:</text>
<input class="search-input" placeholder="请输入" v-model="filterKeyword" />
</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" @click="handleAdd">
<text class="btn-txt">添加文章</text>
</view>
</view>
<!-- 数据表格 -->
<view class="table-header">
<view class="th col-id"><text class="th-txt">ID</text></view>
<view class="th col-img"><text class="th-txt">文章图片</text></view>
<view class="th col-name"><text class="th-txt">文章名称</text></view>
<view class="th col-link"><text class="th-txt">关联商品</text></view>
<view class="th col-v"><text class="th-txt">浏览量</text></view>
<view class="th col-time"><text class="th-txt">时间</text></view>
<view class="th col-op"><text class="th-txt">操作</text></view>
</view>
<view class="table-body">
<view class="table-row" v-for="item in articleList" :key="item.id">
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
<view class="td col-img">
<view class="img-box"><text class="img-placeholder">🖼️</text></view>
</view>
<view class="td col-name"><text class="td-txt">{{ item.name }}</text></view>
<view class="td col-link"><text class="td-txt">{{ item.linkedProduct }}</text></view>
<view class="td col-v"><text class="td-txt">{{ item.views }}</text></view>
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
<view class="td col-op">
<view class="op-links">
<text class="link-txt" @click="handleEdit(item)">编辑</text>
<view class="divider"></view>
<text class="link-txt">关联</text>
<view class="divider"></view>
<text class="link-txt danger">删除</text>
<view class="divider"></view>
<text class="link-txt">复制链接</text>
<text class="arrow-small">▼</text>
</view>
</view>
</view>
</view>
<view class="pagination-bar">
<view class="page-info">
<text class="page-total">共 {{ articleList.length }} 条</text>
</view>
</view>
</view>
</view>
<!-- 侧边弹窗 (Drawer) -->
<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">
<view class="tab-item active">
<text class="tab-txt">文章信息</text>
<view class="tab-line"></view>
</view>
<text class="close-btn" @click="closeDrawer">×</text>
</view>
<scroll-view class="drawer-body" :scroll-y="true">
<!-- 文章信息区块 -->
<view class="section-title">
<view class="title-inner active">
<text class="title-txt">文章信息</text>
<view class="title-line"></view>
</view>
</view>
<view class="form-grid">
<view class="form-row">
<view class="form-col">
<view class="label-box"><text class="required">*</text><text class="label-txt">标题:</text></view>
<view class="input-box">
<input class="input-base" v-model="formTitle" placeholder="请输入" />
<text class="input-count">{{ formTitle.length }}/80</text>
</view>
</view>
<view class="form-col">
<view class="label-box"><text class="label-txt">作者:</text></view>
<view class="input-box">
<input class="input-base" v-model="formAuthor" placeholder="请输入" />
<text class="input-count">{{ formAuthor.length }}/10</text>
</view>
</view>
</view>
<view class="form-row mt-20">
<view class="form-col">
<view class="label-box"><text class="required">*</text><text class="label-txt">文章分类:</text></view>
<view class="input-box">
<view class="select-mock">
<text class="select-val">{{ formCategory || '请选择' }}</text>
<text class="arrow-down">▼</text>
</view>
</view>
</view>
<view class="form-col">
<view class="label-box"><text class="label-txt">文章简介:</text></view>
<view class="input-box">
<textarea class="textarea-base" v-model="formIntro" placeholder="请输入" />
<text class="input-count">{{ formIntro.length }}/300</text>
</view>
</view>
</view>
<view class="form-row mt-20">
<view class="form-col full">
<view class="label-box"><text class="required">*</text><text class="label-txt">图文封面:</text></view>
<view class="upload-container">
<view class="upload-btn">
<text class="plus-icon">+</text>
</view>
<text class="tip-txt mt-10">建议尺寸500 x 312 px</text>
</view>
</view>
</view>
</view>
<!-- 文章内容区块 -->
<view class="section-title mt-40">
<view class="title-inner active">
<text class="title-txt">文章内容</text>
<view class="title-line"></view>
</view>
</view>
<view class="editor-section">
<view class="label-box mb-10"><text class="required">*</text><text class="label-txt">文章内容:</text></view>
<view class="rich-editor-mock">
<view class="editor-toolbar">
<text class="tool-ic">HTML</text>
<text class="tool-ic">H</text>
<text class="tool-ic">B</text>
<text class="tool-ic">T↕</text>
<text class="tool-ic">F</text>
<text class="tool-ic">I</text>
<text class="tool-ic">U</text>
<text class="tool-ic">S</text>
<text class="tool-ic-img">🖼️</text>
<text class="tool-ic-img">🎬</text>
</view>
<view class="editor-content"></view>
</view>
</view>
<!-- 其他设置 -->
<view class="section-title mt-40">
<view class="title-inner active">
<text class="title-txt">其他设置</text>
<view class="title-line"></view>
</view>
</view>
<view class="settings-section">
<view class="form-item">
<view class="label-box-wide"><text class="label-txt">banner显示:</text></view>
<view class="radio-group">
<view class="radio-item" @click="formBanner = true">
<view :class="['radio-circle', formBanner ? 'checked' : '']"><view v-if="formBanner" class="radio-in"></view></view>
<text class="radio-la">显示</text>
</view>
<view class="radio-item" @click="formBanner = false">
<view :class="['radio-circle', !formBanner ? 'checked' : '']"><view v-if="!formBanner" class="radio-in"></view></view>
<text class="radio-la">不显示</text>
</view>
</view>
</view>
<view class="form-item mt-20">
<view class="label-box-wide"><text class="label-txt">热门文章:</text></view>
<view class="radio-group">
<view class="radio-item" @click="formHot = true">
<view :class="['radio-circle', formHot ? 'checked' : '']"><view v-if="formHot" class="radio-in"></view></view>
<text class="radio-la">显示</text>
</view>
<view class="radio-item" @click="formHot = false">
<view :class="['radio-circle', !formHot ? 'checked' : '']"><view v-if="!formHot" class="radio-in"></view></view>
<text class="radio-la">不显示</text>
</view>
</view>
</view>
</view>
<view class="submit-container mt-40">
<view class="btn-submit" @click="handleConfirm">
<text class="submit-txt">提交</text>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const filterCategory = ref('全部')
const filterKeyword = ref('')
const articleList = ref([
{ id: '240', name: '赋能消费 | 卷狗优选迈向文化消费新时代', linkedProduct: '', views: '3349', time: '2025-04-01 16:34' },
{ id: '237', name: '把重要的日子放在桌面', linkedProduct: '2024新款吹风机...', views: '260', time: '2025-04-01 16:32' }
])
const showDrawer = ref(false)
const isClosing = ref(false)
const formTitle = ref('')
const formAuthor = ref('')
const formCategory = ref('')
const formIntro = ref('')
const formBanner = ref(false)
const formHot = ref(false)
const handleAdd = () => {
formTitle.value = ''
formAuthor.value = ''
formCategory.value = ''
formIntro.value = ''
formBanner.value = false
formHot.value = false
isClosing.value = false
showDrawer.value = true
}
const handleEdit = (item: any) => {
formTitle.value = item.name
formAuthor.value = '管理员'
formCategory.value = '全部'
formIntro.value = '这是一段文章简介...'
formBanner.value = false
formHot.value = true
isClosing.value = false
showDrawer.value = true
}
const closeDrawer = () => {
isClosing.value = true
setTimeout(() => {
showDrawer.value = false
isClosing.value = false
}, 300)
}
const handleConfirm = () => { closeDrawer() }
const handleQuery = () => { console.log('Querying...') }
</script>
<style scoped lang="scss">
.admin-cms-article { padding: 20px; background-color: #f5f7fa; min-height: 100vh; }
.border-shadow { background-color: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); }
.filter-card { padding: 20px; display: flex; flex-direction: row; align-items: center; gap: 30px; margin-bottom: 20px; }
.filter-item { display: flex; flex-direction: row; align-items: center; }
.label-txt { font-size: 14px; color: #606266; margin-right: 12px; }
.select-mock { width: 220px; height: 38px; border: 1px solid #dcdfe6; border-radius: 4px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 0 15px; }
.select-val { font-size: 13px; color: #333; }
.arrow-down { font-size: 10px; color: #999; }
.search-input { width: 220px; height: 38px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 15px; font-size: 13px; }
.btn-query { width: 76px; height: 34px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
.query-txt { color: #fff; font-size: 14px; }
.table-card { display: flex; flex-direction: column; }
.card-header { padding: 20px; }
.btn-primary { width: 100px; height: 36px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
.btn-txt { color: #fff; font-size: 13px; }
.table-header { height: 50px; background-color: #eaf2ff; display: flex; flex-direction: row; align-items: center; }
.th { padding: 0 15px; }
.th-txt { font-size: 13px; color: #606266; font-weight: bold; }
.table-row { height: 80px; display: flex; flex-direction: row; align-items: center; border-bottom: 1px solid #f0f0f0; }
.td { padding: 0 15px; }
.td-txt { font-size: 13px; color: #606266; }
.img-box { width: 60px; height: 40px; background-color: #f5f7fa; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
.img-placeholder { font-size: 20px; }
.col-id { width: 80px; justify-content: center; }
.col-img { width: 100px; }
.col-name { flex: 2; }
.col-link { flex: 2; }
.col-v { width: 100px; justify-content: center; }
.col-time { width: 180px; justify-content: center; }
.col-op { width: 220px; }
.op-links { display: flex; flex-direction: row; align-items: center; }
.link-txt { font-size: 13px; color: #2d8cf0; cursor: pointer; }
.danger { color: #ed4014; }
.divider { width: 1px; height: 12px; background-color: #e8eaec; margin: 0 8px; }
.arrow-small { font-size: 8px; color: #2d8cf0; margin-left: 2px; }
.pagination-bar { padding: 20px; display: flex; flex-direction: row; align-items: center; justify-content: flex-end; }
.page-info { display: flex; flex-direction: row; align-items: center; }
.page-total { font-size: 13px; color: #606266; margin-right: 15px; }
.drawer-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.4); z-index: 2000; transition: opacity 0.3s; }
.mask-fade-out { opacity: 0; }
.drawer-content { position: absolute; top: 0; right: 0; width: 50%; height: 100%; background-color: #fff; display: flex; flex-direction: column; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease; }
.slide-out { animation: slideOut 0.3s ease forwards; }
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }
.drawer-header { height: 54px; padding: 0 20px; border-bottom: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
.tab-item { height: 100%; display: flex; align-items: center; position: relative; }
.tab-txt { font-size: 15px; color: #2d8cf0; font-weight: 500; }
.tab-line { position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background-color: #2d8cf0; }
.close-btn { font-size: 24px; color: #999; cursor: pointer; }
.drawer-body { flex: 1; padding: 24px; background-color: #fff; }
.section-title { height: 44px; border-bottom: 1px solid #f0f0f0; margin-bottom: 24px; display: flex; flex-direction: row; }
.title-inner { height: 100%; display: flex; align-items: center; position: relative; padding: 0 10px; }
.title-txt { font-size: 15px; color: #333; font-weight: 500; }
.title-inner.active .title-txt { color: #2d8cf0; }
.title-line { position: absolute; bottom: -1px; left: 0; right: 0; height: 2px; background-color: #2d8cf0; display: none; }
.title-inner.active .title-line { display: block; }
.form-grid { display: flex; flex-direction: column; }
.form-row { display: flex; flex-direction: row; gap: 30px; }
.form-col { flex: 1; display: flex; flex-direction: row; align-items: flex-start; }
.form-col.full { flex: none; width: 100%; }
.label-box { width: 100px; display: flex; flex-direction: row; justify-content: flex-end; margin-right: 15px; padding-top: 8px; flex-shrink: 0; }
.label-box-wide { width: 120px; display: flex; flex-direction: row; justify-content: flex-end; margin-right: 15px; flex-shrink: 0; }
.required { color: #ed4014; margin-right: 4px; }
.label-txt { font-size: 14px; color: #606266; }
.input-box { flex: 1; position: relative; }
.input-base { width: 100%; height: 38px; border: 1px solid #dcdee2; border-radius: 4px; padding: 0 45px 0 12px; font-size: 14px; }
.textarea-base { width: 100%; height: 100px; border: 1px solid #dcdee2; border-radius: 4px; padding: 10px 12px; font-size: 14px; }
.input-count { position: absolute; bottom: 8px; right: 12px; font-size: 12px; color: #999; }
.upload-container { display: flex; flex-direction: column; }
.upload-btn { width: 80px; height: 80px; border: 1px dashed #dcdee2; border-radius: 4px; display: flex; align-items: center; justify-content: center; background-color: #f8f8f9; }
.plus-icon { font-size: 30px; color: #999; }
.tip-txt { font-size: 12px; color: #999; }
.editor-section { display: flex; flex-direction: column; }
.rich-editor-mock { border: 1px solid #dcdee2; border-radius: 4px; min-height: 400px; display: flex; flex-direction: column; }
.editor-toolbar { height: 44px; background-color: #fafafa; border-bottom: 1px solid #dcdee2; display: flex; flex-direction: row; align-items: center; padding: 0 15px; gap: 20px; flex-wrap: wrap; }
.tool-ic { font-size: 14px; color: #515a6e; cursor: pointer; }
.tool-ic-img { font-size: 18px; cursor: pointer; }
.editor-content { flex: 1; background-color: #fff; }
.settings-section { display: flex; flex-direction: column; }
.form-item { display: flex; flex-direction: row; align-items: center; }
.radio-group { display: flex; flex-direction: row; gap: 30px; }
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
.radio-circle { width: 16px; height: 16px; border: 1px solid #dcdee2; border-radius: 50%; margin-right: 8px; display: flex; align-items: center; justify-content: center; }
.radio-circle.checked { border-color: #2d8cf0; }
.radio-in { width: 8px; height: 8px; background-color: #2d8cf0; border-radius: 50%; }
.radio-la { font-size: 14px; color: #606266; }
.submit-container { display: flex; justify-content: flex-start; padding-left: 135px; padding-bottom: 50px; }
.btn-submit { width: 120px; height: 40px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.submit-txt { color: #fff; font-size: 14px; }
.drawer-footer { height: 60px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: flex-end; align-items: center; padding: 0 24px; background-color: #fff; }
.btn-cancel { padding: 8px 20px; border: 1px solid #dcdee2; border-radius: 4px; margin-right: 15px; }
.btn-confirm { padding: 8px 20px; background-color: #2d8cf0; border-radius: 4px; }
.cancel-txt { font-size: 14px; color: #606266; }
.confirm-txt { font-size: 14px; color: #fff; }
.mt-20 { margin-top: 20px; }
.mt-40 { margin-top: 40px; }
.mt-10 { margin-top: 10px; }
.mb-10 { margin-bottom: 10px; }
</style>