Files
X-Financial/src/views/PoliciesView.vue

917 lines
23 KiB
Vue
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>
<section class="knowledge-page">
<div class="knowledge-grid">
<section class="knowledge-main">
<div class="knowledge-metrics panel">
<article v-for="item in metrics" :key="item.label" class="metric-card" :style="{ '--accent': item.accent }">
<span class="metric-icon"><i :class="item.icon"></i></span>
<div>
<p>{{ item.label }} <i v-if="item.help" class="mdi mdi-help-circle-outline"></i></p>
<strong>{{ item.value }}</strong>
<small>{{ item.meta }}</small>
</div>
</article>
</div>
<article class="library-panel panel">
<header class="panel-title">
<h2>文档库 / 文件夹</h2>
</header>
<div class="library-body">
<aside class="folder-rail">
<label class="folder-search">
<i class="mdi mdi-magnify"></i>
<input type="search" placeholder="搜索文件夹" />
<button type="button" aria-label="新增文件夹"><i class="mdi mdi-plus"></i></button>
</label>
<nav class="folder-tree" aria-label="知识库文件夹">
<button v-for="folder in folders" :key="folder.name" type="button" :class="{ active: folder.active }">
<i :class="folder.icon"></i>
<span>{{ folder.name }}</span>
<b>{{ folder.count }}</b>
</button>
</nav>
<button class="new-folder-btn" type="button">
<i class="mdi mdi-plus"></i>
<span>新建文件夹</span>
</button>
</aside>
<section class="document-area">
<div class="upload-zone">
<i class="mdi mdi-cloud-upload"></i>
<strong>拖拽文档到此处或点击上传</strong>
<span>支持 PDF / Word / Excel / PPT 文档单个文件不超过 100MB</span>
</div>
<div class="doc-table-wrap">
<table>
<thead>
<tr>
<th>文件名称</th>
<th>标签</th>
<th>上传时间 <i class="mdi mdi-arrow-down"></i></th>
<th>版本</th>
<th>状态</th>
<th>上传人</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="doc in documents" :key="doc.name">
<td>
<span class="file-name">
<i :class="doc.icon"></i>
{{ doc.name }}
</span>
</td>
<td>
<span class="doc-tag">{{ doc.tag }}</span>
</td>
<td>{{ doc.time }}</td>
<td>{{ doc.version }}</td>
<td><span class="state-tag" :class="doc.stateTone">{{ doc.state }}</span></td>
<td>{{ doc.owner }}</td>
<td><button class="more-btn" type="button" aria-label="更多操作"><i class="mdi mdi-dots-horizontal"></i></button></td>
</tr>
</tbody>
</table>
</div>
<footer class="list-foot">
<span> 18 </span>
<button type="button">10/ <i class="mdi mdi-chevron-down"></i></button>
<div class="pager" aria-label="分页">
<button type="button" aria-label="上一页"><i class="mdi mdi-chevron-left"></i></button>
<button class="active" type="button" aria-current="page">1</button>
<button type="button">2</button>
<button type="button" aria-label="下一页"><i class="mdi mdi-chevron-right"></i></button>
</div>
<label>前往 <input value="1" aria-label="页码" /> </label>
</footer>
</section>
</div>
</article>
</section>
<aside class="analytics-column">
<article class="ops-card panel">
<header class="card-head">
<h2>知识运营概览 <i class="mdi mdi-help-circle-outline"></i></h2>
</header>
<div class="ops-grid">
<div v-for="item in opsMetrics" :key="item.label" class="ops-item" :style="{ '--accent': item.accent }">
<span><i :class="item.icon"></i></span>
<div>
<strong>{{ item.value }}</strong>
<p>{{ item.label }}</p>
<small>{{ item.meta }}</small>
</div>
</div>
</div>
</article>
<article class="top-card panel">
<header class="card-head">
<h2>热门问题 TOP5 <i class="mdi mdi-help-circle-outline"></i></h2>
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
</header>
<ol class="hot-list">
<li v-for="item in hotQuestions" :key="item.title">
<span :class="item.tone">{{ item.rank }}</span>
<strong>{{ item.title }}</strong>
<b>{{ item.count }}</b>
</li>
</ol>
</article>
<article class="feedback-card panel">
<header class="card-head">
<h2>用户点赞最多 <i class="mdi mdi-help-circle-outline"></i></h2>
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
</header>
<ul class="feedback-list positive">
<li v-for="(item, idx) in likedAnswers" :key="item.title">
<span class="rank-badge" :class="idx === 0 ? 'hot' : idx < 3 ? 'warm' : 'normal'">{{ idx + 1 }}</span>
<span>{{ item.title }}</span>
<b><i class="mdi mdi-thumb-up"></i>{{ item.count }}</b>
</li>
</ul>
</article>
<article class="feedback-card panel">
<header class="card-head">
<h2>用户点踩较多 / 待优化 <i class="mdi mdi-help-circle-outline"></i></h2>
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
</header>
<ul class="feedback-list negative">
<li v-for="(item, idx) in dislikedAnswers" :key="item.title">
<span class="rank-badge" :class="idx === 0 ? 'hot' : idx < 3 ? 'warm' : 'normal'">{{ idx + 1 }}</span>
<span>{{ item.title }}</span>
<b><i class="mdi mdi-thumb-down"></i>{{ item.count }}</b>
<em>待优化</em>
</li>
</ul>
</article>
<article class="trend-card panel">
<header class="card-head">
<h2>近7天提问趋势 <i class="mdi mdi-help-circle-outline"></i></h2>
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
</header>
<svg class="trend-chart" viewBox="0 0 420 190" role="img" aria-label="近7天提问趋势">
<line v-for="tick in chartTicks" :key="tick" x1="38" :y1="tick.y" x2="398" :y2="tick.y" />
<text v-for="tick in chartTicks" :key="tick.label" x="8" :y="tick.y + 4">{{ tick.label }}</text>
<polyline :points="trendPoints" />
<circle v-for="point in trendData" :key="point.label" :cx="point.x" :cy="point.y" r="4" />
<text v-for="point in trendData" :key="point.label + '-label'" :x="point.x - 14" y="180">{{ point.label }}</text>
</svg>
</article>
<article class="recent-card panel">
<header class="card-head">
<h2>最近更新知识 <i class="mdi mdi-help-circle-outline"></i></h2>
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
</header>
<ul class="recent-list">
<li v-for="item in recentKnowledge" :key="item.name">
<i :class="item.icon"></i>
<strong>{{ item.name }}</strong>
<time>{{ item.time }}</time>
</li>
</ul>
</article>
</aside>
</div>
</section>
</template>
<script setup>
import { computed } from 'vue'
const metrics = [
{ label: '文档总数', value: '1,248', meta: '较上周 +68', icon: 'mdi mdi-file-document-outline', accent: '#10b981', help: true },
{ label: '文件夹总数', value: '36', meta: '较上周 +2', icon: 'mdi mdi-folder', accent: '#3b82f6' },
{ label: '问答总量', value: '8,562', meta: '较上周 +321', icon: 'mdi mdi-comment-text-multiple-outline', accent: '#8b5cf6' },
{ label: '知识命中率', value: '87.3%', meta: '较上周 +1.2%', icon: 'mdi mdi-bullseye-arrow', accent: '#f59e0b' }
]
const folders = [
{ name: '财务知识库', count: 36, icon: 'mdi mdi-folder', active: false },
{ name: '制度政策', count: 8, icon: 'mdi mdi-folder', active: false },
{ name: '报销制度', count: 12, icon: 'mdi mdi-folder-open', active: false },
{ name: '差旅规范', count: 18, icon: 'mdi mdi-folder', active: true },
{ name: '发票管理', count: 14, icon: 'mdi mdi-folder', active: false },
{ name: '税务合规', count: 16, icon: 'mdi mdi-folder', active: false },
{ name: '预算管理', count: 9, icon: 'mdi mdi-folder', active: false },
{ name: '财务共享', count: 7, icon: 'mdi mdi-folder', active: false },
{ name: '培训资料', count: 6, icon: 'mdi mdi-folder', active: false },
{ name: '常见问答', count: 11, icon: 'mdi mdi-folder', active: false }
]
const documents = [
{ name: '差旅报销管理办法2024版', tag: '差旅 / 制度', time: '2024-05-12 14:35', version: 'v3.2', state: '已生效', stateTone: 'success', owner: '张明', icon: 'mdi mdi-file-document-outline-pdf pdf' },
{ name: '发票查验规范及操作指引', tag: '发票 / 操作', time: '2024-05-10 10:22', version: 'v1.5', state: '已生效', stateTone: 'success', owner: '李娜', icon: 'mdi mdi-file-document-outline-word word' },
{ name: '费用报销标准细则2024', tag: '报销 / 标准', time: '2024-05-08 09:16', version: 'v2.1', state: '已生效', stateTone: 'success', owner: '王磊', icon: 'mdi mdi-file-document-outline-pdf pdf' },
{ name: '差旅费用标准对照表(国内)', tag: '差旅 / 标准', time: '2024-05-05 08:20', version: 'v1.3', state: '审批中', stateTone: 'warning', owner: '陈杰', icon: 'mdi mdi-file-document-outline-excel excel' },
{ name: '借款管理办法及流程', tag: '借款 / 流程', time: '2024-05-03 11:05', version: 'v1.0', state: '已生效', stateTone: 'success', owner: '刘洋', icon: 'mdi mdi-file-document-outline-pdf pdf' }
]
const opsMetrics = [
{ label: '本周新增问题', value: '328', meta: '较上周 +46', icon: 'mdi mdi-help-circle-outline', accent: '#3b82f6' },
{ label: '已解决问题', value: '286', meta: '解决率 87.2%', icon: 'mdi mdi-check', accent: '#10b981' },
{ label: '平均响应时长', value: '2.4h', meta: '较上周 -0.6h', icon: 'mdi mdi-clock-outline', accent: '#3b82f6' },
{ label: '用户满意度', value: '91.2%', meta: '较上周 +2.1%', icon: 'mdi mdi-emoticon-happy-outline', accent: '#10b981' },
{ label: '收藏次数', value: '1,236', meta: '较上周 +128', icon: 'mdi mdi-star-outline', accent: '#f59e0b' },
{ label: '点赞总数', value: '3,582', meta: '较上周 +312', icon: 'mdi mdi-thumb-up-outline', accent: '#f59e0b' }
]
const hotQuestions = [
{ rank: 1, title: '酒店住宿超标怎么办?', count: '1,256', tone: 'hot' },
{ rank: 2, title: '发票抬头不一致如何处理?', count: '1,023', tone: 'warm' },
{ rank: 3, title: '差旅补贴标准是多少?', count: '856', tone: 'warm' },
{ rank: 4, title: '借款申请后如何冲销?', count: '622', tone: 'normal' },
{ rank: 5, title: '预算占用失败怎么处理?', count: '485', tone: 'normal' }
]
const likedAnswers = [
{ title: '差旅报销标准及注意事项汇总', count: 326 },
{ title: '发票查验失败常见原因及处理', count: 285 },
{ title: '报销单据填写规范与示例', count: 241 },
{ title: '借款冲销操作流程详解', count: 198 },
{ title: '预算占用与释放操作指南', count: 176 }
]
const dislikedAnswers = [
{ title: '补贴发放延迟如何处理?', count: 68 },
{ title: '发票跨月入账如何处理?', count: 57 },
{ title: '预填预算申请流程复杂?', count: 43 },
{ title: '费用分类规则不清晰?', count: 38 },
{ title: '固定资产报废流程说明', count: 31 }
]
const trendData = [
{ label: '05-06', value: 220 },
{ label: '05-07', value: 270 },
{ label: '05-08', value: 200 },
{ label: '05-09', value: 250 },
{ label: '05-10', value: 150 },
{ label: '05-11', value: 210 }
].map((item, index) => ({
...item,
x: 48 + index * 66,
y: 164 - (item.value / 320) * 128
}))
const chartTicks = [
{ label: '320', y: 36 },
{ label: '240', y: 68 },
{ label: '160', y: 100 },
{ label: '80', y: 132 },
{ label: '0', y: 164 }
]
const trendPoints = computed(() => trendData.map((point) => `${point.x},${point.y}`).join(' '))
const recentKnowledge = [
{ name: '差旅报销管理办法2024版v3.2', time: '05-12 14:35', icon: 'mdi mdi-file-document-outline-pdf pdf' },
{ name: '发票查验规范及操作指引 v1.5', time: '05-10 10:22', icon: 'mdi mdi-file-document-outline-word word' },
{ name: '费用报销标准细则2024v2.1', time: '05-08 09:16', icon: 'mdi mdi-file-document-outline-pdf pdf' }
]
</script>
<style scoped>
.knowledge-page {
height: 100%;
min-height: 0;
overflow: hidden;
animation: fadeUp 220ms var(--ease) both;
}
.knowledge-grid {
height: 100%;
min-height: 0;
display: grid;
grid-template-columns: minmax(620px, 1.08fr) minmax(560px, .92fr);
gap: 16px;
}
.knowledge-main,
.analytics-column {
min-width: 0;
min-height: 0;
display: grid;
gap: 16px;
}
.knowledge-main {
grid-template-rows: auto minmax(0, 1fr);
}
.analytics-column {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-auto-rows: minmax(0, auto);
overflow-y: auto;
}
.knowledge-metrics {
min-height: 96px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
padding: 0;
overflow: hidden;
}
.metric-card {
min-height: 96px;
display: grid;
grid-template-columns: 54px minmax(0, 1fr);
align-items: center;
gap: 14px;
padding: 16px 20px;
border-right: 1px solid #edf2f7;
}
.metric-card:last-child {
border-right: 0;
}
.metric-icon {
width: 48px;
height: 48px;
display: grid;
place-items: center;
border-radius: 999px;
background: color-mix(in srgb, var(--accent) 14%, white);
color: var(--accent);
font-size: 22px;
}
.metric-card p {
color: #334155;
font-size: 14px;
font-weight: 650;
}
.metric-card p i,
.card-head h2 i {
color: #94a3b8;
font-size: 12px;
}
.metric-card strong {
display: block;
margin-top: 5px;
color: #0f172a;
font-size: 24px;
font-weight: 850;
line-height: 1;
}
.metric-card small {
display: block;
margin-top: 7px;
color: #64748b;
font-size: 13px;
}
.library-panel {
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
padding: 16px 18px;
overflow: hidden;
}
.panel-title h2,
.card-head h2 {
color: #0f172a;
font-size: 19px;
font-weight: 850;
}
.library-body {
min-height: 0;
display: grid;
grid-template-columns: 180px minmax(0, 1fr);
gap: 14px;
margin-top: 16px;
}
.folder-rail {
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
gap: 12px;
border-right: 1px solid #edf2f7;
padding-right: 12px;
}
.folder-search {
height: 36px;
display: grid;
grid-template-columns: 18px minmax(0, 1fr) 24px;
align-items: center;
gap: 6px;
padding: 0 8px;
border: 1px solid #d7e0ea;
border-radius: 8px;
color: #64748b;
}
.folder-search input {
min-width: 0;
border: 0;
color: #0f172a;
font-size: 13px;
}
.folder-search input:focus {
outline: none;
}
.folder-search button {
border: 0;
background: transparent;
color: #64748b;
}
.folder-tree {
min-height: 0;
display: grid;
align-content: start;
gap: 6px;
overflow-y: auto;
}
.folder-tree button {
min-height: 34px;
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
padding: 0 9px;
border: 0;
border-radius: 7px;
background: transparent;
color: #334155;
font-size: 13px;
text-align: left;
}
.folder-tree button.active {
background: #dcfce7;
color: #059669;
font-weight: 850;
}
.folder-tree b {
min-width: 24px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: #f1f5f9;
color: #64748b;
font-size: 11px;
}
.new-folder-btn {
min-height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px solid rgba(16, 185, 129, .28);
border-radius: 8px;
background: #f0fdf4;
color: #059669;
font-size: 13px;
font-weight: 850;
}
.new-folder-btn:hover {
background: #dcfce7;
}
.document-area {
min-width: 0;
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
gap: 12px;
}
.upload-zone {
min-height: 112px;
display: grid;
place-items: center;
align-content: center;
gap: 8px;
border: 1px dashed #93c5fd;
border-radius: 10px;
background: #f8fbff;
color: #334155;
text-align: center;
}
.upload-zone i {
color: #2563eb;
font-size: 31px;
}
.upload-zone strong {
font-size: 13px;
font-weight: 850;
}
.upload-zone span {
color: #64748b;
font-size: 12px;
}
.doc-table-wrap {
min-height: 0;
overflow: auto;
}
table {
width: 100%;
min-width: 690px;
border-collapse: collapse;
}
th,
td {
padding: 12px 10px;
border-bottom: 1px solid #edf2f7;
color: #24324a;
font-size: 12px;
line-height: 1.35;
text-align: left;
vertical-align: middle;
}
th {
background: #f7fafc;
color: #64748b;
font-weight: 800;
}
.file-name {
display: inline-flex;
align-items: center;
gap: 7px;
font-weight: 750;
white-space: nowrap;
}
.file-name .pdf,
.recent-list .pdf { color: #ef4444; }
.file-name .word,
.recent-list .word { color: #2563eb; }
.file-name .excel { color: #10b981; }
.doc-tag {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 7px;
border-radius: 6px;
background: #f1f5f9;
color: #64748b;
font-size: 11px;
font-weight: 750;
}
.state-tag {
min-height: 22px;
display: inline-flex;
align-items: center;
padding: 0 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 800;
white-space: nowrap;
}
.state-tag.success {
background: #dcfce7;
color: #059669;
}
.state-tag.warning {
background: #ffedd5;
color: #f97316;
}
.more-btn {
border: 0;
background: transparent;
color: #2563eb;
}
.list-foot {
display: grid;
grid-template-columns: auto auto 1fr auto;
align-items: center;
gap: 10px;
color: #64748b;
font-size: 13px;
}
.list-foot button {
min-height: 32px;
border: 1px solid #d7e0ea;
border-radius: 8px;
background: #fff;
color: #334155;
font-weight: 750;
}
.pager {
display: inline-flex;
gap: 6px;
}
.pager button {
width: 32px;
padding: 0;
}
.pager button.active {
border-color: #059669;
background: #059669;
color: #fff;
}
.list-foot input {
width: 42px;
height: 30px;
border: 1px solid #d7e0ea;
border-radius: 7px;
text-align: center;
}
.panel {
min-width: 0;
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.card-head button {
border: 0;
background: transparent;
color: #64748b;
font-size: 12px;
font-weight: 850;
}
.ops-card {
grid-column: span 1;
padding: 16px 18px;
}
.ops-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px 12px;
margin-top: 18px;
}
.ops-item {
min-width: 0;
display: grid;
grid-template-columns: 28px minmax(0, 1fr);
gap: 9px;
align-items: start;
}
.ops-item > span {
width: 26px;
height: 26px;
display: grid;
place-items: center;
border-radius: 999px;
background: color-mix(in srgb, var(--accent) 12%, white);
color: var(--accent);
font-size: 16px;
}
.ops-item strong {
color: #0f172a;
font-size: 18px;
font-weight: 900;
}
.ops-item p {
margin-top: 3px;
color: #334155;
font-size: 12px;
font-weight: 750;
}
.ops-item small {
display: block;
margin-top: 8px;
color: #64748b;
font-size: 11px;
}
.top-card,
.feedback-card,
.trend-card,
.recent-card {
padding: 16px 18px;
}
.hot-list,
.feedback-list,
.recent-list {
display: grid;
gap: 13px;
margin: 16px 0 0;
padding: 0;
list-style: none;
}
.hot-list li {
min-height: 32px;
display: grid;
grid-template-columns: 24px minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
}
.hot-list span {
width: 20px;
height: 20px;
display: grid;
place-items: center;
border-radius: 5px;
color: #fff;
font-size: 12px;
font-weight: 900;
}
.hot-list .hot { background: #ef4444; }
.hot-list .warm { background: #f59e0b; }
.hot-list .normal {
background: #f1f5f9;
color: #64748b;
}
.hot-list strong,
.feedback-list span {
min-width: 0;
color: #334155;
font-size: 13px;
font-weight: 800;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.hot-list b {
color: #334155;
font-size: 13px;
font-weight: 850;
}
.feedback-list li {
min-height: 32px;
display: grid;
grid-template-columns: 24px minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
}
.feedback-list.negative li {
grid-template-columns: 24px minmax(0, 1fr) auto auto;
}
.feedback-list b {
display: inline-flex;
align-items: center;
gap: 6px;
color: #059669;
font-size: 13px;
font-weight: 900;
}
.feedback-list .rank-badge {
width: 20px;
height: 20px;
display: grid;
place-items: center;
border-radius: 5px;
color: #fff;
font-size: 12px;
font-weight: 900;
}
.feedback-list .rank-badge.hot { background: #ef4444; }
.feedback-list .rank-badge.warm { background: #f59e0b; }
.feedback-list .rank-badge.normal { background: #f1f5f9; color: #64748b; }
.feedback-list.negative b {
color: #ef4444;
}
.feedback-list em {
padding: 4px 8px;
border-radius: 7px;
background: #fee2e2;
color: #ef4444;
font-size: 12px;
font-style: normal;
font-weight: 850;
}
.trend-chart {
width: 100%;
height: 220px;
margin-top: 12px;
}
.trend-chart line {
stroke: #edf2f7;
}
.trend-chart text {
fill: #64748b;
font-size: 11px;
}
.trend-chart polyline {
fill: none;
stroke: #10b981;
stroke-width: 3;
}
.trend-chart circle {
fill: #fff;
stroke: #10b981;
stroke-width: 3;
}
.recent-list li {
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
}
.recent-list strong {
color: #334155;
font-size: 12px;
font-weight: 850;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.recent-list time {
color: #64748b;
font-size: 12px;
font-weight: 750;
}
@media (max-width: 1380px) {
.knowledge-grid {
grid-template-columns: 1fr;
overflow-y: auto;
}
}
@media (max-width: 980px) {
.knowledge-metrics,
.analytics-column {
grid-template-columns: 1fr;
}
.library-body {
grid-template-columns: 1fr;
}
.folder-rail {
border-right: 0;
border-bottom: 1px solid #edf2f7;
padding: 0 0 12px;
}
.ops-grid {
grid-template-columns: 1fr;
}
}
</style>