评论添加删除
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
|
||||
})
|
||||
}
|
||||
//评论删除 /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>
|
||||
|
||||
@@ -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