评论添加删除
This commit is contained in:
@@ -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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,4 +85,11 @@ export class classifyService {
|
|||||||
params
|
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-author">{{ comment.user.nickname }}</div>
|
||||||
<div class="comment-time">{{ formatTime(comment.created_at) }}</div>
|
<div class="comment-time">{{ formatTime(comment.created_at) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
@click="deleteComment(comment)"
|
||||||
|
v-if="hasAuth('classify_item@del')"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-content">{{ comment.content || '-' }}</div>
|
<div class="comment-content">{{ comment.content || '-' }}</div>
|
||||||
<!-- 回复列表 -->
|
<!-- 回复列表 -->
|
||||||
@@ -148,6 +158,16 @@
|
|||||||
formatTime(reply.created_at || reply.timestamp)
|
formatTime(reply.created_at || reply.timestamp)
|
||||||
}}</div>
|
}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
@click="deleteComment(reply)"
|
||||||
|
v-if="hasAuth('classify_item@del')"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="reply-content">{{ reply.content || '-' }}</div>
|
<div class="reply-content">{{ reply.content || '-' }}</div>
|
||||||
</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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -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"></i></div> -->
|
||||||
|
<div
|
||||||
|
:class="['btn-text', 'bg-secondary']"
|
||||||
|
@click="showDialog('edit', scope.row)"
|
||||||
|
v-if="hasAuth('product_list@edit')"
|
||||||
|
><i class="iconfont-sys"></i></div
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="['btn-text', 'bg-error']"
|
||||||
|
@click="deleteMonthly(scope.row)"
|
||||||
|
v-if="hasAuth('product_list@del')"
|
||||||
|
><i class="iconfont-sys"></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>
|
||||||
Reference in New Issue
Block a user