refactor(admin): 提取公共样式并重构评论编辑弹窗组件

- 将页面、卡片、弹窗等通用样式从各组件样式文件移至 common.less
- 将评论编辑弹窗逻辑抽取为独立组件 ModalEdit.vue
- 统一卡片标题结构,使用 card-title-row 包装
- 清理各组件样式文件中的重复样式定义
This commit is contained in:
anghunk
2026-02-03 14:57:38 +08:00
parent 4783a8d0eb
commit a22e1ff5a8
10 changed files with 322 additions and 385 deletions

View File

@@ -39,4 +39,111 @@ button {
opacity: 1;
transform: translate(-50%, 0);
}
}
.page {
display: flex;
flex-direction: column;
gap: 12px;
}
.page-title {
margin: 0;
font-size: 18px;
color: var(--text-primary);
}
.page-hint {
font-size: 14px;
color: var(--text-secondary);
}
.page-error {
font-size: 14px;
color: var(--color-danger);
}
.card {
background-color: var(--bg-card);
border-radius: 6px;
border: 1px solid var(--border-color);
padding: 12px 18px;
margin-bottom: 1em;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
transition: background-color 0.2s;
}
.card-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.card-icon {
font-size: 12px;
color: var(--text-secondary);
transition: transform 0.2s ease;
}
.card-icon.expanded {
transform: rotate(180deg);
}
.card-subtitle {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
}
.card-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-end;
}
.card-button {
padding: 8px 14px;
border-radius: 4px;
border: none;
background-color: var(--primary-color);
color: var(--text-inverse);
font-size: 14px;
cursor: pointer;
min-width: 100px;
}
.card-button.secondary {
background-color: var(--bg-sider);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.card-button.secondary:hover {
background-color: var(--bg-hover);
border-color: var(--border-color);
}
.card-button:disabled {
opacity: 0.7;
cursor: default;
}
.card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}

View File

@@ -1,15 +1,3 @@
.page {
display: flex;
flex-direction: column;
gap: 12px;
}
.page-title {
margin: 0;
font-size: 18px;
color: var(--text-primary);
}
.toolbar {
display: flex;
justify-content: flex-start;
@@ -33,37 +21,6 @@
color: var(--text-primary);
}
.card {
background-color: var(--bg-card);
border-radius: 6px;
border: 1px solid var(--border-color);
padding: 16px 18px;
}
.card-title {
margin: 0 0 12px;
font-size: 16px;
color: var(--text-primary);
}
.card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.page-hint {
font-size: 14px;
color: var(--text-secondary);
}
.page-error {
font-size: 14px;
color: var(--color-danger);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));

View File

@@ -1,15 +1,3 @@
.page {
display: flex;
flex-direction: column;
gap: 12px;
}
.page-title {
margin: 0;
font-size: 18px;
color: var(--text-primary);
}
.toolbar {
display: flex;
justify-content: space-between;
@@ -49,16 +37,6 @@
min-width: 70px;
}
.page-hint {
font-size: 14px;
color: var(--text-secondary);
}
.page-error {
font-size: 14px;
color: var(--color-danger);
}
.comment-table {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
@@ -386,103 +364,6 @@
text-decoration: underline;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: stretch;
justify-content: flex-end;
z-index: 2000;
}
.modal {
background-color: var(--bg-card);
border-radius: 0;
box-shadow: var(--shadow-card);
width: 100%;
max-width: 600px;
padding: 20px 20px 24px;
display: flex;
flex-direction: column;
gap: 16px;
transform: translateX(0);
animation: drawer-in 0.2s ease-out;
overflow-y: auto;
}
.modal-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.modal-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 4px;
}
.modal-btn {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
border: 1px solid transparent;
}
.modal-btn.primary {
background-color: var(--primary-color);
color: var(--text-inverse);
}
.modal-btn.secondary {
background-color: var(--bg-sider);
border-color: var(--border-color);
color: var(--text-primary);
}
.modal-btn:hover {
opacity: 0.9;
}
.form-item {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-label {
font-size: 13px;
color: var(--text-secondary);
}
.form-input {
padding: 8px 10px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--bg-input);
color: var(--text-primary);
font-size: 13px;
outline: none;
}
.form-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 1px rgba(9, 105, 218, 0.2);
}
@keyframes drawer-in {
from {
transform: translateX(100%);

View File

@@ -1,37 +1,5 @@
.page {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 600px;
/* Increased width slightly */
}
.page-title {
margin: 0;
font-size: 18px;
color: var(--text-primary);
}
.card {
background-color: var(--bg-card);
border-radius: 6px;
border: 1px solid var(--border-color);
padding: 16px 18px;
margin-bottom: 0.5em;
/* Reduced spacing */
}
.card-title {
margin: 0 0 8px;
font-size: 15px;
color: var(--text-primary);
font-weight: 600;
}
.card-desc {
font-size: 13px;
color: var(--text-secondary);
margin: 0 0 16px;
max-width: 620px;
}
.action-row {
@@ -47,6 +15,11 @@
min-width: 40px;
}
.card-desc {
font-size: 14px;
color: var(--text-secondary);
}
.card-button {
padding: 6px 14px;
border-radius: 4px;

View File

@@ -1,14 +1,5 @@
.page {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 620px
}
.page-title {
margin: 0;
font-size: 18px;
color: var(--text-primary);
max-width: 620px;
}
.settings-tabs {
@@ -44,45 +35,6 @@
color: var(--text-inverse);
}
.card {
background-color: var(--bg-card);
border-radius: 6px;
border: 1px solid var(--border-color);
padding: 12px 18px;
margin-bottom: 1em;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
transition: background-color 0.2s;
}
.card-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.card-icon {
font-size: 12px;
color: var(--text-secondary);
transition: transform 0.2s ease;
}
.card-icon.expanded {
transform: rotate(180deg);
}
.card-subtitle {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
}
.divider {
height: 1px;
@@ -241,39 +193,6 @@
transform: translateX(16px);
}
.card-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-end;
}
.card-button {
padding: 8px 14px;
border-radius: 4px;
border: none;
background-color: var(--primary-color);
color: var(--text-inverse);
font-size: 14px;
cursor: pointer;
min-width: 100px;
}
.card-button.secondary {
background-color: var(--bg-sider);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.card-button.secondary:hover {
background-color: var(--bg-hover);
border-color: var(--border-color);
}
.card-button:disabled {
opacity: 0.7;
cursor: default;
}
.form-message {
font-size: 13px;
@@ -321,11 +240,6 @@
}
}
.page-hint {
font-size: 14px;
color: var(--text-secondary);
}
.form-hint {
font-size: 12px;
color: var(--text-secondary);

View File

@@ -1,15 +1,3 @@
.page {
display: flex;
flex-direction: column;
gap: 12px;
}
.page-title {
margin: 0;
font-size: 18px;
color: var(--text-primary);
}
.toolbar {
display: flex;
justify-content: flex-start;
@@ -33,35 +21,9 @@
color: var(--text-primary);
}
.card {
background-color: var(--bg-card);
border-radius: 6px;
border: 1px solid var(--border-color);
padding: 16px 18px;
}
.card-title {
margin: 0 0 12px;
font-size: 16px;
color: var(--text-primary);
}
.card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.page-hint {
font-size: 14px;
color: var(--text-secondary);
}
.page-error {
font-size: 14px;
color: var(--color-danger);
}
.chart-tabs {

View File

@@ -12,7 +12,9 @@
</div>
<div class="card">
<h3 class="card-title">整体概览</h3>
<div class="card-title-row">
<h3 class="card-title">整体概览</h3>
</div>
<div v-if="loading" class="page-hint">加载中...</div>
<div v-else-if="error" class="page-error">{{ error }}</div>
<div v-else>

View File

@@ -0,0 +1,191 @@
<template>
<div v-if="visible" class="modal-overlay">
<div class="modal">
<h3 class="modal-title">编辑评论</h3>
<div v-if="form" class="modal-body">
<div class="form-item">
<label class="form-label">访客昵称</label>
<input v-model="form.name" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">访客邮箱</label>
<input v-model="form.email" class="form-input" type="email" />
</div>
<div class="form-item">
<label class="form-label">访客网址</label>
<input v-model="form.url" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">评论地址</label>
<input v-model="form.postSlug" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">评论内容</label>
<textarea v-model="form.contentText" class="form-input" rows="4"></textarea>
</div>
<div class="form-item">
<label class="form-label">评论状态</label>
<select v-model="form.status" class="form-input">
<option value="approved">已通过</option>
<option value="pending">待审核</option>
<option value="rejected">已拒绝</option>
</select>
</div>
<div class="form-item">
<label class="form-label">置顶权重1 为不置顶数值越大越靠前</label>
<input
v-model.number="form.priority"
class="form-input"
type="number"
min="1"
/>
</div>
</div>
<div class="modal-actions">
<button class="modal-btn secondary" type="button" @click="handleClose">
取消
</button>
<button
class="modal-btn primary"
type="button"
:disabled="saving"
@click="handleSubmit"
>
<span v-if="saving">保存中...</span>
<span v-else>保存</span>
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
interface EditForm {
id: number;
name: string;
email: string;
url: string;
postSlug: string;
contentText: string;
status: string;
priority: number;
}
defineProps<{
visible: boolean;
form: EditForm | null;
saving: boolean;
}>();
const emit = defineEmits<{
(e: "close"): void;
(e: "submit"): void;
}>();
function handleClose() {
emit("close");
}
function handleSubmit() {
emit("submit");
}
</script>
<style scoped lang="less">
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: stretch;
justify-content: flex-end;
z-index: 2000;
}
.modal {
background-color: var(--bg-card);
border-radius: 0;
box-shadow: var(--shadow-card);
width: 100%;
max-width: 600px;
padding: 20px 20px 24px;
display: flex;
flex-direction: column;
gap: 16px;
transform: translateX(0);
animation: drawer-in 0.2s ease-out;
overflow-y: auto;
}
.modal-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.modal-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 4px;
}
.modal-btn {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
border: 1px solid transparent;
}
.modal-btn.primary {
background-color: var(--primary-color);
color: var(--text-inverse);
}
.modal-btn.secondary {
background-color: var(--bg-sider);
border-color: var(--border-color);
color: var(--text-primary);
}
.modal-btn:hover {
opacity: 0.9;
}
.form-item {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-label {
font-size: 13px;
color: var(--text-secondary);
}
.form-input {
padding: 8px 10px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--bg-input);
color: var(--text-primary);
font-size: 13px;
outline: none;
}
.form-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 1px rgba(9, 105, 218, 0.2);
}
</style>

View File

@@ -214,68 +214,13 @@
</div>
</div>
</div>
<div v-if="editVisible" class="modal-overlay">
<div class="modal">
<h3 class="modal-title">编辑评论</h3>
<div v-if="editForm" class="modal-body">
<div class="form-item">
<label class="form-label">访客昵称</label>
<input v-model="editForm.name" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">访客邮箱</label>
<input v-model="editForm.email" class="form-input" type="email" />
</div>
<div class="form-item">
<label class="form-label">访客网址</label>
<input v-model="editForm.url" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">评论地址</label>
<input v-model="editForm.postSlug" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">评论内容</label>
<textarea
v-model="editForm.contentText"
class="form-input"
rows="4"
></textarea>
</div>
<div class="form-item">
<label class="form-label">评论状态</label>
<select v-model="editForm.status" class="form-input">
<option value="approved">已通过</option>
<option value="pending">待审核</option>
<option value="rejected">已拒绝</option>
</select>
</div>
<div class="form-item">
<label class="form-label">置顶权重1 为不置顶,数值越大越靠前)</label>
<input
v-model.number="editForm.priority"
class="form-input"
type="number"
min="1"
/>
</div>
</div>
<div class="modal-actions">
<button class="modal-btn secondary" type="button" @click="closeEdit">
取消
</button>
<button
class="modal-btn primary"
type="button"
:disabled="editSaving"
@click="submitEdit"
>
<span v-if="editSaving">保存中...</span>
<span v-else>保存</span>
</button>
</div>
</div>
</div>
<ModalEdit
:visible="editVisible"
:form="editForm"
:saving="editSaving"
@close="closeEdit"
@submit="submitEdit"
/>
</div>
</template>
@@ -295,6 +240,7 @@ import {
blockEmail,
fetchDomainList,
} from "../../api/admin";
import ModalEdit from "./components/ModalEdit.vue";
const route = useRoute();
const router = useRouter();

View File

@@ -12,7 +12,9 @@
</div>
<div class="card">
<h3 class="card-title">整体概览</h3>
<div class="card-title-row">
<h3 class="card-title">整体概览</h3>
</div>
<div v-if="statsLoading" class="page-hint">加载中...</div>
<div v-else-if="statsError" class="page-error">{{ statsError }}</div>
<div v-else>
@@ -71,7 +73,9 @@
</div>
<div class="card">
<h3 class="card-title">按域名统计</h3>
<div class="card-title-row">
<h3 class="card-title">按域名统计</h3>
</div>
<div v-if="statsLoading" class="page-hint">加载中...</div>
<div v-else-if="statsError" class="page-error">{{ statsError }}</div>
<div v-else-if="domainStats.length === 0" class="page-hint">暂无评论数据</div>