Commit b5334e6d authored by lijin's avatar lijin

增加标题组管理页面

parent 77df13d1
import axios from 'axios'
// 获取所有标题组
export function getTitleGroupList() {
return axios.get(process.env.PUTIN_API + '/title-groups')
.then(response => {
return response.data
})
}
// 获取单个标题组
export function getTitleGroupById(id) {
return axios.get(`${process.env.PUTIN_API}/title-groups/${id}`)
.then(response => {
return response.data
})
}
// 创建标题组
export function createTitleGroup(data) {
return axios.post(`${process.env.PUTIN_API}/title-groups`, data)
.then(response => {
return response.data
})
}
// 更新标题组
export function updateTitleGroup(id, data) {
return axios.put(`${process.env.PUTIN_API}/title-groups/${id}`, data)
.then(response => {
return response.data
})
}
// 删除标题组
export function deleteTitleGroup(id) {
return axios.delete(`${process.env.PUTIN_API}/title-groups/${id}`)
.then(response => {
return response.data
})
}
......@@ -48,9 +48,14 @@ export default {
async fetchOptions() {
try {
const response = await axios.get(process.env.PUTIN_API + '/location-groups')
this.options = response.data
if (response.data.status === 200) {
this.options = response.data.result.data
} else {
this.$message.error('获取地域组失败')
}
} catch (error) {
console.error('Failed to fetch location groups:', error)
this.$message.error('获取地域组失败:' + error.message)
}
},
handleChange(values) {
......
......@@ -48,7 +48,11 @@ export default {
async fetchOptions() {
try {
const response = await axios.get(process.env.PUTIN_API + '/title-groups')
this.options = response.data
if (response.data.status === 200) {
this.options = response.data.result.data || []
} else {
console.error('Failed to fetch title groups: API returned non-200 status')
}
} catch (error) {
console.error('Failed to fetch title groups:', error)
}
......
<template>
<div class="text-table-selecter-container">
<!-- Filter section -->
<div class="filter-section">
<el-form :inline="true" :model="condition" class="filter-form">
<el-form-item label="关联应用">
<el-select v-model="condition.appId" placeholder="关联应用" @change="fetchData" filterable clearable>
<el-option
v-for="item in selectApps"
:key="item.id"
:label="item.label"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="日期范围">
<zx-date-picker end-date="today" interval-days="7" v-model="dateTime" @change="fetchData" clearable></zx-date-picker>
</el-form-item>
<el-form-item label="文案类型">
<el-select
v-model="condition.type"
placeholder="文案类型"
@change="fetchData"
clearable
:disabled="!!fixedType"
>
<el-option label="标题" value="1"></el-option>
<el-option label="描述" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="分类">
<app-category-selector v-model="condition.category" @change="fetchData" />
</el-form-item>
</el-form>
</div>
<el-divider></el-divider>
<!-- Table Header actions -->
<div class="header-actions">
<div class="selected-info" v-if="selectedRows.length > 0">
已选择 <span class="selected-count">{{ selectedRows.length }}</span> 条文案
<el-button size="mini" type="text" @click="clearSelection">清空选择</el-button>
</div>
<div class="header-right">
<el-button icon="el-icon-refresh" @click="fetchData">刷新</el-button>
</div>
</div>
<!-- Main table -->
<el-table
ref="multipleTable"
:data="tableData"
border
style="width: 100%;"
v-loading="loading"
row-key="id"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="text" label="文案内容" min-width="350">
<template slot-scope="scope">
<div class="text-content">{{ scope.row.text }}</div>
</template>
</el-table-column>
<el-table-column prop="type" label="文案类型" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.type === 1 ? 'primary' : 'success'">
{{ scope.row.type === 1 ? '标题' : '描述' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="datadate" label="创建时间" width="180"></el-table-column>
</el-table>
<!-- Pagination -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="total"
layout="total, prev, pager, next, sizes">
</el-pagination>
</div>
</div>
</template>
<script>
import {
getBusinText,
getSelectApps
} from "@/api/cloud";
import AppCategorySelector from "@/components/Selectors/AppCategorySelector";
export default {
name: 'TextTableSelecter',
components: {
AppCategorySelector
},
props: {
// 双向绑定的选中文案数组
value: {
type: Array,
default: () => []
},
// 固定的文案类型,如果传入则不可更改
type: {
type: String,
default: ""
}
},
data() {
return {
// 表格数据
tableData: [],
loading: false,
total: 0,
currentPage: 1,
pageSize: 20,
// 筛选条件
condition: {
appId: "",
type: "",
startDate: "",
endDate: "",
category: ""
},
// 应用选择器数据
selectApps: [],
// 选中的行数据
selectedRows: [],
// 已选中的行ID映射,用于保存跨页选择
selectedRowMap: {}
};
},
computed: {
dateTime: {
get() {
if (this.condition.startDate && this.condition.endDate) {
return [this.condition.startDate, this.condition.endDate];
} else {
return [];
}
},
set(v) {
if (v && v.length === 2) {
this.condition.startDate = v[0];
this.condition.endDate = v[1];
} else {
this.condition.startDate = "";
this.condition.endDate = "";
}
}
},
// 固定的文案类型
fixedType() {
return this.type || "";
}
},
watch: {
// 监听外部value变化,更新内部选中状态
value: {
handler(newVal) {
console.log('Value changed:', newVal);
// 更新选中的行ID映射
this.selectedRowMap = {};
if (Array.isArray(newVal)) {
newVal.forEach(item => {
if (item && item.id) {
this.selectedRowMap[item.id] = item;
}
});
}
// 如果表格已加载,更新表格选中状态
this.$nextTick(() => {
this.updateTableSelection();
});
// 同步更新selectedRows
this.selectedRows = Object.values(this.selectedRowMap);
},
immediate: true,
deep: true
},
// 监听固定类型变化
type: {
handler(newVal) {
if (newVal) {
this.condition.type = newVal;
this.fetchData();
}
},
immediate: true
}
},
created() {
this.fetchApps();
this.fetchData();
// 确保初始化时选中状态正确
if (Array.isArray(this.value) && this.value.length > 0) {
console.log('Initial value in created:', this.value);
this.value.forEach(item => {
if (item && item.id) {
this.selectedRowMap[item.id] = item;
}
});
this.selectedRows = Object.values(this.selectedRowMap);
}
},
mounted() {
// 确保表格渲染后选中状态正确
this.$nextTick(() => {
this.updateTableSelection();
console.log('Table mounted, selection updated');
});
},
methods: {
// 获取应用列表
async fetchApps() {
try {
const params = {
platformId: 5,
menuCode: "game.Overview,android",
};
const response = await getSelectApps(params);
console.log("相应", response)
if (response.status === 200) {
this.selectApps = response.result.data || [];
}
} catch (error) {
console.error("获取应用列表失败:", error);
this.$message.error("获取应用列表失败");
}
},
// 获取文案列表
async fetchData() {
this.loading = true;
try {
const params = {
page: this.currentPage,
size: this.pageSize,
appId: this.condition.appId || '',
type: this.condition.type || '',
startDate: this.condition.startDate || '',
endDate: this.condition.endDate || '',
category: this.condition.category || ''
};
const response = await getBusinText(params);
if (response && response.result) {
this.tableData = response.result.data.content || [];
this.total = response.result.data.total || 0;
// 更新表格选中状态
this.$nextTick(() => {
this.updateTableSelection();
});
}
} catch (error) {
console.error("获取文案列表失败:", error);
this.$message.error("获取文案列表失败");
} finally {
this.loading = false;
}
},
// 分页处理
handleSizeChange(val) {
this.pageSize = val;
this.fetchData();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchData();
},
// 处理表格选择变化
handleSelectionChange(rows) {
console.log('Selection changed:', rows);
// 更新当前页的选中状态到映射中
rows.forEach(row => {
this.selectedRowMap[row.id] = row;
});
// 当前页中未被选中的行,从映射中移除
this.tableData.forEach(row => {
if (!rows.some(selectedRow => selectedRow.id === row.id)) {
delete this.selectedRowMap[row.id];
}
});
// 更新选中的行数组
this.selectedRows = Object.values(this.selectedRowMap);
console.log('Updated selectedRows:', this.selectedRows);
// 向父组件发送更新
// this.$emit('input', this.selectedRows);
this.$emit('change', this.selectedRows);
},
// 更新表格选中状态
updateTableSelection() {
if (!this.$refs.multipleTable) return;
// 先清空所有选择
this.$refs.multipleTable.clearSelection();
// 根据selectedRowMap选中对应行
this.$nextTick(() => {
this.tableData.forEach(row => {
if (this.selectedRowMap[row.id]) {
this.$refs.multipleTable.toggleRowSelection(row, true);
}
});
});
},
// 清空选择
clearSelection() {
this.selectedRowMap = {};
this.selectedRows = [];
if (this.$refs.multipleTable) {
this.$refs.multipleTable.clearSelection();
}
this.$emit('input', []);
this.$emit('change', []);
console.log('Selection cleared');
}
}
};
</script>
<style scoped>
.text-table-selecter-container {
padding: 10px;
}
.filter-section {
margin-bottom: 10px;
}
.header-actions {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.selected-info {
display: flex;
align-items: center;
}
.header-right {
margin-left: auto;
}
.selected-count {
color: #409EFF;
font-weight: bold;
margin: 0 5px;
}
.text-content {
white-space: pre-wrap;
word-break: break-all;
}
.pagination-container {
margin-top: 15px;
text-align: right;
}
</style>
......@@ -138,6 +138,12 @@ export const constantRouterMap = [
component: () => import('@/views/locationGroup/LocationGroupManage'),
meta: { title: '地域组管理' }
},
{
path: '/assetManagement/title-group',
name: 'assetManagement.title-group',
component: () => import('@/views/titleGroup/TitleGroupManage'),
meta: { title: '标题组管理' }
},
]
},
......
......@@ -337,9 +337,9 @@ export default {
])
app.data.forEach(item => this.groupNameMaps.app.set(item.id, item.name))
location.data.forEach(item => this.groupNameMaps.location.set(item.id, item.name))
location.data.result.data.forEach(item => this.groupNameMaps.location.set(item.id, item.name))
material.data.forEach(item => this.groupNameMaps.material.set(item.id, item.name))
title.data.forEach(item => this.groupNameMaps.title.set(item.id, item.name))
title.data.result.data.forEach(item => this.groupNameMaps.title.set(item.id, item.name))
description.data.forEach(item => this.groupNameMaps.description.set(item.id, item.name))
} catch (error) {
console.error('获取组名称失败:', error)
......
......@@ -316,7 +316,7 @@ export default {
menuCode: "game.Overview,android",
};
const response = await getSelectApps(params);
if (response && response.result) {
if (response.status === 200) {
this.selectApps = response.result.data || [];
}
} catch (error) {
......@@ -340,6 +340,7 @@ export default {
};
const response = await getBusinText(params);
if (response && response.result) {
this.tableData = response.result.data.content || [];
this.total = response.result.data.total || 0;
......
......@@ -30,7 +30,7 @@
<el-menu-item index="/assetManagement/app-group">产品组管理</el-menu-item>
<el-menu-item index="/assetManagement/location-group">地域组管理</el-menu-item>
<el-menu-item index="/assetManagement/createDelivery">素材组</el-menu-item>
<el-menu-item index="/assetManagement/createDelivery">标题组</el-menu-item>
<el-menu-item index="/assetManagement/title-group">标题组管理</el-menu-item>
<el-menu-item index="/assetManagement/createDelivery">描述组</el-menu-item>
</el-submenu>
......
<template>
<div class="title-group-container">
<!-- 表格头部操作 -->
<div class="header-actions">
<el-button type="primary" @click="showAddDialog">新增标题组</el-button>
<div class="header-right">
<el-button icon="el-icon-refresh" @click="fetchData">刷新</el-button>
</div>
</div>
<!-- 主表格 -->
<el-table
:data="tableData"
border
style="width: 100%;"
v-loading="loading">
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="名称" width="250"></el-table-column>
<el-table-column label="标题" min-width="300">
<template slot-scope="scope">
<div v-for="(title, index) in scope.row.titles" :key="index" class="title-item">
{{ title }}
</div>
<div v-if="!scope.row.titles || scope.row.titles.length === 0" class="no-titles">
暂无标题
</div>
</template>
</el-table-column>
<!-- <el-table-column prop="createdAt" label="创建时间" width="180">
<template slot-scope="scope">
{{ formatDate(scope.row.createdAt) }}
</template> -->
</el-table-column>
<el-table-column label="操作" align="center" width="180">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="handleEdit(scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="total"
layout="total, prev, pager, next, sizes">
</el-pagination>
</div>
<!-- 新增/编辑对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="60%">
<el-form :model="form" :rules="rules" ref="form" label-width="120px">
<el-form-item label="标题组名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="选择标题" prop="titles">
<div class="title-selector-container">
<div class="selected-titles" v-if="form.titles && form.titles.length > 0">
<div class="selected-title-header">
<span>已选择 {{ form.titles.length }} 个标题</span>
<el-button type="text" @click="form.titles = []">清空</el-button>
</div>
<el-tag
v-for="(title, index) in form.titles"
:key="index"
closable
@close="removeTitle(index)"
class="title-tag">
{{ title }}
</el-tag>
</div>
<el-button type="primary" size="small" @click="showTitleSelectorDialog">添加标题</el-button>
</div>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" :rows="3"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</div>
</el-dialog>
<!-- 标题选择器对话框 -->
<el-dialog
title="选择标题"
:visible.sync="titleSelectorDialogVisible"
width="80%"
append-to-body>
<text-table-selecter
@change="handleTitleSelectionChange"
type="1">
</text-table-selecter>
<div slot="footer" class="dialog-footer">
<el-button @click="titleSelectorDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmTitleSelection">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import TextTableSelecter from '@/components/TextTableSelecter'
import { getTitleGroupList, createTitleGroup, updateTitleGroup, deleteTitleGroup } from '@/api/titleGroup'
export default {
name: 'TitleGroupManage',
components: {
TextTableSelecter
},
data() {
return {
loading: false,
tableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
dialogVisible: false,
dialogTitle: '',
titleSelectorDialogVisible: false,
selectedTitles: [], // 用于TextTableSelecter组件的选中项
form: {
id: null,
name: '',
titles: [],
remark: ''
},
rules: {
name: [
{ required: true, message: '请输入标题组名称', trigger: 'blur' }
]
}
}
},
created() {
this.fetchData()
},
methods: {
// 获取标题组列表
fetchData() {
this.loading = true
getTitleGroupList()
.then(response => {
this.loading = false
if (response.status === 200) {
this.tableData = response.result.data || []
this.total = this.tableData.length
} else {
this.$message.error('获取标题组列表失败')
}
})
.catch(error => {
this.loading = false
console.error('获取标题组列表失败:', error)
this.$message.error('获取标题组列表失败: ' + error.message)
})
},
// 格式化日期
formatDate(date) {
if (!date) return ''
const d = new Date(date)
return d.toLocaleString()
},
// 分页处理
handleSizeChange(val) {
this.pageSize = val
this.fetchData()
},
handleCurrentChange(val) {
this.currentPage = val
this.fetchData()
},
// 显示新增对话框
showAddDialog() {
this.dialogTitle = '新增标题组'
this.form = {
id: null,
name: '',
titles: [],
remark: ''
}
this.dialogVisible = true
// 重置表单验证
if (this.$refs.form) {
this.$refs.form.resetFields()
}
},
// 处理编辑
handleEdit(row) {
this.dialogTitle = '编辑标题组'
// 深拷贝避免直接修改表格数据
this.form = JSON.parse(JSON.stringify(row))
this.dialogVisible = true
// 重置表单验证
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate()
}
})
},
handleTitleSelectionChange(data){
this.selectedTitles = data
},
// 处理删除
handleDelete(row) {
this.$confirm('此操作将永久删除该标题组, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteTitleGroup(row.id)
.then(response => {
if (response.status === 200) {
this.$message.success('删除成功')
this.fetchData()
} else {
this.$message.error('删除失败')
}
})
.catch(error => {
console.error('删除标题组失败:', error)
this.$message.error('删除标题组失败: ' + error.message)
})
}).catch(() => {
})
},
// 提交表单
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
const isEdit = !!this.form.id
const formData = { ...this.form }
const apiCall = isEdit
? updateTitleGroup(formData.id, formData)
: createTitleGroup(formData)
apiCall
.then(response => {
if (response.status === 200) {
this.$message.success(isEdit ? '更新成功' : '创建成功')
this.dialogVisible = false
this.fetchData()
} else {
this.$message.error(isEdit ? '更新失败' : '创建失败')
}
})
.catch(error => {
console.error(isEdit ? '更新标题组失败:' : '创建标题组失败:', error)
this.$message.error((isEdit ? '更新' : '创建') + '标题组失败: ' + error.message)
})
} else {
return false
}
})
},
// 显示标题选择器对话框
showTitleSelectorDialog() {
this.selectedTitles = []
this.titleSelectorDialogVisible = true
},
// 确认标题选择
confirmTitleSelection() {
// 提取文本内容并添加到标题列表
const newTitles = this.selectedTitles.map(item => item.text)
// 合并去重
const uniqueTitles = [...new Set([...this.form.titles, ...newTitles])]
this.form.titles = uniqueTitles
this.titleSelectorDialogVisible = false
},
// 移除标题
removeTitle(index) {
this.form.titles.splice(index, 1)
}
}
}
</script>
<style scoped>
.title-group-container {
padding: 20px;
}
.header-actions {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.title-item {
margin-bottom: 5px;
line-height: 1.5;
word-break: break-all;
}
.no-titles {
color: #999;
font-style: italic;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.title-selector-container {
margin-bottom: 10px;
}
.selected-titles {
margin-bottom: 10px;
padding: 10px;
border: 1px solid #e6e6e6;
border-radius: 4px;
background-color: #f9f9f9;
}
.selected-title-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
}
.title-tag {
margin-right: 5px;
margin-bottom: 5px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment