Commit 011cb0f5 authored by lijin's avatar lijin

增加素材标签

parent e6633eb4
This diff is collapsed.
......@@ -47,7 +47,7 @@ Vue.filter("toFixed", function (price, limit) {
});
router.beforeEach((to, from, next) => {
if (to.path == "/login") {
if (to.path == "/login" || to.path == "/materialUpload" || to.path == "/materialTag") {
next();
} else {
gatewayUserRouters().then(res => {
......
......@@ -132,6 +132,20 @@ export const constantRouterMap = [
path: '',
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