|
|
@@ -0,0 +1,250 @@
|
|
|
+<template>
|
|
|
+ <el-main>
|
|
|
+ <el-card shadow="never">
|
|
|
+ <template #header>
|
|
|
+ <div class="header-row">
|
|
|
+ <span class="card-title">标签管理</span>
|
|
|
+ <div class="header-actions">
|
|
|
+ <el-radio-group v-model="labelType" size="default" @change="onTypeChange">
|
|
|
+ <el-radio-button :label="1">行业标签</el-radio-button>
|
|
|
+ <el-radio-button :label="2">风格标签</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ <el-button type="primary" round @click="openCreate">
|
|
|
+ 新增标签
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-table v-loading="loading" :data="list" stripe class="label-table">
|
|
|
+ <el-table-column prop="id" label="ID" width="90" />
|
|
|
+ <el-table-column label="标签名称" min-width="160" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="label-name">{{ row.label_name ?? row.name ?? "-" }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- <el-table-column label="类型" width="110">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ typeText(row.type ?? labelType) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column> -->
|
|
|
+ <el-table-column label="创建时间" min-width="170" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatTime(row.created_at || row.add_time || row.create_time) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="248" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="table-row-actions">
|
|
|
+ <el-button type="info" size="small" @click="showDetail(row)">详情</el-button>
|
|
|
+ <el-button type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
|
|
+ <el-button type="danger" size="small" @click="removeRow(row)">删除</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-dialog v-model="detailDialog" title="标签详情" width="520px" destroy-on-close>
|
|
|
+ <el-descriptions :column="1" border>
|
|
|
+ <el-descriptions-item label="ID">{{ detail.id ?? "-" }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="标签名称">{{ detail.label_name ?? detail.name ?? "-" }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="类型">{{ typeText(detail.type) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="创建时间">{{ formatTime(detail.created_at || detail.add_time || detail.create_time) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="更新时间">{{ formatTime(detail.updated_at || detail.update_time) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog v-model="editDialog" :title="editTitle" width="440px" destroy-on-close @closed="resetEditForm">
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="96px">
|
|
|
+ <el-form-item label="标签名称" prop="label_name">
|
|
|
+ <el-input v-model="form.label_name" clearable maxlength="64" placeholder="请输入标签名称" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="类型">
|
|
|
+ <el-tag size="small">{{ typeText(labelType) }}</el-tag>
|
|
|
+ <span class="hint">与当前列表分类一致</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="editDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </el-main>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+const TYPE_MAP = {
|
|
|
+ 1: "行业标签",
|
|
|
+ 2: "风格标签"
|
|
|
+};
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "LabelList",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ saving: false,
|
|
|
+ labelType: 1,
|
|
|
+ list: [],
|
|
|
+ detailDialog: false,
|
|
|
+ detail: {},
|
|
|
+ editDialog: false,
|
|
|
+ editTitle: "新增标签",
|
|
|
+ form: {
|
|
|
+ id: "",
|
|
|
+ label_name: ""
|
|
|
+ },
|
|
|
+ rules: {
|
|
|
+ label_name: [{ required: true, message: "请输入标签名称", trigger: "blur" }]
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.getList();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ typeText(type) {
|
|
|
+ if (type == null || type === "") return "-";
|
|
|
+ const n = Number(type);
|
|
|
+ return TYPE_MAP[n] ?? String(type);
|
|
|
+ },
|
|
|
+ formatTime(val) {
|
|
|
+ if (val === undefined || val === null || val === "") return "-";
|
|
|
+ if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}/.test(val.trim())) return val.trim();
|
|
|
+ const num = Number(val);
|
|
|
+ if (!Number.isFinite(num) || num <= 0) return String(val);
|
|
|
+ const sec = num > 1e12 ? Math.floor(num / 1000) : num;
|
|
|
+ return this.$TOOL.getTime(sec, "YYYY-MM-DD HH:mm:ss");
|
|
|
+ },
|
|
|
+ onTypeChange() {
|
|
|
+ this.getList();
|
|
|
+ },
|
|
|
+ async getList() {
|
|
|
+ this.loading = true;
|
|
|
+ try {
|
|
|
+ const res = await this.$API.cutter.labelList.get(this.labelType);
|
|
|
+ if (res.code !== 1) {
|
|
|
+ this.$message.error(res.msg || "获取标签列表失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const raw = res.data;
|
|
|
+ this.list = Array.isArray(raw) ? raw : raw?.list || raw?.data || [];
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async showDetail(row) {
|
|
|
+ const id = row?.id;
|
|
|
+ if (id == null) return;
|
|
|
+ const res = await this.$API.cutter.labelInfo.get(id);
|
|
|
+ if (res.code !== 1) {
|
|
|
+ this.$message.error(res.msg || "获取详情失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.detail = res.data || {};
|
|
|
+ this.detailDialog = true;
|
|
|
+ },
|
|
|
+ openCreate() {
|
|
|
+ this.editTitle = "新增标签";
|
|
|
+ this.form = { id: "", label_name: "" };
|
|
|
+ this.editDialog = true;
|
|
|
+ this.$nextTick(() => this.$refs.formRef?.clearValidate());
|
|
|
+ },
|
|
|
+ openEdit(row) {
|
|
|
+ this.editTitle = "编辑标签";
|
|
|
+ this.form = {
|
|
|
+ id: row.id != null ? String(row.id) : "",
|
|
|
+ label_name: row.label_name ?? row.name ?? ""
|
|
|
+ };
|
|
|
+ this.editDialog = true;
|
|
|
+ this.$nextTick(() => this.$refs.formRef?.clearValidate());
|
|
|
+ },
|
|
|
+ resetEditForm() {
|
|
|
+ this.form = { id: "", label_name: "" };
|
|
|
+ },
|
|
|
+ submitForm() {
|
|
|
+ this.$refs.formRef.validate(async (valid) => {
|
|
|
+ if (!valid) return false;
|
|
|
+ this.saving = true;
|
|
|
+ try {
|
|
|
+ const payload = {
|
|
|
+ label_name: (this.form.label_name || "").trim(),
|
|
|
+ type: String(this.labelType)
|
|
|
+ };
|
|
|
+ if (this.form.id) payload.id = String(this.form.id);
|
|
|
+ const res = await this.$API.cutter.labelSave.post(payload);
|
|
|
+ if (res.code !== 1) {
|
|
|
+ this.$message.error(res.msg || "保存失败");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ this.$message.success(res.msg || "保存成功");
|
|
|
+ this.editDialog = false;
|
|
|
+ this.getList();
|
|
|
+ } finally {
|
|
|
+ this.saving = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async removeRow(row) {
|
|
|
+ const id = row?.id;
|
|
|
+ if (id == null) return;
|
|
|
+ try {
|
|
|
+ await this.$confirm("确认删除该标签吗?", "提示", {
|
|
|
+ type: "warning",
|
|
|
+ confirmButtonText: "删除",
|
|
|
+ cancelButtonText: "取消"
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const res = await this.$API.cutter.labelDel.delete(id);
|
|
|
+ if (res.code !== 1) {
|
|
|
+ this.$message.error(res.msg || "删除失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.$message.success(res.msg || "删除成功");
|
|
|
+ this.getList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.card-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ letter-spacing: 0.02em;
|
|
|
+}
|
|
|
+.header-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+.header-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+.label-table :deep(.el-table__header th.el-table__cell) {
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+}
|
|
|
+.label-name {
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+}
|
|
|
+.hint {
|
|
|
+ margin-left: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+}
|
|
|
+.pager {
|
|
|
+ margin-top: 16px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+</style>
|