Commit 011cb0f5 authored by lijin's avatar lijin

增加素材标签

parent e6633eb4
<template>
<div class="upload-container">
<el-upload
ref="upload"
class="upload-area"
drag
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileChange"
:accept="accept"
multiple
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传图片和视频文件,且不超过500M</div>
</el-upload>
<div class="preview-list" v-if="fileList.length > 0">
<div v-for="(file, index) in fileList"
:key="index"
class="preview-item">
<div class="delete-btn" @click="handleDelete(index)">
<i class="el-icon-delete"></i>
</div>
<div v-if="file.type.includes('image')" class="preview-content">
<img v-if="file.status == 'success'" :src="file.url" :alt="file.name">
</div>
<div v-else-if="file.type.includes('video')" class="preview-content">
<video
v-if="file.status == 'success'"
:src="file.url"
controls
preload="metadata"
></video>
</div>
<el-progress
v-if="file.status === 'uploading' || file.status === 'waiting'"
type="circle"
:percentage="file.displayProgress"
:width="80"
class="progress"
:format="percentFormat"
></el-progress>
<div v-else-if="file.status === 'success'" class="upload-success">
<i class="el-icon-check"></i>
</div>
<div v-else-if="file.status === 'error'" class="upload-error">
<el-tooltip :content="file.errorMessage" placement="top">
<i class="el-icon-close"></i>
</el-tooltip>
</div>
<div class="file-name">{{ file.name }}</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'FileUpload',
props: {
// 接受的文件类型
accept: {
type: String,
default: 'image/*,video/*'
},
// 上传地址
uploadUrl: {
type: String,
required: true
},
// 额外的上传参数
extraData: {
type: Object,
default: () => ({})
},
// 自定义请求头
headers: {
type: Object,
default: () => ({})
}
},
data() {
return {
fileList: [],
uploadQueue: [],
successFiles: [], // 存储上传成功的文件信息
isProcessing: false, // 是否有上传中的文件
}
},
methods: {
// 重置组件状态
reset() {
this.cancelAllUploads()
this.fileList = []
this.uploadQueue = []
this.successFiles = []
},
handleFileChange(file) {
const fileObj = {
raw: file.raw,
name: file.name,
type: file.raw.type,
url: URL.createObjectURL(file.raw),
status: 'waiting',
progress: 0,
displayProgress: 0,
errorMessage: '',
progressTimer: null,
size: file.raw.size
}
this.fileList.push(fileObj)
this.uploadQueue.push(fileObj)
this.processUploadQueue()
// if (this.uploadQueue.length === 1) {
// this.processUploadQueue()
// }
},
handleDelete(index) {
const file = this.fileList[index]
if (file.progressTimer) {
clearInterval(file.progressTimer)
}
if (file.cancelToken) {
file.cancelToken.cancel('用户取消上传')
}
URL.revokeObjectURL(file.url)
this.fileList.splice(index, 1)
// 如果是已经上传成功的文件,也从成功列表中移除
const successIndex = this.successFiles.findIndex(f => f.url === file.url)
if (successIndex > -1) {
this.successFiles.splice(successIndex, 1)
this.$emit('update:files', [...this.successFiles])
}
const queueIndex = this.uploadQueue.indexOf(file)
if (queueIndex > -1) {
this.uploadQueue.splice(queueIndex, 1)
}
},
startProgressSimulation(file) {
let simulatedProgress = 0
const maxSimulatedProgress = 95
file.progressTimer = setInterval(() => {
if (simulatedProgress < maxSimulatedProgress) {
const remainingProgress = maxSimulatedProgress - simulatedProgress
const increment = Math.random() * (remainingProgress * 0.1)
simulatedProgress += increment
simulatedProgress = Math.min(simulatedProgress, maxSimulatedProgress)
file.displayProgress = Math.floor(simulatedProgress)
}
}, 200 + Math.random() * 300)
},
async processUploadQueue() {
while (this.uploadQueue.length > 0) {
if (this.isProcessing) return
this.isProcessing = true
await this.processSingleFile(this.uploadQueue[0])
this.uploadQueue.shift()
this.isProcessing = false
}
// 所有文件上传完成后触发事件
if (this.uploadQueue.length === 0) {
this.$emit('upload-complete', this.successFiles)
}
},
async processSingleFile(file){
file.status = 'uploading'
this.startProgressSimulation(file)
try {
const response = await this.uploadFile(file)
if (file.progressTimer) {
clearInterval(file.progressTimer)
}
// 校验业务状态码
if (response.data.status !== 200) { // 假设1表示成功
file.displayProgress = 100
file.status = 'error'
this.$emit('upload-error', {
file: file,
error: new Error(response.data.msg || '业务状态异常')
})
console.log("文件上传失败,返回信息:", response.data)
}else{
file.displayProgress = 100
file.status = 'success'
console.log("response: ", response)
// 构建成功文件的信息对象
const successFileInfo = {
name: file.name,
url: response.data.url || file.url,
cdnUrl: response.data.result.data.url,
type: file.type,
size: file.size,
response: response.data
}
console.log("successFileInfo: ", successFileInfo)
// 更新成功文件列表
this.successFiles.push(successFileInfo)
// 触发更新事件
this.$emit('update:files', [...this.successFiles])
this.$emit('upload-success', successFileInfo)
}
} catch (error) {
if (file.progressTimer) {
clearInterval(file.progressTimer)
}
if (!axios.isCancel(error)) {
file.status = 'error'
// file.errorMessage = error.response?.data?.message || '上传失败'
this.$emit('upload-error', {
file: file,
error: error
})
}
}
},
uploadFile(file) {
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('files', file.raw)
// 添加额外的上传参数
Object.entries(this.extraData).forEach(([key, value]) => {
formData.append(key, value)
})
const CancelToken = axios.CancelToken
file.cancelToken = CancelToken.source()
axios.post(
this.uploadUrl,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
...this.headers
},
cancelToken: file.cancelToken.token,
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
file.progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
}
}
}
)
.then(resolve)
.catch(reject)
})
},
cancelAllUploads() {
this.fileList.forEach(file => {
if (file.progressTimer) {
clearInterval(file.progressTimer)
}
if (file.status === 'uploading' && file.cancelToken) {
file.cancelToken.cancel('批量取消上传')
}
})
},
percentFormat(percent){
return percent == 0 ? '等待中': `${percent}%`
},
beforeDestroy() {
this.cancelAllUploads()
this.fileList.forEach(file => {
if (file.url && file.url.startsWith('blob:')) {
URL.revokeObjectURL(file.url)
}
})
}
},
}
</script>
<style scoped>
/* 样式代码保持不变 */
.upload-container {
padding: 20px;
}
.preview-list {
display: flex;
flex-wrap: wrap;
margin-top: 20px;
gap: 20px;
}
.preview-item {
position: relative;
width: 200px;
height: 200px;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
.upload-area{
position: relative;
width: 200px;
height: 200px;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
.preview-content {
width: 100%;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f7fa;
}
.preview-content img,
.preview-content video {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.delete-btn {
position: absolute;
top: 5px;
right: 5px;
width: 24px;
height: 24px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 1;
}
.delete-btn i {
color: white;
font-size: 14px;
}
.progress {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.9);
border-radius: 50%;
padding: 10px;
}
.upload-success {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #67c23a;
font-size: 30px;
}
.upload-error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f56c6c;
font-size: 30px;
cursor: pointer;
}
upload-waiting {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f56c6c;
font-size: 30px;
cursor: pointer;
}
.file-name {
margin-top: auto;
padding: 8px;
width: 100%;
text-align: center;
font-size: 12px;
color: #606266;
background-color: #f5f7fa;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
...@@ -47,7 +47,7 @@ Vue.filter("toFixed", function (price, limit) { ...@@ -47,7 +47,7 @@ Vue.filter("toFixed", function (price, limit) {
}); });
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.path == "/login") { if (to.path == "/login" || to.path == "/materialUpload" || to.path == "/materialTag") {
next(); next();
} else { } else {
gatewayUserRouters().then(res => { gatewayUserRouters().then(res => {
......
...@@ -132,6 +132,20 @@ export const constantRouterMap = [ ...@@ -132,6 +132,20 @@ export const constantRouterMap = [
path: '', path: '',
component: () => import('@/views/layout/components/Sidebar/redirect') component: () => import('@/views/layout/components/Sidebar/redirect')
}] }]
},
{
path: "/materialUpload", // 资产上传
name: "materialUpload",
component: () => import('@/views/createMaterial/MaterialUpload'),
hidden: true
},
{
path: "/materialTag", // 素材标签管理
name: "materialTag",
component: () => import('@/views/materialTag'),
hidden: true
} }
]; ];
......
<template>
<div class="upload-page">
<h2>文件上传</h2>
<!-- 使用上传组件 -->
<file-upload
:upload-url="uploadUrl"
:extra-data="extraData"
:headers="headers"
@update:files="handleFilesUpdate"
@upload-success="handleUploadSuccess"
@upload-error="handleUploadError"
@upload-complete="handleUploadComplete"
ref="uploadComponent"
>
<!-- 自定义上传按钮 -->
<template #trigger>
<el-button type="primary" icon="el-icon-upload">
选择图片或视频
</el-button>
</template>
</file-upload>
<!-- 显示上传成功的文件列表 -->
<!-- <div class="success-files" v-if="successFiles.length > 0">-->
<!-- <h3>已上传文件列表:</h3>-->
<!-- <el-table :data="successFiles" style="width: 100%">-->
<!-- <el-table-column prop="name" label="文件名"></el-table-column>-->
<!-- <el-table-column prop="type" label="类型"></el-table-column>-->
<!-- <el-table-column prop="size" label="大小">-->
<!-- <template slot-scope="scope">-->
<!-- {{ formatFileSize(scope.row.size) }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="预览">-->
<!-- <template slot-scope="scope">-->
<!-- <el-button type="text" @click="previewFile(scope.row)">-->
<!-- 预览-->
<!-- </el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- &lt;!&ndash; 清空按钮 &ndash;&gt;-->
<!-- <el-button type="danger" @click="clearFiles" class="clear-btn">-->
<!-- 清空文件-->
<!-- </el-button>-->
<!-- </div>-->
</div>
</template>
<script>
import FileUpload from '../../components/FileUpload/index.vue'
export default {
name: 'UploadPage',
components: {
FileUpload
},
data() {
return {
uploadUrl: 'http://localhost:8567/material/business/youtube/uploadVideo',
successFiles: [],
extraData: {
tags: "abc, def",
director: '1',
resType: '1',
directoryId: '1'
},
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
}
},
methods: {
// 处理文件列表更新
handleFilesUpdate(files) {
this.successFiles = files
console.log("parent receive upload success", files)
},
// 处理单个文件上传成功
handleUploadSuccess(file) {
// this.$message.success(`文件 ${file.name} 上传成功`)
},
// 处理上传错误
handleUploadError({ file, error }) {
// this.$message.error(`文件 ${file.name} 上传失败:${error.message}`)
},
// 处理所有文件上传完成
handleUploadComplete(files) {
// this.$message.success(`所有文件上传完成,共 ${files.length} 个文件`)
},
// 格式化文件大小
formatFileSize(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
},
// 预览文件
previewFile(file) {
if (file.type.includes('image')) {
// 使用 Element UI 的 Image 预览
const h = this.$createElement
this.$msgbox({
title: file.name,
message: h('img', {
attrs: {
src: file.url,
style: 'max-width: 100%'
}
}),
showCancelButton: false,
confirmButtonText: '关闭'
})
} else if (file.type.includes('video')) {
// 使用自定义对话框预览视频
this.$msgbox({
title: file.name,
message: h('video', {
attrs: {
src: file.url,
controls: true,
style: 'max-width: 100%'
}
}),
showCancelButton: false,
confirmButtonText: '关闭'
})
}
},
}
}
</script>
<style scoped>
.upload-page {
padding: 20px;
}
.success-files {
margin-top: 30px;
}
.clear-btn {
margin-top: 20px;
}
</style>
<template>
<div class="cascader-select">
<!-- 触发器按钮 -->
<el-popover
v-model="visible"
placement="bottom-start"
trigger="click"
:width="300"
>
<el-input
v-model="searchQuery"
size="small"
placeholder="搜索标签..."
prefix-icon="el-icon-search"
clearable
@clear="handleSearchClear"
class="mb-2"
/>
<!-- 面包屑导航 -->
<div v-if="currentPath.length" class="mb-2">
<el-breadcrumb separator="/">
<el-breadcrumb-item
v-for="(item, index) in currentPath"
:key="item.id"
:class="{ 'is-link': index !== currentPath.length - 1 }"
@click.native="handlePathClick(index)"
>
{{ item.name }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<!-- 树形选择器 -->
<el-tree
ref="tree"
:data="currentLevelData"
:props="defaultProps"
:filter-node-method="filterNode"
node-key="id"
:expand-on-click-node="false"
@node-click="handleNodeClick"
>
<span slot-scope="{ node, data }" class="custom-tree-node">
<span>{{ node.label }}</span>
<span v-if="isLeaf(data)">
<el-checkbox
v-model="checkedMap[data.id]"
@change="(val) => handleCheck(data, val)"
@click.native.stop
/>
</span>
<i v-else class="el-icon-arrow-right" />
</span>
</el-tree>
<el-button
slot="reference"
:class="{ 'is-selected': selectedNodes.length }"
>
<span v-if="selectedNodes.length">
已选择 {{ selectedNodes.length }}
</span>
<span v-else>{{ placeholder }}</span>
<i class="el-icon-arrow-down el-icon--right" />
</el-button>
</el-popover>
<!-- 已选择标签展示区域 -->
<div class="selected-tags">
<el-tag
v-for="node in selectedNodes"
:key="node.id"
closable
size="small"
class="tag-item"
@close="handleRemoveTag(node)"
>
{{ getNodePath(node) }}
</el-tag>
</div>
</div>
</template>
<script>
export default {
name: 'CascaderSelect',
props: {
data: {
type: Array,
required: true,
default: () => []
},
value: {
type: Array,
default: () => []
},
placeholder: {
type: String,
default: '请选择标签...'
}
},
data() {
return {
visible: false,
searchQuery: '',
currentPath: [],
checkedMap: {},
defaultProps: {
children: 'children',
label: 'name'
}
}
},
computed: {
// 构建树形数据
treeData() {
return this.buildTree(this.data)
},
// 当前层级数据
currentLevelData() {
if (this.searchQuery) {
return this.filterTreeData(this.treeData, this.searchQuery)
}
if (this.currentPath.length === 0) {
return this.treeData
}
const current = this.currentPath[this.currentPath.length - 1]
return current.children || []
},
// 选中的节点
selectedNodes() {
return this.value.map(id => this.findNodeById(this.treeData, id)).filter(Boolean)
}
},
watch: {
value: {
immediate: true,
handler(val) {
// 更新选中状态
const checkedMap = {}
val.forEach(id => {
checkedMap[id] = true
})
this.checkedMap = checkedMap
}
},
searchQuery(val) {
this.$refs.tree && this.$refs.tree.filter(val)
}
},
methods: {
// 构建树形数据
buildTree(items) {
const itemMap = new Map()
const result = []
// 创建所有节点
items.forEach(item => {
itemMap.set(item.id, { ...item, children: [] })
})
// 建立父子关系
items.forEach(item => {
const node = itemMap.get(item.id)
if (item.parentId === null) {
result.push(node)
} else {
const parent = itemMap.get(item.parentId)
if (parent) {
parent.children.push(node)
}
}
})
return result
},
// 判断是否为叶子节点
isLeaf(node) {
return !node.children || node.children.length === 0
},
// 处理节点点击
handleNodeClick(data) {
if (!this.isLeaf(data)) {
this.currentPath.push(data)
}
},
// 处理路径点击
handlePathClick(index) {
this.currentPath = this.currentPath.slice(0, index + 1)
},
// 处理复选框变化
handleCheck(node, checked) {
const newValue = [...this.value]
if (checked) {
if (!newValue.includes(node.id)) {
newValue.push(node.id)
}
} else {
const index = newValue.indexOf(node.id)
if (index > -1) {
newValue.splice(index, 1)
}
}
this.$emit('input', newValue)
this.$emit('change', newValue)
},
// 处理标签移除
handleRemoveTag(node) {
const newValue = this.value.filter(id => id !== node.id)
this.$emit('input', newValue)
this.$emit('change', newValue)
},
// 获取节点完整路径
getNodePath(node) {
const path = this.findNodePath(this.treeData, node.id)
return path ? path.map(n => n.name).join(' / ') : ''
},
// 查找节点路径
findNodePath(nodes, targetId, path = []) {
for (const node of nodes) {
const newPath = [...path, node]
if (node.id === targetId) {
return newPath
}
if (node.children) {
const found = this.findNodePath(node.children, targetId, newPath)
if (found) {
return found
}
}
}
return null
},
// 根据ID查找节点
findNodeById(nodes, targetId) {
for (const node of nodes) {
if (node.id === targetId) {
return node
}
if (node.children) {
const found = this.findNodeById(node.children, targetId)
if (found) {
return found
}
}
}
return null
},
// 搜索过滤
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
// 过滤树数据
filterTreeData(nodes, query) {
return nodes.filter(node => {
if (node.name.indexOf(query) !== -1) {
return true
}
if (node.children) {
const filteredChildren = this.filterTreeData(node.children, query)
if (filteredChildren.length) {
node.children = filteredChildren
return true
}
}
return false
})
},
// 处理搜索清除
handleSearchClear() {
this.searchQuery = ''
this.currentPath = []
}
}
}
</script>
<style scoped>
.cascader-select {
display: flex;
flex-direction: column;
gap: 8px;
}
.custom-tree-node {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.selected-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag-item {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mb-2 {
margin-bottom: 8px;
}
.el-button.is-selected {
color: #409EFF;
border-color: #409EFF;
}
.el-breadcrumb__item.is-link {
cursor: pointer;
color: #409EFF;
}
.el-tree {
max-height: 300px;
overflow-y: auto;
}
</style>
\ No newline at end of file
<template>
<div class="tag-management">
<el-row :gutter="20">
<!-- 左侧树形结构 -->
<el-col :span="6">
<el-card class="tree-card">
<div slot="header" class="card-header">
<span>标签树形结构</span>
<el-button type="text" @click="handleAddRoot">添加根标签</el-button>
</div>
<el-tree
:data="treeData"
:props="defaultProps"
@node-click="handleNodeClick"
default-expand-all
></el-tree>
</el-card>
</el-col>
<!-- 右侧表单和列表 -->
<el-col :span="18">
<el-card>
<div slot="header" class="card-header">
<span>标签列表</span>
<el-button type="primary" size="small" @click="handleAdd">新增标签</el-button>
</div>
<!-- 搜索表单 -->
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="标签名称">
<el-input v-model="searchForm.name" placeholder="请输入标签名称"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="标签名称"></el-table-column>
<el-table-column prop="remark" label="标签备注1"></el-table-column>
<el-table-column prop="remark2" label="标签备注2"></el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180"></el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" @click="handleAddChild(scope.row)">添加子标签</el-button>
<el-button type="text" class="delete-btn" @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"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
</el-card>
</el-col>
</el-row>
<!-- 新增/编辑对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px">
<el-form :model="form" :rules="rules" ref="form" label-width="100px">
<el-form-item label="标签名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="标签备注1" prop="remark">
<el-input v-model="form.remark"></el-input>
</el-form-item>
<el-form-item label="标签备注2" prop="remark2">
<el-input v-model="form.remark2"></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>
export default {
name: 'TagManagement',
data() {
return {
// 树形数据
treeData: [],
defaultProps: {
children: 'children',
label: 'name'
},
// 搜索表单
searchForm: {
name: ''
},
// 表格数据
tableData: [],
// 分页
pagination: {
currentPage: 1,
pageSize: 10,
total: 0
},
// 对话框
dialogVisible: false,
dialogTitle: '',
form: {
id: null,
name: '',
remark: '',
remark2: '',
parent_id: null
},
// 表单验证规则
rules: {
name: [
{ required: true, message: '请输入标签名称', trigger: 'blur' },
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
]
},
// 当前选中的节点
currentNode: null
}
},
created() {
this.fetchData()
this.fetchTreeData()
},
methods: {
// 获取表格数据
async fetchData() {
try {
// 这里替换为实际的API调用
const response = await this.axios.get('/api/material-tags', {
params: {
page: this.pagination.currentPage,
pageSize: this.pagination.pageSize,
name: this.searchForm.name
}
})
this.tableData = response.data.list
this.pagination.total = response.data.total
} catch (error) {
this.$message.error('获取数据失败')
}
},
// 获取树形数据
async fetchTreeData() {
try {
// 这里替换为实际的API调用
const response = await this.axios.get('/api/material-tags/tree')
this.treeData = response.data
} catch (error) {
this.$message.error('获取树形数据失败')
}
},
// 处理搜索
handleSearch() {
this.pagination.currentPage = 1
this.fetchData()
},
// 重置搜索
resetSearch() {
this.searchForm = {
name: ''
}
this.handleSearch()
},
// 处理分页大小变化
handleSizeChange(val) {
this.pagination.pageSize = val
this.fetchData()
},
// 处理页码变化
handleCurrentChange(val) {
this.pagination.currentPage = val
this.fetchData()
},
// 处理节点点击
handleNodeClick(data) {
this.currentNode = data
this.searchForm.name = ''
this.fetchData()
},
// 新增标签
handleAdd() {
this.dialogTitle = '新增标签'
this.form = {
id: null,
name: '',
remark: '',
remark2: '',
parent_id: this.currentNode ? this.currentNode.id : null
}
this.dialogVisible = true
},
// 添加根标签
handleAddRoot() {
this.dialogTitle = '添加根标签'
this.form = {
id: null,
name: '',
remark: '',
remark2: '',
parent_id: null
}
this.dialogVisible = true
},
// 添加子标签
handleAddChild(row) {
this.dialogTitle = '添加子标签'
this.form = {
id: null,
name: '',
remark: '',
remark2: '',
parent_id: row.id
}
this.dialogVisible = true
},
// 编辑标签
handleEdit(row) {
this.dialogTitle = '编辑标签'
this.form = { ...row }
this.dialogVisible = true
},
// 删除标签
handleDelete(row) {
this.$confirm('确认删除该标签吗?', '提示', {
type: 'warning'
}).then(async () => {
try {
// 这里替换为实际的API调用
await this.axios.delete(`/api/material-tags/${row.id}`)
this.$message.success('删除成功')
this.fetchData()
this.fetchTreeData()
} catch (error) {
this.$message.error('删除失败')
}
}).catch(() => {})
},
// 提交表单
submitForm() {
this.$refs.form.validate(async (valid) => {
if (valid) {
try {
if (this.form.id) {
// 编辑
await this.axios.put(`/api/material-tags/${this.form.id}`, this.form)
} else {
// 新增
await this.axios.post('/api/material-tags', this.form)
}
this.$message.success('保存成功')
this.dialogVisible = false
this.fetchData()
this.fetchTreeData()
} catch (error) {
this.$message.error('保存失败')
}
}
})
}
}
}
</script>
<style scoped>
.tag-management {
padding: 20px;
}
.tree-card {
min-height: 400px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.search-form {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.delete-btn {
color: #F56C6C;
}
</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