mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-06 17:09:08 +08:00
feat(seller): 评价管理列表增强(回复状态/有无图片筛选、评价分级 Tab、会员头像列)及多页面样式优化(商品列表、部门管理、OSS 素材、运费模板、页头页脚等)
This commit is contained in:
@@ -3,7 +3,7 @@ var BASE = {
|
||||
* @description api请求基础路径
|
||||
*/
|
||||
API_DEV: {
|
||||
seller: "https://admin-api.pickmall.cn",
|
||||
seller: "http://127.0.0.1:8887",
|
||||
},
|
||||
API_PROD: {
|
||||
seller: "https://admin-api.pickmall.cn",
|
||||
|
||||
@@ -295,7 +295,7 @@ export const deleteDepartment = (ids, params) => {
|
||||
};
|
||||
// 编辑部门
|
||||
export const editDepartment = (ids, params) => {
|
||||
return putRequest(`/department/${ids} `, params);
|
||||
return putRequest(`/department/${ids}`, params);
|
||||
};
|
||||
// 加载部门子级数据
|
||||
export const loadDepartment = (id) => {
|
||||
|
||||
@@ -164,8 +164,14 @@ util.bootstrapDynamicRoutesFromCache = function () {
|
||||
}
|
||||
};
|
||||
|
||||
/** 站点名称:优先取接口下发并缓存的站点名,兜底用本地配置 */
|
||||
util.getSiteName = function () {
|
||||
return window.localStorage.getItem("sellersiteName") || config.title;
|
||||
};
|
||||
|
||||
util.title = function (title) {
|
||||
title = title || `${config.title} 商家后台`;
|
||||
const siteName = util.getSiteName();
|
||||
title = title ? `${title} - ${siteName}商家后台` : `${siteName} 商家后台`;
|
||||
window.document.title = title;
|
||||
};
|
||||
|
||||
@@ -508,9 +514,8 @@ util.initRouterNode = function (routers, data) {
|
||||
}
|
||||
|
||||
const meta = {};
|
||||
meta.title = menu.title
|
||||
? menu.title + " - " + config.title + "商家后台"
|
||||
: null;
|
||||
// 站点后缀统一由 util.title 在路由跳转时动态拼接,避免初始化时定死
|
||||
meta.title = menu.title ? menu.title : null;
|
||||
meta.firstRouterName = item.firstRouterName;
|
||||
meta.keepAlive = menu.keepAlive ? true : false;
|
||||
menu.meta = meta;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Main from "@/views/Main.vue";
|
||||
import config from "@/config/index";
|
||||
// 不作为Main组件的子页面展示的页面单独写,如下
|
||||
export const loginRouter = {
|
||||
path: "/login",
|
||||
name: "login",
|
||||
meta: {
|
||||
title: `登录 - ${config.title}商家后台`
|
||||
// 站点后缀由 util.title 在路由跳转时动态拼接(站点名取自接口下发缓存)
|
||||
title: "登录"
|
||||
},
|
||||
component: () => import("@/views/login.vue")
|
||||
};
|
||||
|
||||
@@ -113,6 +113,11 @@
|
||||
z-index: 4000 !important;
|
||||
}
|
||||
|
||||
/* 全局禁止 textarea 拖拽改变大小 */
|
||||
.el-textarea__inner {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* 统一空状态:表格 / 树 / 列表无数据时的展示(未自定义 #empty 插槽的页面均生效) */
|
||||
.el-table__empty-block {
|
||||
min-height: 160px;
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<el-row justify="center" class="copyright">
|
||||
Copyright © {{ year }} - Present
|
||||
<a href="https://pickmall.cn/" target="_blank" style="margin: 0 5px">{{
|
||||
config.title
|
||||
siteName
|
||||
}}</a>
|
||||
版权所有
|
||||
</el-row>
|
||||
@@ -124,7 +124,7 @@ import { md5 } from "@/utils/md5.js";
|
||||
import * as RegExp from "@/utils/regular.js";
|
||||
import * as apiLogin from "@/api/index.js";
|
||||
import { sendSms } from "@/api/common.js";
|
||||
import config from "@/config/index";
|
||||
import util from "@/libs/util.js";
|
||||
import verify from "@/views/my-components/verify";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
@@ -146,6 +146,8 @@ const form = reactive({
|
||||
oncePasd: "",
|
||||
});
|
||||
const year = new Date().getFullYear();
|
||||
// 站点名优先取接口下发缓存,兜底用本地配置
|
||||
const siteName = util.getSiteName();
|
||||
const step = ref(0);
|
||||
const ruleInline = {
|
||||
mobile: [
|
||||
|
||||
@@ -37,7 +37,12 @@
|
||||
<el-tabs v-model="activeTab" @tab-click="switchTabs">
|
||||
<el-tab-pane label="预警商品" name="warnList">
|
||||
<el-table v-loading="loading" border :data="data" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" min-width="400" show-overflow-tooltip />
|
||||
<el-table-column prop="goodsId" label="商品编号" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="goodsName" label="商品名称" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="sn" label="SKU编码" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="SKU信息" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.simpleSpecs || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" min-width="120">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
@@ -54,7 +59,12 @@
|
||||
|
||||
<el-tab-pane label="设置预警" name="warnSetting">
|
||||
<el-table v-loading="loading" border :data="data" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" />
|
||||
<el-table-column prop="goodsId" label="商品编号" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="goodsName" label="商品名称" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="sn" label="SKU编码" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="SKU信息" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.simpleSpecs || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" width="200">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -6,74 +6,70 @@
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
class="search-form"
|
||||
class="search-form goods-search-form"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<div class="search-form-row">
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input
|
||||
v-model="searchForm.goodsName"
|
||||
placeholder="请输入商品名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU名称" prop="skuName">
|
||||
<el-input
|
||||
v-model="searchForm.skuName"
|
||||
placeholder="请输入SKU名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU编码" prop="skuSn">
|
||||
<el-input
|
||||
v-model="searchForm.skuSn"
|
||||
placeholder="请输入SKU编码"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品ID" prop="id">
|
||||
<el-input
|
||||
v-model="searchForm.id"
|
||||
placeholder="请输入商品ID"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="search-form-row">
|
||||
<el-form-item label="店铺分类" prop="storeCategoryPath">
|
||||
<el-cascader
|
||||
v-model="searchForm.storeCategoryPath"
|
||||
:options="shopCategoryList"
|
||||
:props="shopCategoryProps"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择店铺分类"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="平台分类" prop="categoryPath">
|
||||
<el-cascader
|
||||
v-model="searchForm.categoryPath"
|
||||
:options="platformCategoryList"
|
||||
:props="platformCategoryProps"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择平台商品分类"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品类型" prop="goodsType">
|
||||
<el-select
|
||||
v-model="searchForm.goodsType"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
>
|
||||
<el-option label="实物商品" value="PHYSICAL_GOODS" />
|
||||
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
|
||||
<!-- E_COUPON:卡密商品,可跳转卡池管理 -->
|
||||
<el-option label="电子卡券" value="E_COUPON" />
|
||||
<el-option label="预约商品" value="APPOINTMENT_GOODS" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input
|
||||
v-model="searchForm.goodsName"
|
||||
placeholder="请输入商品名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU名称" prop="skuName">
|
||||
<el-input
|
||||
v-model="searchForm.skuName"
|
||||
placeholder="请输入SKU名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU编码" prop="skuSn">
|
||||
<el-input
|
||||
v-model="searchForm.skuSn"
|
||||
placeholder="请输入SKU编码"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="SPU编码" prop="id">
|
||||
<el-input
|
||||
v-model="searchForm.id"
|
||||
placeholder="请输入SPU编码"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="店铺分类" prop="storeCategoryPath">
|
||||
<el-cascader
|
||||
v-model="searchForm.storeCategoryPath"
|
||||
:options="shopCategoryList"
|
||||
:props="shopCategoryProps"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择店铺分类"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="平台分类" prop="categoryPath">
|
||||
<el-cascader
|
||||
v-model="searchForm.categoryPath"
|
||||
:options="platformCategoryList"
|
||||
:props="platformCategoryProps"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择平台商品分类"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品类型" prop="goodsType">
|
||||
<el-select
|
||||
v-model="searchForm.goodsType"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
>
|
||||
<el-option label="实物商品" value="PHYSICAL_GOODS" />
|
||||
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
|
||||
<!-- E_COUPON:卡密商品,可跳转卡池管理 -->
|
||||
<el-option label="电子卡券" value="E_COUPON" />
|
||||
<el-option label="预约商品" value="APPOINTMENT_GOODS" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="createRange"
|
||||
@@ -81,7 +77,6 @@
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="库存状态" prop="stockStatus">
|
||||
@@ -89,7 +84,6 @@
|
||||
v-model="searchForm.stockStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@change="onStockStatusChange"
|
||||
>
|
||||
<el-option label="有货" value="IN_STOCK" />
|
||||
@@ -97,34 +91,18 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="searchForm.stockStatus === 'IN_STOCK'" label="库存区间">
|
||||
<el-input
|
||||
v-model="searchForm.stockMin"
|
||||
placeholder="≥"
|
||||
clearable
|
||||
style="width: 110px"
|
||||
/>
|
||||
<span style="margin: 0 6px">~</span>
|
||||
<el-input
|
||||
v-model="searchForm.stockMax"
|
||||
placeholder="≤"
|
||||
clearable
|
||||
style="width: 110px"
|
||||
/>
|
||||
<div class="range-inputs">
|
||||
<el-input v-model="searchForm.stockMin" placeholder="≥" clearable />
|
||||
<span class="range-sep">~</span>
|
||||
<el-input v-model="searchForm.stockMax" placeholder="≤" clearable />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="售价区间">
|
||||
<el-input
|
||||
v-model="searchForm.priceMin"
|
||||
placeholder="≥"
|
||||
clearable
|
||||
style="width: 110px"
|
||||
/>
|
||||
<span style="margin: 0 6px">~</span>
|
||||
<el-input
|
||||
v-model="searchForm.priceMax"
|
||||
placeholder="≤"
|
||||
clearable
|
||||
style="width: 110px"
|
||||
/>
|
||||
<div class="range-inputs">
|
||||
<el-input v-model="searchForm.priceMin" placeholder="≥" clearable />
|
||||
<span class="range-sep">~</span>
|
||||
<el-input v-model="searchForm.priceMax" placeholder="≤" clearable />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
||||
@@ -172,7 +150,7 @@
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="100" align="center" />
|
||||
<el-table-column prop="id" label="商品ID" width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="id" label="SPU编码" width="200" show-overflow-tooltip />
|
||||
<el-table-column label="商品图片" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<img alt="商品图片"
|
||||
@@ -219,7 +197,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="320" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="320" align="right" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.marketEnable === 'DOWN'">
|
||||
<a class="link-text" @click="upper(row)">上架</a>
|
||||
@@ -693,28 +671,48 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.search-form-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
margin-bottom: 18px;
|
||||
.goods-search-form {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
column-gap: 16px;
|
||||
align-items: start;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
flex: 1;
|
||||
> :deep(.el-form-item) {
|
||||
display: flex;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
margin-right: 12px;
|
||||
margin-bottom: 0;
|
||||
margin: 8px 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-form-item:last-child) {
|
||||
margin-right: 0;
|
||||
:deep(.el-form-item__content) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.el-input),
|
||||
:deep(.el-select),
|
||||
:deep(.el-cascader) {
|
||||
:deep(.el-cascader),
|
||||
:deep(.el-date-editor) {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.range-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-input) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.range-sep {
|
||||
margin: 0 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="filter-row-field">
|
||||
<span class="filter-row-label">商品ID</span>
|
||||
<span class="filter-row-label">SPU编码</span>
|
||||
<el-input
|
||||
v-model="filterForm.goodsId"
|
||||
placeholder="请输入商品ID"
|
||||
placeholder="请输入SPU编码"
|
||||
clearable
|
||||
class="filter-control"
|
||||
/>
|
||||
|
||||
@@ -20,23 +20,28 @@
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
<a class="company-link" :href="config.company.href" target="_blank">
|
||||
<a class="company-link" :href="companyUrl" target="_blank">
|
||||
<img class="zhizhao" :src="zhizhaoImg" alt="" />
|
||||
{{ config.company.name }}
|
||||
{{ companyName }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="copyright">
|
||||
<p>Copyright © {{ year }} {{ config.title }}</p>
|
||||
<p>Copyright © {{ year }} {{ siteName }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import config from "@/config/index";
|
||||
import util from "@/libs/util.js";
|
||||
import zhizhaoImg from "@/assets/images/zhizhao.jpg";
|
||||
|
||||
const year = new Date().getFullYear();
|
||||
// 站点名/公司信息优先取接口下发缓存,兜底用本地配置
|
||||
const siteName = util.getSiteName();
|
||||
const companyName = localStorage.getItem("sellerCompanyName") || config.company.name;
|
||||
const companyUrl = localStorage.getItem("sellerCompanyUrl") || config.company.href;
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -47,6 +47,8 @@ function getSite() {
|
||||
localStorage.setItem("sellersiteName", data.siteName);
|
||||
localStorage.setItem("sellerlogoImg", data.storeSideLogo);
|
||||
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
||||
localStorage.setItem("sellerCompanyName", data.companyName || "");
|
||||
localStorage.setItem("sellerCompanyUrl", data.companyUrl || "");
|
||||
storeSideLogo.value = data.storeSideLogo;
|
||||
window.document.title = data.siteName;
|
||||
applyFavicon(data.storeSideIcon);
|
||||
|
||||
@@ -10,16 +10,27 @@
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<el-form-item label="会员名称" prop="memberName">
|
||||
<el-input v-model="searchForm.memberName" clearable placeholder="请输入会员名称" style="width: 240px" />
|
||||
<el-input v-model="searchForm.memberName" clearable placeholder="请输入会员名称" style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input v-model="searchForm.goodsName" clearable placeholder="请输入商品名" style="width: 240px" />
|
||||
<el-input v-model="searchForm.goodsName" clearable placeholder="请输入商品名" style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="评价" prop="grade">
|
||||
<el-select v-model="searchForm.grade" placeholder="请选择" clearable style="width: 240px">
|
||||
<el-option label="好评" value="GOOD" />
|
||||
<el-option label="中评" value="MODERATE" />
|
||||
<el-option label="差评" value="WORSE" />
|
||||
<el-form-item label="回复状态" prop="replyStatus">
|
||||
<el-select v-model="searchForm.replyStatus" placeholder="请选择" clearable style="width: 140px">
|
||||
<el-option label="已回复" :value="true" />
|
||||
<el-option label="未回复" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="有无图片" prop="haveImage">
|
||||
<el-select v-model="searchForm.haveImage" placeholder="请选择" clearable style="width: 140px">
|
||||
<el-option label="有图" value="1" />
|
||||
<el-option label="无图" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择" clearable style="width: 140px">
|
||||
<el-option label="展示" value="OPEN" />
|
||||
<el-option label="隐藏" value="CLOSE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评论日期">
|
||||
@@ -42,24 +53,54 @@
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<div class="comment-tab">
|
||||
<el-tabs v-model="currentGrade" @tab-click="onGradeTabClick">
|
||||
<el-tab-pane v-for="item in gradeTabs" :key="item.title" :label="item.title" :name="item.value" />
|
||||
</el-tabs>
|
||||
</div>
|
||||
<el-table v-loading="loading" border :data="data" ref="tableRef" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="memberName" label="会员名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="goodsName" label="商品名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="content" label="评价内容" min-width="300" show-overflow-tooltip />
|
||||
<el-table-column label="评价" width="100">
|
||||
<el-table-column label="会员" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.grade === 'GOOD'" type="success">好评</el-tag>
|
||||
<el-tag v-else-if="row.grade === 'MODERATE'" type="warning">中评</el-tag>
|
||||
<el-tag v-else type="danger">差评</el-tag>
|
||||
<div class="member-cell">
|
||||
<el-avatar :size="32" :src="row.memberProfile" />
|
||||
<span class="cell-name" :title="row.memberName">{{ row.memberName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100">
|
||||
<el-table-column label="商品" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="goods-cell">
|
||||
<el-image
|
||||
v-if="row.goodsImage"
|
||||
:src="row.goodsImage"
|
||||
:preview-src-list="[row.goodsImage]"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
style="width: 40px; height: 40px; flex-shrink: 0; border-radius: 4px"
|
||||
/>
|
||||
<span class="cell-name" :title="row.goodsName">{{ row.goodsName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="content" label="评价内容" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column label="评价" width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="grade-cell">
|
||||
<el-rate :model-value="avgScore(row)" disabled allow-half size="small" />
|
||||
<el-tag v-if="row.grade === 'GOOD'" type="success" size="small">好评</el-tag>
|
||||
<el-tag v-else-if="row.grade === 'MODERATE'" type="warning" size="small">中评</el-tag>
|
||||
<el-tag v-else type="danger" size="small">差评</el-tag>
|
||||
<el-tag v-if="row.top" type="primary" size="small" effect="plain">置顶</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status === 'OPEN'" type="success">展示</el-tag>
|
||||
<el-tag v-else type="danger">隐藏</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回复状态" width="110">
|
||||
<el-table-column label="回复状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.replyStatus" type="success">已回复</el-tag>
|
||||
<el-tag v-else type="primary">未回复</el-tag>
|
||||
@@ -68,7 +109,7 @@
|
||||
<el-table-column prop="createTime" label="创建日期" width="170" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="100">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="detail(row)">详细</a>
|
||||
<a class="link-text" @click="detail(row)">{{ row.replyStatus ? "查看" : "回复" }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -87,42 +128,116 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="modalVisible" :title="modalTitle" width="500px" :close-on-click-modal="false">
|
||||
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
|
||||
<el-form-item label="评价内容">
|
||||
<span v-if="!content">暂无评价</span>
|
||||
<el-input v-else v-model="content" type="textarea" maxlength="200" disabled :rows="4" style="width: 90%" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="detailInfo.haveImage == 1" label="评价图片" style="padding-top: 10px">
|
||||
<upload-pic-thumb v-model="image" :disable="true" :remove="false" :isView="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回复内容" prop="reply">
|
||||
<el-input
|
||||
v-if="!replyStatus"
|
||||
v-model="form.reply"
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
:rows="4"
|
||||
clearable
|
||||
style="width: 90%"
|
||||
/>
|
||||
<el-input v-else v-model="form.reply" type="textarea" maxlength="200" disabled :rows="4" style="width: 90%" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="detailInfo.haveReplyImage == 1 || !replyStatus"
|
||||
label="回复图片"
|
||||
prop="replyImage"
|
||||
style="padding-top: 18px"
|
||||
>
|
||||
<upload-pic-thumb v-if="!replyStatus" v-model="form.replyImage" :limit="5" />
|
||||
<upload-pic-thumb v-else v-model="form.replyImage" :disable="true" :remove="false" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-drawer
|
||||
v-model="modalVisible"
|
||||
title="评价详情"
|
||||
size="600px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-loading="detailLoading" class="comment-detail">
|
||||
<template v-if="detailInfo.id">
|
||||
<div class="detail-header">
|
||||
<el-avatar :size="44" :src="detailInfo.memberProfile" />
|
||||
<div class="detail-header-info">
|
||||
<div class="detail-member-line">
|
||||
<span class="detail-member-name">{{ detailInfo.memberName }}</span>
|
||||
<el-tag v-if="detailInfo.grade === 'GOOD'" type="success" size="small" effect="light">好评</el-tag>
|
||||
<el-tag v-else-if="detailInfo.grade === 'MODERATE'" type="warning" size="small" effect="light">
|
||||
中评
|
||||
</el-tag>
|
||||
<el-tag v-else type="danger" size="small" effect="light">差评</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<span class="detail-time">{{ detailInfo.createTime }}</span>
|
||||
</div>
|
||||
|
||||
<p class="detail-content" :class="{ 'is-empty': !content }">
|
||||
{{ content || "买家未填写评价内容" }}
|
||||
</p>
|
||||
<div v-if="image.length" class="image-list">
|
||||
<el-image
|
||||
v-for="(img, index) in image"
|
||||
:key="index"
|
||||
:src="img"
|
||||
:preview-src-list="image"
|
||||
:initial-index="index"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
class="detail-image"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="detail-meta">
|
||||
<div class="meta-scores">
|
||||
<span class="score-item">
|
||||
描述相符
|
||||
<el-rate :model-value="detailInfo.descriptionScore" disabled size="small" />
|
||||
</span>
|
||||
<span class="score-item">
|
||||
服务态度
|
||||
<el-rate :model-value="detailInfo.serviceScore" disabled size="small" />
|
||||
</span>
|
||||
<span class="score-item">
|
||||
物流服务
|
||||
<el-rate :model-value="detailInfo.deliveryScore" disabled size="small" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="meta-goods">
|
||||
<el-image
|
||||
v-if="detailInfo.goodsImage"
|
||||
:src="detailInfo.goodsImage"
|
||||
:preview-src-list="[detailInfo.goodsImage]"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
class="meta-goods-image"
|
||||
/>
|
||||
<span class="meta-goods-name" :title="detailInfo.goodsName">{{ detailInfo.goodsName }}</span>
|
||||
<span class="meta-order-no">订单号:{{ detailInfo.orderNo || "-" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reply-section">
|
||||
<div class="reply-title">商家回复</div>
|
||||
<div v-if="replyStatus" class="reply-bubble">
|
||||
<p class="reply-text">{{ form.reply || "-" }}</p>
|
||||
<div v-if="form.replyImage.length" class="image-list">
|
||||
<el-image
|
||||
v-for="(img, index) in form.replyImage"
|
||||
:key="index"
|
||||
:src="img"
|
||||
:preview-src-list="form.replyImage"
|
||||
:initial-index="index"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
class="detail-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-form v-else ref="formRef" :model="form" :rules="formValidate" label-position="top">
|
||||
<el-form-item prop="reply">
|
||||
<el-input
|
||||
v-model="form.reply"
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
:rows="4"
|
||||
resize="none"
|
||||
show-word-limit
|
||||
placeholder="请输入回复内容,回复后将展示在商品评价中"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回复图片" class="reply-upload-item">
|
||||
<upload-pic-thumb v-model="form.replyImage" :limit="5" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="modalVisible = false">取消</el-button>
|
||||
<el-button v-if="!replyStatus" type="primary" :loading="submitLoading" @click="handleSubmit">回复</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -138,10 +253,11 @@ const detailInfo = ref({});
|
||||
const image = ref([]);
|
||||
const replyStatus = ref(false);
|
||||
const modalVisible = ref(false);
|
||||
const modalTitle = ref("");
|
||||
const loading = ref(true);
|
||||
const detailLoading = ref(false);
|
||||
const content = ref("");
|
||||
const searchForm = ref({
|
||||
|
||||
const defaultSearchForm = () => ({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
@@ -149,8 +265,17 @@ const searchForm = ref({
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
});
|
||||
const searchForm = ref(defaultSearchForm());
|
||||
const selectDate = ref(null);
|
||||
const gradeTabs = [
|
||||
{ title: "全部评价", value: "" },
|
||||
{ title: "好评", value: "GOOD" },
|
||||
{ title: "中评", value: "MODERATE" },
|
||||
{ title: "差评", value: "WORSE" },
|
||||
];
|
||||
const currentGrade = ref("");
|
||||
const form = reactive({
|
||||
id: "",
|
||||
replyImage: [],
|
||||
reply: "",
|
||||
});
|
||||
@@ -187,15 +312,15 @@ function handleSearch() {
|
||||
getDataList();
|
||||
}
|
||||
function handleReset() {
|
||||
searchForm.value = {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
};
|
||||
searchForm.value = defaultSearchForm();
|
||||
selectDate.value = null;
|
||||
currentGrade.value = "";
|
||||
getDataList();
|
||||
}
|
||||
function onGradeTabClick(tab) {
|
||||
currentGrade.value = tab.paneName;
|
||||
searchForm.value.grade = tab.paneName || undefined;
|
||||
searchForm.value.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
function selectDateRange(v) {
|
||||
@@ -207,55 +332,233 @@ function selectDateRange(v) {
|
||||
searchForm.value.endTime = "";
|
||||
}
|
||||
}
|
||||
function avgScore(row) {
|
||||
const scores = [row.deliveryScore, row.serviceScore, row.descriptionScore].filter(
|
||||
(s) => typeof s === "number"
|
||||
);
|
||||
if (!scores.length) return 0;
|
||||
return scores.reduce((a, b) => a + b, 0) / scores.length;
|
||||
}
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
API_Member.getMemberReview(searchForm.value).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
});
|
||||
API_Member.getMemberReview(searchForm.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
data.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
function handleSubmit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
API_Member.replyMemberReview(form.id, form).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (!valid) return;
|
||||
submitLoading.value = true;
|
||||
API_Member.replyMemberReview(form.id, form)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("回复成功");
|
||||
getDataList();
|
||||
modalVisible.value = false;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function detail(v) {
|
||||
modalVisible.value = true;
|
||||
detailInfo.value = {};
|
||||
form.reply = "";
|
||||
form.replyImage = [];
|
||||
loading.value = true;
|
||||
API_Member.getMemberInfoReview(v.id).then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
form.id = res.result.id;
|
||||
content.value = res.result.content;
|
||||
form.reply = res.result.reply;
|
||||
replyStatus.value = res.result.replyStatus;
|
||||
image.value = res.result.images ? (res.result.images || "").split(",") : [];
|
||||
form.replyImage = res.result.replyImage ? (res.result.replyImage || "").split(",") : [];
|
||||
detailInfo.value = res.result;
|
||||
modalVisible.value = true;
|
||||
modalTitle.value = "详细";
|
||||
}
|
||||
});
|
||||
image.value = [];
|
||||
detailLoading.value = true;
|
||||
API_Member.getMemberInfoReview(v.id)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
form.id = res.result.id;
|
||||
content.value = res.result.content;
|
||||
form.reply = res.result.reply;
|
||||
replyStatus.value = res.result.replyStatus;
|
||||
image.value = res.result.images ? (res.result.images || "").split(",") : [];
|
||||
form.replyImage = res.result.replyImage ? (res.result.replyImage || "").split(",") : [];
|
||||
detailInfo.value = res.result;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
detailLoading.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/table-common.scss";
|
||||
.link-text {
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.comment-tab {
|
||||
:deep(.el-tabs__item) {
|
||||
font-size: 14px;
|
||||
}
|
||||
:deep(.el-tabs__header) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.member-cell,
|
||||
.goods-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.cell-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.grade-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.comment-detail {
|
||||
min-height: 200px;
|
||||
}
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
.detail-header-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
.detail-member-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.detail-member-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
.detail-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.detail-content {
|
||||
margin: 14px 0 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
&.is-empty {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.detail-meta {
|
||||
margin-top: 14px;
|
||||
padding: 10px 12px;
|
||||
background: #f7f8fa;
|
||||
border-radius: 6px;
|
||||
.meta-scores {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 20px;
|
||||
.score-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
.meta-goods {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed #e4e7ed;
|
||||
.meta-goods-image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.meta-goods-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
.meta-order-no {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.reply-section {
|
||||
margin-top: 16px;
|
||||
.reply-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
&::before {
|
||||
content: "";
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
border-radius: 2px;
|
||||
background: #409eff;
|
||||
}
|
||||
}
|
||||
.reply-bubble {
|
||||
padding: 12px;
|
||||
background: #ecf5ff;
|
||||
border-radius: 6px;
|
||||
.reply-text {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
.reply-upload-item {
|
||||
margin-bottom: 0;
|
||||
:deep(.el-form-item__label) {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
.detail-image {
|
||||
width: 92px;
|
||||
height: 92px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<a class="link-text" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
|
||||
</div>
|
||||
<div style="color: #999; font-size: 12px; margin-top: 5px">
|
||||
商品ID: {{ row.goodsId }}
|
||||
SPU编码: {{ row.goodsId }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<a class="link-text" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
|
||||
</div>
|
||||
<div style="color: #999; font-size: 12px; margin-top: 5px">
|
||||
商品ID: {{ row.goodsId }}
|
||||
SPU编码: {{ row.goodsId }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
|
||||
<el-table-column prop="updateTime" label="修改时间" width="180" align="center" />
|
||||
<el-table-column
|
||||
v-if="searchForm.pageType !== 'SPECIAL'"
|
||||
label="状态"
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-row>
|
||||
<div>
|
||||
<el-button @click="refresh">刷新</el-button>
|
||||
<el-button @click="add" type="primary">添加</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-tabs @tab-click="handleClickType" v-model="currentTab" style="margin-top: 10px">
|
||||
<el-tab-pane label="运费模板" name="INFO">
|
||||
<el-empty v-if="!shipInfo.length" description="暂无运费模板,点击右上角「添加」创建" />
|
||||
<table class="ncsc-default-table order m-b-30" :key="index" v-for="(item,index) in shipInfo">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="sep-row" colspan="20"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="20">
|
||||
<span class="temp-name">{{item.name}}</span>
|
||||
<el-tag v-if="item.pricingMethod==='FREE'" class="baoyou" color="warning">包邮</el-tag>
|
||||
<span class="fr m-r-5">
|
||||
<time style="margin-right: 20px" title="最后编辑时间">
|
||||
<i class="icon-time"></i>{{item.updateTime}}
|
||||
</time>
|
||||
<a
|
||||
style="color:#2d8cf0;cursor:pointer;text-decoration:none"
|
||||
@click="edit(item)"
|
||||
>修改</a>
|
||||
<span style="margin: 0 8px; color: #dcdee2">|</span>
|
||||
<a
|
||||
style="color:#2d8cf0;cursor:pointer;text-decoration:none"
|
||||
@click="remove(item.id)"
|
||||
>删除</a>
|
||||
</span>
|
||||
<th colspan="20" class="template-head">
|
||||
<div class="template-head-inner">
|
||||
<span class="temp-name">{{item.name}}</span>
|
||||
<el-tag v-if="item.pricingMethod==='FREE'" type="warning" size="small">包邮</el-tag>
|
||||
<div class="template-head-right">
|
||||
<time class="update-time" title="最后编辑时间">{{item.updateTime}}</time>
|
||||
<a class="action-link" @click="edit(item)">修改</a>
|
||||
<span class="action-split">|</span>
|
||||
<a class="action-link" @click="remove(item.id)">删除</a>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr v-if="item.pricingMethod!=='FREE'">
|
||||
@@ -499,10 +494,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
tbody td {
|
||||
color: #999;
|
||||
color: #606266;
|
||||
background-color: #fff;
|
||||
text-align: center;
|
||||
padding: 6px 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,30 +531,41 @@ onMounted(() => {
|
||||
border-left: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.order tbody tr th .temp-name {
|
||||
float: left;
|
||||
.order tbody tr th.template-head {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.template-head-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.temp-name {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
// line-height: 44px;
|
||||
margin: 7px 0 0 10px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.m-r-5 {
|
||||
margin-right: 5px !important;
|
||||
.template-head-right {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right !important;
|
||||
.update-time {
|
||||
margin-right: 16px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.m-b-30 {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
Button {
|
||||
margin: 3px 5px 0px 5px;
|
||||
}
|
||||
|
||||
thead {
|
||||
display: table-header-group;
|
||||
vertical-align: middle;
|
||||
@@ -704,14 +710,8 @@ em {
|
||||
font-size: 15px;
|
||||
color: #f00;
|
||||
}
|
||||
.baoyou {
|
||||
margin: 6px 10px 0;
|
||||
}
|
||||
/* 操作链接与分隔符不加粗 */
|
||||
.order tbody tr th .fr a {
|
||||
font-weight: normal;
|
||||
}
|
||||
.order tbody tr th .fr span {
|
||||
font-weight: normal;
|
||||
|
||||
.action-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<el-table-column prop="addressName" label="自提点名称" min-width="120" />
|
||||
<el-table-column prop="address" label="详细地址" min-width="280" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="120" sortable />
|
||||
<el-table-column prop="updateTime" label="修改时间" min-width="120" sortable />
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="edit(row)">修改</a>
|
||||
|
||||
@@ -1,81 +1,37 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-row class="operation">
|
||||
<el-switch
|
||||
v-model="strict"
|
||||
active-text="级联"
|
||||
inactive-text="单选"
|
||||
style="margin-right: 5px"
|
||||
/>
|
||||
<el-button @click="addRoot">添加部门</el-button>
|
||||
<el-button type="primary" @click="add">添加子部门</el-button>
|
||||
<div class="operation">
|
||||
<el-button type="primary" @click="openAdd('0')">添加部门</el-button>
|
||||
<el-button @click="delAll">批量删除</el-button>
|
||||
<el-button @click="getParentList">刷新</el-button>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="start">
|
||||
<el-col :md="8" :lg="8" :xl="6">
|
||||
<el-alert type="info" show-icon :closable="false">
|
||||
当前选择编辑:
|
||||
<span class="select-title">{{ editTitle }}</span>
|
||||
<a v-if="form.id" class="select-clear" @click="cancelSelect">取消选择</a>
|
||||
</el-alert>
|
||||
<div v-loading="loading" class="tree-bar" :style="{ maxHeight: maxHeight }">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="data"
|
||||
:props="treeProps"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
highlight-current
|
||||
default-expand-all
|
||||
:check-strictly="!strict"
|
||||
:current-node-key="currentNodeKey"
|
||||
@node-click="onNodeClick"
|
||||
@check="onCheckChange"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :md="15" :lg="13" :xl="9" style="margin-left: 10px">
|
||||
<el-form ref="formRef" :model="form" label-width="100px" :rules="formValidate">
|
||||
<el-form-item label="部门名称" prop="title">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择角色">
|
||||
<el-select
|
||||
v-model="selectedRole"
|
||||
multiple
|
||||
:loading="userLoading"
|
||||
placeholder="请选择角色"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in users"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序值" prop="sortOrder">
|
||||
<el-tooltip placement="right" content="值越小越靠前,支持小数">
|
||||
<el-input-number v-model="form.sortOrder" :max="1000" :min="0" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
style="margin-right: 5px"
|
||||
:loading="submitLoading"
|
||||
@click="submitEdit"
|
||||
>
|
||||
修改并保存
|
||||
</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-button @click="getList">刷新</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
row-key="id"
|
||||
border
|
||||
default-expand-all
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column prop="title" label="部门名称" min-width="180" />
|
||||
<el-table-column label="上级部门" min-width="140">
|
||||
<template #default="{ row }">{{ parentTitle(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sortOrder" label="排序值" width="90" align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" align="center" />
|
||||
<el-table-column prop="updateTime" label="修改时间" width="170" align="center" />
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="openAdd(row.id)">添加子级</a>
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="openEdit(row)">编辑</a>
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="delOne(row)">删除</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
@@ -83,30 +39,55 @@
|
||||
:title="modalTitle"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form ref="formAddRef" :model="formAdd" label-width="85px" :rules="formValidate">
|
||||
<div v-if="showParent">
|
||||
<el-form-item label="上级部门:">{{ form.title }}</el-form-item>
|
||||
</div>
|
||||
<el-form ref="formRef" :model="form" label-width="85px" :rules="formValidate">
|
||||
<el-form-item label="上级部门">
|
||||
<el-tree-select
|
||||
v-model="form.parentId"
|
||||
:data="parentOptions"
|
||||
:props="{ label: 'title', children: 'children' }"
|
||||
node-key="id"
|
||||
check-strictly
|
||||
default-expand-all
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门名称" prop="title">
|
||||
<el-input v-model="formAdd.title" />
|
||||
<el-input v-model="form.title" placeholder="请输入部门名称" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="modalType === 1" label="选择角色">
|
||||
<el-select
|
||||
v-model="selectedRole"
|
||||
multiple
|
||||
:loading="roleLoading"
|
||||
placeholder="请选择角色"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in roles"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序值" prop="sortOrder">
|
||||
<el-tooltip placement="right" content="值越小越靠前,支持小数">
|
||||
<el-input-number v-model="formAdd.sortOrder" :max="1000" :min="0" />
|
||||
<el-input-number v-model="form.sortOrder" :max="1000" :min="0" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="cancelAdd">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitAdd">提交</el-button>
|
||||
<el-button @click="modalVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import {
|
||||
initDepartment,
|
||||
addDepartment,
|
||||
@@ -120,36 +101,24 @@ import { Message, Modal } from "@/utils/message";
|
||||
|
||||
defineOptions({ name: "store-department-manage" });
|
||||
|
||||
const treeRef = ref(null);
|
||||
const formRef = ref(null);
|
||||
const formAddRef = ref(null);
|
||||
|
||||
const loading = ref(true);
|
||||
const maxHeight = ref("500px");
|
||||
const strict = ref(true);
|
||||
const userLoading = ref(false);
|
||||
const modalVisible = ref(false);
|
||||
const selectList = ref([]);
|
||||
const selectCount = ref(0);
|
||||
const showParent = ref(false);
|
||||
const modalType = ref(0); // 0 添加 1 编辑
|
||||
const modalTitle = ref("");
|
||||
const editTitle = ref("");
|
||||
const submitLoading = ref(false);
|
||||
const roleLoading = ref(false);
|
||||
const data = ref([]);
|
||||
const roles = ref([]);
|
||||
const selectedRole = ref([]);
|
||||
const currentNodeKey = ref(null);
|
||||
const treeProps = {
|
||||
label: "title",
|
||||
children: "children",
|
||||
isLeaf: "isLeaf",
|
||||
};
|
||||
const selectedRows = ref([]);
|
||||
const form = ref({
|
||||
id: "",
|
||||
parentId: "0",
|
||||
title: "",
|
||||
parentId: "",
|
||||
parentTitle: "",
|
||||
sortOrder: 0,
|
||||
status: 0,
|
||||
});
|
||||
const formAdd = ref({});
|
||||
const formValidate = {
|
||||
title: [{ required: true, message: "名称不能为空", trigger: "blur" }],
|
||||
sortOrder: [
|
||||
@@ -161,23 +130,57 @@ const formValidate = {
|
||||
},
|
||||
],
|
||||
};
|
||||
const submitLoading = ref(false);
|
||||
const data = ref([]);
|
||||
const users = ref([]);
|
||||
|
||||
function init() {
|
||||
getParentList();
|
||||
// id -> title 映射,用于展示上级部门名称
|
||||
const titleMap = computed(() => {
|
||||
const map = {};
|
||||
const walk = (list) => {
|
||||
list.forEach((item) => {
|
||||
map[item.id] = item.title;
|
||||
if (item.children?.length) walk(item.children);
|
||||
});
|
||||
};
|
||||
walk(data.value);
|
||||
return map;
|
||||
});
|
||||
|
||||
// 上级部门下拉树:根节点为“一级部门”;编辑时剔除自身及子级,防止循环挂级
|
||||
const parentOptions = computed(() => {
|
||||
const tree =
|
||||
modalType.value === 1 ? pruneTree(data.value, form.value.id) : data.value;
|
||||
return [{ id: "0", title: "一级部门", children: tree }];
|
||||
});
|
||||
|
||||
function pruneTree(list, excludeId) {
|
||||
return list
|
||||
.filter((item) => item.id !== excludeId)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
children: item.children?.length
|
||||
? pruneTree(item.children, excludeId)
|
||||
: undefined,
|
||||
}));
|
||||
}
|
||||
function getParentList() {
|
||||
|
||||
function parentTitle(row) {
|
||||
if (row.parentId === "0" || row.parentId == null) return "-";
|
||||
return titleMap.value[row.parentId] || "-";
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
initDepartment().then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
const list = Array.isArray(res.result) ? res.result : [];
|
||||
data.value = bubbleSort(normalizeDepartmentTree(list));
|
||||
}
|
||||
});
|
||||
initDepartment()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
const list = Array.isArray(res.result) ? res.result : [];
|
||||
data.value = sortTree(normalizeDepartmentTree(list));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeDepartmentTree(list) {
|
||||
return list.map((item) => {
|
||||
const node = { ...item };
|
||||
@@ -189,90 +192,71 @@ function normalizeDepartmentTree(list) {
|
||||
return node;
|
||||
});
|
||||
}
|
||||
function onNodeClick(data) {
|
||||
if (!data) {
|
||||
cancelSelect();
|
||||
return;
|
||||
}
|
||||
const row = JSON.parse(JSON.stringify(data));
|
||||
for (const attr in row) {
|
||||
if (row[attr] == null) {
|
||||
row[attr] = "";
|
||||
}
|
||||
}
|
||||
editTitle.value = row.title;
|
||||
currentNodeKey.value = row.id;
|
||||
userLoading.value = true;
|
||||
|
||||
getUserByDepartmentId(row.id).then((res) => {
|
||||
const way = [];
|
||||
res.result &&
|
||||
res.result.forEach((item) => {
|
||||
way.push(item.roleId);
|
||||
});
|
||||
selectedRole.value = way;
|
||||
function sortTree(list) {
|
||||
list.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
list.forEach((item) => {
|
||||
if (item.children?.length) sortTree(item.children);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
getRoleList({
|
||||
pageNumber: 1,
|
||||
pageSize: 10000,
|
||||
}).then((res) => {
|
||||
userLoading.value = false;
|
||||
if (res.success) {
|
||||
users.value = res.result.records;
|
||||
form.value = row;
|
||||
}
|
||||
});
|
||||
function onSelectionChange(rows) {
|
||||
selectedRows.value = rows;
|
||||
}
|
||||
function onCheckChange(_data, { checkedNodes }) {
|
||||
selectList.value = checkedNodes;
|
||||
selectCount.value = checkedNodes.length;
|
||||
|
||||
function openAdd(parentId) {
|
||||
modalType.value = 0;
|
||||
modalTitle.value = parentId === "0" ? "添加部门" : "添加子部门";
|
||||
form.value = { id: "", parentId, title: "", sortOrder: 0, status: 0 };
|
||||
modalVisible.value = true;
|
||||
}
|
||||
function bubbleSort(array) {
|
||||
if (!Array.isArray(array)) return [];
|
||||
const len = array.length;
|
||||
if (len < 2) return array;
|
||||
for (let i = 0; i < len; i++) {
|
||||
for (let j = 0; j < i; j++) {
|
||||
if (array[j].sortOrder > array[i].sortOrder) {
|
||||
const temp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
function cancelSelect() {
|
||||
treeRef.value?.setCurrentKey(null);
|
||||
currentNodeKey.value = null;
|
||||
formRef.value.resetFields();
|
||||
delete form.value.id;
|
||||
editTitle.value = "";
|
||||
|
||||
function openEdit(row) {
|
||||
modalType.value = 1;
|
||||
modalTitle.value = "编辑部门";
|
||||
form.value = {
|
||||
id: row.id,
|
||||
parentId: row.parentId ?? "0",
|
||||
title: row.title,
|
||||
sortOrder: row.sortOrder ?? 0,
|
||||
status: 0,
|
||||
};
|
||||
selectedRole.value = [];
|
||||
roleLoading.value = true;
|
||||
modalVisible.value = true;
|
||||
Promise.all([
|
||||
getUserByDepartmentId(row.id),
|
||||
getRoleList({ pageNumber: 1, pageSize: 10000 }),
|
||||
]).then(([bindRes, roleRes]) => {
|
||||
roleLoading.value = false;
|
||||
if (bindRes.result) {
|
||||
selectedRole.value = bindRes.result.map((item) => item.roleId);
|
||||
}
|
||||
if (roleRes.success) {
|
||||
roles.value = roleRes.result.records;
|
||||
}
|
||||
});
|
||||
}
|
||||
function cancelAdd() {
|
||||
modalVisible.value = false;
|
||||
}
|
||||
function handleReset() {
|
||||
formRef.value.resetFields();
|
||||
form.value.status = 0;
|
||||
}
|
||||
function submitEdit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
if (!form.value.id) {
|
||||
Message.warning("请先点击选择要修改的部门");
|
||||
return;
|
||||
}
|
||||
const roleWay = [];
|
||||
selectedRole.value.forEach((item) => {
|
||||
roleWay.push({
|
||||
departmentId: form.value.id,
|
||||
roleId: item,
|
||||
});
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.value.validate((valid) => {
|
||||
if (!valid) return;
|
||||
submitLoading.value = true;
|
||||
if (modalType.value === 0) {
|
||||
addDepartment(form.value).then((res) => {
|
||||
submitLoading.value = false;
|
||||
if (res.success) {
|
||||
Message.success("添加成功");
|
||||
modalVisible.value = false;
|
||||
getList();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const roleWay = selectedRole.value.map((roleId) => ({
|
||||
departmentId: form.value.id,
|
||||
roleId,
|
||||
}));
|
||||
Promise.all([
|
||||
editDepartment(form.value.id, form.value),
|
||||
updateDepartmentRole(form.value.id, roleWay),
|
||||
@@ -280,76 +264,48 @@ function submitEdit() {
|
||||
submitLoading.value = false;
|
||||
if (res[0].success) {
|
||||
Message.success("编辑成功");
|
||||
init();
|
||||
modalVisible.value = false;
|
||||
getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function submitAdd() {
|
||||
formAddRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true;
|
||||
addDepartment(formAdd.value).then((res) => {
|
||||
submitLoading.value = false;
|
||||
|
||||
function delOne(row) {
|
||||
Modal.confirm({
|
||||
title: "确认删除",
|
||||
content: "删除该部门将同时删除其下级所有部门,确认删除?",
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
deleteDepartment(row.id).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success("添加成功");
|
||||
init();
|
||||
modalVisible.value = false;
|
||||
Message.success("删除成功");
|
||||
getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
function add() {
|
||||
if (form.value.id == "" || form.value.id == null) {
|
||||
Message.warning("请先点击选择一个部门");
|
||||
return;
|
||||
}
|
||||
modalTitle.value = "添加子部门";
|
||||
showParent.value = true;
|
||||
formAdd.value = {
|
||||
parentId: form.value.id,
|
||||
sortOrder: 0,
|
||||
status: 0,
|
||||
};
|
||||
modalVisible.value = true;
|
||||
}
|
||||
function addRoot() {
|
||||
modalTitle.value = "添加一级部门";
|
||||
showParent.value = false;
|
||||
formAdd.value = {
|
||||
parentId: 0,
|
||||
sortOrder: 0,
|
||||
status: 0,
|
||||
};
|
||||
modalVisible.value = true;
|
||||
}
|
||||
|
||||
function delAll() {
|
||||
if (selectCount.value <= 0) {
|
||||
if (selectedRows.value.length === 0) {
|
||||
Message.warning("您还未勾选要删除的数据");
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "确认删除",
|
||||
content:
|
||||
"您确认要删除所选的 " + selectCount.value + " 条数据及其下级所有数据?",
|
||||
content: `您确认要删除所选的 ${selectedRows.value.length} 条数据及其下级所有数据?`,
|
||||
loading: true,
|
||||
onOk: () => {
|
||||
let ids = "";
|
||||
selectList.value.forEach(function (e) {
|
||||
ids += e.id + ",";
|
||||
});
|
||||
ids = ids.substring(0, ids.length - 1);
|
||||
const ids = selectedRows.value.map((e) => e.id).join(",");
|
||||
deleteDepartment(ids).then((res) => {
|
||||
Modal.remove();
|
||||
if (res.success) {
|
||||
Message.success("删除成功");
|
||||
selectList.value = [];
|
||||
selectCount.value = 0;
|
||||
cancelSelect();
|
||||
init();
|
||||
selectedRows.value = [];
|
||||
getList();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -357,12 +313,21 @@ function delAll() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const height = document.documentElement.clientHeight;
|
||||
maxHeight.value = Number(height - 287) + "px";
|
||||
init();
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/tree-common.scss";
|
||||
.operation {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.link-text {
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.op-split {
|
||||
margin: 0 8px;
|
||||
color: #dcdee2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
|
||||
<el-table-column prop="updateTime" label="修改时间" width="180" align="center" />
|
||||
<el-table-column label="状态" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="goodsName" label="销售商品" min-width="160" />
|
||||
<el-table-column prop="goodsId" label="商品ID" width="140" />
|
||||
<el-table-column prop="goodsId" label="SPU编码" width="140" />
|
||||
<el-table-column prop="salesNum" label="商品销售数量" width="110" />
|
||||
<el-table-column prop="salesAmount" label="商品销售金额" width="120">
|
||||
<template #default="{ row }">{{ formatMoney(row.salesAmount) }}</template>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// 商家素材库(OSS 管理)样式:全部规则挂在 .oss-manage 根类下,避免全局污染
|
||||
// 商家素材库(OSS 管理)样式。
|
||||
// 本文件经 @import 全局引入且未加 scoped,页面内规则全部挂在 .oss-manage 根类下避免污染;
|
||||
// 全局样式里不要写 :deep(),浏览器会把含未知伪类的整条规则丢弃。
|
||||
.oss-manage {
|
||||
// 单面板容器
|
||||
.oss-panel {
|
||||
@@ -91,11 +93,11 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
gap: 16px 14px;
|
||||
margin-top: 14px;
|
||||
|
||||
.oss-grid-item {
|
||||
width: 120px;
|
||||
margin: 0 14px 14px 0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -104,12 +106,15 @@
|
||||
.oss-card {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ebeef5;
|
||||
background: #f7f8fa;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
transition: border-color 0.2s;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
|
||||
img,
|
||||
.card-media {
|
||||
@@ -150,7 +155,7 @@
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
:deep(.el-checkbox__label) {
|
||||
.el-checkbox__label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -186,6 +191,7 @@
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
|
||||
.oss-card-check {
|
||||
opacity: 1;
|
||||
@@ -236,6 +242,92 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧分组树
|
||||
.oss-group-tree {
|
||||
flex: 1;
|
||||
padding: 4px 4px 0;
|
||||
|
||||
.el-tree-node__expand-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-tree-node {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.el-tree-node__content {
|
||||
padding: 0 10px !important;
|
||||
height: 38px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
.el-tree-node__content:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.el-tree-node.is-current > .el-tree-node__content {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-tree-node.is-current .oss-tree-icon {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.oss-tree-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding-right: 4px;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover .oss-tree-more {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.oss-tree-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oss-tree-icon {
|
||||
margin-right: 6px;
|
||||
flex-shrink: 0;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.oss-tree-more {
|
||||
cursor: pointer;
|
||||
color: #909399;
|
||||
padding: 0 4px;
|
||||
font-size: 16px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// picker 模式的底部确认栏
|
||||
.oss-select-footer {
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ededed;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
// 弹窗选择器模式:去掉面板阴影,固定内容区最小高度,避免空分组时布局塌陷
|
||||
&.oss-manage-picker {
|
||||
.oss-panel {
|
||||
@@ -272,3 +364,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 以下两条用于 el-dialog 内部;el-dialog 默认挂载到 body 下,必须保持全局
|
||||
.modal-footer {
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-preview-fallback {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 16px;
|
||||
color: #606266;
|
||||
|
||||
.file-preview-name {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-preview-tip {
|
||||
margin: 0 0 20px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +1,5 @@
|
||||
<style lang="scss">
|
||||
@import "./ossManage.scss";
|
||||
|
||||
.table {
|
||||
flex: 11;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.oss-select-footer {
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ededed;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.oss-group-tree {
|
||||
flex: 1;
|
||||
padding: 4px 4px 0;
|
||||
|
||||
:deep(.el-tree-node__expand-icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__content) {
|
||||
padding: 0 10px !important;
|
||||
height: 38px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__content:hover) {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node.is-current > .el-tree-node__content) {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.oss-tree-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding-right: 4px;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover .oss-tree-more {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.oss-tree-more {
|
||||
cursor: pointer;
|
||||
color: #909399;
|
||||
padding: 0 4px;
|
||||
font-size: 16px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.file-preview-fallback {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 16px;
|
||||
color: #606266;
|
||||
|
||||
.file-preview-name {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-preview-tip {
|
||||
margin: 0 0 20px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<div class="search oss-manage" :class="{ 'oss-manage-picker': isComponent }">
|
||||
@@ -167,7 +62,10 @@
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span v-if="data" class="oss-tree-node">
|
||||
<span>{{ data.title }}</span>
|
||||
<span class="oss-tree-label">
|
||||
<el-icon class="oss-tree-icon"><Folder /></el-icon>
|
||||
{{ data.title }}
|
||||
</span>
|
||||
<el-dropdown
|
||||
v-if="data.value !== '0' && !isComponent"
|
||||
trigger="click"
|
||||
@@ -197,8 +95,6 @@
|
||||
'is-checked': selectedOss.includes(item.id + ',' + item.url),
|
||||
'oss-card-selectable': isComponent && selectImage,
|
||||
}"
|
||||
@mouseenter="onMouseOver(item, index)"
|
||||
@mouseleave="onMouseOut(item, index)"
|
||||
@click="handleCardClick(item, $event)"
|
||||
>
|
||||
<el-checkbox
|
||||
@@ -206,7 +102,15 @@
|
||||
class="oss-card-check"
|
||||
@click.stop
|
||||
/>
|
||||
<img v-if="isImageFile(item)" :src="item.url" alt="" />
|
||||
<img
|
||||
v-if="isImageFile(item) && !imgFailIds.has(item.id)"
|
||||
:src="item.url"
|
||||
alt=""
|
||||
@error="onImgError(item)"
|
||||
/>
|
||||
<div v-else-if="isImageFile(item)" class="card-file">
|
||||
<el-icon :size="28"><Picture /></el-icon>
|
||||
</div>
|
||||
<video
|
||||
v-else-if="isVideoFile(item)"
|
||||
class="card-media"
|
||||
@@ -216,7 +120,7 @@
|
||||
<div v-else class="card-file">
|
||||
<el-icon :size="28"><Document /></el-icon>
|
||||
</div>
|
||||
<div v-if="item.isShowPreview" class="oss-card-ops">
|
||||
<div class="oss-card-ops">
|
||||
<div @click.prevent="download(item)">
|
||||
<el-tooltip content="下载" placement="top">
|
||||
<el-icon :size="18"><Download /></el-icon>
|
||||
@@ -365,14 +269,13 @@ let dp;
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Delete, Download, View, Document } from "@element-plus/icons-vue";
|
||||
import { Delete, Download, View, Document, Folder, Picture } from "@element-plus/icons-vue";
|
||||
import {
|
||||
addFileDirectory,
|
||||
deleteFile,
|
||||
@@ -411,8 +314,11 @@ const props = defineProps({
|
||||
});
|
||||
const emit = defineEmits(["callback", "selected"]);
|
||||
|
||||
// $forceUpdate 依赖组件实例
|
||||
const { proxy } = getCurrentInstance();
|
||||
// 加载失败的图片 id 集合:裂图时用图标占位代替浏览器默认裂图样式
|
||||
const imgFailIds = ref(new Set());
|
||||
function onImgError(item) {
|
||||
imgFailIds.value.add(item.id);
|
||||
}
|
||||
|
||||
const groupFormValidate = ref({
|
||||
id: "",
|
||||
@@ -601,13 +507,6 @@ function onTreeDropdownCommand(cmd, data) {
|
||||
handleContextMenuDelete();
|
||||
}
|
||||
}
|
||||
function onMouseOver(item, index) {
|
||||
data.value[index].isShowPreview = true;
|
||||
proxy.$forceUpdate();
|
||||
}
|
||||
function onMouseOut(item, index) {
|
||||
data.value[index].isShowPreview = false;
|
||||
}
|
||||
function handleCategoryTabChange(name) {
|
||||
activeCategoryTab.value = name;
|
||||
searchForm.fileCategory = name;
|
||||
@@ -884,6 +783,7 @@ function getDataList(type = null) {
|
||||
getFileListData(searchForm)
|
||||
.then((res) => {
|
||||
if (res && res.success && res.result) {
|
||||
imgFailIds.value = new Set();
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total || 0;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user