Commit 83007dea authored by lijin's avatar lijin

增加素材组管理功能

parent 2b9434c0
import request from '@/utils/request'
// 获取所有素材组
export function getAllMaterialGroups() {
return request({
url: `${process.env.PUTIN_API}/material-groups`,
method: 'get'
})
}
// 根据ID获取素材组
export function getMaterialGroupById(id) {
return request({
url: `${process.env.PUTIN_API}/material-groups/${id}`,
method: 'get'
})
}
// 创建素材组
export function createMaterialGroup(data) {
return request({
url: `${process.env.PUTIN_API}/material-groups`,
method: 'post',
data
})
}
// 更新素材组
export function updateMaterialGroup(id, data) {
return request({
url: `${process.env.PUTIN_API}/material-groups/${id}`,
method: 'put',
data
})
}
// 删除素材组
export function deleteMaterialGroup(id) {
return request({
url: `${process.env.PUTIN_API}/material-groups/${id}`,
method: 'delete'
})
}
<template>
<div class="tag-transfer-container">
<el-row :gutter="20">
<!-- 左侧树形结构 -->
<el-col :span="10">
<el-card class="tree-card">
<div slot="header" class="card-header">
<span>可选标签</span>
<div class="search-input">
<el-input
v-model="searchQuery"
placeholder="搜索标签"
prefix-icon="el-icon-search"
clearable
@input="filterTree"
></el-input>
</div>
</div>
<!-- 使用v-if强制重新渲染树组件 -->
<el-tree
v-if="treeKey"
ref="tree"
:key="treeKey"
:data="treeData"
:props="defaultProps"
node-key="id"
show-checkbox
:filter-node-method="filterNode"
:default-checked-keys="selectedKeys"
@check="handleCheck"
>
</el-tree>
</el-card>
</el-col>
<!-- 右侧已选标签 -->
<el-col :span="10">
<el-card class="selected-card">
<div slot="header" class="card-header">
<span>已选标签</span>
<el-button type="text" @click="clearSelected">清空</el-button>
</div>
<div class="selected-tags" v-if="selectedTags.length > 0">
<el-tag
v-for="tag in selectedTags"
:key="tag.id"
closable
@close="removeTag(tag.id)"
class="tag-item"
>
{{ getTagPath(tag) }}
</el-tag>
</div>
<div v-else class="no-data">暂无选中标签</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getAllMaterialTags } from '@/api/materialTag'
export default {
name: 'TagTransfer',
props: {
value: {
type: Array,
default: () => []
}
},
// 强制组件重新渲染
beforeUpdate() {
console.log('TagTransfer beforeUpdate, value:', this.value)
},
// 添加updated钩子检测值变化
updated() {
console.log('TagTransfer updated, value:', this.value, 'selectedKeys:', this.selectedKeys)
},
data() {
return {
allTags: [],
searchQuery: '',
defaultProps: {
children: 'children',
label: 'name'
},
selectedKeys: [],
tagsMap: {},
treeKey: 1 // 树组件的key,用于强制重新渲染
}
},
computed: {
treeData() {
return this.buildTreeData()
},
selectedTags() {
return this.selectedKeys.map(id => this.tagsMap[id]).filter(Boolean)
}
},
watch: {
value: {
immediate: true,
deep: true,
handler(newVal) {
console.log('TagTransfer value changed:', newVal)
if (!Array.isArray(newVal)) {
console.warn('TagTransfer received non-array value:', newVal)
this.selectedKeys = []
} else {
// 使用JSON序列化确保引用断开
this.selectedKeys = JSON.parse(JSON.stringify(newVal))
}
// 增加treeKey强制树组件重新渲染
this.treeKey++
console.log('强制重新渲染树组件, treeKey:', this.treeKey)
// 等待树组件重新渲染后再设置选中状态
this.$nextTick(() => {
if (this.$refs.tree) {
console.log('Setting checked keys in watcher:', this.selectedKeys)
this.$refs.tree.setCheckedKeys(this.selectedKeys)
}
})
}
}
},
created() {
this.fetchTags()
},
// 组件更新时的生命周期钩子
updated() {
console.log('TagTransfer updated, selectedKeys:', this.selectedKeys)
},
methods: {
// 获取标签数据
fetchTags() {
getAllMaterialTags().then(response => {
if (response.status === 200 && response.result && response.result.data) {
this.allTags = response.result.data
// 创建标签映射关系
this.tagsMap = {}
this.allTags.forEach(tag => {
this.tagsMap[tag.id] = tag
})
// 增加treeKey强制树组件重新渲染
this.treeKey++
console.log('标签加载完成,强制重新渲染树组件, treeKey:', this.treeKey)
// 等待树组件重新渲染后再设置选中状态
this.$nextTick(() => {
if (this.$refs.tree) {
console.log('设置选中标签:', this.selectedKeys)
this.$refs.tree.setCheckedKeys(this.selectedKeys)
}
})
}
}).catch(error => {
console.error('获取标签失败:', error)
this.$message.error('获取标签失败')
})
},
// 构建树形数据
buildTreeData() {
// 根据父子关系构建树
const rootNodes = this.allTags.filter(tag => !tag.parentId)
return this.buildTree(rootNodes)
},
// 递归构建树
buildTree(nodes) {
return nodes.map(node => {
const children = this.allTags.filter(tag => tag.parentId === node.id)
const treeNode = {
id: node.id,
name: node.name,
parentId: node.parentId
}
if (children.length > 0) {
treeNode.children = this.buildTree(children)
}
return treeNode
})
},
// 过滤节点
filterNode(value, data) {
if (!value) return true
return data.name.toLowerCase().includes(value.toLowerCase())
},
// 过滤树
filterTree() {
this.$refs.tree.filter(this.searchQuery)
},
// 处理选中变化
handleCheck(data, checked) {
console.log('树选中变化:', checked)
// 使用JSON序列化确保断开引用
this.selectedKeys = JSON.parse(JSON.stringify(checked.checkedKeys))
// 使用setTimeout确保在当前事件循环结束后触发事件
setTimeout(() => {
this.$emit('input', this.selectedKeys)
this.$emit('change', this.selectedKeys)
}, 0)
},
// 移除标签
removeTag(tagId) {
this.selectedKeys = this.selectedKeys.filter(id => id !== tagId)
// 增加treeKey强制树组件重新渲染
this.treeKey++
// 等待树组件重新渲染后再设置选中状态
this.$nextTick(() => {
if (this.$refs.tree) {
this.$refs.tree.setCheckedKeys(this.selectedKeys)
}
// 使用setTimeout确保在当前事件循环结束后触发事件
setTimeout(() => {
const selectedKeysToEmit = JSON.parse(JSON.stringify(this.selectedKeys))
this.$emit('input', selectedKeysToEmit)
this.$emit('change', selectedKeysToEmit)
}, 0)
})
},
// 清空选中
clearSelected() {
this.selectedKeys = []
// 增加treeKey强制树组件重新渲染
this.treeKey++
// 等待树组件重新渲染后再设置选中状态
this.$nextTick(() => {
if (this.$refs.tree) {
this.$refs.tree.setCheckedKeys([])
}
// 使用setTimeout确保在当前事件循环结束后触发事件
setTimeout(() => {
this.$emit('input', [])
this.$emit('change', [])
}, 0)
})
},
// 获取标签路径
getTagPath(tag) {
if (!tag) return ''
const path = []
let currentTag = tag
while (currentTag) {
path.unshift(currentTag.name)
if (currentTag.parentId) {
currentTag = this.tagsMap[currentTag.parentId]
} else {
break
}
}
return path.join(' / ')
}
}
}
</script>
<style scoped>
.tag-transfer-container {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.search-input {
width: 150px;
}
.tree-card, .selected-card {
height: 400px;
overflow-y: auto;
}
.selected-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag-item {
margin-right: 8px;
margin-bottom: 8px;
}
.no-data {
color: #909399;
text-align: center;
margin-top: 20px;
}
</style>
......@@ -156,6 +156,12 @@ export const constantRouterMap = [
component: () => import('@/views/materialTag'),
meta: { title: '标签管理' }
},
{
path: '/assetManagement/material-group',
name: 'assetManagement.material-group',
component: () => import('@/views/materialGroup'),
meta: { title: '素材组管理' }
},
]
},
......
......@@ -26,10 +26,10 @@
<!-- 带子菜单的导航项 -->
<el-submenu index="4">
<template slot="title">资产管理</template>
<el-menu-item index="/assetManagement/material-group">素材组管理</el-menu-item>
<el-menu-item index="/assetManagement/copywritingLibrary">文案管理</el-menu-item>
<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/title-group">标题组管理</el-menu-item>
<el-menu-item index="/assetManagement/description-group">描述组管理</el-menu-item>
<el-menu-item index="/assetManagement/material-tag">标签管理</el-menu-item>
......
<template>
<div class="material-group-container">
<!-- 过滤条件 -->
<div class="filter-section">
<el-form :inline="true" :model="condition" class="filter-form">
<el-form-item label="标签">
<material-tag-select
v-model="condition.tags"
@change="handleTagChange"
/>
</el-form-item>
</el-form>
</div>
<el-divider></el-divider>
<!-- 表格头部操作 -->
<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">
<el-tag
v-for="tagId in scope.row.tags || []"
:key="tagId"
size="small"
style="margin: 2px">
{{ getTagName(tagId) }}
</el-tag>
<span v-if="!scope.row.tags || scope.row.tags.length === 0" class="no-tags">
暂无标签
</span>
</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="pagination.currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.pageSize"
:total="pagination.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="tags">
<tag-transfer
:key="dialogVisible + '-' + form.id + '-' + JSON.stringify(form.tags)"
v-model="form.tags"
@change="handleFormTagChange">
</tag-transfer>
</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>
</div>
</template>
<script>
import MaterialTagSelect from '@/components/MaterialTagSelect'
import TagTransfer from '@/components/TagTransfer'
import { getAllMaterialGroups, getMaterialGroupById, createMaterialGroup, updateMaterialGroup, deleteMaterialGroup } from '@/api/materialGroup'
import { getAllMaterialTags } from '@/api/materialTag'
export default {
name: 'MaterialGroupManage',
components: {
MaterialTagSelect,
TagTransfer
},
data() {
return {
// 表格数据
tableData: [],
originalData: [], // 原始完整数据
loading: false,
// 分页
pagination: {
currentPage: 1,
pageSize: 10,
total: 0
},
// 过滤条件
condition: {
tags: []
},
// 对话框
dialogVisible: false,
dialogTitle: '',
form: {
id: null,
name: '',
tags: [],
remark: ''
},
// 表单验证
rules: {
name: [
{ required: true, message: '请输入素材组名称', trigger: 'blur' },
{ min: 1, max: 50, message: '长度在1-50个字符之间', trigger: 'blur' }
]
},
// 标签数据
allTags: [],
tagsMap: {}
}
},
created() {
this.fetchData()
this.fetchAllTags()
},
methods: {
// 获取素材组数据
fetchData() {
this.loading = true
getAllMaterialGroups().then(response => {
this.loading = false
if (response.status === 200 && response.result && response.result.data) {
// 保存原始数据,以便编辑时可以获取完整数据
this.originalData = response.result.data
let filteredData = [...this.originalData]
// 如果有标签过滤条件
if (this.condition.tags && this.condition.tags.length > 0) {
filteredData = filteredData.filter(item => {
if (!item.tags || item.tags.length === 0) return false
return this.condition.tags.some(tagId => item.tags.includes(tagId))
})
}
this.pagination.total = filteredData.length
// 分页处理
const start = (this.pagination.currentPage - 1) * this.pagination.pageSize
const end = start + this.pagination.pageSize
this.tableData = filteredData.slice(start, end)
} else {
this.$message.error('获取素材组数据失败')
}
}).catch(error => {
this.loading = false
console.error('获取素材组失败:', error)
this.$message.error('获取素材组失败: ' + error.message)
})
},
// 获取所有标签
fetchAllTags() {
getAllMaterialTags().then(response => {
if (response.status === 200 && response.result && response.result.data) {
this.allTags = response.result.data
// 创建标签映射
this.tagsMap = {}
this.allTags.forEach(tag => {
this.tagsMap[tag.id] = tag
})
} else {
this.$message.error('获取标签数据失败')
}
}).catch(error => {
console.error('获取标签失败:', error)
this.$message.error('获取标签失败: ' + error.message)
})
},
// 获取标签名称
getTagName(tagId) {
const tag = this.tagsMap[tagId]
return tag ? tag.name : '未知标签'
},
// 获取标签路径
getTagPath(tag) {
if (!tag) return ''
const path = []
let currentTag = tag
while (currentTag) {
path.unshift(currentTag.name)
if (currentTag.parentId) {
currentTag = this.tagsMap[currentTag.parentId]
} else {
break
}
}
return path.join(' / ')
},
// 处理标签变化
handleTagChange() {
this.fetchData()
},
// 重置过滤条件
resetFilter() {
this.condition = {
tags: []
}
this.fetchData()
},
// 处理分页大小变化
handleSizeChange(val) {
this.pagination.pageSize = val
this.fetchData()
},
// 处理页码变化
handleCurrentChange(val) {
this.pagination.currentPage = val
this.fetchData()
},
// 显示新增对话框
showAddDialog() {
// 先重置表单
this.form = {
id: null,
name: '',
tags: [],
remark: ''
}
// 设置对话框标题并显示
this.dialogTitle = '新增素材组'
this.dialogVisible = true
// 重置表单验证
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.resetFields()
}
})
},
// 处理编辑
handleEdit(row) {
// 先重置表单
this.form = {
id: null,
name: '',
tags: [],
remark: ''
}
// 然后设置对话框标题并显示
this.dialogTitle = '编辑素材组'
this.dialogVisible = true
// 从原始数据中获取完整数据
const fullData = this.originalData.find(item => item.id === row.id)
console.log('编辑素材组数据:', fullData || row)
// 设置表单数据
const dataToUse = fullData || row
// 先重置form对象
this.$set(this, 'form', {
id: null,
name: '',
tags: [],
remark: ''
})
// 使用nextTick确保DOM更新后再设置新值
this.$nextTick(() => {
// 使用Vue.set确保响应式更新
this.$set(this, 'form', {
id: dataToUse.id,
name: dataToUse.name,
tags: Array.isArray(dataToUse.tags) ? [...dataToUse.tags] : [],
remark: dataToUse.remark || ''
})
console.log('设置表单数据:', this.form)
})
// 重置表单验证
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.resetFields()
}
})
},
// 处理删除
handleDelete(row) {
this.$confirm('确认删除该素材组吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteMaterialGroup(row.id).then(response => {
if (response.status === 200) {
this.$message.success('删除成功')
this.fetchData()
} else {
this.$message.error('删除失败: ' + response.message)
}
}).catch(error => {
console.error('删除素材组失败:', error)
this.$message.error('删除失败: ' + error.message)
})
}).catch(() => {
// 取消删除
})
},
// 处理表单标签变化
handleFormTagChange(tags) {
console.log('标签变化:', tags)
if (Array.isArray(tags)) {
// 使用JSON序列化确保断开引用
this.form.tags = JSON.parse(JSON.stringify(tags))
} else {
this.form.tags = []
}
// 强制触发数据更新
this.$forceUpdate()
},
// 提交表单
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
const formData = {
name: this.form.name,
tags: this.form.tags,
remark: this.form.remark
}
if (this.form.id) {
// 更新
updateMaterialGroup(this.form.id, formData).then(response => {
if (response.status === 200) {
this.$message.success('更新成功')
this.dialogVisible = false
this.fetchData()
} else {
this.$message.error('更新失败: ' + response.message)
}
}).catch(error => {
console.error('更新素材组失败:', error)
this.$message.error('更新失败: ' + error.message)
})
} else {
// 新增
createMaterialGroup(formData).then(response => {
if (response.status === 200) {
this.$message.success('创建成功')
this.dialogVisible = false
this.fetchData()
} else {
this.$message.error('创建失败: ' + response.message)
}
}).catch(error => {
console.error('创建素材组失败:', error)
this.$message.error('创建失败: ' + error.message)
})
}
} else {
return false
}
})
}
}
}
</script>
<style scoped>
.material-group-container {
padding: 20px;
}
.filter-section {
margin-bottom: 20px;
}
.header-actions {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.no-tags {
color: #999;
font-size: 12px;
}
.tag-item {
margin: 2px;
}
</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