358 lines
8.7 KiB
Vue
358 lines
8.7 KiB
Vue
<template>
|
|
<div class="preset-comments-page">
|
|
<a-card class="mb-4">
|
|
<template #title>预设评语管理</template>
|
|
<template #extra>
|
|
<a-space>
|
|
<a-button type="primary" @click="handleAdd">
|
|
<template #icon><PlusOutlined /></template>
|
|
新增
|
|
</a-button>
|
|
<a-popconfirm
|
|
title="确定要删除选中的评语吗?"
|
|
:disabled="selectedRowKeys.length === 0"
|
|
@confirm="handleBatchDelete"
|
|
>
|
|
<a-button danger :disabled="selectedRowKeys.length === 0">
|
|
<template #icon><DeleteOutlined /></template>
|
|
删除
|
|
</a-button>
|
|
</a-popconfirm>
|
|
</a-space>
|
|
</template>
|
|
</a-card>
|
|
|
|
<!-- 数据表格 -->
|
|
<a-table
|
|
:columns="columns"
|
|
:data-source="dataSource"
|
|
:loading="loading"
|
|
:row-selection="rowSelection"
|
|
row-key="id"
|
|
:pagination="false"
|
|
>
|
|
<template #bodyCell="{ column, record, index }">
|
|
<template v-if="column.key === 'index'">
|
|
{{ index + 1 }}
|
|
</template>
|
|
<template v-else-if="column.key === 'content'">
|
|
<a-tooltip :title="record.content">
|
|
<span class="content-cell">{{ record.content }}</span>
|
|
</a-tooltip>
|
|
</template>
|
|
<template v-else-if="column.key === 'useCount'">
|
|
<a-tag v-if="record.useCount > 0" color="blue">
|
|
{{ record.useCount }}次
|
|
</a-tag>
|
|
<span v-else>0次</span>
|
|
</template>
|
|
<template v-else-if="column.key === 'action'">
|
|
<a-space>
|
|
<a-button type="link" size="small" @click="handleEdit(record)">
|
|
编辑
|
|
</a-button>
|
|
<a-popconfirm
|
|
title="确定要删除这条评语吗?"
|
|
@confirm="handleDelete(record.id)"
|
|
>
|
|
<a-button type="link" danger size="small">删除</a-button>
|
|
</a-popconfirm>
|
|
</a-space>
|
|
</template>
|
|
</template>
|
|
</a-table>
|
|
|
|
<!-- 新增/编辑评语弹框 -->
|
|
<a-modal
|
|
v-model:open="modalVisible"
|
|
:title="isEditing ? '编辑评语' : '新增评语'"
|
|
:confirm-loading="submitLoading"
|
|
@ok="handleSubmit"
|
|
@cancel="handleCancel"
|
|
>
|
|
<a-form
|
|
ref="formRef"
|
|
:model="form"
|
|
:rules="rules"
|
|
:label-col="{ span: 6 }"
|
|
:wrapper-col="{ span: 18 }"
|
|
>
|
|
<a-form-item label="评语内容" name="content">
|
|
<a-textarea
|
|
v-model:value="form.content"
|
|
placeholder="请输入评语内容"
|
|
:rows="4"
|
|
:maxlength="500"
|
|
show-count
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
</a-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, onMounted } from "vue"
|
|
import { message } from "ant-design-vue"
|
|
import type { FormInstance, TableProps } from "ant-design-vue"
|
|
import { PlusOutlined, DeleteOutlined } from "@ant-design/icons-vue"
|
|
import { presetCommentsApi, type PresetComment } from "@/api/preset-comments"
|
|
|
|
// 表格数据
|
|
const loading = ref(false)
|
|
const dataSource = ref<PresetComment[]>([])
|
|
|
|
// 表格选择
|
|
const selectedRowKeys = ref<number[]>([])
|
|
const rowSelection = computed<TableProps["rowSelection"]>(() => ({
|
|
selectedRowKeys: selectedRowKeys.value,
|
|
onChange: (keys: any) => {
|
|
selectedRowKeys.value = keys
|
|
},
|
|
}))
|
|
|
|
// 弹框相关
|
|
const modalVisible = ref(false)
|
|
const isEditing = ref(false)
|
|
const editingId = ref<number | null>(null)
|
|
const submitLoading = ref(false)
|
|
const formRef = ref<FormInstance>()
|
|
|
|
const form = reactive<{
|
|
content: string
|
|
}>({
|
|
content: "",
|
|
})
|
|
|
|
// 表单验证规则
|
|
const rules = {
|
|
content: [{ required: true, message: "请输入评语内容", trigger: "blur" }],
|
|
}
|
|
|
|
// 表格列定义
|
|
const columns = [
|
|
{
|
|
title: "序号",
|
|
key: "index",
|
|
width: 70,
|
|
},
|
|
{
|
|
title: "评语内容",
|
|
key: "content",
|
|
dataIndex: "content",
|
|
ellipsis: true,
|
|
},
|
|
{
|
|
title: "使用次数",
|
|
key: "useCount",
|
|
dataIndex: "useCount",
|
|
width: 100,
|
|
},
|
|
{
|
|
title: "操作",
|
|
key: "action",
|
|
width: 150,
|
|
fixed: "right" as const,
|
|
},
|
|
]
|
|
|
|
// 加载评语列表(序号防止快速返回时旧请求覆盖表格)
|
|
let loadCommentsSeq = 0
|
|
const loadComments = async () => {
|
|
const seq = ++loadCommentsSeq
|
|
loading.value = true
|
|
try {
|
|
const data = await presetCommentsApi.getList()
|
|
if (seq !== loadCommentsSeq) return
|
|
dataSource.value = data
|
|
} catch (error: any) {
|
|
if (seq === loadCommentsSeq) {
|
|
message.error(error?.response?.data?.message || "获取评语列表失败")
|
|
}
|
|
} finally {
|
|
if (seq === loadCommentsSeq) {
|
|
loading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// 新增
|
|
const handleAdd = () => {
|
|
isEditing.value = false
|
|
editingId.value = null
|
|
modalVisible.value = true
|
|
form.content = ""
|
|
}
|
|
|
|
// 编辑
|
|
const handleEdit = (record: PresetComment) => {
|
|
isEditing.value = true
|
|
editingId.value = record.id
|
|
modalVisible.value = true
|
|
form.content = record.content
|
|
}
|
|
|
|
// 删除
|
|
const handleDelete = async (id: number) => {
|
|
try {
|
|
await presetCommentsApi.delete(id)
|
|
message.success("删除成功")
|
|
loadComments()
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "删除失败")
|
|
}
|
|
}
|
|
|
|
// 批量删除
|
|
const handleBatchDelete = async () => {
|
|
if (selectedRowKeys.value.length === 0) return
|
|
try {
|
|
await presetCommentsApi.batchDelete(selectedRowKeys.value)
|
|
message.success("批量删除成功")
|
|
selectedRowKeys.value = []
|
|
loadComments()
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "批量删除失败")
|
|
}
|
|
}
|
|
|
|
// 提交表单
|
|
const handleSubmit = async () => {
|
|
try {
|
|
await formRef.value?.validate()
|
|
submitLoading.value = true
|
|
|
|
if (isEditing.value && editingId.value) {
|
|
await presetCommentsApi.update(editingId.value, {
|
|
content: form.content,
|
|
})
|
|
message.success("编辑成功")
|
|
} else {
|
|
await presetCommentsApi.create({
|
|
content: form.content,
|
|
})
|
|
message.success("创建成功")
|
|
}
|
|
|
|
modalVisible.value = false
|
|
loadComments()
|
|
} catch (error: any) {
|
|
if (error?.errorFields) {
|
|
return
|
|
}
|
|
message.error(
|
|
error?.response?.data?.message ||
|
|
(isEditing.value ? "编辑失败" : "创建失败"),
|
|
)
|
|
} finally {
|
|
submitLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 取消
|
|
const handleCancel = () => {
|
|
modalVisible.value = false
|
|
formRef.value?.resetFields()
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadComments()
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
// 主色调
|
|
$primary: #1890ff;
|
|
$primary-dark: #0958d9;
|
|
$gradient-primary: linear-gradient(135deg, $primary 0%, $primary-dark 100%);
|
|
|
|
.preset-comments-page {
|
|
// 标题卡片样式
|
|
:deep(.ant-card) {
|
|
border: none;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
margin-bottom: 16px;
|
|
|
|
.ant-card-head {
|
|
border-bottom: none;
|
|
padding: 16px 24px;
|
|
|
|
.ant-card-head-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
}
|
|
}
|
|
|
|
.ant-card-body {
|
|
padding: 0;
|
|
}
|
|
}
|
|
|
|
// 渐变主按钮样式
|
|
:deep(.ant-btn-primary) {
|
|
background: $gradient-primary;
|
|
border: none;
|
|
box-shadow: 0 4px 12px rgba($primary, 0.35);
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
background: linear-gradient(
|
|
135deg,
|
|
$primary-dark 0%,
|
|
darken($primary-dark, 8%) 100%
|
|
);
|
|
box-shadow: 0 6px 16px rgba($primary, 0.45);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
&:active {
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
// 表格样式
|
|
:deep(.ant-table-wrapper) {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
overflow: hidden;
|
|
|
|
.ant-table {
|
|
.ant-table-thead > tr > th {
|
|
background: #fafafa;
|
|
font-weight: 600;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.ant-table-tbody > tr {
|
|
transition: all 0.2s ease;
|
|
|
|
&:hover > td {
|
|
background: rgba($primary, 0.04);
|
|
}
|
|
|
|
> td {
|
|
border-bottom: 1px solid #f5f5f5;
|
|
}
|
|
}
|
|
}
|
|
|
|
.ant-table-pagination {
|
|
padding: 16px;
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.content-cell {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
</style>
|