评论添加删除

This commit is contained in:
浅念
2025-12-23 17:08:56 +08:00
parent 681b09c65c
commit 60f80c4eff
4 changed files with 1059 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
import request from '@/utils/http'
export class attributeService {
//属性列表
static getattributeList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/sku/attribute/list',
params
})
}
//属性添加
static addattribute(params: any) {
return request.post<any>({
url: '/admin/sku/attribute/store',
params
})
}
//属性编辑
static editattribute(params: any) {
return request.post<any>({
url: '/admin/sku/attribute/update',
params
})
}
//属性删除
static deleteattribute(params: any) {
return request.post<any>({
url: '/admin/sku/attribute/destroy',
params
})
}
//属性详情
static attributedetail(params: any) {
return request.get<any>({
url: '/admin/sku/attribute/show',
params
})
}
//sku列表
static getskuList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/sku/list',
params
})
}
//sku添加
static addsku(params: any) {
return request.post<any>({
url: '/admin/sku/store',
params
})
}
//sku编辑
static editsku(params: any) {
return request.post<any>({
url: '/admin/sku/update',
params
})
}
//sku删除
static deletesku(params: any) {
return request.post<any>({
url: '/admin/sku/destroy',
params
})
}
//sku详情
static skudetail(params: any) {
return request.get<any>({
url: '/admin/sku/show',
params
})
}
//分类下拉
static getcategoryOptions() {
return request.get<Api.Common.CommonListData>({
url: '/admin/post/category/preset'
})
}
//属性下拉
static getattributeOptions(params?: any) {
return request.get<Api.Common.CommonListData>({
url: '/admin/sku/attribute/preset',
params
})
}
//产品导入
static importSku(formData: FormData) {
return request.request<any>({
url: '/admin/sku/import',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
//下载导入模板
static downloadTemplate() {
return request.get<Blob>({
url: '/admin/sku/template',
responseType: 'blob'
})
}
}
+7
View File
@@ -85,4 +85,11 @@ export class classifyService {
params
})
}
//评论删除 /admin/post/comment/destroy
static deleteclassifycontentcomment(params: any) {
return request.post<any>({
url: '/admin/post/comment/destroy',
params
})
}
}
@@ -135,6 +135,16 @@
<div class="comment-author">{{ comment.user.nickname }}</div>
<div class="comment-time">{{ formatTime(comment.created_at) }}</div>
</div>
<div>
<el-button
type="danger"
link
@click="deleteComment(comment)"
v-if="hasAuth('classify_item@del')"
>
删除
</el-button>
</div>
</div>
<div class="comment-content">{{ comment.content || '-' }}</div>
<!-- 回复列表 -->
@@ -148,6 +158,16 @@
formatTime(reply.created_at || reply.timestamp)
}}</div>
</div>
<div>
<el-button
type="danger"
link
@click="deleteComment(reply)"
v-if="hasAuth('classify_item@del')"
>
删除
</el-button>
</div>
</div>
<div class="reply-content">{{ reply.content || '-' }}</div>
</div>
@@ -308,6 +328,47 @@
}
})
}
//删除评论
const deleteComment = (row: any) => {
ElMessageBox.confirm('确定删除该评论吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
try {
const { message } = await classifyService.deleteclassifycontentcomment({ id: row.id })
ElMessage.success(message)
getMonthlyList()
// 从评论列表中移除已删除的评论
const removeComment = (list: any[]) => {
const index = list.findIndex((item) => item.id === row.id)
if (index > -1) {
list.splice(index, 1)
return true
}
// 检查是否有子回复需要删除
for (const item of list) {
if (item.child && item.child.length > 0) {
const childIndex = item.child.findIndex((child: any) => child.id === row.id)
if (childIndex > -1) {
item.child.splice(childIndex, 1)
return true
}
}
}
return false
}
const removed = removeComment(commentList.value)
if (removed) {
// 更新详情中的评论总数
detailData.value.comment = Math.max(0, detailData.value.comment - 1)
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
})
}
</script>
<style lang="scss" scoped>
+886
View File
@@ -0,0 +1,886 @@
<template>
<div class="color-page">
<div class="color-toolbar boxshadow">
<div class="color-search-group">
<el-form-item label="关键字:" label-width="70px">
<el-input
style="min-width: 120px"
v-model="keyword"
placeholder="请输入关键字"
clearable
@keyup.enter="getMonthlyList"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="getMonthlyList"
v-ripple
v-if="hasAuth('product_list@add')"
>搜索</el-button
>
</el-form-item>
</div>
<div>
<el-form-item>
<el-button
type="primary"
@click="showImportDialog"
v-ripple
v-if="hasAuth('product_list@export')"
>
导入
</el-button>
</el-form-item>
</div>
</div>
<div class="boxshadow">
<ElRow>
<div style="width: 12px"></div>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<ElButton @click="showDialog('add')" v-ripple type="primary"> 新增 </ElButton>
</ElCol>
</ElRow>
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<!-- <el-table-column prop="main_image" label="主图" align="center">
<template #default="scope">
<img
:src="scope.row.main_image_pull"
alt="主图"
class="avatar"
@click="handleAvatarClick(scope.row.main_image_pull)"
style="cursor: pointer"
/>
</template>
</el-table-column> -->
<ElTableColumn label="产品名称" prop="product_name" min-width="120" />
<ElTableColumn label="型号" prop="model" min-width="120" />
<ElTableColumn label="品牌" prop="brand" min-width="120" />
<ElTableColumn label="分类" prop="category_name" min-width="120" />
<ElTableColumn label="创建时间" prop="created_at" min-width="120" />
<ElTableColumn fixed="right" label="操作" width="200">
<template #default="scope">
<ElRow>
<!-- <div :class="['btn-text', 'bg-primary']" @click="addchild(scope.row)"><i class="iconfont-sys">&#xe602;</i></div> -->
<div
:class="['btn-text', 'bg-secondary']"
@click="showDialog('edit', scope.row)"
v-if="hasAuth('product_list@edit')"
><i class="iconfont-sys">&#xe642;</i></div
>
<div
:class="['btn-text', 'bg-error']"
@click="deleteMonthly(scope.row)"
v-if="hasAuth('product_list@del')"
><i class="iconfont-sys">&#xe783;</i></div
>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<!-- 新增/编辑对话框 -->
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增产品' : '编辑产品'"
align-center
width="780px"
>
<ElForm ref="formRef" :model="form" :rules="rules" class="form" label-width="70px">
<div class="form-section">
<div class="section-title">基础信息</div>
<ElRow :gutter="16">
<ElCol :span="12">
<el-form-item label="产品名">
<el-input v-model="form.product_name" placeholder="请输入产品名" clearable />
</el-form-item>
</ElCol>
<ElCol :span="12">
<el-form-item label="型号">
<el-input v-model="form.model" placeholder="请输入型号" clearable />
</el-form-item>
</ElCol>
<ElCol :span="12">
<el-form-item label="品牌">
<el-input v-model="form.brand" placeholder="默认西门子,可手动修改" clearable />
</el-form-item>
</ElCol>
<ElCol :span="12">
<el-form-item label="价格">
<el-input v-model="form.price" placeholder="请输入价格" clearable />
</el-form-item>
</ElCol>
<ElCol :span="12">
<el-form-item label="分类">
<el-select v-model="form.category_id" placeholder="请选择分类" style="width: 100%">
<el-option
v-for="item in categoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</ElCol>
<ElCol :span="12">
<ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="form.sort" :min="0" :max="999999" style="width: 100%" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="16" class="media-row">
<ElCol :span="24">
<el-form-item label="主图">
<el-upload
class="qrcode-upload"
:action="actionImageUplode"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleUploadavatarpreset"
name="image"
>
<div class="qrcode-upload-box">
<img
class="iconimg"
:src="
imageUrlavatarpreset != '' && imageUrlavatarpreset != null
? imageUrlavatarpreset
: upimg
"
/>
</div>
</el-upload>
</el-form-item>
</ElCol>
<ElCol :span="24">
<el-form-item label="商品图片">
<el-upload
ref="imageUploadRef"
class="image-upload"
:action="actionImageUplode"
:headers="uploadHeaders"
:on-success="handleImageUploadSuccess"
:on-remove="handleImageRemove"
:on-progress="handleUploadProgress"
:file-list="imageFileList"
:multiple="true"
:limit="20"
:auto-upload="true"
list-type="picture-card"
name="image"
accept="image/*"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
</ElCol>
</ElRow>
</div>
<div class="form-section">
<div class="section-title">详情</div>
<el-form-item label="产品详情">
<ArtWangEditor class="el-top" v-model="form.desc" />
</el-form-item>
</div>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
</div>
</template>
</ElDialog>
<!-- 图片预览组件 -->
<ElImageViewer
v-if="showImageViewer"
:url-list="[currentImageUrl]"
:hide-on-click-modal="true"
@close="showImageViewer = false"
/>
<!-- 导入对话框 -->
<ElDialog v-model="importDialogVisible" title="导入产品" align-center width="500px">
<div class="import-content">
<div class="import-section">
<div class="section-title">下载模板</div>
<el-button type="primary" @click="downloadTemplate" :loading="templateLoading">
<el-icon><Download /></el-icon>
下载模板文件
</el-button>
<div class="tip-text">请先下载模板文件,按照模板格式填写数据后上传</div>
</div>
<div class="import-section">
<div class="section-title">选择文件</div>
<div class="file-upload-wrapper">
<input
ref="fileInputRef"
type="file"
@change="handleFileSelect"
style="display: none"
/>
<el-button type="primary" @click="triggerFileSelect" :loading="importLoading">
<el-icon><UploadFilled /></el-icon>
选择文件
</el-button>
<div v-if="importFile" class="file-info">
<el-icon class="success-icon"><Check /></el-icon>
<span class="file-name">{{ importFile.name }}</span>
</div>
<!-- <div class="tip-text">只能上传 xlsx/xls 文件,且不超过 10MB</div> -->
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="importDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { nextTick } from 'vue'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
import type { FormInstance, FormRules, UploadFile } from 'element-plus'
import { Download, UploadFilled, Check } from '@element-plus/icons-vue'
import { attributeService } from '@/api/attribute'
import { useAuth } from '@/composables/useAuth'
import { useUploadHeaders } from '@/composables/useUploadHeaders'
const { uploadHeaders } = useUploadHeaders()
import upimgUrl from '@/assets/img/user/upimg.png'
const upimg = ref(upimgUrl)
let actionImageUplode = `${import.meta.env.VITE_API_URL}/upload/image?scene=product`
const { hasAuth } = useAuth()
defineOptions({ name: 'ProductList' })
// 响应式数据
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref<FormInstance>()
const keyword = ref<string>('')
// 导入相关
const importDialogVisible = ref(false)
const importLoading = ref(false)
const templateLoading = ref(false)
const fileInputRef = ref<HTMLInputElement>()
const importFile = ref<File | null>(null)
// 表单数据
const form = reactive<{
id: string | number
product_name: string
price: number | string
model: string
brand: string
category_id: string | number
main_image: string
image: any[]
sort: number
desc: string
status: number
}>({
id: '',
product_name: '',
model: '',
price: '',
brand: '西门子',
category_id: '',
main_image: '',
image: [],
sort: 0,
desc: '',
status: 1
})
// 图片预览相关
const showImageViewer = ref(false)
const currentImageUrl = ref('')
const handleAvatarClick = (avatarUrl: string) => {
if (avatarUrl) {
currentImageUrl.value = avatarUrl
showImageViewer.value = true
}
}
// 表单验证规则
const rules = reactive<FormRules>({
product_name: [{ required: true, message: '请输入产品名', trigger: 'blur' }],
category_id: [{ required: true, message: '请选择分类', trigger: 'change' }]
})
/* 这里通过API获取属性列表 */
const attrList = ref<any[]>([])
//属性下拉
const getAttrList = async () => {
const data = await attributeService.getattributeOptions()
const list = (data as any)?.records ?? data
attrList.value = (list as any[]) || []
}
// 分页数据
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
const tableRef = ref()
const categoryList = ref<any[]>([])
//分类下拉
const getCategoryList = async () => {
const data = await attributeService.getcategoryOptions()
const list = (data as any)?.records ?? data
categoryList.value = (list as any[]) || []
}
const imageUrlavatarpreset = ref('')
function handleUploadavatarpreset(response: any) {
// 假设返回图片地址字段为 response.url 或 response.data.url
imageUrlavatarpreset.value = response.data.url
// 如需同步到 form.picture
form.main_image = response.data.href
}
type UploadFileWithStorage = UploadFile & { storagePath?: string }
const imageFileList = ref<UploadFileWithStorage[]>([])
// 背景图片上传 loading
const uploadLoadingInstance = shallowRef<ReturnType<typeof ElLoading.service> | null>(null)
const handleUploadProgress = () => {
uploadLoadingInstance.value = ElLoading.service({
lock: true,
text: '上传中...',
background: 'rgba(0, 0, 0, 0.7)'
})
}
const syncFormValueFromImages = () => {
// 将上传列表同步为表单需要的精简字段
form.image = imageFileList.value.filter((item) => item.url).map((item) => item.storagePath)
}
const handleImageUploadSuccess = (
response: any,
uploadFile: UploadFile,
uploadFiles: UploadFile[]
) => {
const fileUrl = response.data.url
const storagePath = response.data.href || ''
uploadFile.url = fileUrl
uploadFile.response = response
;(uploadFile as UploadFileWithStorage).storagePath = storagePath
// 更新文件列表(仅保留成功或准备中的文件)
imageFileList.value = uploadFiles.filter(
(item) => item.status === 'success' || item.status === 'ready'
) as UploadFileWithStorage[]
syncFormValueFromImages()
ElMessage.success(response?.message || '图片上传成功')
uploadLoadingInstance.value?.close()
}
const handleImageRemove = (_file: UploadFile, uploadFiles: UploadFile[]) => {
imageFileList.value = uploadFiles as UploadFileWithStorage[]
syncFormValueFromImages()
}
const hydrateImagesFromRow = (row: any) => {
const urls: string[] = row?.image_pull || []
const storagePaths: string[] = row?.image || []
imageFileList.value = urls.map((url, idx) => ({
name: `图片${idx + 1}`,
url,
status: 'success',
uid: Date.now() + idx,
storagePath: storagePaths[idx] || ''
})) as UploadFileWithStorage[]
syncFormValueFromImages()
}
// 生命周期
onMounted(() => {
getMonthlyList()
})
// 监听搜索条件变化
watch([keyword], () => {
pagination.currentPage = 1
getMonthlyList()
})
// 获取月度业绩列表
const getMonthlyList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
keyword: keyword.value
}
const { records, current, size, total } = await attributeService.getskuList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
ElMessage.error('获取列表失败')
} finally {
loading.value = false
}
}
// 处理分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getMonthlyList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getMonthlyList()
}
// 显示对话框
const showDialog = async (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
// 先加载分类和属性列表
await Promise.all([getCategoryList()])
if (type === 'edit' && row) {
// 编辑模式
imageUrlavatarpreset.value = row.main_image_pull || ''
Object.assign(form, {
id: row.id || '',
product_name: row.product_name || '',
model: row.model || '',
brand: row.brand || '西门子',
price: row.price || '',
category_id: row.category_id || '',
main_image: row.main_image || '',
image: row.image || [],
sort: row.sort ?? 0,
desc: row.desc || '',
status: row.status ?? 1
})
console.log(form)
hydrateImagesFromRow(row)
} else {
// 新增模式
imageUrlavatarpreset.value = ''
imageFileList.value = []
Object.assign(form, {
id: '',
product_name: '',
model: '',
brand: '西门子',
category_id: '',
main_image: '',
image: [],
sort: 0,
desc: '',
status: 1
})
formRef.value?.resetFields?.()
}
}
// 删除月度业绩
const deleteMonthly = (row: any) => {
ElMessageBox.confirm('确定删除该记录吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
try {
const { message } = await attributeService.deletesku({ id: row.id })
ElMessage.success(message)
getMonthlyList()
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
})
}
// 提交表单
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (!valid) return
try {
if (dialogType.value === 'add') {
// 新增
const { message } = await attributeService.addsku({
product_name: form.product_name,
price: form.price,
model: form.model,
brand: form.brand,
category_id: form.category_id,
main_image: form.main_image,
image: form.image,
sort: form.sort,
desc: form.desc,
status: form.status
})
ElMessage.success(message)
} else {
// 编辑
const { message } = await attributeService.editsku({
id: form.id,
product_name: form.product_name,
price: form.price,
model: form.model,
brand: form.brand,
category_id: form.category_id,
main_image: form.main_image,
image: form.image,
sort: form.sort,
desc: form.desc,
status: form.status
})
ElMessage.success(message)
}
dialogVisible.value = false
getMonthlyList()
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
})
}
// 显示导入对话框
const showImportDialog = () => {
importDialogVisible.value = true
importFile.value = null
if (fileInputRef.value) {
fileInputRef.value.value = ''
}
}
// 下载模板文件
const downloadTemplate = async () => {
try {
templateLoading.value = true
const urls = import.meta.env.VITE_API_URL + `/admin/sku/template`
// 2. 创建blob对象
// const blob = new Blob([res], { type: 'application/vnd.ms-excel' }) // 或根据后端实际类型调整
// 3. 创建下载链接
const url = urls
const link = document.createElement('a')
link.href = url
link.download = '客户数据.xlsx' // 可自定义文件名
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('模板下载成功')
} catch (error) {
console.error('下载模板失败:', error)
ElMessage.error('下载模板失败')
} finally {
templateLoading.value = false
}
}
// 触发文件选择
const triggerFileSelect = () => {
fileInputRef.value?.click()
}
// 处理文件选择
const handleFileSelect = async (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
// 验证文件类型
// const isExcel =
// file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
// file.type === 'application/vnd.ms-excel' ||
// file.name.endsWith('.xlsx') ||
// file.name.endsWith('.xls')
// if (!isExcel) {
// ElMessage.error('只能上传 Excel 文件(.xlsx/.xls)')
// target.value = ''
// return
// }
// // 验证文件大小
// const isLt10M = file.size / 1024 / 1024 < 10
// if (!isLt10M) {
// ElMessage.error('文件大小不能超过 10MB')
// target.value = ''
// return
// }
importFile.value = file
// 直接调用导入接口
await handleImportSubmit()
}
// 确认导入
const handleImportSubmit = async () => {
if (!importFile.value) {
ElMessage.warning('请先选择要导入的文件')
return
}
try {
importLoading.value = true
const formData = new FormData()
formData.append('file', importFile.value)
const { message } = await attributeService.importSku(formData)
ElMessage.success(message || '导入成功')
importDialogVisible.value = false
importFile.value = null
if (fileInputRef.value) {
fileInputRef.value.value = ''
}
getMonthlyList()
} catch (error: any) {
console.error('导入失败:', error)
ElMessage.error(error?.message || '导入失败,请检查文件格式')
} finally {
importLoading.value = false
}
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.color-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.color-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 20px 20px;
background: var(--art-main-bg-color);
}
.color-search-group {
display: flex;
gap: 12px;
align-items: center;
}
.form {
padding: 12px 0 4px;
}
.dialog-footer {
text-align: right;
}
.el-col2 {
margin-bottom: 16px;
}
.iconimg {
width: 60px;
}
.btn-text {
display: inline-block;
min-width: 34px;
height: 34px;
padding: 0 10px;
margin-right: 10px;
font-size: 13px;
line-height: 34px;
color: #666;
cursor: pointer;
background-color: rgba(var(--art-gray-200-rgb), 0.7);
border-radius: 6px;
&:hover {
color: var(--main-color);
background-color: rgba(var(--art-gray-300-rgb), 0.5);
}
}
.form-section {
background: #fafafa;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.section-title {
font-weight: 600;
margin-bottom: 12px;
color: #303133;
}
.media-row {
align-items: center;
}
.avatar-uploader {
display: inline-flex;
width: 140px;
height: 140px;
border: 1px dashed #dcdfe6;
border-radius: 8px;
cursor: pointer;
justify-content: center;
align-items: center;
overflow: hidden;
&:hover {
border-color: var(--el-color-primary);
}
}
.avatar {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-uploader-icon {
font-size: 24px;
color: #8c939d;
}
.upload-card {
display: inline-flex;
width: 140px;
height: 140px;
border: 1px dashed #dcdfe6;
border-radius: 8px;
align-items: center;
justify-content: center;
color: #606266;
background: #fff;
transition: all 0.2s;
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
}
}
.spec-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
.spec-item {
padding: 12px;
border: 1px solid #ebeef5;
border-radius: 6px;
background: #fff;
}
.spec-row {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.spec-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.import-content {
padding: 20px 0;
}
.import-section {
margin-bottom: 30px;
&:last-child {
margin-bottom: 0;
}
}
.import-section .section-title {
font-weight: 600;
margin-bottom: 12px;
color: #303133;
font-size: 14px;
}
.tip-text {
margin-top: 8px;
color: #909399;
font-size: 12px;
}
:deep(.el-upload-dragger) {
width: 100%;
}
.file-upload-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
.file-info {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: #f5f7fa;
border-radius: 6px;
}
.file-name {
flex: 1;
color: #606266;
font-size: 14px;
}
.file-path-info {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: #f0f9ff;
border: 1px solid #b3d8ff;
border-radius: 6px;
color: #409eff;
font-size: 14px;
}
.success-icon {
color: #67c23a;
}
</style>