|
@@ -0,0 +1,328 @@
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+/**
|
|
|
|
+ * @Name: keyword-manage-table.vue
|
|
|
|
+ * @Description: 关键词管理表格
|
|
|
|
+ * @Author: Cheney
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+import { nextTick, onMounted, reactive, ref } from 'vue';
|
|
|
|
+import { Plus } from '@element-plus/icons-vue';
|
|
|
|
+import * as api from '../api';
|
|
|
|
+import { ElMessage, FormInstance, FormRules } from 'element-plus';
|
|
|
|
+
|
|
|
|
+interface DataItem {
|
|
|
|
+ id: number;
|
|
|
|
+ modifier_name: string;
|
|
|
|
+ creator_name: string;
|
|
|
|
+ create_datetime: string;
|
|
|
|
+ update_datetime: string;
|
|
|
|
+ description: string | null;
|
|
|
|
+ modifier: string;
|
|
|
|
+ searchTerm: string;
|
|
|
|
+ searchTerm_type: string;
|
|
|
|
+ add_date: string;
|
|
|
|
+ creator: number;
|
|
|
|
+ isEditing: boolean; // 添加isEditing字段
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const SUCCESS_CODE = 2000;
|
|
|
|
+const currentDate = new Date().toISOString().split('T')[0];
|
|
|
|
+
|
|
|
|
+const tableLoading = ref(false);
|
|
|
|
+const tableData = ref<DataItem[]>([]);
|
|
|
|
+const total = ref(0);
|
|
|
|
+const currentPage = ref(1);
|
|
|
|
+const pageSize = ref(10);
|
|
|
|
+
|
|
|
|
+// const searchTermTypeSelect = ref('positive');
|
|
|
|
+const searchTermInpRef = ref();
|
|
|
|
+
|
|
|
|
+const dialogVisible = ref(false);
|
|
|
|
+const formSearchTermInpRef = ref();
|
|
|
|
+const ruleFormRef = ref<FormInstance>();
|
|
|
|
+const ruleForm = reactive({
|
|
|
|
+ searchTerm: '',
|
|
|
|
+ searchTermType: 'positive',
|
|
|
|
+});
|
|
|
|
+const rules = reactive<FormRules<typeof ruleForm>>({
|
|
|
|
+ searchTerm: [{ required: true, validator: checkSearchTerm, trigger: 'blur' }],
|
|
|
|
+ searchTermType: [{ required: true, validator: checkSearchTermType, trigger: 'blur' }],
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ fetchSearchTermList();
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 添加关键词
|
|
|
|
+ */
|
|
|
|
+async function addSearchTerm() {
|
|
|
|
+ const body = {
|
|
|
|
+ searchTerm: ruleForm.searchTerm,
|
|
|
|
+ searchTerm_type: ruleForm.searchTermType,
|
|
|
|
+ add_date: currentDate, // 使用当前日期代替硬编码日期
|
|
|
|
+ };
|
|
|
|
+ try {
|
|
|
|
+ const response = await api.postCreateSearchTerm(body);
|
|
|
|
+ handleResponse(response);
|
|
|
|
+ if (response.code === SUCCESS_CODE) {
|
|
|
|
+ await fetchSearchTermList();
|
|
|
|
+ } else {
|
|
|
|
+ ElMessage.error('添加失败');
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('error:', error);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 删除行数据
|
|
|
|
+ * @param row 行数据
|
|
|
|
+ */
|
|
|
|
+async function handleDelete(row: any) {
|
|
|
|
+ try {
|
|
|
|
+ const response = await api.deleteSearchTerm(row.id);
|
|
|
|
+ handleResponse(response);
|
|
|
|
+ if (response.code === SUCCESS_CODE) {
|
|
|
|
+ tableData.value = tableData.value.filter((item) => item.id !== row.id);
|
|
|
|
+ await fetchSearchTermList();
|
|
|
|
+ } else {
|
|
|
|
+ ElMessage.error('删除失败');
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.log('error:', error);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 获取关键词列表数据
|
|
|
|
+ */
|
|
|
|
+async function fetchSearchTermList() {
|
|
|
|
+ tableLoading.value = true;
|
|
|
|
+ const query = {
|
|
|
|
+ page: currentPage.value,
|
|
|
|
+ limit: pageSize.value,
|
|
|
|
+ searchTerm: '', // TODO: 暂时写死
|
|
|
|
+ };
|
|
|
|
+ try {
|
|
|
|
+ const response = await api.getSearchTermList(query);
|
|
|
|
+ total.value = response.total;
|
|
|
|
+ const responseData: DataItem[] = response.data;
|
|
|
|
+ tableData.value = responseData.map((item) => ({ ...item, isEditing: false }));
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.log('error:', error);
|
|
|
|
+ } finally {
|
|
|
|
+ tableLoading.value = false;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 更新关键词
|
|
|
|
+ * @param row 修改行数据
|
|
|
|
+ */
|
|
|
|
+async function updateSearchTerm(row: any) {
|
|
|
|
+ row.isEditing = false;
|
|
|
|
+ const data = {
|
|
|
|
+ searchTerm: row.searchTerm,
|
|
|
|
+ searchTerm_type: row.searchTerm_type,
|
|
|
|
+ };
|
|
|
|
+ try {
|
|
|
|
+ const response = await api.postUpdateSearchTerm({ data, id: row.id });
|
|
|
|
+ handleResponse(response);
|
|
|
|
+ if (response.code === SUCCESS_CODE) {
|
|
|
|
+ await fetchSearchTermList();
|
|
|
|
+ } else {
|
|
|
|
+ ElMessage.error('更新失败');
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.log('error:', error);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function updateSearchTermType(row: any, newValue: string) {
|
|
|
|
+ const data = {
|
|
|
|
+ searchTerm: row.searchTerm,
|
|
|
|
+ searchTerm_type: newValue,
|
|
|
|
+ };
|
|
|
|
+ try {
|
|
|
|
+ const response = await api.postUpdateSearchTerm({ data, id: row.id });
|
|
|
|
+ handleResponse(response);
|
|
|
|
+ if (response.code === SUCCESS_CODE) {
|
|
|
|
+ await fetchSearchTermList();
|
|
|
|
+ } else {
|
|
|
|
+ ElMessage.error('更新失败');
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.log('error:', error);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 切换编辑状态, 并自动获取焦点
|
|
|
|
+ * @param row 行数据
|
|
|
|
+ */
|
|
|
|
+function handleClick(row: any) {
|
|
|
|
+ row.isEditing = !row.isEditing;
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ searchTermInpRef.value.focus();
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 切换页码
|
|
|
|
+ * @param newPage 新页码
|
|
|
|
+ */
|
|
|
|
+async function handleCurrentChange(newPage: number) {
|
|
|
|
+ currentPage.value = newPage;
|
|
|
|
+ await fetchSearchTermList();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 切换每页条数
|
|
|
|
+ * @param newSize 新每页条数
|
|
|
|
+ */
|
|
|
|
+async function handleSizeChange(newSize: number) {
|
|
|
|
+ currentPage.value = 1;
|
|
|
|
+ pageSize.value = newSize;
|
|
|
|
+ await fetchSearchTermList();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 关闭对话框时清空表单
|
|
|
|
+ * @param done
|
|
|
|
+ */
|
|
|
|
+function handleClose(done: Function) {
|
|
|
|
+ if (ruleFormRef.value) ruleFormRef.value.resetFields();
|
|
|
|
+ done();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 打开对话框, 并自动获取焦点
|
|
|
|
+ */
|
|
|
|
+function handleDialogVisible() {
|
|
|
|
+ dialogVisible.value = true;
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ formSearchTermInpRef.value.focus();
|
|
|
|
+ }, 100);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function checkSearchTerm(rule: any, value: any, callback: any) {
|
|
|
|
+ if (!value) {
|
|
|
|
+ return callback(new Error('请输入关键词'));
|
|
|
|
+ } else {
|
|
|
|
+ callback();
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function checkSearchTermType(rule: any, value: any, callback: any) {
|
|
|
|
+ if (!value) {
|
|
|
|
+ callback(new Error('请选择关键词类型'));
|
|
|
|
+ } else {
|
|
|
|
+ callback();
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function submitForm(formEl: FormInstance | undefined) {
|
|
|
|
+ if (!formEl) return;
|
|
|
|
+ formEl.validate((valid) => {
|
|
|
|
+ if (valid) {
|
|
|
|
+ dialogVisible.value = false;
|
|
|
|
+ addSearchTerm();
|
|
|
|
+ formEl.resetFields();
|
|
|
|
+ } else {
|
|
|
|
+ console.log('error submit!');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function resetForm(formEl: FormInstance | undefined) {
|
|
|
|
+ if (!formEl) return;
|
|
|
|
+ formEl.resetFields();
|
|
|
|
+ dialogVisible.value = false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 统一处理响应
|
|
|
|
+ * @param response 后端返回的响应
|
|
|
|
+ */
|
|
|
|
+function handleResponse(response: any) {
|
|
|
|
+ if (response.code === SUCCESS_CODE) {
|
|
|
|
+ ElMessage.success(response.msg);
|
|
|
|
+ } else {
|
|
|
|
+ ElMessage.error(response.msg || '请联系管理员');
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<template>
|
|
|
|
+ <div>
|
|
|
|
+ <el-card v-loading="tableLoading">
|
|
|
|
+ <el-button plain type="primary" @click="handleDialogVisible" class="mb-2">
|
|
|
|
+ <el-icon>
|
|
|
|
+ <Plus />
|
|
|
|
+ </el-icon>
|
|
|
|
+ 添加关键词
|
|
|
|
+ </el-button>
|
|
|
|
+ <el-table :data="tableData" stripe height="530" style="width: 100%">
|
|
|
|
+ <el-table-column fixed="left" prop="add_date" label="添加日期" width="180" />
|
|
|
|
+ <el-table-column prop="searchTerm" label="关键词">
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
+ <el-input ref="searchTermInpRef" v-if="row.isEditing" v-model="row.searchTerm" @change="updateSearchTerm(row)" />
|
|
|
|
+ <span class="font-bold" v-else>{{ row.searchTerm }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="searchTerm_type" label="关键词类型">
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
+ <el-select v-model="row.searchTerm_type" @change="(value) => updateSearchTermType(row, value)">
|
|
|
|
+ <el-option label="positive" value="positive" />
|
|
|
|
+ <el-option label="negative" value="negative" />
|
|
|
|
+ </el-select>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column fixed="right" label="操作" width="120">
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
+ <el-button link type="primary" size="small" @click="handleClick(row)" v-if="!row.isEditing"> 编辑</el-button>
|
|
|
|
+ <el-button link type="primary" size="small" @click="handleClick(row)" v-else> 取消</el-button>
|
|
|
|
+ <el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ <div class="mt-3.5 flex justify-end">
|
|
|
|
+ <el-pagination
|
|
|
|
+ v-model:current-page="currentPage"
|
|
|
|
+ v-model:page-size="pageSize"
|
|
|
|
+ :page-sizes="[10, 20, 30, 50]"
|
|
|
|
+ layout="sizes, prev, pager, next"
|
|
|
|
+ :total="total"
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
+ @current-change="handleCurrentChange" />
|
|
|
|
+ </div>
|
|
|
|
+ </el-card>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <el-dialog v-model="dialogVisible" title="添加关键词" width="500" :before-close="handleClose">
|
|
|
|
+ <el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" status-icon :rules="rules" label-width="auto">
|
|
|
|
+ <el-form-item label="关键词" prop="searchTerm">
|
|
|
|
+ <el-input ref="formSearchTermInpRef" v-model="ruleForm.searchTerm" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="关键词类型" prop="searchTermType">
|
|
|
|
+ <el-select v-model="ruleForm.searchTermType">
|
|
|
|
+ <el-option label="positive" value="positive" />
|
|
|
|
+ <el-option label="negative" value="negative" />
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <!--<el-form-item>-->
|
|
|
|
+ <!-- <el-button type="primary" @click="submitForm(ruleFormRef)"> Submit</el-button>-->
|
|
|
|
+ <!-- <el-button @click="resetForm(ruleFormRef)">Reset</el-button>-->
|
|
|
|
+ <!--</el-form-item>-->
|
|
|
|
+ </el-form>
|
|
|
|
+ <template #footer>
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
+ <el-button @click="resetForm(ruleFormRef)">取消</el-button>
|
|
|
|
+ <el-button type="primary" @click="submitForm(ruleFormRef)"> 确定</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ </el-dialog>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<style scoped></style>
|