refactor: 四端完成 Options API → script setup + vuex → pinia 迁移

- buyer(95) / seller(169) / im(53) / manager(280) 共 597 个组件全部从 Options API 转换为 <script setup>
- 四端 vuex 全部移除,状态管理统一迁移至 pinia 4(stores/ 目录)
- util.js 等基础设施去除 vm 首参依赖;全局属性($Message/$filters 等)在 script 内改为具名导入
- 被外部 ref 访问的公共组件均已补齐 defineExpose(清单见各端 MIGRATION-VUE3.md)
- 各端 eslint 启用 vue/component-api-style 防止新代码回退 Options API
- 四端 yarn build 全部通过;迁移约定与注意事项已更新至各端 MIGRATION-VUE3.md
This commit is contained in:
lifenlong
2026-10-13 07:06:59 +08:00
parent 870bacdd8c
commit 88fc1b0b2a
674 changed files with 71220 additions and 69943 deletions

View File

@@ -39,7 +39,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="form" :model="formAdd" label-width="100px" :rules="formValidate">
<el-form ref="formRef" :model="formAdd" label-width="100px" :rules="formValidate">
<el-form-item v-if="showParent" label="上级分类" prop="parentId">
{{ parentTitle }}
<el-input v-model="formAdd.parentId" style="display: none" />
@@ -59,137 +59,137 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { saveArticleCategory, getArticleCategory, delArticleCategory, updateArticleCategory } from "@/api/pages";
import { regular } from "@/utils";
import { Message, Modal } from "@/utils/message";
export default {
name: "lili-components",
data() {
return {
submitLoading: false,
loading: false,
expandLevel: 1,
modalType: 0,
modalVisible: false,
modalTitle: "",
showParent: false,
parentTitle: "",
formAdd: {
parentId: "",
sort: 1,
level: 0,
articleCategoryName: "",
},
formValidate: {
articleCategoryName: [regular.REQUIRED],
sort: [regular.REQUIRED, regular.INTEGER],
},
tableData: [],
};
},
methods: {
init() {
this.getAllList();
},
addChildren(v) {
this.modalType = 0;
this.modalTitle = "添加子分类";
this.formAdd.articleCategoryName = "";
this.parentTitle = v.articleCategoryName;
this.formAdd.level = Number(v.level) + 1;
this.showParent = true;
delete this.formAdd.id;
this.formAdd.parentId = v.id;
this.modalVisible = true;
},
edit(v) {
this.modalType = 1;
this.modalTitle = "编辑";
this.formAdd.id = v.id;
this.formAdd.articleCategoryName = v.articleCategoryName;
this.formAdd.level = v.level;
this.formAdd.parentId = v.parentId;
this.formAdd.sort = v.sort;
this.showParent = false;
this.modalVisible = true;
},
addParent() {
this.modalType = 0;
this.modalTitle = "添加一级分类";
this.parentTitle = "顶级分类";
this.showParent = true;
this.$refs.form?.resetFields();
delete this.formAdd.id;
this.formAdd.parentId = 0;
this.modalVisible = true;
},
Submit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
if (this.modalType === 0) {
delete this.formAdd.id;
saveArticleCategory(this.formAdd).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("添加成功");
this.formAdd = {
parentId: "",
sort: 1,
level: 0,
articleCategoryName: "",
};
}
this.getAllList();
this.modalVisible = false;
});
} else {
updateArticleCategory(this.formAdd, this.formAdd.id).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("修改成功");
}
this.getAllList();
this.modalVisible = false;
this.$refs.form.resetFields();
});
defineOptions({ name: "lili-components" });
const submitLoading = ref(false);
const loading = ref(false);
const expandLevel = ref(1);
const modalType = ref(0);
const modalVisible = ref(false);
const modalTitle = ref("");
const showParent = ref(false);
const parentTitle = ref("");
const formAdd = ref({
parentId: "",
sort: 1,
level: 0,
articleCategoryName: "",
});
const formValidate = ref({
articleCategoryName: [regular.REQUIRED],
sort: [regular.REQUIRED, regular.INTEGER],
});
const tableData = ref([]);
// 模板 el-form 的 ref(原 ref="form",避免与顶层变量重名,改名为 formRef)
const formRef = ref(null);
function init() {
getAllList();
}
function addChildren(v) {
modalType.value = 0;
modalTitle.value = "添加子分类";
formAdd.value.articleCategoryName = "";
parentTitle.value = v.articleCategoryName;
formAdd.value.level = Number(v.level) + 1;
showParent.value = true;
delete formAdd.value.id;
formAdd.value.parentId = v.id;
modalVisible.value = true;
}
function edit(v) {
modalType.value = 1;
modalTitle.value = "编辑";
formAdd.value.id = v.id;
formAdd.value.articleCategoryName = v.articleCategoryName;
formAdd.value.level = v.level;
formAdd.value.parentId = v.parentId;
formAdd.value.sort = v.sort;
showParent.value = false;
modalVisible.value = true;
}
function addParent() {
modalType.value = 0;
modalTitle.value = "添加一级分类";
parentTitle.value = "顶级分类";
showParent.value = true;
formRef.value?.resetFields();
delete formAdd.value.id;
formAdd.value.parentId = 0;
modalVisible.value = true;
}
function Submit() {
formRef.value.validate((valid) => {
if (!valid) return;
submitLoading.value = true;
if (modalType.value === 0) {
delete formAdd.value.id;
saveArticleCategory(formAdd.value).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("添加成功");
formAdd.value = {
parentId: "",
sort: 1,
level: 0,
articleCategoryName: "",
};
}
getAllList();
modalVisible.value = false;
});
} else {
updateArticleCategory(formAdd.value, formAdd.value.id).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("修改成功");
}
getAllList();
modalVisible.value = false;
formRef.value.resetFields();
});
}
});
}
function remove(v) {
Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.articleCategoryName + " ?",
loading: true,
onOk: () => {
delArticleCategory(v.id).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
getAllList();
}
});
},
remove(v) {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.articleCategoryName + " ?",
loading: true,
onOk: () => {
delArticleCategory(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getAllList();
}
});
},
});
},
getAllList() {
this.loading = true;
getArticleCategory()
.then((res) => {
if (res.success) {
this.tableData = res.result || [];
}
})
.finally(() => {
this.loading = false;
});
},
},
mounted() {
this.init();
},
};
});
}
function getAllList() {
loading.value = true;
getArticleCategory()
.then((res) => {
if (res.success) {
tableData.value = res.result || [];
}
})
.finally(() => {
loading.value = false;
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -22,84 +22,81 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { Chart } from "@antv/g2";
import { getHotWordsHistory } from "@/api/index";
import { Message } from "@/utils/message";
import { unixToDate } from "@/utils/filters";
export default {
name: "HOTWORDS_HISTORY",
data() {
return {
params: {
date: "",
},
hotWordsChart: null,
hotWordsData: [],
endMonth: null,
};
},
created() {
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
this.params.date = this.$filters.unixToDate(Math.floor(yesterday.getTime() / 1000), "yyyy-MM-dd");
},
methods: {
disabledDate(date) {
if (this.endMonth) {
const endDate = this.getDate(this.endMonth);
return (date && date > endDate) || date > new Date();
}
return date && date > new Date();
defineOptions({ name: "HOTWORDS_HISTORY" });
const params = ref({
date: "",
});
const hotWordsChart = ref(null);
const hotWordsData = ref([]);
const endMonth = ref(null);
// 原 created
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
params.value.date = unixToDate(Math.floor(yesterday.getTime() / 1000), "yyyy-MM-dd");
function disabledDate(date) {
if (endMonth.value) {
const endDate = getDate(endMonth.value);
return (date && date > endDate) || date > new Date();
}
return date && date > new Date();
}
function getDate(monthStr) {
const [year, month] = monthStr.split("-");
return new Date(year, month, 0);
}
async function search(val) {
if (val) {
params.value.date = val;
}
const res = await getHotWordsHistory(params.value);
if (res.success) {
hotWordsData.value = res.result || [];
if (hotWordsChart.value) {
hotWordsChart.value.changeData(hotWordsData.value);
hotWordsChart.value.render();
}
if (!hotWordsData.value.length) {
Message.error("暂无数据");
}
}
}
function init() {
const chart = new Chart({
container: "container",
autoFit: true,
height: 500,
padding: [50, 20, 50, 20],
});
chart.scale("score", {
alias: "搜索次数",
});
chart.axis("keywords", {
tickLine: {
alignTick: false,
},
getDate(monthStr) {
const [year, month] = monthStr.split("-");
return new Date(year, month, 0);
},
async search(val) {
if (val) {
this.params.date = val;
}
const res = await getHotWordsHistory(this.params);
if (res.success) {
this.hotWordsData = res.result || [];
if (this.hotWordsChart) {
this.hotWordsChart.changeData(this.hotWordsData);
this.hotWordsChart.render();
}
if (!this.hotWordsData.length) {
Message.error("暂无数据");
}
}
},
init() {
const chart = new Chart({
container: "container",
autoFit: true,
height: 500,
padding: [50, 20, 50, 20],
});
chart.scale("score", {
alias: "搜索次数",
});
chart.axis("keywords", {
tickLine: {
alignTick: false,
},
});
chart.axis("score", false);
chart.tooltip({
showMarkers: false,
});
chart.interval().position("keywords*score").color("#f59b99");
chart.interaction("element-active");
this.hotWordsChart = chart;
this.search();
},
},
mounted() {
this.init();
},
};
});
chart.axis("score", false);
chart.tooltip({
showMarkers: false,
});
chart.interval().position("keywords*score").color("#f59b99");
chart.interaction("element-active");
hotWordsChart.value = chart;
search();
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -29,7 +29,7 @@
append-to-body
destroy-on-close
>
<el-form ref="form" :model="form" label-width="100px" :rules="formValidate">
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
<el-form-item label="热词" prop="keywords">
<el-input v-model="form.keywords" clearable style="width: 100%" />
</el-form-item>
@@ -45,86 +45,84 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { Close } from "@element-plus/icons-vue";
import { getHotWords, setHotWords, deleteHotWords } from "@/api/index";
import { regular } from "@/utils";
import { Message, Modal } from "@/utils/message";
export default {
name: "HOTWORDS_TODAY",
components: { Close },
data() {
return {
submitLoading: false,
modalTitle: "",
loading: true,
modalVisible: false,
form: {
keywords: "",
point: 0,
},
data: [],
formValidate: {
keywords: [regular.REQUIRED, regular.VARCHAR20],
point: [regular.REQUIRED, regular.NUMBER],
},
};
},
methods: {
init() {
this.getDataList();
},
getDataList() {
this.loading = true;
getHotWords().then((res) => {
this.loading = false;
defineOptions({ name: "HOTWORDS_TODAY" });
const formRef = ref(null);
const submitLoading = ref(false);
const modalTitle = ref("");
const loading = ref(true);
const modalVisible = ref(false);
const form = ref({
keywords: "",
point: 0,
});
const data = ref([]);
const formValidate = reactive({
keywords: [regular.REQUIRED, regular.VARCHAR20],
point: [regular.REQUIRED, regular.NUMBER],
});
function init() {
getDataList();
}
function getDataList() {
loading.value = true;
getHotWords().then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result;
}
});
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
setHotWords(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
this.data = res.result;
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
}
});
}
function add(words) {
modalTitle.value = "设置热词";
form.value.keywords = words || "";
form.value.point = 1;
modalVisible.value = true;
}
function deleteWords(words) {
Modal.confirm({
title: "是否确定删除热词",
content: "您确定要删除此热词吗?",
okText: "确定",
cancelText: "取消",
onOk: () => {
deleteHotWords({ words }).then((res) => {
if (res.success) {
Message.success("删除成功");
getDataList();
}
});
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
this.submitLoading = true;
setHotWords(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
}
});
},
add(words) {
this.modalTitle = "设置热词";
this.form.keywords = words || "";
this.form.point = 1;
this.modalVisible = true;
},
deleteWords(words) {
this.$Modal.confirm({
title: "是否确定删除热词",
content: "您确定要删除此热词吗?",
okText: "确定",
cancelText: "取消",
onOk: () => {
deleteHotWords({ words }).then((res) => {
if (res.success) {
this.$Message.success("删除成功");
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();
},
};
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -1,7 +1,7 @@
<template>
<div class="layout">
<el-form
ref="formValidate"
ref="formValidateRef"
label-width="150px"
label-position="right"
:model="formValidate"
@@ -42,68 +42,67 @@
</div>
</template>
<script>
<script setup>
import { ref } from "vue";
import { setSetting } from "@/api/index";
import { Message } from "@/utils/message";
export default {
props: ["res", "type"],
data() {
return {
ruleValidate: {},
formValidate: {
saveNum: 1,
hotWordsSettingItems: [
{
keywords: "",
score: 1,
},
],
},
};
},
created() {
this.init();
},
methods: {
addSetItem() {
if (this.formValidate.hotWordsSettingItems.length >= 5) {
this.$Message.error("最多5个热词项");
} else {
this.formValidate.hotWordsSettingItems.push({
keywords: "",
score: 1,
});
}
const props = defineProps(["res", "type"]);
const formValidateRef = ref(null);
// 动态 $refs[name] 查找的映射表
const formRefs = { formValidate: formValidateRef };
const ruleValidate = ref({});
const formValidate = ref({
saveNum: 1,
hotWordsSettingItems: [
{
keywords: "",
score: 1,
},
submit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.setupSetting();
} else {
this.$Message.error("请正确填写内容!");
}
});
},
setupSetting() {
setSetting(this.type || "HOT_WORDS", this.formValidate).then((res) => {
if (res.success) {
this.$Message.success("保存成功!");
} else {
this.$Message.error("保存失败!");
}
});
},
init() {
if (!this.res) {
return;
}
const parsed = typeof this.res === "string" ? JSON.parse(this.res) : this.res;
if (parsed.hotWordsSettingItems) {
this.formValidate = { ...parsed };
}
},
},
};
],
});
init();
function addSetItem() {
if (formValidate.value.hotWordsSettingItems.length >= 5) {
Message.error("最多5个热词项");
} else {
formValidate.value.hotWordsSettingItems.push({
keywords: "",
score: 1,
});
}
}
function submit(name) {
formRefs[name].value.validate((valid) => {
if (valid) {
setupSetting();
} else {
Message.error("请正确填写内容!");
}
});
}
function setupSetting() {
setSetting(props.type || "HOT_WORDS", formValidate.value).then((res) => {
if (res.success) {
Message.success("保存成功!");
} else {
Message.error("保存失败!");
}
});
}
function init() {
if (!props.res) {
return;
}
const parsed = typeof props.res === "string" ? JSON.parse(props.res) : props.res;
if (parsed.hotWordsSettingItems) {
formValidate.value = { ...parsed };
}
}
</script>
<style lang="scss" scoped>

View File

@@ -15,7 +15,7 @@
<div class="article-page__main">
<el-card class="article-detail">
<el-form
ref="searchForm"
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
@@ -100,7 +100,7 @@
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="form" :model="form" label-width="100px" :rules="formValidate">
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
<el-form-item label="文章标题" prop="title">
<el-input v-model="form.title" clearable style="width: 40%" />
</el-form-item>
@@ -144,7 +144,8 @@
</div>
</template>
<script>
<script setup>
import { ref, watch, nextTick, onMounted } from "vue";
import {
getArticleCategory,
saveArticle,
@@ -156,6 +157,7 @@ import {
} from "@/api/pages";
import tinymec from "@/components/editor/index.vue";
import { regular } from "@/utils";
import { Message, Modal } from "@/utils/message";
const validateArticleContent = (rule, value, callback) => {
const html = String(value || "").trim();
@@ -175,280 +177,281 @@ const validateArticleContent = (rule, value, callback) => {
callback();
};
export default {
components: {
tinymec,
defineProps({
selected: {
type: Boolean,
default: false,
},
props: {
selected: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["callbacked"]);
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
// 模板 el-form 的 ref(原 ref="form",与 data 同名会互相覆盖,改名为 formRef)
const formRef = ref(null);
const editor = ref(null);
const selectedIndex = ref(99999);
const loading = ref(true);
const modalType = ref(0);
const modalVisible = ref(false);
const modalTitle = ref("");
const treeDataDefault = ref([]);
const searchForm = ref({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
categoryId: "",
});
const searchTreeValue = ref("");
const form = ref({
openStatus: false,
title: "",
categoryId: "",
sort: 1,
content: "",
id: "",
});
const formValidate = ref({
title: [{ required: true, message: "请输入文章标题", trigger: "blur" }],
categoryId: [{ required: true, message: "请选择文章分类", trigger: "change" }],
sort: [regular.REQUIRED, regular.INTEGER],
content: [{ required: true, validator: validateArticleContent, trigger: "change" }],
});
const list = ref([]);
const treeValue = ref("");
const treeData = ref([]);
const submitLoading = ref(false);
const data = ref([]);
const total = ref(0);
watch(
() => searchForm.value.categoryId,
() => {
handleSearch();
},
data() {
return {
selectedIndex: 99999,
loading: true,
modalType: 0,
modalVisible: false,
modalTitle: "",
treeDataDefault: [],
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
categoryId: "",
},
searchTreeValue: "",
form: {
openStatus: false,
title: "",
categoryId: "",
sort: 1,
content: "",
id: "",
},
formValidate: {
title: [{ required: true, message: "请输入文章标题", trigger: "blur" }],
categoryId: [{ required: true, message: "请选择文章分类", trigger: "change" }],
sort: [regular.REQUIRED, regular.INTEGER],
content: [{ required: true, validator: validateArticleContent, trigger: "change" }],
},
list: [],
treeValue: "",
treeData: [],
submitLoading: false,
data: [],
total: 0,
};
{ deep: true }
);
watch(
() => searchForm.value.title,
() => {
handleSearch();
},
watch: {
"searchForm.categoryId": {
handler() {
this.handleSearch();
},
deep: true,
},
"searchForm.title": {
handler() {
this.handleSearch();
},
deep: true,
},
},
methods: {
getDefaultForm() {
return {
openStatus: false,
title: "",
categoryId: "",
sort: 1,
content: "",
};
},
init() {
this.getDataList();
this.getAllList(0);
},
selectRow(row, index) {
this.selectedIndex = index;
this.$emit("callbacked", row);
},
handleCateChange(data) {
if (!data) return;
const { value, title } = data;
this.searchForm.categoryId =
value === "0" || value === 0 ? "" : value;
this.searchTreeValue = title;
},
changeSwitch(v) {
const params = { status: v.openStatus };
updateArticleStatus(v.id, params).then((res) => {
this.submitLoading = false;
if (res.success) {
}
{ deep: true }
);
function getDefaultForm() {
return {
openStatus: false,
title: "",
categoryId: "",
sort: 1,
content: "",
};
}
function init() {
getDataList();
getAllList(0);
}
function selectRow(row, index) {
selectedIndex.value = index;
emit("callbacked", row);
}
function handleCateChange(data) {
if (!data) return;
const { value, title } = data;
searchForm.value.categoryId =
value === "0" || value === 0 ? "" : value;
searchTreeValue.value = title;
}
function changeSwitch(v) {
const params = { status: v.openStatus };
updateArticleStatus(v.id, params).then((res) => {
submitLoading.value = false;
if (res.success) {
}
});
}
function handleCategoryChange(value) {
form.value.categoryId = value;
const findTitle = (nodes, id) => {
for (const node of nodes || []) {
if (node.value === id) return node.title;
const childTitle = findTitle(node.children, id);
if (childTitle) return childTitle;
}
return "";
};
treeValue.value = findTitle(treeDataDefault.value, value);
}
function handleCheckChange(data) {
let value = "";
let title = "";
list.value = [];
data.forEach((item) => {
value += `${item.value},`;
title += `${item.title},`;
});
value = value.substring(0, value.length - 1);
title = title.substring(0, title.length - 1);
list.value.push({ value, title });
form.value.categoryId = value;
treeValue.value = title;
}
function changePage(v) {
searchForm.value.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.value.pageNumber = 1;
searchForm.value.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.value.pageNumber = 1;
searchForm.value.pageSize = 20;
getDataList();
}
function getAllList(parent_id) {
loading.value = true;
getArticleCategory(parent_id).then((res) => {
loading.value = false;
if (res.success) {
treeData.value = getTree(res.result);
treeDataDefault.value = getTree(res.result);
treeData.value.unshift({
title: "全部",
level: 0,
children: [],
id: "0",
categoryId: 0,
value: "0",
});
},
handleCategoryChange(value) {
this.form.categoryId = value;
const findTitle = (nodes, id) => {
for (const node of nodes || []) {
if (node.value === id) return node.title;
const childTitle = findTitle(node.children, id);
if (childTitle) return childTitle;
}
return "";
};
this.treeValue = findTitle(this.treeDataDefault, value);
},
handleCheckChange(data) {
let value = "";
let title = "";
this.list = [];
data.forEach((item) => {
value += `${item.value},`;
title += `${item.title},`;
});
value = value.substring(0, value.length - 1);
title = title.substring(0, title.length - 1);
this.list.push({ value, title });
this.form.categoryId = value;
this.treeValue = title;
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
getAllList(parent_id) {
this.loading = true;
getArticleCategory(parent_id).then((res) => {
this.loading = false;
if (res.success) {
this.treeData = this.getTree(res.result);
this.treeDataDefault = this.getTree(res.result);
this.treeData.unshift({
title: "全部",
level: 0,
children: [],
id: "0",
categoryId: 0,
value: "0",
});
}
});
},
getTree(tree = []) {
const arr = [];
if (!!tree && tree.length !== 0) {
tree.forEach((item) => {
const obj = {};
obj.title = item.articleCategoryName;
obj.value = item.id;
obj.attr = item.articleCategoryName;
obj.children = this.getTree(item.children);
arr.push(obj);
});
}
});
}
function getTree(tree = []) {
const arr = [];
if (!!tree && tree.length !== 0) {
tree.forEach((item) => {
const obj = {};
obj.title = item.articleCategoryName;
obj.value = item.id;
obj.attr = item.articleCategoryName;
obj.children = getTree(item.children);
arr.push(obj);
});
}
return arr;
}
function buildArticleSearchParams() {
const params = { ...searchForm.value };
const categoryId = params.categoryId;
if (
categoryId === "" ||
categoryId === null ||
categoryId === undefined ||
categoryId === 0 ||
categoryId === "0"
) {
delete params.categoryId;
}
return params;
}
function getDataList(val) {
if (val) form.value = {};
loading.value = true;
getArticle(buildArticleSearchParams())
.then((res) => {
if (res.success) {
total.value = res.result.total;
data.value = res.result.records?.length ? res.result.records : [];
}
return arr;
},
buildArticleSearchParams() {
const params = { ...this.searchForm };
const categoryId = params.categoryId;
if (
categoryId === "" ||
categoryId === null ||
categoryId === undefined ||
categoryId === 0 ||
categoryId === "0"
) {
delete params.categoryId;
}
return params;
},
getDataList(val) {
if (val) this.form = {};
this.loading = true;
getArticle(this.buildArticleSearchParams())
.then((res) => {
})
.finally(() => {
loading.value = false;
});
}
function handleSubmit() {
if (editor.value?.getContent) {
form.value.content = editor.value.getContent();
}
formRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
if (modalType.value === 0) {
delete form.value.id;
saveArticle(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
this.total = res.result.total;
this.data = res.result.records?.length ? res.result.records : [];
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
})
.finally(() => {
this.loading = false;
});
},
handleSubmit() {
if (this.$refs.editor?.getContent) {
this.form.content = this.$refs.editor.getContent();
}
this.$refs.form.validate((valid) => {
if (valid) {
this.submitLoading = true;
if (this.modalType === 0) {
delete this.form.id;
saveArticle(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
updateArticle(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
updateArticle(form.value).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
}
}
});
}
function add() {
modalType.value = 0;
modalTitle.value = "添加文章";
treeValue.value = "";
form.value = getDefaultForm();
modalVisible.value = true;
nextTick(() => {
formRef.value?.clearValidate?.();
});
}
function edit(data) {
modalType.value = 1;
modalTitle.value = "编辑文章";
treeValue.value = "";
form.value = getDefaultForm();
seeArticle(data.id).then((res) => {
if (res.result) {
modalVisible.value = true;
form.value.categoryId = res.result.categoryId;
treeValue.value = data.articleCategoryName;
form.value.id = data.id;
form.value.content = res.result.content;
form.value.title = res.result.title;
form.value.sort = res.result.sort;
form.value.openStatus = res.result.openStatus;
}
});
}
function remove(v) {
Modal.confirm({
title: "确认删除",
content: "您确认要删除么?",
loading: true,
onOk: () => {
delArticle(v.id).then((res) => {
Modal.remove();
if (res.success) {
Message.success("操作成功");
getDataList();
}
});
},
add() {
this.modalType = 0;
this.modalTitle = "添加文章";
this.treeValue = "";
this.form = this.getDefaultForm();
this.modalVisible = true;
this.$nextTick(() => {
this.$refs.form?.clearValidate?.();
});
},
edit(data) {
this.modalType = 1;
this.modalTitle = "编辑文章";
this.treeValue = "";
this.form = this.getDefaultForm();
seeArticle(data.id).then((res) => {
if (res.result) {
this.modalVisible = true;
this.form.categoryId = res.result.categoryId;
this.treeValue = data.articleCategoryName;
this.form.id = data.id;
this.form.content = res.result.content;
this.form.title = res.result.title;
this.form.sort = res.result.sort;
this.form.openStatus = res.result.openStatus;
}
});
},
remove(v) {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除么?",
loading: true,
onOk: () => {
delArticle(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();
},
};
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -20,32 +20,22 @@
</div>
</template>
<script>
<script setup>
import { ref } from "vue";
import todayHotWords from "./template/todayHotWords";
import historyHotWords from "./template/historyHotWords";
import setupHotWords from "./template/setupHotWords";
import statisticsHotWords from "./template/statisticsHotWords";
export default {
name: "hotWords",
components: {
todayHotWords,
historyHotWords,
setupHotWords,
statisticsHotWords,
},
data() {
return {
templatesWay: [
{ template: "todayHotWords", label: "今日热词" },
{ template: "historyHotWords", label: "历史热词" },
{ template: "statisticsHotWords", label: "热词统计" },
{ template: "setupHotWords", label: "设置热词" },
],
currentTemplate: "todayHotWords",
};
},
};
defineOptions({ name: "hotWords" });
const templatesWay = ref([
{ template: "todayHotWords", label: "今日热词" },
{ template: "historyHotWords", label: "历史热词" },
{ template: "statisticsHotWords", label: "热词统计" },
{ template: "setupHotWords", label: "设置热词" },
]);
const currentTemplate = ref("todayHotWords");
</script>
<style lang="scss" scoped></style>

View File

@@ -22,81 +22,76 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { Chart } from "@antv/g2";
import { getHotWordsHistory } from "@/api/index";
import { unixToDate } from "@/utils/filters";
export default {
data() {
return {
params: {
date: "",
},
hotWordsChart: null,
hotWordsData: [],
hasData: true,
endMonth: null,
};
},
created() {
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
this.params.date = this.$filters.unixToDate(Math.floor(yesterday.getTime() / 1000), "yyyy-MM-dd");
},
methods: {
disabledDate(date) {
if (this.endMonth) {
const endDate = this.getDate(this.endMonth);
return (date && date > endDate) || date > new Date();
}
return date && date > new Date();
const params = reactive({
date: "",
});
const hotWordsChart = ref(null);
const hotWordsData = ref([]);
const hasData = ref(true);
const endMonth = ref(null);
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
params.date = unixToDate(Math.floor(yesterday.getTime() / 1000), "yyyy-MM-dd");
function disabledDate(date) {
if (endMonth.value) {
const endDate = getDate(endMonth.value);
return (date && date > endDate) || date > new Date();
}
return date && date > new Date();
}
function getDate(monthStr) {
const [year, month] = monthStr.split("-");
return new Date(year, month, 0);
}
async function search(val) {
if (val) {
params.date = val;
}
const res = await getHotWordsHistory(params);
if (res.success) {
hotWordsData.value = res.result || [];
hasData.value = hotWordsData.value.length > 0;
if (hasData.value && hotWordsChart.value) {
hotWordsChart.value.changeData(hotWordsData.value);
hotWordsChart.value.render();
}
}
}
function init() {
const chart = new Chart({
container: "container",
autoFit: true,
height: 500,
padding: [50, 20, 50, 20],
});
chart.scale("score", {
alias: "搜索次数",
});
chart.axis("keywords", {
tickLine: {
alignTick: false,
},
getDate(monthStr) {
const [year, month] = monthStr.split("-");
return new Date(year, month, 0);
},
async search(val) {
if (val) {
this.params.date = val;
}
const res = await getHotWordsHistory(this.params);
if (res.success) {
this.hotWordsData = res.result || [];
this.hasData = this.hotWordsData.length > 0;
if (this.hasData && this.hotWordsChart) {
this.hotWordsChart.changeData(this.hotWordsData);
this.hotWordsChart.render();
}
}
},
init() {
const chart = new Chart({
container: "container",
autoFit: true,
height: 500,
padding: [50, 20, 50, 20],
});
chart.scale("score", {
alias: "搜索次数",
});
chart.axis("keywords", {
tickLine: {
alignTick: false,
},
});
chart.axis("score", false);
chart.tooltip({
showMarkers: false,
});
chart.interval().position("keywords*score").color("#f59b99");
chart.interaction("element-active");
this.hotWordsChart = chart;
this.search();
},
},
mounted() {
this.init();
},
};
});
chart.axis("score", false);
chart.tooltip({
showMarkers: false,
});
chart.interval().position("keywords*score").color("#f59b99");
chart.interaction("element-active");
hotWordsChart.value = chart;
search();
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -1,7 +1,7 @@
<template>
<div class="layout">
<el-form
ref="formValidate"
ref="formRef"
label-width="150px"
label-position="right"
:model="formValidate"
@@ -49,65 +49,60 @@
</div>
</template>
<script>
<script setup>
import { ref } from "vue";
import { setSetting, getSetting } from "@/api/index";
import { Message } from "@/utils/message";
export default {
data() {
return {
ruleValidate: {},
formValidate: {
saveNum: 1,
hotWordsSettingItems: [
{
keywords: "",
score: 1,
},
],
},
};
},
created() {
this.init();
},
methods: {
addSetItem() {
if (this.formValidate.hotWordsSettingItems.length >= 5) {
this.$Message.error("最多5个热词项");
} else {
this.formValidate.hotWordsSettingItems.push({
keywords: "",
score: 1,
});
}
const formRef = ref(null);
const ruleValidate = ref({});
const formValidate = ref({
saveNum: 1,
hotWordsSettingItems: [
{
keywords: "",
score: 1,
},
submit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.setupSetting();
} else {
this.$Message.error("请正确填写内容!");
}
});
},
setupSetting() {
setSetting("HOT_WORDS", this.formValidate).then((res) => {
if (res.success) {
this.$Message.success("保存成功!");
} else {
this.$Message.error("保存失败!");
}
this.init();
});
},
async init() {
const res = await getSetting("HOT_WORDS");
if (res.success && res.result) {
this.formValidate = res.result;
}
},
},
};
],
});
init();
function addSetItem() {
if (formValidate.value.hotWordsSettingItems.length >= 5) {
Message.error("最多5个热词项");
} else {
formValidate.value.hotWordsSettingItems.push({
keywords: "",
score: 1,
});
}
}
function submit(name) {
formRef.value.validate((valid) => {
if (valid) {
setupSetting();
} else {
Message.error("请正确填写内容!");
}
});
}
function setupSetting() {
setSetting("HOT_WORDS", formValidate.value).then((res) => {
if (res.success) {
Message.success("保存成功!");
} else {
Message.error("保存失败!");
}
init();
});
}
async function init() {
const res = await getSetting("HOT_WORDS");
if (res.success && res.result) {
formValidate.value = res.result;
}
}
</script>
<style lang="scss" scoped>

View File

@@ -22,91 +22,88 @@
</div>
</template>
<script>
<script setup>
import { ref, shallowRef, watch, onMounted } from "vue";
import { Chart } from "@antv/g2";
import { getHotWordsStatistics } from "@/api/index";
import affixTime from "@/components/affix-time";
export default {
components: {
affixTime,
const params = ref({
searchType: "LAST_SEVEN",
year: "",
month: "",
top: 50,
});
// G2 Chart 实例不做深度响应式(避免 Proxy 包装破坏图表内部状态)
const hotWordsChart = shallowRef("");
const hotWordsData = ref([]);
const hasData = ref(true);
// 历史遗留:原 watch 监听的 year 从未在 data 中声明,此处补齐声明以保留监听行为
const year = ref("");
watch(
params,
() => {
search();
},
data() {
return {
params: {
searchType: "LAST_SEVEN",
year: "",
month: "",
top: 50,
{ deep: true, immediate: true }
);
watch(year, (val) => {
params.value.year = new Date(val).getFullYear();
});
function clickBreadcrumb(val) {
params.value = { ...params.value, ...val };
}
async function search() {
const res = await getHotWordsStatistics(params.value);
if (res.success) {
hotWordsData.value = res.result || [];
hasData.value = hotWordsData.value.length > 0;
if (!hasData.value) {
return;
}
if (!hotWordsChart.value) {
hotWordsChart.value = new Chart({
container: "container",
autoFit: true,
height: 500,
padding: [50, 20, 50, 20],
});
}
init();
}
}
function init() {
if (hotWordsChart.value) {
hotWordsChart.value.data(hotWordsData.value);
hotWordsChart.value.scale("score", {
alias: "搜索次数",
});
hotWordsChart.value.axis("keywords", {
tickLine: {
alignTick: false,
},
hotWordsChart: "",
hotWordsData: [],
hasData: true,
};
},
watch: {
params: {
handler() {
this.search();
},
deep: true,
immediate: true,
},
year(val) {
this.params.year = new Date(val).getFullYear();
},
},
methods: {
clickBreadcrumb(val) {
this.params = { ...this.params, ...val };
},
async search() {
const res = await getHotWordsStatistics(this.params);
if (res.success) {
this.hotWordsData = res.result || [];
this.hasData = this.hotWordsData.length > 0;
if (!this.hasData) {
return;
}
if (!this.hotWordsChart) {
this.hotWordsChart = new Chart({
container: "container",
autoFit: true,
height: 500,
padding: [50, 20, 50, 20],
});
}
this.init();
}
},
init() {
if (this.hotWordsChart) {
this.hotWordsChart.data(this.hotWordsData);
this.hotWordsChart.scale("score", {
alias: "搜索次数",
});
});
hotWordsChart.value.axis("score", false);
this.hotWordsChart.axis("keywords", {
tickLine: {
alignTick: false,
},
});
this.hotWordsChart.axis("score", false);
hotWordsChart.value.tooltip({
showMarkers: false,
});
hotWordsChart.value.interval().position("keywords*score").color("#409EFF");
hotWordsChart.value.interaction("element-active");
this.hotWordsChart.tooltip({
showMarkers: false,
});
this.hotWordsChart.interval().position("keywords*score").color("#409EFF");
this.hotWordsChart.interaction("element-active");
hotWordsChart.value.render();
}
}
this.hotWordsChart.render();
}
},
},
mounted() {
this.init();
},
};
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -33,7 +33,7 @@
append-to-body
destroy-on-close
>
<el-form ref="form" :model="form" label-width="100px" :rules="formValidate">
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
<el-form-item label="热词" prop="keywords">
<el-input v-model="form.keywords" clearable style="width: 100%" />
</el-form-item>
@@ -49,85 +49,91 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { getHotWords, setHotWords, deleteHotWords } from "@/api/index";
import { regular } from "@/utils";
import { Message, Modal } from "@/utils/message";
import { useAppStore } from "@/stores/app";
export default {
name: "todayHotWords",
data() {
return {
submitLoading: false,
modalTitle: "",
loading: true,
modalVisible: false,
form: {
keywords: "",
point: 0,
},
data: [],
formValidate: {
keywords: [regular.REQUIRED, regular.VARCHAR20],
point: [regular.REQUIRED, regular.NUMBER],
},
};
},
methods: {
init() {
this.getDataList();
},
getDataList() {
this.loading = true;
getHotWords().then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result;
this.$store.state.app.hotWords = this.data;
}
});
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
this.submitLoading = true;
setHotWords(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
}
});
},
add(words) {
this.modalTitle = "设置热词";
this.form.keywords = words || "";
this.form.point = 1;
this.modalVisible = true;
},
deleteWords(words) {
this.$Modal.confirm({
title: "是否确定删除热词",
content: "您确定要删除此热词吗?",
okText: "确定",
cancelText: "取消",
onOk: () => {
deleteHotWords({ words }).then((res) => {
if (res.success) {
this.$Message.success("删除成功");
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();
},
defineOptions({ name: "todayHotWords" });
const appStore = useAppStore();
const submitLoading = ref(false);
const modalTitle = ref("");
const loading = ref(true);
const modalVisible = ref(false);
const form = reactive({
keywords: "",
point: 0,
});
const data = ref([]);
const formValidate = {
keywords: [regular.REQUIRED, regular.VARCHAR20],
point: [regular.REQUIRED, regular.NUMBER],
};
const formRef = ref(null);
function init() {
getDataList();
}
function getDataList() {
loading.value = true;
getHotWords().then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result;
appStore.hotWords = data.value;
}
});
}
function handleSubmit() {
formRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
setHotWords(form).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success("操作成功");
getDataList();
modalVisible.value = false;
}
});
}
});
}
function add(words) {
modalTitle.value = "设置热词";
form.keywords = words || "";
form.point = 1;
modalVisible.value = true;
}
function deleteWords(words) {
Modal.confirm({
title: "是否确定删除热词",
content: "您确定要删除此热词吗?",
okText: "确定",
cancelText: "取消",
onOk: () => {
deleteHotWords({ words }).then((res) => {
if (res.success) {
Message.success("删除成功");
getDataList();
}
});
},
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -88,61 +88,61 @@
</div>
</template>
<script>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { getMemberFeedback, getMemberFeedbackDetail } from "@/api/other";
export default {
name: "feedback",
data() {
return {
loading: true,
form: {},
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
},
detailVisible: false,
data: [],
total: 0,
};
},
methods: {
init() {
this.getFeedback();
},
changePage() {
this.getFeedback();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getFeedback();
},
getFeedback() {
this.loading = true;
getMemberFeedback(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
detail(v) {
getMemberFeedbackDetail(v.id).then((res) => {
this.loading = false;
if (res.success) {
this.form = res.result;
this.detailVisible = true;
}
});
},
},
mounted() {
this.init();
},
};
defineOptions({ name: "feedback" });
// 模板 el-table / el-form 的模板 ref(对应原 $refs.table / $refs.formValidate)
const table = ref(null);
const formValidate = ref(null);
const loading = ref(true);
const form = ref({});
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
});
const detailVisible = ref(false);
const data = ref([]);
const total = ref(0);
function init() {
getFeedback();
}
function changePage() {
getFeedback();
}
function changePageSize() {
searchForm.pageNumber = 1;
getFeedback();
}
function getFeedback() {
loading.value = true;
getMemberFeedback(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
function detail(v) {
getMemberFeedbackDetail(v.id).then((res) => {
loading.value = false;
if (res.success) {
form.value = res.result;
detailVisible.value = true;
}
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>
.ops a {