Merge branch 'vue3'

This commit is contained in:
田香琪
2026-07-09 11:03:16 +08:00
672 changed files with 77633 additions and 60878 deletions

View File

@@ -1,23 +1,45 @@
<template>
<div id="main" class="app-main">
<router-view></router-view>
<router-view />
</div>
</template>
<script>
import Cookies from "js-cookie";
import util from "@/libs/util";
import { getCategoryTree } from "@/api/goods.js";
export default {
name: "App",
mounted() {
const loggedIn =
this.getStore("accessToken") || Cookies.get("userInfoSeller");
if (loggedIn) {
util.bootstrapDynamicRoutesFromCache();
util.initRouter(this);
this.$store.commit("setOpenedList");
this.$store.commit("initCachepage");
if (!localStorage.getItem("category")) {
getCategoryTree().then((res) => {
if (res.success && Array.isArray(res.result)) {
localStorage.setItem("category", JSON.stringify(res.result));
}
});
}
}
},
};
</script>
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #f0f0f0;
font-size: 14px;
}
.app-main {
@@ -33,14 +55,7 @@ body {
margin-right: 5px;
}
.ivu-btn-text:focus {
box-shadow: none !important;
}
.ivu-tag {
cursor: pointer;
}
.tox-notifications-container{
.tox-notifications-container {
display: none !important;
}
</style>

25
seller/src/api/finance.js Normal file
View File

@@ -0,0 +1,25 @@
/**
* 商家端财务 API。
* 所有接口由后端强制限定为当前登录店铺,无需前端传 storeId。
*/
import { getRequest } from "@/libs/axios";
/** 导出本店流水明细 */
export const exportStoreFlow = (params) => {
return getRequest("/finance/store-flow/export", params, "blob");
};
/** 导出本店结算单列表 */
export const exportBillList = (params) => {
return getRequest("/finance/bill-list/export", params, "blob");
};
/** 本店周期财务汇总查询 */
export const getStoreSummary = (params) => {
return getRequest("/finance/report/store-summary", params);
};
/** 导出本店周期财务汇总 */
export const exportStoreSummary = (params) => {
return getRequest("/finance/report/store-summary/export", params, "blob");
};

View File

@@ -147,6 +147,16 @@ export const getGoodsSkuData = params => {
export const getGoodsListData = params => {
return getRequest("/goods/goods/list", params);
};
// 商品分组分页
export const getGoodsGroupByPage = params => {
return getRequest("/goods/goodsGroup/getByPage", params);
};
// 设定商品分组(批量)
export const addGoodsGroupItems = (groupId, goodsIds) => {
return postRequest(`/goods/goodsGroup/${groupId}/goods`, {
goodsIds: Array.isArray(goodsIds) ? goodsIds.join(",") : goodsIds,
});
};
// 获取商品数量
export const getGoodsNumerData = (params) => {
return getRequest('/goods/goods/goodsNumber', params)
@@ -176,6 +186,9 @@ export const getGoodsCategoryAll = () => {
return getRequest(`/goods/category/all`);
};
// 兼容旧页面的分类树命名
export const getCategoryTree = getGoodsCategoryAll;
// 获取当前店铺分类
export const getShopGoodsLabelListSeller = () => {
return getRequest(`/goods/label`);

View File

@@ -1,5 +1,5 @@
// 统一请求路径前缀在libs/axios.js中修改
import { getRequest, postRequest, putRequest ,postRequestWithNoForm } from "@/libs/axios";
import { getRequest, postRequest, putRequest, deleteRequest, postRequestWithNoForm } from "@/libs/axios";
import { baseUrl } from "@/libs/axios.js";
@@ -42,6 +42,11 @@ export const getOrderDetail = sn => {
return getRequest(`/order/order/${sn}`);
};
// 订单付款
export const orderPay = sn => {
return postRequest(`/order/order/${sn}/pay`);
};
// 调整订单金额
export const modifyOrderPrice = (sn, params) => {
return putRequest(`/order/order/update/${sn}/price`, params);
@@ -65,18 +70,31 @@ export const editOrderConsignee = (sn, params) => {
export const getComplainPage = params => {
return getRequest(`/order/complain`, params);
};
export const getOrderComplain = getComplainPage;
//获取投诉详情
export const getComplainDetail = id => {
return getRequest(`/order/complain/${id}`);
};
export const getOrderComplainDetail = getComplainDetail;
//添加交易投诉对话
export const addOrderComplaint = params => {
return postRequest(`/order/complain/communication/`, params);
return postRequest(`/order/complain/communication`, params);
};
export const addOrderCommunication = addOrderComplaint;
// 更新投诉状态
export const storeComplain = params => {
return putRequest(`/order/complain/status`, params);
};
//添加交易投诉对话
// 仲裁完成投诉
export const orderComplete = (id, params) => {
return putRequest(`/order/complain/complete/${id}`, params);
};
//商家申诉
export const appeal = params => {
return putRequest(`/order/complain/appeal`, params);
};
@@ -110,11 +128,43 @@ export const orderTake = (sn, verificationCode) => {
export const afterSaleOrderPage = params => {
return getRequest(`/order/afterSale/page`, params);
};
export const getAfterSaleOrderPage = afterSaleOrderPage;
// 售后服务单详情
export const afterSaleOrderDetail = sn => {
return getRequest(`/order/afterSale/${sn}`);
};
export const getAfterSaleOrderDetail = afterSaleOrderDetail;
// 获取售后原因分页列表
export const getAfterSaleReasonPage = params => {
return getRequest(`/order/afterSaleReason/getByPage`, params);
};
// 删除售后原因
export const delAfterSaleReason = id => {
return deleteRequest(`/order/afterSaleReason/delByIds/${id}`);
};
// 添加售后原因
export const addAfterSaleReason = params => {
return postRequest(`/order/afterSaleReason`, params);
};
// 修改售后原因
export const editAfterSaleReason = (id, params) => {
return putRequest(`/order/afterSaleReason/update/${id}`, params);
};
// 售后单商家收货信息
export const storeAddress = sn => {
return getRequest(`/order/afterSale/getStoreAfterSaleAddress/${sn}`);
};
// 售后退款
export const refundPrice = (afterSaleSn, params) => {
return putRequest(`/order/afterSale/refund/${afterSaleSn}`, params);
};
// 商家审核
export const afterSaleSellerReview = (sn, params) => {

View File

@@ -6,6 +6,12 @@ import { getRequest, postRequest, postRequestWithNoForm, putRequest, deleteReque
export const getLiveList = (params) => {
return getRequest('/broadcast/studio', params)
}
// 是否推荐直播间
export const whetherStar = (params) => {
return putRequest(`/broadcast/studio/recommend/${params.id}`, params)
}
// 添加直播间
export const addLive = (params) => {
return postRequest('/broadcast/studio', params)
@@ -145,6 +151,17 @@ export const removeSeckillGoods = (seckillId, ids) => {
export const seckillDetail = (seckillId) => {
return getRequest(`/promotion/seckill/${seckillId}`)
}
// 删除秒杀活动
export const delSeckill = (id) => {
return deleteRequest(`/promotion/seckill/${id}`)
}
// 关闭秒杀活动
export const updateSeckillStatus = (id, params) => {
return putRequest(`/promotion/seckill/status/${id}`, params)
}
// 删除秒杀商品
export const delSeckillGoods = params => {
return deleteRequest(`/promotion/seckill/apply/${params.seckillId}/${params.id}`);
@@ -182,3 +199,26 @@ export const updateFullDiscount = (id, params) => {
export const getCouponReceiveList = (params) => {
return getRequest("/promotion/coupon/received", params);
};
// ========== 限时直降 ==========
export const getFlashDiscountList = (params) => getRequest('/promotion/flashDiscount', params)
export const getFlashDiscountDetail = (id) => getRequest(`/promotion/flashDiscount/${id}`)
export const saveFlashDiscount = (params) => postRequest('/promotion/flashDiscount', params, { 'Content-type': 'application/json' })
export const editFlashDiscount = (params) => putRequest('/promotion/flashDiscount', params, { 'Content-type': 'application/json' })
export const deleteFlashDiscount = (id) => deleteRequest(`/promotion/flashDiscount/${id}`)
export const updateFlashDiscountStatus = (id, params) => putRequest(`/promotion/flashDiscount/status/${id}`, params)
// ========== 第N件优惠 ==========
export const getNthItemDiscountList = (params) => getRequest('/promotion/nthItemDiscount', params)
export const getNthItemDiscountDetail = (id) => getRequest(`/promotion/nthItemDiscount/${id}`)
export const saveNthItemDiscount = (params) => postRequest('/promotion/nthItemDiscount', params, { 'Content-type': 'application/json' })
export const editNthItemDiscount = (params) => putRequest('/promotion/nthItemDiscount', params, { 'Content-type': 'application/json' })
export const updateNthItemDiscountStatus = (id, params) => putRequest(`/promotion/nthItemDiscount/status/${id}`, params)
// 兼容平台优惠券命名(商家端实际调用店铺优惠券接口)
export const getPlatformCouponList = getShopCouponList
export const savePlatformCoupon = saveShopCoupon
export const editPlatformCoupon = editShopCoupon
export const getPlatformCoupon = getShopCoupon
export const deletePlatformCoupon = deleteShopCoupon
export const updatePlatformCouponStatus = updateCouponStatus

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,140 @@
<template>
<div class="set-password">
<el-popover trigger="focus" placement="right" :width="250">
<template #reference>
<el-input
v-model="currentValue"
type="password"
show-password
style="width: 350px"
:maxlength="maxlength"
:size="size"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
@input="handleChange"
/>
</template>
<div :class="tipStyle">
<div class="words">强度 : {{ strength }}</div>
<el-progress
:percentage="strengthValue"
:status="progressStatus"
:show-text="false"
style="margin: 13px 0"
/>
<br />请至少输入 6 个字符请不要使用容易被猜到的密码
</div>
</el-popover>
</div>
</template>
<script>
export default {
name: "setPassword",
props: {
modelValue: String,
value: String,
size: String,
placeholder: {
type: String,
default: "请输入密码长度为6-20个字符",
},
disabled: {
type: Boolean,
default: false,
},
readonly: {
type: Boolean,
default: false,
},
maxlength: {
type: Number,
default: 20,
},
},
emits: ["update:modelValue", "input", "on-change"],
data() {
return {
currentValue: this.modelValue ?? this.value ?? "",
tipStyle: "password-tip-none",
strengthValue: 0,
progressStatus: "",
strength: "无",
grade: 0,
};
},
watch: {
modelValue(val) {
this.setCurrentValue(val);
},
value(val) {
this.setCurrentValue(val);
},
},
methods: {
checkStrengthValue(v) {
let grade = 0;
if (/\d/.test(v)) grade++;
if (/[a-z]/.test(v)) grade++;
if (/[A-Z]/.test(v)) grade++;
if (/\W/.test(v)) grade++;
if (v.length >= 10) grade++;
this.grade = grade;
return grade;
},
strengthChange() {
if (!this.currentValue) {
this.tipStyle = "password-tip-none";
this.strength = "无";
this.strengthValue = 0;
return;
}
const grade = this.checkStrengthValue(this.currentValue);
if (grade <= 1) {
this.progressStatus = "exception";
this.tipStyle = "password-tip-weak";
this.strength = "弱";
this.strengthValue = 33;
} else if (grade >= 2 && grade <= 4) {
this.progressStatus = "";
this.tipStyle = "password-tip-middle";
this.strength = "中";
this.strengthValue = 66;
} else {
this.progressStatus = "success";
this.tipStyle = "password-tip-strong";
this.strength = "强";
this.strengthValue = 100;
}
},
handleChange() {
this.strengthChange();
this.$emit("update:modelValue", this.currentValue);
this.$emit("input", this.currentValue);
this.$emit("on-change", this.currentValue, this.grade, this.strength);
},
setCurrentValue(value) {
if (value === this.currentValue) return;
this.currentValue = value ?? "";
this.strengthChange();
this.$emit("on-change", this.currentValue, this.grade, this.strength);
},
},
};
</script>
<style lang="scss" scoped>
.password-tip-none {
padding: 1vh 0;
}
.password-tip-weak .words {
color: #ed3f14;
}
.password-tip-middle .words {
color: #2d8cf0;
}
.password-tip-strong .words {
color: #52c41a;
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<span :style="priceStyle">
{{ dot }}{{ displayText }}
<slot />
</span>
</template>
<script>
import { unitPrice } from "@/utils/filters";
export default {
name: "priceColorScheme",
props: {
value: {
default: 0,
validator(val) {
return (
val === null ||
val === undefined ||
typeof val === "number" ||
typeof val === "string"
);
},
},
unit: {
type: String,
default: "¥",
},
dot: {
type: String,
default: "",
},
color: {
type: String,
default: "",
},
customStyle: {
type: Object,
default: () => ({}),
},
},
computed: {
displayText() {
const val = this.value;
if (val === null || val === undefined || val === "" || val === "null") {
return `${this.unit || "¥"}0.00`;
}
return unitPrice(val, this.unit);
},
priceStyle() {
const resolvedColor = this.color || this.$mainColor || "";
return resolvedColor
? { color: resolvedColor, ...this.customStyle }
: { ...this.customStyle };
},
},
};
</script>

View File

@@ -1,6 +1,12 @@
module.exports = {
title: "lilishop", //配置显示在浏览器标签的title
icpCard: "", // icp证
company: {
href: "https://pickmall.cn",
name: "北京宏业汇成科技有限公司",
}, //公司信息
icpMessage: "京ICP备20009696号-1", //icp备案
/**
* 高德地图申请链接
* https://lbs.amap.com/api/javascript-api/guide/abc/prepare

View File

@@ -1,7 +1,7 @@
import axios from "axios";
import { getStore, setStore } from "./storage";
import { router } from "../router/index";
import { Message } from "view-design";
import { Message } from "@/utils/message";
import Cookies from "js-cookie";
import { handleRefreshToken } from "@/api/index";
import {v4 as uuidv4} from 'uuid';
@@ -25,8 +25,8 @@ const service = axios.create({
});
axios.defaults.timeout = 100000
const recordCurrentPath = () => {
return router.history.current.fullPath
}
return router.currentRoute.value.fullPath;
};
// 跳转登录页
const redirectLogin = () => {
router.push({path:'/login',query:{redirect: recordCurrentPath()}});

View File

@@ -1,13 +1,162 @@
import lazyLoading from './lazyLoading.js';
import Cookies from "js-cookie";
import { result } from './routerJson.js';
import { getCurrentPermissionList } from "@/api/index";
import lazyLoading from "./lazyLoading.js";
import { router } from "@/router/index";
import Cookies from "js-cookie";
import config from "@/config/index";
const config = require('@/config/index')
let util = {};
let util = {
/** 静态路由名,禁止被动态路由清理误删 */
const STATIC_ROUTE_NAMES = new Set([
"home_index",
"change_pass",
"message_index",
"main",
"renovation",
"login",
"forgetPassword",
]);
util.dynamicRouteNames = [];
util.clearDynamicRoutes = function () {
util.dynamicRouteNames.forEach((name) => {
if (STATIC_ROUTE_NAMES.has(name)) {
return;
}
if (router.hasRoute(name)) {
router.removeRoute(name);
}
});
util.dynamicRouteNames = util.dynamicRouteNames.filter(
(name) => !STATIC_ROUTE_NAMES.has(name)
);
};
util.resolveRouteComponent = function (menu) {
const routeKey =
menu.frontRoute ||
(typeof menu.component === "string" &&
menu.component &&
menu.component !== "Main"
? menu.component
: "");
return routeKey ? lazyLoading(routeKey) : null;
};
util.collectLeafRoutes = function (routes, result = [], parentPath = "") {
routes.forEach((route) => {
let segment = route.path != null ? String(route.path) : "";
segment = segment.replace(/^\//, "");
const fullPath = [parentPath, segment].filter(Boolean).join("/");
const hasChildren = route.children && route.children.length > 0;
if (hasChildren) {
util.collectLeafRoutes(route.children, result, fullPath);
} else if (
route.name &&
typeof route.component === "function" &&
!String(route.name).endsWith("__layout")
) {
result.push({
path: fullPath || segment || route.name,
name: route.name,
component: route.component,
meta: route.meta || {},
});
}
});
return result;
};
util.registerDynamicRoutes = function (menuData, options = {}) {
const { rematch = true } = options;
const pendingPath = router.currentRoute.value.fullPath;
const pendingUnmatched = router.currentRoute.value.matched.length === 0;
util.clearDynamicRoutes();
const constRoutes = [];
util.initAllMenuData(constRoutes, menuData);
const leaves = [];
constRoutes.forEach((top) => {
const base = (top.path || "").replace(/^\//, "");
if (top.children && top.children.length) {
util.collectLeafRoutes(top.children, leaves, base);
} else if (
top.name &&
typeof top.component === "function" &&
!String(top.name).endsWith("__layout")
) {
leaves.push({
path: base || top.name,
name: top.name,
component: top.component,
meta: top.meta || {},
});
}
});
leaves.forEach((leaf) => {
const path = (leaf.path || leaf.name || "").replace(/^\//, "");
let component = leaf.component;
if (typeof component === "string") {
component = util.resolveRouteComponent({ component });
}
if (
typeof component !== "function" ||
STATIC_ROUTE_NAMES.has(leaf.name) ||
router.hasRoute(leaf.name)
) {
return;
}
router.addRoute("otherRouter", {
path,
name: leaf.name,
component,
meta: leaf.meta,
});
util.dynamicRouteNames.push(leaf.name);
});
if (!router.hasRoute("error-404")) {
router.addRoute({
path: "/:pathMatch(.*)*",
name: "error-404",
component: lazyLoading("error-page/404"),
meta: { title: "404-页面不存在" },
});
util.dynamicRouteNames.push("error-404");
}
if (
rematch &&
pendingUnmatched &&
pendingPath &&
pendingPath !== "/login"
) {
const resolved = router.resolve(pendingPath);
if (resolved.matched.length > 0) {
router.replace(pendingPath).catch(() => {});
}
}
};
util.bootstrapDynamicRoutesFromCache = function () {
if (!Cookies.get("userInfoSeller")) {
return false;
}
try {
const raw = window.localStorage.getItem("menuData");
if (!raw) {
return false;
}
util.registerDynamicRoutes(JSON.parse(raw), { rematch: false });
return true;
} catch (e) {
console.warn("[router] menuData parse failed", e);
return false;
}
};
util.title = function (title) {
@@ -15,51 +164,8 @@ util.title = function (title) {
window.document.title = title;
};
util.millsToTime = function (mills) {
if (!mills) {
return "";
}
let s = mills / 1000;
if (s < 60) {
return s.toFixed(0) + " 秒"
}
let m = s / 60;
if (m < 60) {
return m.toFixed(0) + " 分钟"
}
let h = m / 60;
if (h < 24) {
return h.toFixed(0) + " 小时"
}
let d = h / 24;
if (d < 30) {
return d.toFixed(0) + " 天"
}
let month = d / 30
if (month < 12) {
return month.toFixed(0) + " 个月"
}
let year = month / 12
return year.toFixed(0) + " 年"
};
util.inOf = function (arr, targetArr) {
let res = true;
arr.forEach(item => {
if (targetArr.indexOf(item) < 0) {
res = false;
}
});
return res;
};
util.oneOf = function (ele, targetArr) {
if (targetArr.indexOf(ele) >= 0) {
return true;
} else {
return false;
}
return targetArr.indexOf(ele) >= 0;
};
util.getRouterObjByName = function (routers, name) {
@@ -80,29 +186,31 @@ util.getRouterObjByName = function (routers, name) {
};
util.handleTitle = function (vm, item) {
if (typeof item.title == 'object') {
return item.title;
} else {
if (!item) {
return "";
}
if (typeof item.title == "object") {
return item.title;
}
return item.title;
};
util.setCurrentPath = function (vm, name) {
let title = '';
let title = "";
let isOtherRouter = false;
vm.$store.state.app.routers.forEach(item => {
vm.$store.state.app.routers.forEach((item) => {
if (item.children.length == 1) {
if (item.children[0].name == name) {
title = util.handleTitle(vm, item);
if (item.name == 'otherRouter') {
if (item.name == "otherRouter") {
isOtherRouter = true;
}
}
} else {
item.children.forEach(child => {
item.children.forEach((child) => {
if (child.name == name) {
title = util.handleTitle(vm, child);
if (item.name == 'otherRouter') {
if (item.name == "otherRouter") {
isOtherRouter = true;
}
}
@@ -110,64 +218,71 @@ util.setCurrentPath = function (vm, name) {
}
});
let currentPathArr = [];
if (name == 'home_index') {
if (name == "home_index") {
currentPathArr = [
{
title: util.handleTitle(vm, util.getRouterObjByName(vm.$store.state.app.routers, 'home_index')),
path: '',
name: 'home_index'
}
title: util.handleTitle(
vm,
util.getRouterObjByName(vm.$store.state.app.routers, "home_index")
),
path: "",
name: "home_index",
},
];
} else if ((name.indexOf('_index') >= 0 || isOtherRouter) && name !== 'home_index') {
} else if ((name.indexOf("_index") >= 0 || isOtherRouter) && name !== "home_index") {
currentPathArr = [
{
title: util.handleTitle(vm, util.getRouterObjByName(vm.$store.state.app.routers, 'home_index')),
path: '/home',
name: 'home_index'
title: util.handleTitle(
vm,
util.getRouterObjByName(vm.$store.state.app.routers, "home_index")
),
path: "/home",
name: "home_index",
},
{
title: title,
path: '',
name: name
}
path: "",
name: name,
},
];
} else {
let currentPathObj = vm.$store.state.app.routers.filter(item => {
let currentPathObj = vm.$store.state.app.routers.filter((item) => {
if (item.children.length <= 1) {
return item.children[0].name == name;
} else {
let i = 0;
let childArr = item.children;
let len = childArr.length;
while (i < len) {
if (childArr[i].name == name) {
return true;
}
i++;
}
return false;
}
})[0];
if (currentPathObj.children.length <= 1 && currentPathObj.name == 'home') {
currentPathArr = [
{
title: '首页',
path: '',
name: 'home_index'
let i = 0;
let childArr = item.children;
let len = childArr.length;
while (i < len) {
if (childArr[i].name == name) {
return true;
}
];
} else if (currentPathObj.children.length <= 1 && currentPathObj.name !== 'home') {
i++;
}
return false;
})[0];
if (!currentPathObj) {
currentPathArr = [];
} else if (currentPathObj.children.length <= 1 && currentPathObj.name == "home") {
currentPathArr = [
{
title: '首页',
path: '/home',
name: 'home_index'
title: "首页",
path: "",
name: "home_index",
},
];
} else if (currentPathObj.children.length <= 1 && currentPathObj.name !== "home") {
currentPathArr = [
{
title: "首页",
path: "/home",
name: "home_index",
},
{
title: currentPathObj.title,
path: '',
name: name
}
path: "",
name: name,
},
];
} else {
let childObj = currentPathObj.children.filter((child) => {
@@ -175,24 +290,24 @@ util.setCurrentPath = function (vm, name) {
})[0];
currentPathArr = [
{
title: '首页',
path: '/home',
name: 'home_index'
title: "首页",
path: "/home",
name: "home_index",
},
{
title: currentPathObj.title,
path: '',
name: currentPathObj.name
path: "",
name: currentPathObj.name,
},
{
title: childObj.title,
path: currentPathObj.path + '/' + childObj.path,
name: name
}
path: currentPathObj.path + "/" + childObj.path,
name: name,
},
];
}
}
vm.$store.commit('setCurrentPath', currentPathArr);
vm.$store.commit("setCurrentPath", currentPathArr);
return currentPathArr;
};
@@ -206,11 +321,11 @@ util.openNewPage = function (vm, name, argu, query) {
let i = 0;
let tagHasOpened = false;
while (i < openedPageLen) {
if (name == storeOpenedList[i].name) { // 页面已经打开
vm.$store.commit('storeOpenedList', {
if (name == storeOpenedList[i].name) {
vm.$store.commit("storeOpenedList", {
index: i,
argu: argu,
query: query
query: query,
});
tagHasOpened = true;
break;
@@ -221,23 +336,18 @@ util.openNewPage = function (vm, name, argu, query) {
let tag = vm.$store.state.app.tagsList.filter((item) => {
if (item.children) {
return name == item.children[0].name;
} else {
return name == item.name;
}
return name == item.name;
});
tag = tag[0];
if (tag) {
tag = tag.children ? tag.children[0] : tag;
if (argu) {
tag.argu = argu;
}
if (query) {
tag.query = query;
}
vm.$store.commit('increateTag', tag);
if (argu) tag.argu = argu;
if (query) tag.query = query;
vm.$store.commit("increateTag", tag);
}
}
vm.$store.commit('setCurrentPageName', name);
vm.$store.commit("setCurrentPageName", name);
};
util.toDefaultPage = function (routers, name, route, next) {
@@ -245,9 +355,13 @@ util.toDefaultPage = function (routers, name, route, next) {
let i = 0;
let notHandle = true;
while (i < len) {
if (routers[i].name == name && routers[i].children && routers[i].redirect == undefined) {
if (
routers[i].name == name &&
routers[i].children &&
routers[i].redirect == undefined
) {
route.replace({
name: routers[i].children[0].name
name: routers[i].children[0].name,
});
notHandle = false;
next();
@@ -260,172 +374,101 @@ util.toDefaultPage = function (routers, name, route, next) {
}
};
// 将Csv文件解析为二维数组
export const getArrayFromFile = (file) => {
let nameSplit = file.name.split('.')
let format = nameSplit[nameSplit.length - 1]
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.readAsText(file) // 以文本格式读取
let arr = []
reader.onload = function (evt) {
let data = evt.target.result // 读到的数据
let pasteData = data.trim()
arr = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => {
return row.split('\t')
}).map(item => {
return item[0].split(',')
})
if (format == 'csv') resolve(arr)
else reject(new Error('[Format Error]:不是Csv文件'))
}
})
}
// 将二维数组转为表格数据
export const getTableDataFromArray = (array) => {
let columns = []
let tableData = []
if (array.length > 1) {
let titles = array.shift()
columns = titles.map(item => {
return {
title: item,
key: item
}
})
tableData = array.map(item => {
let res = {}
item.forEach((col, i) => {
res[titles[i]] = col
})
return res
})
}
return {
columns,
tableData
}
}
util.initRouter = function (vm) { // 初始化路由
util.initRouter = function (vm) {
const constRoutes = [];
const otherRoutes = [];
// 404路由需要和动态路由一起加载
const otherRouter = [{
path: '/*',
name: 'error-404',
meta: {
title: '404-页面不存在'
},
component: 'error-page/404'
}];
// 判断用户是否登录
let userInfo = Cookies.get('userInfoSeller')
let userInfo = Cookies.get("userInfoSeller");
if (!userInfo) {
// 未登录
return;
}
if (!vm.$store.state.app.added) {
getCurrentPermissionList().then((res) => {
if (!res.success) return false;
let menuData = res.result;
// 加载菜单
getCurrentPermissionList().then(res => {
if (!res.success) return false;
let menuData = res.result;
// 格式化数据,设置 空children 为 null
for (let i = 0; i < menuData.length; i++) {
let t = menuData[i].children
for (let k = 0; k < t.length; k++) {
let tt = t[k].children;
for (let z = 0; z < tt.length; z++) {
tt[z].children = null
// 给所有三级路由添加字段显示一级菜单name方便点击页签时的选中筛选
tt[z].firstRouterName = menuData[i].name
for (let i = 0; i < menuData.length; i++) {
let t = menuData[i].children;
for (let k = 0; k < t.length; k++) {
let tt = t[k].children;
for (let z = 0; z < tt.length; z++) {
tt[z].children = null;
tt[z].firstRouterName = menuData[i].name;
}
}
}
}
if (!menuData) {
if (!menuData) {
return;
}
util.initAllMenuData(constRoutes, menuData);
util.registerDynamicRoutes(menuData);
vm.$store.commit(
"updateAppRouter",
constRoutes.filter((item) => item.children && item.children.length > 0)
);
util.initMenuData(vm, menuData);
window.localStorage.setItem("menuData", JSON.stringify(menuData));
vm.$store.commit("setAdded", true);
});
} else {
let data = window.localStorage.getItem("menuData");
if (!data) {
vm.$store.commit("setAdded", false);
util.initRouter(vm);
return;
}
util.initAllMenuData(constRoutes, menuData);
util.initRouterNode(otherRoutes, otherRouter);
// 添加所有主界面路由
vm.$store.commit('updateAppRouter', constRoutes.filter(item => item.children.length > 0));
// 添加全局路由
vm.$store.commit('updateDefaultRouter', otherRoutes);
// 添加菜单路由
let menuData = JSON.parse(data);
util.registerDynamicRoutes(menuData);
util.initMenuData(vm, menuData);
// 缓存数据 修改加载标识
window.localStorage.setItem('menuData', JSON.stringify(menuData));
vm.$store.commit('setAdded', true);
if(vm.$store.state.app.refMenu){
vm.$nextTick(()=>{
vm.$store.state.app.refMenu.updateActiveName();
});
}
})
}
};
// 添加所有顶部导航栏下的菜单路由
util.initAllMenuData = function (constRoutes, data) {
let allMenuData = [];
data.forEach(e => {
data.forEach((e) => {
if (e.level == 0) {
e.children.forEach(item => {
e.children.forEach((item) => {
allMenuData.push(item);
})
});
}
})
});
util.initRouterNode(constRoutes, allMenuData);
}
};
// 生成菜单格式数据
util.initMenuData = function (vm, data) {
const menuRoutes = [];
let menuData = data;
// 顶部菜单
let navList = [];
menuData.forEach(e => {
let nav = {
name: e.name,
title: e.title,
}
navList.push(nav);
})
menuData.forEach((e) => {
navList.push({ name: e.name, title: e.title });
});
if (navList.length < 1) {
return;
}
// 存入vuex
vm.$store.commit('setNavList', navList);
let currNav = window.localStorage.getItem('currNav')
vm.$store.commit("setNavList", navList);
let currNav = window.localStorage.getItem("currNav");
if (currNav) {
// 读取缓存title
for (var item of navList) {
for (let item of navList) {
if (item.name == currNav) {
vm.$store.commit('setCurrNavTitle', item.title);
vm.$store.commit("setCurrNavTitle", item.title);
break;
}
}
} else {
// 默认第一个
currNav = navList[0].name;
vm.$store.commit('setCurrNavTitle', navList[0].title);
vm.$store.commit("setCurrNavTitle", navList[0].title);
}
vm.$store.commit('setCurrNav', currNav);
for (var item of menuData) {
vm.$store.commit("setCurrNav", currNav);
for (let item of menuData) {
if (item.name == currNav) {
// 过滤
menuData = item.children;
break;
}
}
util.initRouterNode(menuRoutes, menuData);
// 刷新界面菜单
vm.$store.commit('updateMenulist', menuRoutes.filter(item => item.children.length > 0));
vm.$store.commit(
"updateMenulist",
menuRoutes.filter((item) => item.children.length > 0)
);
let tagsList = [];
vm.$store.state.app.routers.map((item) => {
@@ -435,24 +478,41 @@ util.initMenuData = function (vm, data) {
tagsList.push(...item.children);
}
});
vm.$store.commit('setTagsList', tagsList);
vm.$store.commit("setTagsList", tagsList);
};
// 生成路由节点
util.initRouterNode = function (routers, data) { // data为所有子菜单数据
util.initRouterNode = function (routers, data) {
for (let item of data) {
const menu = Object.assign({}, item);
const hasChildren = item.children && item.children.length > 0;
for (var item of data) {
let menu = Object.assign({}, item);
menu.component = lazyLoading(menu.frontRoute);
if (item.children && item.children.length > 0) {
if (hasChildren) {
menu.children = [];
if (menu.name) {
menu.name = `${menu.name}__layout`;
}
util.initRouterNode(menu.children, item.children);
const layoutComponent = util.resolveRouteComponent(menu);
if (layoutComponent) {
menu.component = layoutComponent;
} else {
delete menu.component;
}
} else {
const component = util.resolveRouteComponent(menu);
if (component) {
menu.component = component;
} else {
delete menu.component;
}
}
let meta = {};
// 给页面添加标题
meta.title = menu.title ? menu.title + " - " + config.title + "商家后台" : null;
meta.firstRouterName = item.firstRouterName
meta.keepAlive = menu.keepAlive ? true : false
const meta = {};
meta.title = menu.title
? menu.title + " - " + config.title + "商家后台"
: null;
meta.firstRouterName = item.firstRouterName;
meta.keepAlive = menu.keepAlive ? true : false;
menu.meta = meta;
routers.push(menu);

View File

@@ -1,122 +1,87 @@
import Vue from "vue";
import ViewUI from "view-design";
import "./styles/theme.less";
import { createApp } from "vue";
import "core-js/stable";
import vueQr from "vue-qr";
import liliDialog from '@/views/lili-dialog'
import App from "./App";
import {router} from "./router/index";
import "./styles/theme.less";
import App from "./App.vue";
import { router } from "./router/index";
import store from "./store";
import { setupElementPlus } from "@/plugins/element";
import { setupLegacyMessage } from "@/utils/message";
import liliDialog from "@/views/lili-dialog";
import PriceColorScheme from "@/components/price-color-scheme.vue";
import { install as installVueQr } from "vue-qr";
import VueLazyload from "vue-lazyload";
import {
getRequest,
postRequest,
putRequest,
deleteRequest,
importRequest,
uploadFileRequest
uploadFileRequest,
} from "@/libs/axios";
import {setStore, getStore, removeStore} from "@/libs/storage";
import { setStore, getStore, removeStore } from "@/libs/storage";
import util from "@/libs/util";
import { md5 } from "@/utils/md5.js";
import * as filters from "@/utils/filters";
import config from "@/config/index";
import imgError from "./assets/img-error.png";
import loadingGif from "./assets/loading2.gif";
import VueLazyload from "vue-lazyload";
const { aMapSecurityJsCode, mainColor } = config;
import * as filters from "@/utils/filters"; // global filter
import {md5} from "@/utils/md5.js";
const {aMapSecurityJsCode, inputMaxLength,mainColor} = require("@/config");
// 打印
import Print from 'vue-print-nb';
Vue.use(Print);
// 高德安全密钥
if (aMapSecurityJsCode) {
window._AMapSecurityConfig = {
securityJsCode: aMapSecurityJsCode,
};
}
Vue.config.devtools = true;
Vue.config.productionTip = false;
Vue.use(VueLazyload, {
error: require("./assets/img-error.png"),
loading: require("./assets/loading2.gif")
const PC_URL = BASE.PC_URL;
const WAP_URL = BASE.WAP_URL;
util.bootstrapDynamicRoutesFromCache();
const app = createApp(App);
setupElementPlus(app);
setupLegacyMessage(app);
installVueQr(app);
app.use(VueLazyload, {
error: imgError,
loading: loadingGif,
});
// 引入价格格式化组件
import priceColorScheme from 'price-color'
Vue.use(priceColorScheme);
app.use(router);
app.use(store);
const copyViewUi = {...ViewUI}
copyViewUi.Input.props.maxlength.default = inputMaxLength // 挂载最大输入值
Vue.use(copyViewUi);
app.component("liliDialog", liliDialog);
app.component("priceColorScheme", PriceColorScheme);
Vue.component('liliDialog', liliDialog)
app.config.globalProperties.getRequest = getRequest;
app.config.globalProperties.postRequest = postRequest;
app.config.globalProperties.putRequest = putRequest;
app.config.globalProperties.deleteRequest = deleteRequest;
app.config.globalProperties.importRequest = importRequest;
app.config.globalProperties.uploadFileRequest = uploadFileRequest;
app.config.globalProperties.setStore = setStore;
app.config.globalProperties.getStore = getStore;
app.config.globalProperties.removeStore = removeStore;
app.config.globalProperties.$mainColor = mainColor;
app.config.globalProperties.md5 = md5;
app.config.globalProperties.$filters = filters;
Vue.component('liliDialog', liliDialog)
Vue.component("vue-qr", vueQr); //此处将vue-qr添加为全局组件
// 挂载全局使用的方法
Vue.prototype.getRequest = getRequest;
Vue.prototype.postRequest = postRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.deleteRequest = deleteRequest;
Vue.prototype.importRequest = importRequest;
Vue.prototype.uploadFileRequest = uploadFileRequest;
Vue.prototype.setStore = setStore;
Vue.prototype.getStore = getStore;
Vue.prototype.removeStore = removeStore;
Vue.prototype.$mainColor = mainColor;
Vue.prototype.md5 = md5;
const PC_URL = BASE.PC_URL; // 跳转买家端地址 pc端
const WAP_URL = BASE.WAP_URL; // 跳转买家端地址 wap端
Vue.prototype.linkTo = function (goodsId, skuId) {
// 跳转买家端商品
app.config.globalProperties.linkTo = function (goodsId, skuId) {
window.open(
`${PC_URL}/goodsDetail?skuId=${skuId}&goodsId=${goodsId}`,
"_blank"
);
};
Vue.prototype.wapLinkTo = function (goodsId, skuId) {
// app端二维码
app.config.globalProperties.wapLinkTo = function (goodsId, skuId) {
return `${WAP_URL}/pages/product/goods?id=${skuId}&goodsId=${goodsId}`;
};
Array.prototype.remove = function (from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key]);
router.isReady().then(() => {
app.mount("#app");
});
/* eslint-disable no-new */
new Vue({
el: "#app",
router,
store,
render: h => h(App),
data: {
currentPageName: ""
},
mounted() {
// 初始化菜单
util.initRouter(this);
this.currentPageName = this.$route.name;
// 显示打开的页面的列表
this.$store.commit("setOpenedList");
this.$store.commit("initCachepage");
}
});
export { app, util };

View File

@@ -0,0 +1,11 @@
import ElementPlus from "element-plus";
import zhCn from "element-plus/es/locale/lang/zh-cn";
import "element-plus/dist/index.css";
import "@/styles/element.scss";
export function setupElementPlus(app) {
app.use(ElementPlus, {
locale: zhCn,
size: "default",
});
}

View File

@@ -1,59 +1,50 @@
import Vue from 'vue';
import ViewUI from 'view-design';
import Util from '../libs/util';
import VueRouter from 'vue-router';
import Cookies from 'js-cookie';
import { routers } from './router';
import { createRouter, createWebHistory } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import Util from "../libs/util";
import Cookies from "js-cookie";
import store from "@/store";
import { routers } from "./router";
Vue.use(VueRouter);
NProgress.configure({ showSpinner: false });
// 路由配置
const RouterConfig = {
mode: 'history',
routes: routers
};
/**
* 解决重复点击菜单会控制台报错bug
*/
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error => error)
}
export const router = new VueRouter(RouterConfig);
export const router = createRouter({
history: createWebHistory(),
routes: routers,
});
router.beforeEach((to, from, next) => {
ViewUI.LoadingBar.start();
NProgress.start();
Util.title(to.meta.title);
next();
const name = to.name;
const hasToken = Cookies.get("userInfoSeller");
if (!Cookies.get('userInfoSeller') && name !== 'login') {
if (name === 'forgetPassword') {
console.log(name)
if (!hasToken && name !== "login") {
if (name === "forgetPassword") {
Util.toDefaultPage([...routers], name, router, next);
} else {
// 判断是否已经登录且前往的页面不是登录页
next({
name: 'login'
});
return;
}
} else if (Cookies.get('userInfoSeller') && name === 'login') {
// 判断是否已经登录且前往的是登录页
Util.title();
next({
name: 'home_index'
});
} else {
Util.toDefaultPage([...routers], name, router, next);
next({ name: "login" });
return;
}
if (hasToken && name === "login") {
Util.title();
next({ name: "home_index" });
return;
}
if (hasToken) {
Util.toDefaultPage([...routers], name, router, next);
return;
}
next();
});
router.afterEach((to) => {
Util.openNewPage(router.app, to.name, to.params, to.query);
ViewUI.LoadingBar.finish();
Util.openNewPage({ $store: store }, to.name, to.params, to.query);
NProgress.done();
window.scrollTo(0, 0);
});

View File

@@ -1,6 +1,5 @@
import Main from "@/views/Main.vue";
const config = require('@/config/index')
import config from "@/config/index";
// 不作为Main组件的子页面展示的页面单独写如下
export const loginRouter = {
path: "/login",
@@ -109,15 +108,17 @@ export const otherRouter = {
},
{
path: "/floorList/main",
title: "编辑模板",
title: "移动装修",
name: "main",
component: () => import("@/views/shop/wap/main.vue")
meta: { title: "移动装修" },
component: () => import("@/views/shop/wap/main.vue"),
},
{
path: "/pcFloorList/main",
title: "编辑模板",
title: "PC装修",
name: "renovation",
component: () => import("@/views/shop/renovation.vue")
meta: { title: "PC装修" },
component: () => import("@/views/shop/renovation.vue"),
},
{
path: "order-complaint-detail",
@@ -139,44 +140,36 @@ export const otherRouter = {
name: "full-discount-detail",
component: () => import("@/views/promotion/full-discount/full-discount-add.vue")
},
{
path: "flash-discount-add",
title: "限时直降",
name: "flash-discount-add",
component: () => import("@/views/promotion/flash-discount/flash-discount-add.vue")
},
{
path: "nth-item-discount-add",
title: "第N件优惠",
name: "nth-item-discount-add",
component: () => import("@/views/promotion/nth-item-discount/nth-item-discount-add.vue")
},
{
path: "export-order-deliver",
title: "发货",
name: "export-order-deliver",
component: () => import("@/views/order/order/exportOrderDeliver.vue")
},
{
path: "order-detail",
title: "订单详情",
name: "order-detail",
component: () => import("@/views/order/order/orderDetail.vue")
},
{
path: "/floorList/main",
title: "编辑模板",
name: "main",
component: () => import("@/views/shop/wap/main.vue")
},
{
path: "/pcFloorList/main",
title: "编辑模板",
name: "renovation",
component: () => import("@/views/shop/renovation.vue")
},
{
path: "promotions/coupon-receive",
title: "优惠券领取记录",
name: "coupon-receive",
component: () => import("@/views/promotion/coupon/coupon-receive.vue"),
},
// {
// path: "/*",
// name: "error-404",
// meta: {
// title: "404-页面不存在"
// },
// component: () => import("@/views/error-page/404.vue")
// }
{
path: "shop-finance-summary",
title: "财务汇总",
name: "shop-finance-summary",
component: () => import("@/views/shop/finance/summary.vue")
}
]
};

View File

@@ -1,30 +1,19 @@
import Vue from 'vue';
import Vuex from 'vuex';
import { createStore } from "vuex";
import app from "./modules/app";
import setting from "./modules/setting";
import user from "./modules/user";
import dict from "./modules/dict";
import app from './modules/app';
import setting from './modules/setting';
import user from './modules/user';
import dict from './modules/dict';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 状态
},
mutations: {
// 改变方法
},
actions: {
},
modules: {
app,
user,
setting,
dict
}
const store = createStore({
state: {},
mutations: {},
actions: {},
modules: {
app,
user,
setting,
dict,
},
});
export default store;

View File

@@ -1,61 +1,52 @@
import { otherRouter } from '@/router/router';
import { router } from '@/router/index';
import Util from '@/libs/util';
import Vue from 'vue';
import { otherRouter } from "@/router/router";
import Util from "@/libs/util";
const app = {
state: {
shipTemplates: "",
regions: [], //此处是在地区选择器时赋值一次
styleStore: "", //移动端楼层装修中选择风格存储
loading: false, // 全局加载动画
added: false, // 加载路由标识
navList: [], // 顶部菜单
currNav: "", // 当前顶部菜单name
currNavTitle: "", // 当前顶部菜单标题
cachePage: [], // 缓存的页面
lang: '',
regions: [],
styleStore: "",
loading: false,
added: false,
navList: [],
currNav: "",
currNavTitle: "",
cachePage: [],
lang: "",
isFullScreen: false,
openedSubmenuArr: [], // 要展开的菜单数组
menuTheme: 'dark', // 主题
themeColor: '',
storeOpenedList: [{
title: '首页',
path: '',
name: 'home_index'
}],
currentPageName: '',
openedSubmenuArr: [],
menuTheme: "dark",
themeColor: "",
storeOpenedList: [
{
title: "首页",
path: "",
name: "home_index",
},
],
currentPageName: "",
currentPath: [
{
title: '首页',
path: '',
name: 'home_index'
}
title: "首页",
path: "",
name: "home_index",
},
],
// 面包屑数组 左侧菜单
menuList: [],
routers: [
otherRouter
],
routers: [otherRouter],
tagsList: [...otherRouter.children],
messageCount: 0,
// 在这里定义你不想要缓存的页面的name属性值(参见路由配置router.js)
dontCache: ['test', 'test'],
refMenu:""
dontCache: ["test", "test"],
refMenu: "",
},
mutations: {
childrenMenu(state,v){
state.refMenu = v
childrenMenu(state, v) {
state.refMenu = v;
},
// 动态添加主界面路由,需要缓存
updateAppRouter(state, routes) {
state.routers.push(...routes);
router.addRoutes(routes);
},
// 动态添加全局路由404、500等页面不需要缓存
updateDefaultRouter(state, routes) {
router.addRoutes(routes);
},
updateDefaultRouter() {},
setLoading(state, v) {
state.loading = v;
},
@@ -78,15 +69,7 @@ const app = {
state.menuList = routes;
},
addOpenSubmenu(state, name) {
let hasThisName = false;
let isEmpty = false;
if (name.length == 0) {
isEmpty = true;
}
if (state.openedSubmenuArr.indexOf(name) > -1) {
hasThisName = true;
}
if (!hasThisName && !isEmpty) {
if (name.length && state.openedSubmenuArr.indexOf(name) === -1) {
state.openedSubmenuArr.push(name);
}
},
@@ -112,19 +95,15 @@ const app = {
},
storeOpenedList(state, get) {
let openedPage = state.storeOpenedList[get.index];
if (get.argu) {
openedPage.argu = get.argu;
}
if (get.query) {
openedPage.query = get.query;
}
if (get.argu) openedPage.argu = get.argu;
if (get.query) openedPage.query = get.query;
state.storeOpenedList.splice(get.index, 1, openedPage);
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
},
clearAllTags(state) {
state.storeOpenedList.splice(1);
state.cachePage.length = 0;
localStorage.cachePage = '';
localStorage.cachePage = "";
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
},
clearOtherTags(state, vm) {
@@ -141,15 +120,14 @@ const app = {
state.storeOpenedList.splice(currentIndex + 1);
state.storeOpenedList.splice(1, currentIndex - 1);
}
let newCachepage = state.cachePage.filter(item => {
return item == currentName;
});
state.cachePage = newCachepage;
state.cachePage = state.cachePage.filter((item) => item == currentName);
localStorage.cachePage = JSON.stringify(state.cachePage);
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
},
setOpenedList(state) {
state.storeOpenedList = localStorage.storeOpenedList ? JSON.parse(localStorage.storeOpenedList) : [otherRouter.children[0]];
state.storeOpenedList = localStorage.storeOpenedList
? JSON.parse(localStorage.storeOpenedList)
: [otherRouter.children[0]];
},
setCurrentPath(state, pathArr) {
state.currentPath = pathArr;
@@ -163,7 +141,6 @@ const app = {
switchLang(state, lang) {
state.lang = lang;
localStorage.lang = lang;
Vue.config.lang = lang;
},
clearOpenedSubmenu(state) {
state.openedSubmenuArr.length = 0;
@@ -171,7 +148,6 @@ const app = {
setMessageCount(state, count) {
state.messageCount = count;
},
// 新增页签
increateTag(state, tagObj) {
if (!Util.oneOf(tagObj.name, state.dontCache)) {
state.cachePage.push(tagObj.name);
@@ -179,8 +155,8 @@ const app = {
}
state.storeOpenedList.push(tagObj);
localStorage.storeOpenedList = JSON.stringify(state.storeOpenedList);
}
}
},
},
};
export default app;

View File

@@ -1,4 +1,6 @@
const { setting } = require("@/config");
import config from "@/config/index";
const { setting } = config;
const localSetting = window.localStorage.getItem('setting')
const settingData = {

View File

@@ -25,6 +25,13 @@
.width_1200 {
width: 1200px;
}
.width_1200_auto {
width: 1200px;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
box-sizing: border-box;
}
.width_800 {
width: 800px;
}
@@ -133,3 +140,9 @@ $theme_color: #F31947;
color: $theme_color !important;
}
$bg_color: #f1f6fa;
@import "./table-common.scss";
.el-table table {
width: 100% !important;
}

View File

@@ -0,0 +1,100 @@
/* Element Plus 主题覆盖(对齐原 iView 主色) */
:root {
// --el-color-primary: #f31947;
// --el-color-success: #67c23a;
// --el-color-warning: #fa6419;
// --el-color-danger: #ff3c2a;
--el-font-size-extra-small: 12px;
--el-font-size-small: 13px;
--el-font-size-base: 14px;
--el-font-size-large: 16px;
}
// .el-button--primary {
// --el-button-bg-color: #f31947;
// --el-button-border-color: #f31947;
// --el-button-hover-bg-color: #ff4d6d;
// --el-button-hover-border-color: #ff4d6d;
// }
.el-table--border {
.el-table__cell {
border-right: none !important;
}
.el-table__border-left-patch {
display: none;
}
&::before,
&::after,
.el-table__inner-wrapper::before,
.el-table__inner-wrapper::after {
width: 0 !important;
}
&.el-table--group::before,
&.el-table--group::after,
&.el-table--group .el-table__inner-wrapper::before,
&.el-table--group .el-table__inner-wrapper::after {
width: 0 !important;
}
}
.el-table__fixed-right::before,
.el-table__fixed::before {
background-color: transparent;
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
&:hover {
color: #66b1ff;
}
&.disabled {
color: #c0c4cc;
cursor: not-allowed;
pointer-events: none;
}
}
.op-split {
margin: 0 8px;
color: #dcdfe6;
}
.el-table .ops {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
a:not(.link-text) {
color: #409eff;
cursor: pointer;
text-decoration: none;
&:hover {
color: #66b1ff;
}
}
> span:not(.op-split) {
margin: 0 8px;
color: #dcdfe6;
}
}
.gcc-disabled-action {
color: #c0c4cc;
cursor: not-allowed;
}
/* MessageBox 需高于嵌套 Dialog如上传图片/资源库 z-index: 3500~3700 */
.el-overlay.is-message-box {
z-index: 4000 !important;
}

View File

@@ -16,17 +16,19 @@
width: 100% !important;
display: flex;
align-items: center;
// background-color: #f0f0f0;
border-radius: 0.4em;
flex-wrap: wrap;
> .ivu-form-item {
> .el-form-item {
margin: 8px 10px !important;
}
}
.padding-row {
margin-top: 15px;
margin-bottom: 15px;
}
.search-btn {
margin-left: 20px;
}
@@ -38,7 +40,6 @@
overflow: hidden;
}
// 为Card组件之间增加间距
.ivu-card + .ivu-card {
.search > .el-card + .el-card {
margin-top: 10px;
}
}

View File

@@ -1,6 +1,4 @@
@import "~view-design/src/styles/index.less";
// iview 自定义样式
// Element Plus 主题变量(原 view-design less 已移除)
@primary-color: #F31947;
@info-color: #fa6419;
@success-color: #68cabe;
@@ -10,3 +8,8 @@
@table-td-hover-bg: #ededed;
@table-td-highlight-bg: #ededed;
@font-size-base: 12px;
.el-drawer,
.drawer {
z-index: 2600 !important;
}

View File

@@ -0,0 +1,15 @@
/**
* 触发浏览器下载 blob 响应
*/
export function downloadBlob(blob, filename) {
if (!blob) return;
const link = document.createElement("a");
link.style.display = "none";
const url = window.URL.createObjectURL(new Blob([blob]));
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}

View File

@@ -126,8 +126,6 @@ export function unixSellerBillStatus(status_code) {
return "已出账";
case "CHECK":
return "已对账";
case "EXAMINE":
return "已审核";
case "PAY":
return "已结算";
case "COMPLETE":

View File

@@ -0,0 +1,85 @@
import { ElMessage, ElMessageBox } from "element-plus";
/**
* 兼容原 view-design Message API便于业务页渐进迁移
*/
export const Message = {
success(content) {
return ElMessage.success(normalize(content));
},
error(content) {
return ElMessage.error(normalize(content));
},
warning(content) {
return ElMessage.warning(normalize(content));
},
info(content) {
return ElMessage.info(normalize(content));
},
};
export const Notice = {
open(options = {}) {
const fn = Message[options.type] || Message.info;
return fn(options.desc || options.title || "");
},
info(options) {
return Message.info(options?.desc || options?.title || "");
},
success(options) {
return Message.success(options?.desc || options?.title || "");
},
warning(options) {
return Message.warning(options?.desc || options?.title || "");
},
error(options) {
return Message.error(options?.desc || options?.title || "");
},
};
export const Modal = {
confirm(options = {}) {
const content = options.content || options.title || "确认操作?";
return ElMessageBox.confirm(content, options.title || "提示", {
confirmButtonText: options.okText || "确定",
cancelButtonText: options.cancelText || "取消",
type: options.type || "warning",
})
.then(() => {
if (typeof options.onOk === "function") {
return options.onOk();
}
})
.catch(() => {
if (typeof options.onCancel === "function") {
options.onCancel();
}
});
},
warning(options = {}) {
const content = options.content || options.title || "";
return ElMessageBox.alert(content, options.title || "提示", {
confirmButtonText: options.okText || "确定",
type: "warning",
}).then(() => {
if (typeof options.onOk === "function") {
return options.onOk();
}
});
},
remove() {
ElMessageBox.close();
},
};
function normalize(content) {
if (typeof content === "string") return content;
if (content && content.content) return content.content;
return String(content ?? "");
}
export function setupLegacyMessage(app) {
app.config.globalProperties.$Message = Message;
app.config.globalProperties.$Modal = Modal;
app.config.globalProperties.$Notice = Notice;
}

26
seller/src/utils/print.js Normal file
View File

@@ -0,0 +1,26 @@
/**
* 打印指定 DOM 区域(替代 vue-print-nb
*/
export function printElement(elementId, title = "打印") {
const el = document.getElementById(elementId);
if (!el) {
console.warn(`[print] element #${elementId} not found`);
return;
}
const iframe = document.createElement("iframe");
iframe.style.cssText = "position:fixed;right:0;bottom:0;width:0;height:0;border:0";
document.body.appendChild(iframe);
const doc = iframe.contentWindow.document;
doc.open();
doc.write(
`<!DOCTYPE html><html><head><title>${title}</title><style>
body{font-family:Arial,sans-serif;padding:12px;color:#333}
table{width:100%;border-collapse:collapse}
td,th{border:1px solid #ddd;padding:6px}
</style></head><body>${el.innerHTML}</body></html>`
);
doc.close();
iframe.contentWindow.focus();
iframe.contentWindow.print();
setTimeout(() => document.body.removeChild(iframe), 1000);
}

View File

@@ -85,3 +85,26 @@ export function memberPromotionsStatusRender(h, status) {
),
]);
}
/**
* 优惠券「活动时间 / 有效期」文案活动获取券effectiveDays 为领取后有效天数)
*/
export function formatPromotionCouponValidityHtml(row) {
if (!row) return "-";
if (row.getType === "ACTIVITY") {
const days = row.effectiveDays;
if (days !== undefined && days !== null && days !== "") {
const n = Number(days);
if (!Number.isNaN(n) && n > 0) {
return `领取后${n}天有效`;
}
}
}
if (row.startTime && row.endTime) {
return `${row.startTime}<br/>${row.endTime}`;
}
if (row.getType === "ACTIVITY" && row.rangeDayType === "DYNAMICTIME") {
return "长期有效";
}
return "-";
}

View File

@@ -1,222 +1,241 @@
<template>
<div class="forget-password" @click='$refs.verify.show = false'>
<div style="height:50px;"></div>
<!-- 顶部logo -->
<div class="forget-password" @click="$refs.verify.show = false">
<div style="height: 50px"></div>
<div class="logo-box">
<img
:src="$store.state.logoImg" width='150'
@click="$router.push('/')"
/>
<img :src="$store.state.logoImg" width="150" @click="$router.push('/')" />
<div>修改密码</div>
</div>
<div class="login-container">
<!-- 验证手机号 -->
<Form
ref="formFirst"
:model="formFirst"
:rules="ruleInline"
style="width:300px;"
v-show="step === 0"
>
<FormItem prop="mobile">
<i-input
type="text"
v-model="formFirst.mobile"
clearable
placeholder="手机号"
>
<Icon type="md-phone-portrait" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="code">
<i-input
type="text"
v-model="formFirst.code"
clearable
placeholder="手机验证码"
>
<Icon
type="ios-text-outline"
style="font-weight: bold"
slot="prepend"
/>
<Button slot="append" @click="sendCode">{{ codeMsg }}</Button>
</i-input>
</FormItem>
<FormItem>
<Button @click="verifyBtnClick" long :type="verifyStatus?'success':'default'">{{verifyStatus?'验证通过':'点击完成安全验证'}}</Button>
</FormItem>
<FormItem>
<Button type="error" @click="next" :loading="loading" long>下一步</Button>
</FormItem>
</Form>
<Form
ref="form"
:model="form"
:rules="ruleInline"
style="width:300px;"
v-show="step === 1"
>
<FormItem prop="password">
<i-input
type="password"
v-model="form.password"
clearable
placeholder="请输入至少六位密码"
>
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="password">
<i-input
type="password"
v-model="form.oncePasd"
clearable
placeholder="请再次输入密码"
>
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem>
<Button type="error" size="large" @click="handleSubmit" :loading="loading1" long>提交</Button>
</FormItem>
</Form>
<!-- 拼图验证码 -->
<verify
ref="verify"
class="verify-con"
:verifyType="verifyType"
@change="verifyChange"
></verify>
<div class="login-btn"><a @click="$router.push('login')">前往登录</a></div>
<el-form
ref="formFirst"
:model="formFirst"
:rules="ruleInline"
style="width: 300px"
v-show="step === 0"
>
<el-form-item prop="mobile">
<el-input v-model="formFirst.mobile" clearable placeholder="手机号">
<template #prepend>
<el-icon><Iphone /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input v-model="formFirst.code" clearable placeholder="手机验证码">
<template #prepend>
<el-icon><Message /></el-icon>
</template>
<template #append>
<el-button @click="sendCode">{{ codeMsg }}</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button
style="width: 100%"
:type="verifyStatus ? 'success' : 'default'"
@click="verifyBtnClick"
>
{{ verifyStatus ? "验证通过" : "点击完成安全验证" }}
</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger" style="width: 100%" :loading="loading" @click="next">
下一步
</el-button>
</el-form-item>
</el-form>
<el-form
ref="form"
:model="form"
:rules="ruleInline"
style="width: 300px"
v-show="step === 1"
>
<el-form-item prop="password">
<el-input
v-model="form.password"
type="password"
show-password
clearable
placeholder="请输入至少六位密码"
>
<template #prepend>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="oncePasd">
<el-input
v-model="form.oncePasd"
type="password"
show-password
clearable
placeholder="请再次输入密码"
>
<template #prepend>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button
type="danger"
size="large"
style="width: 100%"
:loading="loading1"
@click="handleSubmit"
>
提交
</el-button>
</el-form-item>
</el-form>
<verify
ref="verify"
class="verify-con"
:verifyType="verifyType"
@change="verifyChange"
/>
<div class="login-btn"><a @click="$router.push('login')">前往登录</a></div>
</div>
<div class="foot">
<Row type="flex" justify="space-around" class="help">
<el-row justify="space-around" class="help">
<a class="item" href="https://pickmall.cn/" target="_blank">帮助</a>
<a class="item" href="https://pickmall.cn/" target="_blank">隐私</a>
<a class="item" href="https://pickmall.cn/" target="_blank">条款</a>
</Row>
<Row type="flex" justify="center" class="copyright">
Copyright © {{year}} - Present
<a href="https://pickmall.cn/" target="_blank" style="margin: 0 5px"
>{{config.title}}</a
>
</el-row>
<el-row justify="center" class="copyright">
Copyright © {{ year }} - Present
<a href="https://pickmall.cn/" target="_blank" style="margin: 0 5px">{{
config.title
}}</a>
版权所有
</Row>
</el-row>
</div>
</div>
</template>
<script>
import * as RegExp from '@/utils/regular.js';
import * as apiLogin from '@/api/index.js';
import { sendSms } from '@/api/common.js';
import { Iphone, Message, Lock } from "@element-plus/icons-vue";
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 verify from "@/views/my-components/verify";
export default {
name: 'ForgetPassword',
components: { verify },
data () {
name: "ForgetPassword",
components: { verify, Iphone, Message, Lock },
data() {
return {
config:require('@/config'),
loading: false, // 加载状态
loading1: false, // 第二步加载状态
formFirst: { // 手机验证码表单
// 注册表单
mobile: '',
code: ''
config,
loading: false,
loading1: false,
formFirst: {
mobile: "",
code: "",
},
form: { // 密码
password: '',
oncePasd: ''
form: {
password: "",
oncePasd: "",
},
year: new Date().getFullYear(), // 当前年份
step: 0, // 步骤
year: new Date().getFullYear(),
step: 0,
ruleInline: {
// 验证规则
mobile: [
{ required: true, message: '请输入手机号码' },
{ required: true, message: "请输入手机号码" },
{
pattern: RegExp.mobile,
trigger: 'blur',
message: '请输入正确的手机号'
}
trigger: "blur",
message: "请输入正确的手机号",
},
],
code: [{ required: true, message: "请输入手机验证码" }],
password: [
{ required: true, message: "密码不能为空" },
{ pattern: RegExp.password, message: "密码不能少于6位" },
],
code: [{ required: true, message: '请输入手机验证码' }],
password: [{required: true, message: '密码不能为空'}, {pattern: RegExp.password, message: '密码不能少于6位'}]
},
verifyStatus: false, // 图片验证状态
verifyType: 'FIND_USER', // 图片验证类型
codeMsg: '发送验证码', // 验证码文字
interval: '', // 定时器
time: 60 // 倒计时时间
verifyStatus: false,
verifyType: "FIND_USER",
codeMsg: "发送验证码",
interval: "",
time: 60,
};
},
methods: {
// 提交短信验证码,修改密码
next () {
next() {
this.$refs.formFirst.validate((valid) => {
if (valid) {
this.loading = true;
let data = JSON.parse(JSON.stringify(this.formFirst));
apiLogin.validateCode(data).then((res) => {
this.loading = false;
if (res.success) {
// this.$Message.success('');
this.step = 1;
} else {
this.$Message.warning(res.message);
}
}).catch(() => { this.loading = false; });
} else {}
const data = JSON.parse(JSON.stringify(this.formFirst));
apiLogin
.validateCode(data)
.then((res) => {
this.loading = false;
if (res.success) {
this.step = 1;
} else {
this.$Message.warning(res.message);
}
})
.catch(() => {
this.loading = false;
});
}
});
},
handleSubmit () { // 提交密码
this.$refs.form.validate(valid => {
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
let params = JSON.parse(JSON.stringify(this.form));
const params = JSON.parse(JSON.stringify(this.form));
if (params.password !== params.oncePasd) {
this.$Message.warning('两次输入密码不一致');
this.$Message.warning("两次输入密码不一致");
return;
};
}
params.mobile = this.formFirst.mobile;
params.password = this.md5(params.password);
delete params.oncePasd;
this.loading1 = true;
apiLogin.forgetAndModify(params).then(res => {
this.loading1 = false;
if (res.success) {
this.$Message.success('修改密码成功');
this.$router.push('login');
}
}).catch(() => { this.loading = false; });
};
apiLogin
.forgetAndModify(params)
.then((res) => {
this.loading1 = false;
if (res.success) {
this.$Message.success("修改密码成功");
this.$router.push("login");
}
})
.catch(() => {
this.loading1 = false;
});
}
});
},
sendCode () { // 发送验证码
sendCode() {
if (this.time === 60) {
if (this.formFirst.mobile === '') {
this.$Message.warning('请先填写手机号');
if (this.formFirst.mobile === "") {
this.$Message.warning("请先填写手机号");
return;
}
if (!this.verifyStatus) {
this.$Message.warning('请先完成安全验证');
this.$Message.warning("请先完成安全验证");
return;
}
let params = {
const params = {
mobile: this.formFirst.mobile,
verificationEnums: 'FIND_USER'
verificationEnums: "FIND_USER",
};
sendSms(params).then(res => {
sendSms(params).then((res) => {
if (res.success) {
this.$Message.success('验证码发送成功');
let that = this;
this.$Message.success("验证码发送成功");
const that = this;
this.interval = setInterval(() => {
that.time--;
if (that.time === 0) {
that.time = 60;
that.codeMsg = '重新发送';
that.codeMsg = "重新发送";
that.verifyStatus = false;
clearInterval(that.interval);
} else {
@@ -229,28 +248,26 @@ export default {
});
}
},
verifyChange (con) { // 验证通过
verifyChange(con) {
if (!con.status) return;
this.$refs.verify.show = false;
this.verifyStatus = true;
},
verifyBtnClick () {
verifyBtnClick() {
if (!this.verifyStatus) {
this.$refs.verify.init();
}
}
},
},
mounted () {
document.querySelector('.forget-password').style.height = window.innerHeight + 'px'
mounted() {
document.querySelector(".forget-password").style.height = window.innerHeight + "px";
this.$refs.formFirst.resetFields();
},
watch: {
}
};
</script>
<style scoped lang="scss">
.forget-password{
.forget-password {
min-height: 700px;
}
.logo-box {
@@ -276,38 +293,20 @@ export default {
width: 600px;
background-color: #fff;
padding: 20px 150px;
.login-btn{
.login-btn {
position: absolute;
right: 20px;
top: -45px;
}
}
.verify-con{
.verify-con {
position: absolute;
left: 140px;
top: -30px;
z-index: 10;
}
.other-login {
margin: 0 auto;
.ivu-icon {
font-size: 24px;
}
}
.regist {
display: flex;
justify-content: flex-end;
margin-top: -10px;
span {
margin-left: 10px;
&:hover {
cursor: pointer;
color: $theme_color;
}
}
}
.foot {
position: fixed;
bottom: 4vh;

View File

@@ -1,4 +1,4 @@
<style lang="scss" scoped>
<style lang="scss">
@import "./main.scss";
</style>
@@ -8,177 +8,153 @@
<div class="logo-con">
<img :src="storeSideLogo" key="max-logo" />
</div>
<shrinkable-menu></shrinkable-menu>
<shrinkable-menu />
</div>
<!-- 顶部标题栏主体 -->
<div class="main-header-con" :style="{ height: setting.isUseTabsRouter ? '100px' : '60px' }">
<div class="main-header-con" :style="{ height: setting.isUseTabsRouter ? '106px' : '60px' }">
<div class="main-header">
<div
class="header-avator-con"
>
<!-- 左侧栏 -->
<div>
</div>
<!-- 用户头像 -->
<div class="header-avator-con">
<div></div>
<div class="user-dropdown-menu-con">
<Row
type="flex"
<el-row
justify="end"
align="middle"
class="user-dropdown-innercon"
>
<ul class="nav-list">
<li class="nav-item " @click="im">
<Tooltip content="联系客服">
<Button type="info" size="small" :loading='load' icon="md-chatbubbles">客服</Button>
</Tooltip>
<li class="nav-item" @click="im">
<el-tooltip content="联系客服" placement="bottom">
<el-button type="warning" size="small" :loading="load">
<el-icon><ChatDotRound /></el-icon>
客服
</el-button>
</el-tooltip>
</li>
<li class="nav-item " @click="handleClickSetting">
<Tooltip content="设置">
<Icon size="16" type="md-settings" />
</Tooltip>
<li class="nav-item" @click="handleClickSetting">
<el-tooltip content="设置" placement="bottom">
<el-icon :size="16"><Setting /></el-icon>
</el-tooltip>
</li>
</ul>
<Dropdown
transfer
trigger="hover"
@on-click="handleClickUserDropdown"
>
<el-dropdown trigger="hover" @command="handleClickUserDropdown">
<div class="dropList">
<Avatar
icon="ios-person"
<el-avatar
:size="32"
:src="userInfo.storeLogo"
style="background: #fff; margin-left: 10px"
></Avatar>
>
<el-icon><UserFilled /></el-icon>
</el-avatar>
</div>
<DropdownMenu slot="list">
<DropdownItem name="changePass">修改密码</DropdownItem>
<DropdownItem name="loginOut" divided>退出</DropdownItem>
</DropdownMenu>
</Dropdown>
</Row>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="changePass">修改密码</el-dropdown-item>
<el-dropdown-item divided command="loginOut">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-row>
</div>
</div>
</div>
<!-- 已打开的页面标签 -->
<div class="tags-con" v-if="setting.isUseTabsRouter">
<tags-page-opened :pageTagsList="pageTagsList"></tags-page-opened>
<tags-page-opened :pageTagsList="pageTagsList" />
</div>
</div>
<div class="single-page-con" :style="{ 'top': setting.isUseTabsRouter ? '100px' : '60px', height: setting.isUseTabsRouter ? 'calc(100% - 110px)' : 'calc(100% - 70px)' }">
<div
class="single-page-con"
:style="{
top: setting.isUseTabsRouter ? '106px' : '60px',
height: setting.isUseTabsRouter ? 'calc(100% - 116px)' : 'calc(100% - 70px)',
}"
>
<div class="single-page">
<!-- <keep-alive :include="cachePage"> -->
<!-- </keep-alive> -->
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
<router-view v-slot="{ Component }">
<component :is="Component" v-if="Component" :key="$route.fullPath" />
</router-view>
</div>
</div>
<!-- 全局加载动画 -->
<circleLoading class="loading-position" v-show="loading" />
<!-- 右侧抽屉配置 -->
<configDrawer ref="config"/>
<configDrawer ref="config" />
</div>
</template>
<script>
import { ChatDotRound, Setting, UserFilled } from "@element-plus/icons-vue";
import shrinkableMenu from "./main-components/shrinkable-menu/shrinkable-menu.vue";
import tagsPageOpened from "./main-components/tags-page-opened.vue";
import circleLoading from "@/views/my-components/lili/circle-loading.vue";
import configDrawer from "@/views/main-components/config-drawer.vue";
import Cookies from "js-cookie";
import util from "@/libs/util.js";
import { logout } from "@/api/index";
import { getIMDetail } from "@/api/common";
import { userMsg } from "@/api/index";
const config = require("@/config/index.js");
import config from "@/config/index.js";
export default {
components: {
ChatDotRound,
Setting,
UserFilled,
shrinkableMenu,
tagsPageOpened,
circleLoading,
configDrawer
configDrawer,
},
data() {
return {
config,
sliceNum: 5, // 展示nav数量
userInfo: {}, // 用户信息
storeSideLogo: "", //logo图片
IMLink: "", // IM链接
load: false, // 加载IM状态
sliceNum: 5,
userInfo: {},
storeSideLogo: "",
IMLink: "",
load: false,
};
},
computed: {
setting(){
let data = this.$store.state.setting
return data.setting
setting() {
return this.$store.state.setting.setting;
},
loading() {
return this.$store.state.app.loading;
},
pageTagsList() {
return this.$store.state.app.storeOpenedList; // 打开的页面的页面对象
},
cachePage() {
return this.$store.state.app.cachePage;
return this.$store.state.app.storeOpenedList;
},
lang() {
return this.$store.state.app.lang;
},
mesCount() {
return 0;
},
},
methods: {
handleClickSetting() {
this.$refs.config.open();
},
/**
* 点击登录im的时候需要去判断一下当前店铺信息是否失效
* 失效的话重新请求刷新token保证最新的token去访问im
*/
async im () {
// 获取访问Token
let accessToken = this.getStore("accessToken");
this.load = true
await this.getIMDetailMethods();
const userInfo = await userMsg();
this.load = false
if (userInfo.success && this.IMLink) {
window.open(`${this.IMLink}?token=` + accessToken);
}
else{
this.$Message.error("请登录后再联系客服");
}
},
// 获取im信息
async getIMDetailMethods () {
let res = await getIMDetail();
if (res.success) {
this.IMLink = res.result;
}
},
// 初始化方法
async im() {
const accessToken = this.getStore("accessToken");
this.load = true;
await this.getIMDetailMethods();
const userInfo = await userMsg();
this.load = false;
if (userInfo.success && this.IMLink) {
window.open(`${this.IMLink}?token=` + accessToken);
} else {
this.$Message.error("请登录后再联系客服");
}
},
async getIMDetailMethods() {
const res = await getIMDetail();
if (res.success) {
this.IMLink = res.result;
}
},
init() {
// 菜单
let pathArr = util.setCurrentPath(this, this.$route.name);
const pathArr = util.setCurrentPath(this, this.$route.name);
if (pathArr.length >= 2) {
this.$store.commit("addOpenSubmenu", pathArr[1].name);
}
this.storeSideLogo = localStorage.getItem("sellerlogoImg");
window.document.title = localStorage.getItem("sellersiteName");
//动态获取icon
let link =
const link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
@@ -186,47 +162,33 @@ export default {
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
let userInfo = JSON.parse(Cookies.get("userInfoSeller"));
const userInfo = JSON.parse(Cookies.get("userInfoSeller"));
this.userInfo = userInfo;
this.checkTag(this.$route.name);
let currWidth = document.body.clientWidth;
const currWidth = document.body.clientWidth;
if (currWidth <= 1200) {
this.sliceNum = 2;
}
},
// 用户头像下拉
handleClickUserDropdown(name) {
if (name == "ownSpace") {
util.openNewPage(this, "personal-enter");
this.$router.push({
name: "personal-enter",
});
} else if (name == "changePass") {
if (name === "changePass") {
util.openNewPage(this, "change_pass");
this.$router.push({
name: "change_pass",
});
} else if (name == "loginOut") {
logout().then(res => {
this.$router.push({ name: "change_pass" });
} else if (name === "loginOut") {
logout().then(() => {
Cookies.set("accessToken", "");
this.$store.commit("logout", this);
this.$store.commit("clearOpenedSubmenu");
this.setStore("accessToken", "");
this.setStore("refreshToken", "");
this.$router.push({ path: "/login" });
})
});
}
},
// 快捷页签选中状态
checkTag(name) {
let openpageHasTag = this.pageTagsList.some((item) => {
if (item.name == name) {
return true;
}
});
const openpageHasTag = this.pageTagsList.some((item) => item.name === name);
if (!openpageHasTag) {
// 解决关闭当前标签后再点击回退按钮会退到当前页时没有标签的问题
util.openNewPage(
this,
name,
@@ -235,21 +197,16 @@ export default {
);
}
},
// 宽度变化
resize() {
let currWidth = document.body.clientWidth;
let count = currWidth / 300;
if (count > 6) {
this.sliceNum = 6;
} else {
this.sliceNum = count;
}
const currWidth = document.body.clientWidth;
const count = currWidth / 300;
this.sliceNum = count > 6 ? 6 : count;
},
},
watch: {
$route(to) {
this.$store.commit("setCurrentPageName", to.name);
let pathArr = util.setCurrentPath(this, to.name);
const pathArr = util.setCurrentPath(this, to.name);
if (pathArr.length > 2) {
this.$store.commit("addOpenSubmenu", pathArr[1].name);
}
@@ -257,20 +214,19 @@ export default {
localStorage.currentPageName = to.name;
},
lang() {
util.setCurrentPath(this, this.$route.name); // 在切换语言时用于刷新面包屑
util.setCurrentPath(this, this.$route.name);
},
},
created() {
this.$store.commit("setOpenedList");
},
mounted() {
this.init();
let that = this;
this.resize();
window.addEventListener("resize", function () {
that.resize();
});
window.addEventListener("resize", this.resize);
},
created() {
// 显示打开的页面的列表
this.$store.commit("setOpenedList");
beforeUnmount() {
window.removeEventListener("resize", this.resize);
},
};
</script>

View File

@@ -1,19 +0,0 @@
.change-pass {
&-btn-box {
margin-bottom: 10px;
button {
padding-left: 0;
span {
color: #2D8CF0;
transition: all .2s;
}
span:hover {
color: #0C25F1;
transition: all .2s;
}
}
}
}

View File

@@ -1,47 +1,55 @@
<template>
<div>
<Card class="change-pass">
<p slot="title"><Icon type="key"></Icon>修改密码</p>
<div>
<Form
ref="editPasswordForm"
:model="editPasswordForm"
:label-width="100"
label-position="right"
:rules="passwordValidate"
style="width:450px"
>
<FormItem label="原密码" prop="oldPass">
<Input type="password" v-model="editPasswordForm.oldPass" placeholder="请输入现在使用的密码"></Input>
</FormItem>
<FormItem label="新密码" prop="newPassword">
<SetPassword style="width:350px;" v-model="editPasswordForm.newPassword" @on-change="changeInputPass" />
</FormItem>
<FormItem label="确认新密码" prop="rePass">
<Input type="password" v-model="editPasswordForm.rePass" placeholder="请再次输入新密码"></Input>
</FormItem>
<FormItem>
<Button
type="primary"
style="width: 100px;margin-right:5px"
:loading="savePassLoading"
@click="editPassword"
>保存</Button>
<Button @click="cancelEditPass">取消</Button>
</FormItem>
</Form>
</div>
</Card>
<el-card class="change-pass">
<template #header>修改密码</template>
<el-form
ref="editPasswordForm"
:model="editPasswordForm"
label-width="100px"
label-position="right"
:rules="passwordValidate"
style="width: 450px"
class="mt_10"
>
<el-form-item label="原密码" prop="oldPass">
<el-input
v-model="editPasswordForm.oldPass"
type="password"
show-password
placeholder="请输入现在使用的密码"
/>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<SetPassword v-model="editPasswordForm.newPassword" @on-change="changeInputPass" />
</el-form-item>
<el-form-item label="确认新密码" prop="rePass">
<el-input
v-model="editPasswordForm.rePass"
type="password"
show-password
placeholder="请再次输入新密码"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="savePassLoading" @click="editPassword">
保存
</el-button>
<el-button @click="cancelEditPass">取消</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import SetPassword from "@/views/my-components/lili/set-password";
import SetPassword from "@/components/lili/set-password";
import { changePass } from "@/api/index";
import { ElMessageBox } from "element-plus";
export default {
name: "change_pass",
components: {
SetPassword
SetPassword,
},
data() {
const valideRePassword = (rule, value, callback) => {
@@ -52,124 +60,65 @@ export default {
}
};
return {
savePassLoading: false, // 保存loading
editPasswordForm: { // 修改密码表单
oldPass: "", // 旧密码
newPassword: "", // 新密码
rePass: "" // 从新输入新密码
savePassLoading: false,
editPasswordForm: {
oldPass: "",
newPassword: "",
rePass: "",
},
strength: "", // 密码强度
// 验证规则
strength: "",
passwordValidate: {
oldPass: [
{
required: true,
message: "请输入原密码",
trigger: "blur"
}
],
oldPass: [{ required: true, message: "请输入原密码", trigger: "blur" }],
newPassword: [
{
required: true,
message: "请输入新密码",
trigger: "blur"
},
{
min: 6,
message: "请至少输入6个字符",
trigger: "blur"
},
{
max: 32,
message: "最多输入32个字符",
trigger: "blur"
}
{ required: true, message: "请输入新密码", trigger: "blur" },
{ min: 6, message: "请至少输入6个字符", trigger: "blur" },
{ max: 32, message: "最多输入32个字符", trigger: "blur" },
],
rePass: [
{
required: true,
message: "请再次输入新密码",
trigger: "blur"
},
{
validator: valideRePassword,
trigger: "blur"
}
]
}
{ required: true, message: "请再次输入新密码", trigger: "blur" },
{ validator: valideRePassword, trigger: "blur" },
],
},
};
},
methods: {
// 新密码回调
changeInputPass(v, grade, strength) {
this.strength = strength;
},
// 修改密码
editPassword() {
let params = {
const params = {
password: this.md5(this.editPasswordForm.oldPass),
newPassword: this.md5(this.editPasswordForm.newPassword)
newPassword: this.md5(this.editPasswordForm.newPassword),
};
this.$refs["editPasswordForm"].validate(valid => {
if (valid) {
this.savePassLoading = true;
changePass(params).then(res => {
this.savePassLoading = false;
if (res.success) {
this.$Modal.success({
title: "修改密码成功",
content: "修改密码成功,需重新登录",
onOk: () => {
this.$store.commit("logout", this);
this.$store.commit("clearOpenedSubmenu");
this.$router.push({
name: "login"
});
}
this.$refs.editPasswordForm.validate((valid) => {
if (!valid) return;
this.savePassLoading = true;
changePass(params)
.then((res) => {
if (res?.success) {
ElMessageBox.alert("修改密码成功,需重新登录", "修改密码成功", {
confirmButtonText: "确定",
}).then(() => {
this.$store.commit("logout", this);
this.$store.commit("clearOpenedSubmenu");
this.$router.push({ name: "login" });
});
}
})
.finally(() => {
this.savePassLoading = false;
});
}
});
},
// 取消修改密码
cancelEditPass() {
this.$store.commit("removeTag", "change_pass");
localStorage.storeOpenedList = JSON.stringify(
this.$store.state.app.storeOpenedList
);
let lastPageName = "";
let length = this.$store.state.app.storeOpenedList.length;
if (length > 1) {
lastPageName = this.$store.state.app.storeOpenedList[length - 1].name;
} else {
lastPageName = this.$store.state.app.storeOpenedList[0].name;
}
this.$router.push({
name: lastPageName
});
}
}
const list = this.$store.state.app.storeOpenedList;
const lastPageName = list.length > 1 ? list[list.length - 1].name : list[0].name;
this.$router.push({ name: lastPageName });
},
},
};
</script>
<style lang="scss" scoped>
.change-pass {
&-btn-box {
margin-bottom: 10px;
button {
padding-left: 0;
span {
color: #2d8cf0;
transition: all 0.2s;
}
span:hover {
color: #0c25f1;
transition: all 0.2s;
}
}
}
}
</style>

View File

@@ -1,291 +1,290 @@
<template>
<div class="search">
<Card>
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="商品名称" prop="goodsName">
<Input type="text" v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 240px"/>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
</Form>
</Row>
</Card>
<Card>
<Row class="operation padding-row">
<Button @click="add" type="primary">添加</Button>
</Row>
<Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table" >
<!-- 商品栏目格式化 -->
<template slot="goodsSlot" slot-scope="{row}">
<div style="margin-top: 5px;height: 70px; display: flex;">
<div style="">
<img :src="row.thumbnail" style="height: 60px;margin-top: 3px;width: 60px">
</div>
<div>
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter.prevent="handleSearch"
>
<el-form-item label="商品名称" prop="goodsName">
<el-input
v-model="searchForm.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<div style="margin-left: 13px;">
<div class="div-zoom">
<a @click="linkTo(row.goodsId,row.skuId)">{{row.goodsName}}</a>
</div>
<Poptip trigger="hover" title="扫码在手机中查看" transfer>
<div slot="content">
<vue-qr :text="wapLinkTo(row.goodsId,row.skuId)" :margin="0" colorDark="#000" colorLight="#fff" :size="150"></vue-qr>
</div>
<img src="../../assets/qrcode.svg" class="hover-pointer" width="20" height="20" alt="">
</Poptip>
</div>
</div>
</template>
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10,20,50]" size="small" show-total show-elevator show-sizer></Page>
</Row>
</Card>
<liliDialog
ref="liliDialog"
@selectedGoodsData="selectedGoodsData"
></liliDialog>
<Modal
:title="modalTitle"
v-model="modalVisible"
:mask-closable="false"
:width="500"
>
<Form ref="form" :model="form" :label-width="100" :rules="formValidate">
<FormItem label="分销佣金" prop="commission">
<Input v-model="form.commission" clearable style="width: 100%"/>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="modalVisible = false">取消</Button>
<Button type="primary" :loading="submitLoading" @click="handleSubmit"
>提交
</Button
>
<el-card>
<div class="operation">
<el-button type="primary" @click="add">添加</el-button>
</div>
</Modal>
<el-table
ref="table"
v-loading="loading"
border
:data="data"
class="mt_10"
style="width: 100%"
>
<el-table-column label="商品名称" min-width="250">
<template #default="{ row }">
<div v-if="row" class="goods-msg">
<img :src="row.thumbnail" width="60" height="60" alt="" />
<div>
<div class="div-zoom">
<a class="link-text" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
</div>
<el-popover trigger="hover" title="扫码在手机中查看" placement="top" width="180">
<template #reference>
<img
src="../../assets/qrcode.svg"
class="hover-pointer"
width="20"
height="20"
alt="qrcode"
/>
</template>
<vue-qr
:text="wapLinkTo(row.goodsId, row.skuId)"
:margin="0"
color-dark="#000"
color-light="#fff"
:size="150"
/>
</el-popover>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="商品价格" min-width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">{{ $filters.unitPrice(row.price, "") }}</span>
</template>
</el-table-column>
<el-table-column prop="quantity" label="库存" min-width="80" />
<el-table-column prop="createTime" label="添加时间" min-width="160" />
<el-table-column label="佣金金额" min-width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.commission ?? 0, "") }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" min-width="100" align="center" fixed="right">
<template #default="{ row }">
<a v-if="row" class="link-text" @click="remove(row)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
<liliDialog ref="liliDialog" @selectedGoodsData="selectedGoodsData" />
<el-dialog
v-model="modalVisible"
:title="modalTitle"
width="500px"
:close-on-click-modal="false"
append-to-body
>
<el-form ref="form" :model="form" label-width="100px" :rules="formValidate">
<el-form-item label="分销佣金" prop="commission">
<el-input v-model="form.commission" clearable placeholder="请输入分销佣金" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="modalVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import {
getDistributionGoods,
distributionGoodsCancel,
distributionGoodsCheck
} from "@/api/distribution";
import liliDialog from "../lili-dialog/index";
getDistributionGoods,
distributionGoodsCancel,
distributionGoodsCheck,
} from "@/api/distribution";
import liliDialog from "@/views/lili-dialog";
import vueQr from "vue-qr";
import {getShopListData} from '@/api/shops'
export default {
name: "distributionGoods",
components: {
liliDialog
},
components: { liliDialog, vueQr },
data() {
return {
modalVisible: false, // 添加或编辑显示
modalTitle: "", // 添加或编辑标题
submitLoading: false, // 添加或编辑提交状态
shopList:[], // 店铺列表
loading: true, // 表单加载状态
searchForm: { // 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
modalVisible: false,
modalTitle: "添加分销商品",
submitLoading: false,
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 10,
sort: "createTime",
order: "desc",
},
selectList: [], // 多选数据
form:{
commission : 1 // 分销金额
form: {
commission: 1,
},
skuId:0, // 当前分销商品的skuId
skuId: 0,
formValidate: {
commission: [
{ required: true, message: '请输入大于1小于9999的合法分销金额'},
{ required: true, message: "请输入大于1小于9999的合法佣金金额", trigger: "blur" },
{
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: "请输入大于1小于9999的合法分销金额",
trigger: "change"
}],
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: "请输入大于1小于9999的合法佣金金额",
trigger: "blur",
},
],
},
columns: [ // 表格表头
{
title: "商品名称",
key: "goodsName",
minWidth: 250,
slot: "goodsSlot",
},
{
title: "商品价格",
key: "price",
width: 130,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.price,color:this.$mainColor}} );
}
},
{
title: "库存",
key: "quantity",
width: 100
},
{
title: "店铺名称",
key: "storeName",
minWidth: 120,
},
{
title: "佣金金额",
key: "commission",
width: 120,
render: (h, params) => {
if(params.row.commission !=null){
return h("div", this.$options.filters.unitPrice(params.row.commission,'¥'));
}else{
return h("div", this.$options.filters.unitPrice(0,'¥'));
}
}
},
{
title: "操作",
key: "action",
align: "center",
width: 150,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.remove(params.row);
},
},
},
"删除"
),
]);
}
}
],
data: [], // 表单数据
total: 0 // 表单数据总数
data: [],
total: 0,
};
},
methods: {
init() { // 初始化数据
init() {
this.getDataList();
},
// 选择商品回调
selectedGoodsData(v){
this.modalVisible = true
this.form.commission = 1
this.modalTitle = "保存分销商品"
this.skuId = v[0].id
add() {
this.$refs.liliDialog.goodsData = [];
this.$refs.liliDialog.open("goods", "single");
},
// 添加商品modal
add(){
this.$refs.liliDialog.flag = true;
this.$refs.liliDialog.goodsFlag = true;
this.$refs.liliDialog.singleGoods();
selectedGoodsData(selected) {
if (!selected?.length) return;
this.modalVisible = true;
this.form.commission = 1;
this.modalTitle = "添加分销商品";
this.skuId = selected[0].id;
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
distributionGoodsCheck(this.skuId, this.form)
.then((res) => {
if (res?.success || res?.message === "success") {
this.$Message.success("添加成功");
}
this.modalVisible = false;
this.getDataList();
})
.finally(() => {
this.submitLoading = false;
});
});
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.$refs.table.selectAll(false);
},
// 添加商品
handleSubmit(){
this.$refs['form'].validate((valid) => {
if (valid) {
distributionGoodsCheck(this.skuId,this.form).then(res => {
if(res.message === 'success') {
this.$Message.success("添加成功");
}
this.modalVisible = false
this.getDataList()
});
}
})
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
this.searchForm.pageNumber = 1;
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.getDataList();
},
// 获取商品列表
getDataList() {
this.loading = true;
// 带多条件搜索参数获取表单数据 请自行修改接口
getDistributionGoods(this.searchForm).then(res => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
this.total = this.data.length;
this.loading = false;
getDistributionGoods(this.searchForm)
.then((res) => {
if (res?.success) {
const page = res.result || {};
this.data = page.records || [];
this.total = page.total || 0;
} else {
this.data = [];
this.total = 0;
}
})
.finally(() => {
this.loading = false;
});
},
// 删除商品
remove(v) {
remove(row) {
this.$Modal.confirm({
title: "确认删除",
// 记得确认修改此处
content: "您确认要删除此分销商品么?",
content: "您确认要删除此分销商品吗?",
loading: true,
onOk: () => {
// 删除
distributionGoodsCancel(v.id).then(res => {
distributionGoodsCancel(row.id).then((res) => {
this.$Modal.remove();
if (res.success) {
if (res?.success) {
this.$Message.success("删除成功");
this.getDataList();
}
});
}
},
});
},
// 获取店铺列表 搜索项用
getShopList (val) {
const params = {
pageNumber:1,
pageSize:10,
storeName:''
}
if (val) {
params.storeName = val;
} else {
params.storeName = ''
}
getShopListData(params).then(res => {
this.shopList = res.result.records
})
},
searchChange(val){
this.getShopList(val)
}
},
mounted() {
this.init();
}
},
};
</script>
<style lang="scss" scoped>
@import "@/styles/table-common.scss";
.search-form{
width: 100%;
.operation {
margin: 10px 0;
}
.goods-msg {
display: flex;
align-items: flex-start;
gap: 13px;
padding: 5px 0;
img {
object-fit: contain;
}
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.div-zoom {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 4px;
}
.hover-pointer {
cursor: pointer;
vertical-align: middle;
}
.mt_10 {
margin-top: 10px;
}
</style>

View File

@@ -1,173 +1,253 @@
<template>
<div class="search">
<Card>
<Form ref="searchForm" :model="searchForm" inline :label-width="70" @keydown.enter.native="handleSearch" class="search-form">
<Form-item label="订单编号" prop="orderSn">
<Input type="text" v-model="searchForm.orderSn" placeholder="请输入订单编号" clearable style="width: 240px" />
</Form-item>
<Form-item label="订单时间">
<DatePicker type="daterange" v-model="timeRange" format="yyyy-MM-dd" placeholder="选择时间" style="width: 240px"></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
</Form>
</Card>
<Card>
<Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10,20,50]" size="small"
show-total show-elevator show-sizer></Page>
</Row>
</Card>
<div>
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="订单编号" prop="orderSn">
<el-input
v-model="searchForm.orderSn"
placeholder="请输入订单编号"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="分销商" prop="distributionName">
<el-input
v-model="searchForm.distributionName"
placeholder="请输入分销商名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="订单时间">
<el-date-picker
v-model="timeRange"
type="daterange"
value-format="YYYY-MM-DD"
start-placeholder="开始日期"
end-placeholder="结束日期"
placeholder="选择时间"
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<el-table
ref="table"
v-loading="loading"
border
:data="data"
class="mt_10"
style="width: 100%"
>
<el-table-column
prop="orderSn"
label="订单编号"
min-width="180"
fixed="left"
show-overflow-tooltip
/>
<el-table-column label="商品信息" min-width="200">
<template #default="{ row }">
<div v-if="row" class="goods-msg">
<img :src="row.image" width="60" height="60" alt="" />
<div>
<div class="div-zoom">
<a class="link-text" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
</div>
<div style="color: #999; font-size: 10px">数量x{{ row.num }}</div>
<el-popover trigger="hover" title="扫码在手机中查看" placement="top" width="180">
<template #reference>
<img
src="../../assets/qrcode.svg"
class="hover-pointer"
width="20"
height="20"
alt="qrcode"
/>
</template>
<vue-qr
:text="wapLinkTo(row.goodsId, row.skuId)"
:margin="0"
color-dark="#000"
color-light="#fff"
:size="150"
/>
</el-popover>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="distributionName" label="分销商" min-width="100" show-overflow-tooltip />
<el-table-column label="状态" min-width="90">
<template #default="{ row }">
<el-tag v-if="row" :type="filterStatusTagType(row.distributionOrderStatus)">
{{ filterStatus(row.distributionOrderStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="佣金金额" min-width="100">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.rebate, "") }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="160" fixed="right" />
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import { getDistributionOrder } from "@/api/distribution";
import { orderStatusList } from "./dataJson";
import vueQr from "vue-qr";
export default {
name: "distributionOrder",
components: {},
components: { vueQr },
data() {
return {
timeRange: [], // 范围时间
orderStatusList, // 订单状态列表
distributionId: this.$route.query.id, // 分销id
loading: true, // 表单加载状态
timeRange: [],
orderStatusList,
distributionId: this.$route.query.id,
loading: true,
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort:"create_time",
order:"desc"
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
},
columns: [
{
title: "订单编号",
key: "orderSn",
minWidth: 120,
tooltip: true,
},
{
title: "商品名称",
key: "goodsName",
minWidth: 120,
tooltip: true,
},
{
title: "状态",
key: "distributionOrderStatus",
width: 100,
sortable: false,
render: (h, params) => {
if (params.row.distributionOrderStatus == "NO_COMPLETED") {
return h("Tag", { props: { color: "orange" } }, "未完成");
} else if (params.row.distributionOrderStatus == "COMPLETE") {
return h("Tag", { props: { color: "green" } }, "完成");
} else if (params.row.distributionOrderStatus == "REFUND") {
return h("Tag", { props: { color: "red" } }, "退款");
} else {
return h("Tag", { props: { color: "orange" } }, "未完成");
}
},
},
{
title: "佣金金额",
key: "rebate",
width: 120,
sortable: false,
render: (h, params) => {
if (params.row.rebate == null) {
return h("div", this.$options.filters.unitPrice(0, "¥"));
} else {
return h(
"div",
this.$options.filters.unitPrice(params.row.rebate, "¥")
);
}
},
},
{
title: "创建时间",
key: "createTime",
width: 180,
sortable: false,
},
{
title: "解冻日期T+1",
key: "settleCycle",
width: 180,
sortable: false,
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
data: [],
total: 0,
};
},
methods: {
init() { // 初始化数据
this.getDataList();
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.getDataList();
},
// 获取订单数据
getDataList() {
this.searchForm.distributionId = this.distributionId;
this.loading = true;
if (this.timeRange && this.timeRange[0]) {
let startTime = this.timeRange[0];
let endTime = this.timeRange[1];
this.searchForm.startTime = this.$options.filters.unixToDate(
startTime / 1000
);
this.searchForm.endTime = this.$options.filters.unixToDate(
endTime / 1000
);
}
// 带多条件搜索参数获取表单数据 请自行修改接口
getDistributionOrder(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
this.total = this.data.length;
this.loading = false;
},
},
mounted() {
this.init();
},
watch: {
$route(e) {
this.distributionId = e.query.id ? e.query.id : undefined;
this.getDataList();
},
},
methods: {
init() {
this.getDataList();
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
getDataList() {
this.searchForm.distributionId = this.distributionId;
this.loading = true;
if (this.timeRange && this.timeRange[0] && this.timeRange[1]) {
const startTime = new Date(this.timeRange[0]).getTime();
const endTime = new Date(this.timeRange[1]).getTime();
this.searchForm.startTime = this.$filters.unixToDate(startTime / 1000);
this.searchForm.endTime = this.$filters.unixToDate(endTime / 1000);
} else {
this.searchForm.startTime = null;
this.searchForm.endTime = null;
}
getDistributionOrder(this.searchForm)
.then((res) => {
if (res?.success) {
const page = res.result || {};
this.data = page.records || [];
this.total = page.total || 0;
} else {
this.data = [];
this.total = 0;
}
})
.finally(() => {
this.loading = false;
});
},
filterStatus(status) {
const arr = [
{ status: "NO_COMPLETED", title: "未完成" },
{ status: "COMPLETE", title: "完成" },
{ status: "REFUND", title: "退款" },
];
for (let i = 0; i < arr.length; i++) {
if (arr[i].status === status) {
return arr[i].title;
}
}
return "未完成";
},
filterStatusTagType(status) {
const arr = [
{ status: "NO_COMPLETED", type: "warning" },
{ status: "COMPLETE", type: "success" },
{ status: "REFUND", type: "danger" },
];
for (let i = 0; i < arr.length; i++) {
if (arr[i].status === status) {
return arr[i].type;
}
}
return "warning";
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss" >
@import "@/styles/table-common.scss";
</style>
<style lang="scss">
.goods-msg {
display: flex;
align-items: center;
> div {
margin-left: 10px;
}
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.hover-pointer {
cursor: pointer;
}
.mt_10 {
margin-top: 10px;
}
</style>

View File

@@ -1,19 +1,18 @@
<template>
<div class="error403">
<div class="error403-body-con">
<Card>
<div class="error403-body-con-title">4<span class="error403-0-span">
<Icon type="android-lock"></Icon>
</span><span class="error403-key-span">
<Icon size="220" type="ios-bolt"></Icon>
</span></div>
<el-card>
<div class="error403-body-con-title">
4<span class="error403-0-span">🔒</span><span class="error403-key-span"></span>
</div>
<p class="error403-body-con-message">You don't have permission</p>
<div class="error403-btn-con">
<Button @click="goHome" size="large" style="width: 200px;" type="text">返回首页</Button>
<Button @click="backPage" size="large" style="width: 200px;margin-left: 40px;" type="primary">返回上一页</Button>
<el-button size="large" style="width: 200px" @click="goHome">返回首页</el-button>
<el-button size="large" type="primary" style="width: 200px; margin-left: 40px" @click="backPage">
返回上一页
</el-button>
</div>
</Card>
</el-card>
</div>
</div>
</template>
@@ -33,6 +32,7 @@ export default {
},
};
</script>
<style lang="scss" scoped>
@keyframes error403animation {
0% {
@@ -82,14 +82,8 @@ export default {
border: 20px solid #ed3f14;
color: #ed3f14;
margin-right: 10px;
i {
display: inline-block;
font-size: 120px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
font-size: 80px;
line-height: 130px;
}
.error403-key-span {
display: inline-block;
@@ -98,15 +92,8 @@ export default {
height: 190px;
border-radius: 50%;
margin-right: 10px;
i {
display: inline-block;
font-size: 190px;
position: absolute;
left: 20px;
transform: translate(-50%, -60%);
transform-origin: center bottom;
animation: error403animation 2.8s ease 0s infinite;
}
font-size: 80px;
animation: error403animation 2.8s ease 0s infinite;
}
}
&-message {
@@ -125,4 +112,3 @@ export default {
}
}
</style>

View File

@@ -1,91 +1,95 @@
<template>
<div class="error404">
<div class="error404-body-con">
<Card>
<div class="error404-body-con-title">4<span><Icon type="ios-navigate-outline"></Icon></span>4</div>
<p class="error404-body-con-message">YOU&nbsp;&nbsp;LOOK&nbsp;&nbsp;LOST</p>
<div class="error404-btn-con">
<Button @click="goHome" size="large" style="width: 200px;" type="text">返回首页</Button>
<Button @click="backPage" size="large" style="width: 200px;margin-left: 40px;" type="primary">返回上一页</Button>
</div>
</Card>
<div class="error404">
<div class="error404-body-con">
<el-card>
<div class="error404-body-con-title">
4<span>🧭</span>4
</div>
<p class="error404-body-con-message">YOU&nbsp;&nbsp;LOOK&nbsp;&nbsp;LOST</p>
<div class="error404-btn-con">
<el-button size="large" style="width: 200px" @click="goHome">返回首页</el-button>
<el-button size="large" type="primary" style="width: 200px; margin-left: 40px" @click="backPage">
返回上一页
</el-button>
</div>
</el-card>
</div>
</div>
</template>
<script>
export default {
name: 'Error404',
methods: {
backPage () {
this.$router.go(-1);
},
goHome () {
this.$router.push({
name: 'home_index'
});
}
}
name: "Error404",
methods: {
backPage() {
this.$router.go(-1);
},
goHome() {
this.$router.push({
name: "home_index",
});
},
},
};
</script>
<style lang="scss" scoped>
@keyframes error404animation {
0% {
transform: rotateZ(0deg);
}
20% {
transform: rotateZ(-60deg);
}
40% {
transform: rotateZ(-10deg);
}
60% {
transform: rotateZ(50deg);
}
80% {
transform: rotateZ(-20deg);
}
100% {
transform: rotateZ(0deg);
}
@keyframes error404animation {
0% {
transform: rotateZ(0deg);
}
20% {
transform: rotateZ(-60deg);
}
40% {
transform: rotateZ(-10deg);
}
60% {
transform: rotateZ(50deg);
}
80% {
transform: rotateZ(-20deg);
}
100% {
transform: rotateZ(0deg);
}
}
.error404{
&-body-con{
width: 700px;
height: 500px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
&-title{
text-align: center;
font-size: 240px;
font-weight: 700;
color: #2d8cf0;
height: 260px;
line-height: 260px;
margin-top: 40px;
span{
display: inline-block;
color: #19be6b;
font-size: 230px;
animation: error404animation 3s ease 0s infinite alternate;
}
}
&-message{
display: block;
text-align: center;
font-size: 30px;
font-weight: 500;
letter-spacing: 12px;
color: #dddde2;
}
.error404 {
&-body-con {
width: 700px;
height: 500px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
&-title {
text-align: center;
font-size: 240px;
font-weight: 700;
color: #2d8cf0;
height: 260px;
line-height: 260px;
margin-top: 40px;
span {
display: inline-block;
color: #19be6b;
font-size: 120px;
animation: error404animation 3s ease 0s infinite alternate;
}
}
&-btn-con{
text-align: center;
padding: 20px 0;
margin-bottom: 40px;
&-message {
display: block;
text-align: center;
font-size: 30px;
font-weight: 500;
letter-spacing: 12px;
color: #dddde2;
}
}
&-btn-con {
text-align: center;
padding: 20px 0;
margin-bottom: 40px;
}
}
</style>

View File

@@ -1,107 +1,102 @@
<template>
<div class="error500">
<div class="error500-body-con">
<Card>
<div class="error500-body-con-title">
5<span class="error500-0-span"><Icon type="social-freebsd-devil"></Icon></span><span class="error500-0-span"><Icon type="social-freebsd-devil"></Icon></span>
</div>
<p class="error500-body-con-message">Oops! the server is wrong</p>
<div class="error500-btn-con">
<Button @click="goHome" size="large" style="width: 200px;" type="text">返回首页</Button>
<Button @click="backPage" size="large" style="width: 200px;margin-left: 40px;" type="primary">返回上一页</Button>
</div>
</Card>
<div class="error500">
<div class="error500-body-con">
<el-card>
<div class="error500-body-con-title">
5<span class="error500-0-span">😈</span><span class="error500-0-span">😈</span>
</div>
<p class="error500-body-con-message">Oops! the server is wrong</p>
<div class="error500-btn-con">
<el-button size="large" style="width: 200px" @click="goHome">返回首页</el-button>
<el-button size="large" type="primary" style="width: 200px; margin-left: 40px" @click="backPage">
返回上一页
</el-button>
</div>
</el-card>
</div>
</div>
</template>
<script>
export default {
name: 'Error500',
methods: {
backPage () {
this.$router.go(-1);
},
goHome () {
this.$router.push({
name: 'home_index'
});
}
}
name: "Error500",
methods: {
backPage() {
this.$router.go(-1);
},
goHome() {
this.$router.push({
name: "home_index",
});
},
},
};
</script>
<style lang="scss" scoped>
@keyframes error500animation {
0% {
transform: rotateZ(0deg);
}
20% {
transform: rotateZ(-10deg);
}
40% {
transform: rotateZ(5deg);
}
60% {
transform: rotateZ(-5deg);
}
80% {
transform: rotateZ(10deg);
}
100% {
transform: rotateZ(0deg);
}
@keyframes error500animation {
0% {
transform: rotateZ(0deg);
}
20% {
transform: rotateZ(-10deg);
}
40% {
transform: rotateZ(5deg);
}
60% {
transform: rotateZ(-5deg);
}
80% {
transform: rotateZ(10deg);
}
100% {
transform: rotateZ(0deg);
}
}
.error500{
&-body-con{
width: 700px;
height: 500px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
&-title{
text-align: center;
font-size: 240px;
font-weight: 700;
color: #2d8cf0;
height: 260px;
line-height: 260px;
margin-top: 40px;
.error500-0-span{
display: inline-block;
position: relative;
width: 170px;
height: 170px;
border-radius: 50%;
border: 20px solid #ed3f14;
color: #ed3f14;
margin-right: 10px;
i{
display: inline-block;
font-size: 120px;
position: absolute;
bottom: -10px;
left: 10px;
transform-origin: center bottom;
animation: error500animation 3s ease 0s infinite alternate;
}
}
}
&-message{
display: block;
text-align: center;
font-size: 30px;
font-weight: 500;
letter-spacing: 4px;
color: #dddde2;
}
.error500 {
&-body-con {
width: 700px;
height: 500px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
&-title {
text-align: center;
font-size: 240px;
font-weight: 700;
color: #2d8cf0;
height: 260px;
line-height: 260px;
margin-top: 40px;
.error500-0-span {
display: inline-block;
position: relative;
width: 120px;
height: 120px;
border-radius: 50%;
border: 20px solid #ed3f14;
color: #ed3f14;
margin-right: 10px;
font-size: 60px;
line-height: 80px;
animation: error500animation 3s ease 0s infinite alternate;
}
}
&-btn-con{
text-align: center;
padding: 20px 0;
margin-bottom: 40px;
&-message {
display: block;
text-align: center;
font-size: 30px;
font-weight: 500;
letter-spacing: 4px;
color: #dddde2;
}
}
&-btn-con {
text-align: center;
padding: 20px 0;
margin-bottom: 40px;
}
}
</style>

View File

@@ -1,251 +1,253 @@
<template>
<div>
<Card>
<div class="operation">
<Button @click="addParent">添加一级分类</Button>
<Button @click="refresh">刷新列表</Button>
<el-card>
<div class="mb_10">
<el-button type="primary" @click="addParent">添加一级分类</el-button>
</div>
<tree-table
ref="treeTable"
size="small"
:loading="loading"
<el-table
v-loading="loading"
class="table"
:data="tableData"
row-key="id"
border
:tree-props="{ children: 'children' }"
style="width: 100%"
>
<el-table-column prop="name" label="分类名称" min-width="200" />
<el-table-column label="操作" min-width="220" fixed="right">
<template #default="{ row }">
<div v-if="row" class="ops">
<a class="link-text" @click="edit(row)">编辑</a>
<span class="op-split">|</span>
<a class="link-text" @click="remove(row)">删除</a>
<template v-if="row.level === 0">
<span class="op-split">|</span>
<a class="link-text" @click="addChildren(row)">添加子分类</a>
</template>
</div>
</template>
</el-table-column>
</el-table>
:columns="columns"
:border="true"
:show-index="false"
:is-fold="true"
:expand-type="false"
primary-key="id">
<template slot="action" slot-scope="scope">
<a @click="edit(scope.row)" style="color:#2d8cf0;cursor:pointer;text-decoration:none">编辑</a>
<span style="margin:0 8px;color:#dcdee2">|</span>
<a v-show="scope.row.level != 1" @click="addChildren(scope.row)" style="color:#2d8cf0;cursor:pointer;text-decoration:none">添加子分类</a>
<span style="margin:0 8px;color:#dcdee2">|</span>
<a @click="remove(scope.row)" style="color:#2d8cf0;cursor:pointer;text-decoration:none">删除</a>
<el-dialog
v-model="modalVisible"
:title="modalTitle"
width="500px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="form" :model="formAdd" label-width="120px" :rules="formValidate">
<el-form-item v-if="showParent" label="上级分类" prop="parentId">
{{ parentTitle }}
<el-input v-model="formAdd.parentId" style="display: none" />
</el-form-item>
<el-form-item label="层级" prop="level" style="display: none">
<el-input v-model="formAdd.level" />
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="formAdd.name" clearable />
</el-form-item>
<el-form-item v-if="formAdd.level !== 1" label="分类图标" prop="image">
<upload-pic-input v-model="formAdd.image" style="width: 100%" />
</el-form-item>
<el-form-item label="排序值" prop="sortOrder">
<el-input-number v-model="formAdd.sortOrder" style="width: 200px" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="modalVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="Submit">提交</el-button>
</template>
</tree-table>
</el-dialog>
<Modal :title="modalTitle" v-model="modalVisible" :mask-closable='false' :width="500">
<Form ref="formAdd" :model="formAdd" :label-width="100" :rules="formValidate">
<div v-if="showParent">
<FormItem label="上级分类" prop="parentId">
{{ parentTitle }}
<Input v-model="formAdd.parentId" clearable style="width:100%;display:none"/>
</FormItem>
</div>
<FormItem label="层级" prop="level" style="display:none">
<Input v-model="formAdd.level" clearable style="width:100%"/>
</FormItem>
<FormItem label="分类名称" prop="labelName">
<Input v-model="formAdd.labelName" maxlength="12" clearable style="width:100%"/>
</FormItem>
<FormItem label="排序值" prop="sortOrder" style="width:345px">
<InputNumber v-model="formAdd.sortOrder" :min="1"></InputNumber>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="modalVisible=false">取消</Button>
<Button type="primary" :loading="submitLoading" @click="submit">提交</Button>
</div>
</Modal>
</Card>
</el-card>
</div>
</template>
<script>
import * as API_Goods from "@/api/goods";
import TreeTable from "@/views/my-components/tree-table/Table/Table";
import {
addShopGoodsLabel,
delCategdelShopGoodsLabel,
editShopGoodsLabel,
getShopGoodsLabelList,
} from "@/api/goods";
import uploadPicInput from "@/views/my-components/lili/upload-pic-input";
import { regular } from "@/utils";
import {VARCHAR20} from "../../../utils/regular";
export default {
name: "store-category",
name: "goods-category",
components: {
TreeTable
uploadPicInput,
},
data() {
return {
submitLoading: false, // 提交loading
loading: false, //表格加载的loading
modalType: 0, // 添加或编辑标识
modalVisible: false, // 添加或编辑显示
modalTitle: "", // 添加或编辑标题
showParent: false, // 是否展示上级菜单
parentTitle: "", // 父级菜单名称
formAdd: { // 添加或编辑表单对象初始化数据
parentId: "",
labelName: "",
sortOrder: 1,
level: 0,
},
// 表单验证规则
submitLoading: false,
categoryList: [],
loading: false,
modalType: 0,
modalVisible: false,
modalTitle: "",
showParent: false,
parentTitle: "",
formAdd: this.createDefaultForm(),
formValidate: {
labelName: [
regular.REQUIRED,
regular.VARCHAR20
],
sortOrder: [
regular.REQUIRED,
regular.INTEGER
],
name: [regular.REQUIRED, regular.VARCHAR20],
sortOrder: [regular.REQUIRED, regular.INTEGER],
},
columns: [
{
title: "分类名称",
key: "labelName",
align: "left",
minWidth: "120px",
},
{
title: "操作",
key: "action",
align: "left",
headerAlign: "center",
width: "280px",
type: "template",
template: "action",
}
],
// 表格数据
tableData: []
tableData: [],
};
},
methods: {
// 初始化数据
createDefaultForm() {
return {
parentId: "",
name: "",
image: "",
sortOrder: 0,
level: 0,
};
},
normalizeCategoryTree(list, parentId = 0, level = 0) {
if (!Array.isArray(list) || list.length === 0) return;
list.forEach((item) => {
if (!item || typeof item !== "object") return;
item.name = item.name || item.labelName;
item.parentId = item.parentId ?? parentId;
item.level = item.level ?? level;
if (Array.isArray(item.children) && item.children.length) {
this.normalizeCategoryTree(item.children, item.id, Number(item.level) + 1);
}
});
},
init() {
this.getAllList();
},
// 刷新列表
refresh() {
this.loading = true;
let that = this;
setTimeout(function () {
that.init();
that.$Message.success("刷新成功");
that.loading = false;
}, 500);
},
//添加子分类
addChildren(v) {
this.modalType = 0;
this.modalTitle = "添加子分类";
this.parentTitle = v.labelName;
this.formAdd.level = eval(v.level + "+1");
this.formAdd.labelName = "";
this.parentTitle = v.name;
this.formAdd = this.createDefaultForm();
this.formAdd.level = Number(v.level) + 1;
this.showParent = true;
delete this.formAdd.id;
this.formAdd.parentId = v.id || 0;
this.formAdd.parentId = v.id;
this.modalVisible = true;
},
// 编辑分类
edit(v) {
this.modalType = 1;
this.modalTitle = "编辑";
this.formAdd.id = v.id;
this.formAdd.labelName = v.labelName;
this.formAdd.name = v.name;
this.formAdd.level = v.level;
this.formAdd.parentId = v.parentId || 0;
this.formAdd.parentId = v.parentId ?? 0;
this.formAdd.sortOrder = v.sortOrder;
this.formAdd.image = v.image;
this.showParent = false;
this.modalVisible = true;
},
//添加一级分类
addParent() {
this.modalType = 0;
this.formAdd.labelName = "";
this.modalTitle = "添加一级分类";
this.parentTitle = "顶级分类";
this.showParent = true;
this.formAdd = this.createDefaultForm();
delete this.formAdd.id;
this.formAdd.parentId = 0;
this.formAdd.sortOrder = 1;
this.formAdd.level = 0;
this.modalVisible = true;
},
//提交编辑和添加
submit() {
this.$refs.formAdd.validate(valid => {
if (valid) {
this.submitLoading = true;
if (this.modalType === 0) {
// 添加 避免编辑后传入id等数据 记得删除
delete this.formAdd.id;
API_Goods.addShopGoodsLabel(this.formAdd).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("添加成功");
this.getAllList(0);
this.modalVisible = false;
}
});
} else {
// 编辑
API_Goods.editShopGoodsLabel(this.formAdd).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("修改成功");
this.getAllList(0);
this.modalVisible = false;
}
});
}
Submit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
const params = this.buildLabelParams(this.formAdd);
if (this.modalType === 0) {
delete params.id;
addShopGoodsLabel(params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("添加成功");
this.getAllList();
this.modalVisible = false;
this.$refs.form.resetFields();
}
});
} else {
editShopGoodsLabel(params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("修改成功");
this.getAllList();
this.modalVisible = false;
this.$refs.form.resetFields();
}
});
}
});
},
// 确认删除分类
remove(v) {
this.$Modal.confirm({
title: "确认删除",
// 记得确认修改此处
content: "您确认要删除 " + v.labelName + " ?",
content: "您确认要删除 " + v.name + " ?",
loading: true,
onOk: () => {
// 删除
API_Goods.delCategdelShopGoodsLabel(v.id).then(res => {
delCategdelShopGoodsLabel(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getAllList();
}
});
}
},
});
},
// 获取分类
getAllList() {
this.loading = true;
API_Goods.getShopGoodsLabelList(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
res.result.forEach(firstCate => {
if (firstCate.children && firstCate.children.length) {
firstCate.children.forEach(secondCate => {
secondCate.parentId = firstCate.id
})
}
});
this.tableData = res.result;
getShopGoodsLabelList()
.then((res) => {
this.loading = false;
if (res.success) {
this.normalizeCategoryTree(res.result);
this.categoryList = JSON.parse(JSON.stringify(res.result));
this.tableData = JSON.parse(JSON.stringify(res.result));
}
})
.catch(() => {
this.loading = false;
});
},
buildLabelParams(form) {
const params = {
id: form.id,
parentId: form.parentId,
labelName: form.name,
image: form.image,
sortOrder: form.sortOrder,
level: form.level,
};
Object.keys(params).forEach((key) => {
if (params[key] === undefined || params[key] === "") {
delete params[key];
}
});
return params;
},
},
mounted() {
this.init();
}
},
};
</script>
<style lang="scss" scoped>
::v-deep .ivu-table-wrapper {
:deep(.el-table__body-wrapper) {
overflow: auto;
}
.table {
min-height: 100vh;
height: auto;
min-height: 60vh;
}
.operation {
.mb_10 {
margin-bottom: 10px;
}
</style>

View File

@@ -78,15 +78,51 @@
}
.sku-val {
width: 100%;
justify-content: flex-start;
flex-wrap: wrap;
>.ivu-form {
.sku-val-label {
margin-bottom: 10px;
font-weight: bold;
}
.sku-val-form {
flex-wrap: wrap !important;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
}
>.el-form {
flex-wrap: wrap !important;
}
::v-deep .sku-item-content-val {
:deep(.sku-item-content-val) {
margin-right: 20px;
margin-left: 0 !important;
width: auto !important;
min-width: auto !important;
.el-form-item__content {
margin-left: 0 !important;
align-items: flex-start;
}
}
.sku-val-item {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.sku-val-input-row {
display: flex;
align-items: center;
}
.sku-val-image {
margin-top: 10px;
}
}
@@ -139,11 +175,11 @@ div.base-info-item {
justify-content: flex-start;
width: 100%;
>.ivu-card-body {
>.el-card__body {
width: 100%;
}
.ivu-card-body {
.el-card__body {
width: 100%;
justify-content: center;
align-items: flex-start;
@@ -151,9 +187,23 @@ div.base-info-item {
.sku-item-content-name {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
margin-bottom: 10px;
.sku-item-label {
font-weight: bold;
margin-bottom: 8px;
line-height: 1.5;
}
.sku-item-input-row {
display: flex;
align-items: center;
flex-wrap: wrap;
}
}
}
}
@@ -215,7 +265,7 @@ div.base-info-item {
}
/*teatarea*/
::v-deep .el-textarea {
:deep(.el-textarea) {
width: 150%;
}
@@ -226,7 +276,7 @@ div.base-info-item {
/*折叠面板*/
.el-collapse-item {
::v-deep .el-collapse-item__header {
:deep(.el-collapse-item__header) {
text-align: left;
background-color: #f8f8f8;
padding: 0 10px;
@@ -240,7 +290,7 @@ div.base-info-item {
width: 25%;
}
::v-deep .el-form-item__content {
:deep(.el-form-item__content) {
margin-left: 120px;
text-align: left;
}
@@ -251,7 +301,7 @@ div.base-info-item {
color: #999;
}
::v-deep .el-collapse-item__content {
:deep(.el-collapse-item__content) {
padding: 10px 0;
text-align: left;
}
@@ -279,6 +329,11 @@ div.base-info-item {
}
/** 底部步骤 */
.footer-btns {
display: flex;
gap: 10px;
}
.footer {
width: 100%;
margin-top: 20px;
@@ -289,18 +344,18 @@ div.base-info-item {
text-align: center;
z-index: 999;
>.ivu-btn {
>.el-button {
margin: 0 10px;
}
}
/*图片上传组件第一张图设置封面*/
.goods-images {
::v-deep li.el-upload-list__item:first-child {
:deep(li.el-upload-list__item:first-child) {
position: relative;
}
::v-deep li.el-upload-list__item:first-child:after {
:deep(li.el-upload-list__item:first-child:after) {
content: "";
color: #fff;
font-weight: bold;
@@ -394,7 +449,7 @@ div.base-info-item {
}
.required {
::v-deep .ivu-form-item-label::before {
:deep(.el-form-item__label::before) {
content: "*";
display: inline-block;
margin-right: 4px;
@@ -483,7 +538,7 @@ div.base-info-item {
justify-content: center;
align-items: center;
::v-deep img {
:deep(img) {
margin-right: 20px;
width: 100px;
margin-left: 10px;
@@ -491,7 +546,7 @@ div.base-info-item {
::v-deep p {
:deep(p) {
color: #999;
font-size: 14px;
margin-top: 10px;

View File

@@ -1,185 +1,187 @@
<template>
<div class="search">
<Card>
<Row @keydown.enter.native="handleSearch">
<Form
ref="searchForm"
:model="searchForm"
inline
:label-width="70"
class="search-form"
>
<Form-item label="商品名称" prop="goodsName">
<Input
type="text"
v-model="searchForm.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 200px"
/>
</Form-item>
<Form-item label="商品编号" prop="goodsId">
<Input
type="text"
v-model="searchForm.goodsId"
placeholder="请输入商品编号"
clearable
style="width: 200px"
/>
</Form-item>
<Form-item label="状态" prop="status">
<Select
v-model="searchForm.marketEnable"
placeholder="请选择"
clearable
style="width: 200px"
>
<Option value="DOWN">下架</Option>
<Option value="UPPER">上架</Option>
</Select>
</Form-item>
<Form-item label="商品分类" prop="category">
<Cascader
<el-card>
<div @keyup.enter="handleSearch">
<el-form ref="searchForm" :model="searchForm" inline label-width="70px" class="search-form">
<el-form-item label="商品名称" prop="goodsName">
<el-input v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="商品编号" prop="goodsId">
<el-input v-model="searchForm.goodsId" placeholder="请输入商品编号" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="searchForm.marketEnable" placeholder="请选择" clearable style="width: 200px">
<el-option label="下架" value="DOWN" />
<el-option label="上架" value="UPPER" />
</el-select>
</el-form-item>
<el-form-item label="商品分类" prop="category">
<el-cascader
v-model="category"
:options="categoryList"
placeholder="请选择商品分类"
style="width: 200px"
:data="categoryList"
></Cascader>
</Form-item>
<Form-item label="货号" prop="id">
<Input
type="text"
v-model="searchForm.skuSn"
placeholder="请输入货号"
clearable
style="width: 200px"
/>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Row>
<Tabs @on-click="switchTabs" value="updateStock" v-model="stockType">
<TabPane label="商品库存管理" name="stockManage">
<Row class="operation padding-row">
<Button @click="exportStock" type="primary" class="export">批量导出</Button>
<Button @click="openImportStock" class="export">批量导入</Button>
</Row>
<Table
class="mt_10"
border
:loading="loading"
:columns="stockManageColumns"
:data="stockAllData"
ref="table"
>
<template slot="goodsSlot" slot-scope="{ row }">
<div style="margin-top: 5px; height: 90px; display: flex">
<div style="">
<img :src="row.thumbnail" style="height: 80px; margin-top: 3px; width: 70px"/>
</div>
<div style="margin-left: 13px;margin-top: 5px;">
<div class="div-zoom" style="color: black;">
{{ row.goodsName }}
</div>
<div class="div-zoom" style="margin-top: 5px;">
ID: {{ row.goodsId }}
</div>
</div>
</div>
</template>
<template slot="skuSlot" slot-scope="{ row }">
<div style="margin-top: 5px; height: 90px; display: flex">
<div style="margin-left: 13px;margin-top: 5px;">
<div class="div-zoom" style="color: black;">
{{ row.simpleSpecs }}
</div>
<div class="div-zoom" style="margin-top: 5px;">
ID: {{ row.id }}
</div>
</div>
</div>
</template>
</Table>
</TabPane>
<TabPane label="预警商品" name="warnList">
<Table
class="mt_10"
border
:loading="loading"
:columns="columns"
:data="warnData"
ref="table"
>
</Table>
</TabPane>
<TabPane label="设置预警" name="warnSetting">
<Table
class="mt_10"
border
:loading="loading"
:columns="settingColumns"
:data="skuAllData"
ref="table"
>
<template slot="alertQuantitySlot" slot-scope="{ row }">
<Input type="number" v-model="row.alertQuantity" clearable placeholder="请输入预警库存"
@on-blur="updateWarnStock(row)" @on-change="checkVal(row)"/>
</template>
</Table>
</TabPane>
</Tabs>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
<Modal
title="更新库存"
v-model="updateStockModalVisible"
:mask-closable="false"
:width="610"
>
<Table
class="mt_10"
:columns="updateStockColumns"
:data="stockList"
border
></Table>
<div slot="footer">
<Button type="text" @click="updateStockModalVisible = false">取消</Button>
<Button type="primary" @click="updateStock">更新</Button>
</el-form-item>
<el-form-item label="货号" prop="sn">
<el-input v-model="searchForm.sn" placeholder="请输入货号" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-button class="search-btn" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</Modal>
<Modal title="导入商品信息" v-model="importModal" :mask-closable="false">
<div style="text-align: center">
<Upload :before-upload="handleUpload" name="files"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
multiple type="drag" :action="action" :headers="accessToken">
<el-tabs v-model="stockType" @tab-click="switchTabs">
<el-tab-pane label="商品库存管理" name="stockManage">
<div class="operation padding-row">
<el-button type="primary" class="export" @click="exportStock">批量导出</el-button>
<el-button class="export" @click="openImportStock">批量导入</el-button>
</div>
<el-table ref="table" v-loading="loading" border :data="stockAllData" class="mt_10" style="width: 100%">
<el-table-column label="商品信息" min-width="400">
<template #default="{ row }">
<div style="margin-top: 5px; height: 90px; display: flex">
<img :src="row.thumbnail" style="height: 80px; margin-top: 3px; width: 70px" alt="" />
<div style="margin-left: 13px; margin-top: 5px">
<div class="div-zoom" style="color: black">{{ row.goodsName }}</div>
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.goodsId }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="SKU信息" min-width="400">
<template #default="{ row }">
<div style="margin-top: 5px">
<div class="div-zoom" style="color: black">{{ row.simpleSpecs }}</div>
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.id }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="上架状态" width="130">
<template #default="{ row }">
<el-tag :type="row.marketEnable === 'DOWN' ? 'danger' : 'success'">
{{ row.marketEnable === "DOWN" ? "下架" : "上架" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="审核状态" width="120">
<template #default="{ row }">
<el-tag
:type="
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'warning' : 'danger'
"
>
{{
row.authFlag === "PASS" ? "通过" : row.authFlag === "TOBEAUDITED" ? "待审核" : "审核拒绝"
}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="库存" min-width="120">
<template #default="{ row }">{{ row.quantity || 0 }}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="预警商品" name="warnList">
<el-table v-loading="loading" border :data="warnData" class="mt_10" style="width: 100%">
<el-table-column prop="goodsName" label="商品名称" min-width="400" show-overflow-tooltip />
<el-table-column label="库存" min-width="120">
<template #default="{ row }">{{ row.quantity || 0 }}</template>
</el-table-column>
<el-table-column label="预警值" min-width="120">
<template #default="{ row }">{{ row.alertQuantity || 0 }}</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<a class="link-text" @click="openUpdataStockModal(row)">库存</a>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="设置预警" name="warnSetting">
<el-table v-loading="loading" border :data="skuAllData" class="mt_10" style="width: 100%">
<el-table-column prop="goodsName" label="商品名称" />
<el-table-column label="库存" width="200">
<template #default="{ row }">{{ row.quantity || 0 }}</template>
</el-table-column>
<el-table-column label="预警值" width="200">
<template #default="{ row }">
<el-input
v-model="row.alertQuantity"
type="number"
clearable
placeholder="请输入预警库存"
@blur="updateWarnStock(row)"
@change="checkVal(row)"
/>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
<el-dialog v-model="updateStockModalVisible" title="更新库存" width="610px" :close-on-click-modal="false">
<el-table :data="stockList" border class="mt_10" style="width: 100%">
<el-table-column label="sku规格" min-width="120">
<template #default="{ row }">{{ row.simpleSpecs }}</template>
</el-table-column>
<el-table-column label="审核状态" width="130">
<template #default="{ row }">
<el-tag
:type="
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'primary' : 'danger'
"
>
{{
row.authFlag === "TOBEAUDITED" ? "待审核" : row.authFlag === "PASS" ? "通过" : "审核拒绝"
}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="{ row }">
<el-input-number v-model="row.quantity" :min="0" />
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="updateStockModalVisible = false">取消</el-button>
<el-button type="primary" @click="updateStock">更新</el-button>
</template>
</el-dialog>
<el-dialog v-model="importModal" title="导入商品信息" :close-on-click-modal="false">
<div v-loading="spinShow" style="text-align: center">
<el-upload drag :before-upload="handleUpload" :show-file-list="false" accept=".xlsx,.xls">
<div style="padding: 50px 0">
<Icon type="ios-cloud-upload" size="102" style="color: #3399ff"></Icon>
<div style="font-size: 48px; color: #3399ff"></div>
<h2>选择或拖拽文件上传</h2>
</div>
<Spin fix v-if="spinShow"></Spin>
</Upload>
</el-upload>
</div>
<div slot="footer">
<Button type="text" @click="importModal = false">确定</Button>
</div>
</Modal>
<template #footer>
<el-button type="primary" @click="importModal = false">确定</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -191,7 +193,7 @@ import {
importStockExcel,
queryExportStock,
updateGoodsAlertStocks,
updateGoodsSkuStocks
updateGoodsSkuStocks,
} from "@/api/goods";
export default {
@@ -199,227 +201,44 @@ export default {
data() {
return {
importModal: false,
id: "", //要操作的id
loading: true, // 表单加载状态
updateStockModalVisible: false, // 更新库存模态框显隐
stockAllUpdate: undefined, // 更新库存数量
stockType: 'stockManage',
spinShow: false,
loading: true,
updateStockModalVisible: false,
stockType: "stockManage",
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "create_time", // 默认排序字段
order: "desc", // 默认排序方式
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
},
checkFlag: false, //检测成功标志
// 表单验证规则
formValidate: {},
//修改库存的数据
checkFlag: false,
stockList: [],
stockManageColumns: [
{
title: "商品信息",
key: "goodsName",
midwidth: 400,
slot: "goodsSlot",
}, {
title: "SKU信息",
key: "simpleSpecs",
midwidth: 400,
tooltip: true,
slot: "skuSlot",
}, {
title: "上架状态",
key: "marketEnable",
width: 130,
sortable: false,
render: (h, params) => {
if (params.row.marketEnable == "DOWN") {
return h("Tag", {props: {color: "red"}}, "下架");
} else if (params.row.marketEnable == "UPPER") {
return h("Tag", {props: {color: "green"}}, "上架");
}
},
}, {
title: "审核状态",
key: "authFlag",
width: 120,
render: (h, params) => {
if (params.row.authFlag == "PASS") {
return h("Tag", {props: {color: "green"}}, "通过");
} else if (params.row.authFlag == "TOBEAUDITED") {
return h("Tag", {props: {color: "volcano"}}, "待审核");
} else if (params.row.authFlag == "REFUSE") {
return h("Tag", {props: {color: "red"}}, "审核拒绝");
}
},
}, {
title: "库存",
key: "quantity",
midwidth: 280,
render: (h, params) => {
if (params.row.quantity) {
return h("div", params.row.quantity);
} else {
return h("div", 0);
}
},
},],
//列表名称
columns: [
{
title: "商品名称",
key: "goodsName",
midwidth: 400,
tooltip: true,
},
{
title: "库存",
key: "quantity",
midwidth: 280,
render: (h, params) => {
if (params.row.quantity) {
return h("div", params.row.quantity);
} else {
return h("div", 0);
}
},
},
{
title: "预警值",
key: "alertQuantity",
midwidth: 280,
render: (h, params) => {
if (params.row.alertQuantity) {
return h("div", params.row.alertQuantity);
} else {
return h("div", 0);
}
},
},
{
title: "操作",
key: "action",
align: "center",
fixed: "right",
width: 200,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
marginRight: "5px",
},
on: {
click: () => {
this.openUpdataStockModal(params.row);
},
},
},
"库存"
),
]);
},
},
],
settingColumns: [
{
title: "商品名称",
key: "goodsName",
},
{
title: "库存",
key: "quantity",
width: 400,
render: (h, params) => {
if (params.row.quantity) {
return h("div", params.row.quantity);
} else {
return h("div", 0);
}
},
},
{
title: "预警值",
key: "alertQuantity",
width: 400,
slot: 'alertQuantitySlot',
},
],
updateStockColumns: [
{
title: "sku规格",
key: "sn",
minWidth: 120,
render: (h, params) => {
return h("div", {}, params.row.simpleSpecs);
},
},
{
title: "审核状态",
key: "authFlag",
width: 130,
render: (h, params) => {
if (params.row.authFlag == "TOBEAUDITED") {
return h("Tag", {props: {color: "blue"}}, "待审核");
} else if (params.row.authFlag == "PASS") {
return h("Tag", {props: {color: "green"}}, "通过");
} else if (params.row.authFlag == "REFUSE") {
return h("Tag", {props: {color: "red"}}, "审核拒绝");
}
},
},
{
title: "操作",
key: "action",
align: "center",
width: 200,
render: (h, params) => {
let vm = this;
return h("InputNumber", {
props: {
value: params.row.quantity,
},
on: {
"on-change": (event) => {
vm.stockList[params.index].quantity = event;
},
},
});
},
},
],
warnData: [], // 表单数据
skuAllData: [], //SKU数据
stockAllData: [],//SKU库存数据
total: 0, //sku数据总数
categoryList: [], //分类列表
category: '', //选中分类
warnData: [],
skuAllData: [],
stockAllData: [],
total: 0,
categoryList: [],
category: [],
selectedSku: {},
file: null,
};
},
methods: {
init() {
// 初始化数据
this.getDataList();
this.deepGroup();
},
openUpdataStockModal(row) {
this.stockList = []
this.stockList = [];
this.selectedSku = JSON.parse(JSON.stringify(row));
this.stockList.push(this.selectedSku);
this.updateStockModalVisible = true;
},
// 更新库存
updateStock() {
let updateStockList = this.stockList.map((i) => {
let j = {skuId: i.id, quantity: i.quantity};
return j;
});
const updateStockList = this.stockList.map((i) => ({
skuId: i.id,
quantity: i.quantity,
}));
updateGoodsSkuStocks(updateStockList).then((res) => {
if (res.success) {
this.updateStockModalVisible = false;
@@ -428,192 +247,144 @@ export default {
}
});
},
//修改预警值
updateWarnStock(row) {
if (this.checkFlag) {
let submit = {skuId: row.id, alertQuantity: row.alertQuantity}
updateGoodsAlertStocks(submit).then(res => {
updateGoodsAlertStocks({ skuId: row.id, alertQuantity: row.alertQuantity }).then((res) => {
if (res.success) {
this.$Message.success('更新成功')
this.$Message.success("更新成功");
}
})
});
}
},
//检测输入值是否正确
checkVal(row) {
if (
!/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/.test(row.alertQuantity) ||
parseInt(row.alertQuantity) < 0 ||
parseInt(row.alertQuantity) > 99999999
) {
// 校验未通过 进行提示
this.$Message.error("请输入0~99999999之间的数字值")
row.alertQuantity = 0
this.$Message.error("请输入0~99999999之间的数字值");
row.alertQuantity = 0;
this.checkFlag = false;
return;
}
this.checkFlag = true;
},
//切换分页
switchTabs() {
this.handleReset();
this.getDataList();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
//改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.clearSelectAll();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.categoryPath = this.category ? this.category.join(",") : null;
this.searchForm.categoryPath = this.category?.length ? this.category.join(",") : null;
this.getDataList();
},
// 重置搜索条件
handleReset() {
this.searchForm = {};
this.searchForm = {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
};
this.category = [];
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
// 重新加载数据
this.getDataList();
},
// 获取商品列表数据
getDataList() {
this.loading = true;
// 带多条件搜索参数获取表单数据
if (this.stockType == 'warnList') {
//调用预警库存
getGoodsListDataByStockSeller(this.searchForm).then(res => {
if (this.stockType === "warnList") {
getGoodsListDataByStockSeller(this.searchForm).then((res) => {
if (res.success) {
this.warnData = res.result.records
this.total = res.result.total
this.loading = false;
this.warnData = res.result.records;
this.total = res.result.total;
}
})
} else if (this.stockType == 'warnSetting') {
//调用获取全部sku
getGoodsSkuListDataSeller(this.searchForm).then(res => {
this.loading = false;
});
} else if (this.stockType === "warnSetting") {
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
if (res.success) {
this.skuAllData = res.result.records
this.total = res.result.total
this.loading = false;
this.skuAllData = res.result.records;
this.total = res.result.total;
}
})
} else if (this.stockType == 'stockManage') {
//调用获取全部sku
getGoodsSkuListDataSeller(this.searchForm).then(res => {
this.loading = false;
});
} else {
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
if (res.success) {
this.stockAllData = res.result.records
this.total = res.result.total
this.loading = false;
this.stockAllData = res.result.records;
this.total = res.result.total;
}
})
this.loading = false;
});
}
},
//组织分类树
deepGroup() {
getGoodsCategoryAll().then(res => {
getGoodsCategoryAll().then((res) => {
if (res.success) {
res.result.forEach((item) => {
let childWay = []; //第二级
// 第二层
if (item.children) {
item.children.forEach((child) => {
// // 第三层
if (child.children) {
child.children.forEach((grandson, index, arr) => {
arr[index] = {
value: grandson.id,
label: grandson.name,
children: "",
};
});
}
let children = {
value: child.id,
label: child.name,
children: child.children,
};
childWay.push(children);
});
}
// 第一层
let way = {
value: item.id,
label: item.name,
children: childWay,
};
this.categoryList.push(way);
});
this.categoryList = res.result.map((item) => ({
value: item.id,
label: item.name,
children: (item.children || []).map((child) => ({
value: child.id,
label: child.name,
children: (child.children || []).map((grandson) => ({
value: grandson.id,
label: grandson.name,
})),
})),
}));
}
})
});
},
// 导出订单
async exportStock() {
let randomNumber = '';
let randomNumber = "";
for (let i = 0; i < 10; i++) {
randomNumber += Math.floor(Math.random() * 10);
}
;
queryExportStock(this.searchForm)
.then((res) => {
const blob = new Blob([res], {
type: "application/vnd.ms-excel;charset=utf-8",
});
//对于<a>标签,只有 Firefox 和 Chrome内核 支持 download 属性
//IE10以上支持blob但是依然不支持download
const blob = new Blob([res], { type: "application/vnd.ms-excel;charset=utf-8" });
if ("download" in document.createElement("a")) {
//支持a标签download的浏览器
const link = document.createElement("a"); //创建a标签
link.download = randomNumber + ".xlsx"; //a标签添加属性
const link = document.createElement("a");
link.download = randomNumber + ".xlsx";
link.style.display = "none";
link.href = URL.createObjectURL(blob);
document.body.appendChild(link);
link.click(); //执行下载
URL.revokeObjectURL(link.href); //释放url
document.body.removeChild(link); //释放标签
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
} else {
navigator.msSaveBlob(blob, fileName);
navigator.msSaveBlob(blob, randomNumber + ".xlsx");
}
})
.catch((err) => {
console.log(err);
});
.catch((err) => console.log(err));
},
openImportStock() {
this.importModal = true;
},
// 上传数据
handleUpload(file) {
this.file = file;
this.upload();
return false;
},
/**
* 上传文件
*/
upload() {
let fd = new FormData();
const fd = new FormData();
fd.append("files", this.file);
this.spinShow = true;
importStockExcel(fd).then(res => {
importStockExcel(fd).then((res) => {
this.spinShow = false;
if (res.success) {
this.spinShow = false;
this.$Message.success("导入成功");
this.importModal = false;
this.getDataList();
}
})
});
},
},
mounted() {
@@ -623,4 +394,9 @@ export default {
</script>
<style lang="scss" scoped>
@import "@/styles/table-common.scss";
.link-text {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
</style>

View File

@@ -1,174 +1,104 @@
<template>
<div class="search">
<Card>
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form mb_10"
@keydown.enter.native="handleSearch">
<Form-item label="商品名称" prop="goodsName">
<Input type="text" v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 240px" />
</Form-item>
<Form-item label="商品编号" prop="id">
<Input
type="text"
v-model="searchForm.id"
placeholder="商品编号"
clearable
style="width: 200px"
/>
</Form-item>
<Form-item style="margin-left: -35px" class="br">
<Button @click="handleSearch" type="primary" icon="ios-search"
>搜索</Button
>
<Button @click="handleReset">重置</Button>
</Form-item>
</Form>
</Card>
<Card>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" class="mt_10"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage"
@on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small" show-total show-elevator
show-sizer></Page>
</Row>
</Card>
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form mb_10"
@keyup.enter="handleSearch"
>
<el-form-item label="商品名称" prop="goodsName">
<el-input v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 240px" />
</el-form-item>
<el-form-item label="商品编号" prop="id">
<el-input v-model="searchForm.id" placeholder="商品编号" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<el-table v-loading="loading" border :data="data" ref="table" class="mt_10" style="width: 100%">
<el-table-column prop="id" label="编号" min-width="120" />
<el-table-column label="商品原图" width="120" align="center">
<template #default="{ row }">
<img
:src="row.original"
alt="加载图片失败"
style="cursor: pointer; width: 80px; height: 60px; margin: 10px 0; object-fit: contain"
/>
</template>
</el-table-column>
<el-table-column prop="goodsName" label="商品名称" min-width="120" show-overflow-tooltip />
<el-table-column label="商品价格" width="120">
<template #default="{ row }">
<priceColorScheme :value="row.price || 0" :color="$mainColor" />
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="120" />
<el-table-column label="操作" align="center" width="150">
<template #default="{ row }">
<a class="link-text" @click="editGoods(row)">编辑</a>
<span class="op-split">|</span>
<a class="link-text" @click="removeDraft(row.id)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import { getDraftGoodsListData, deleteDraftGoods } from "@/api/goods";
export default {
name: "goods",
components: {},
data() {
return {
loading: true, // 表单加载状态
loading: true,
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "create_time", // 默认排序字段
order: "desc", // 默认排序方式
pageNumber: 1,
pageSize: 10,
sort: "create_time",
order: "desc",
saveType: "TEMPLATE",
},
columns: [
// 表头
{
title: "编号",
key: "id",
minWidth: 120,
},
{
title: "商品原图",
key: "original",
width: 120,
align: "center",
render: (h, params) => {
return h("img", {
attrs: {
src: params.row.original,
alt: "加载图片失败",
},
style: {
cursor: "pointer",
width: "80px",
height: "60px",
margin: "10px 0",
"object-fit": "contain",
},
});
},
},
{
title: "商品名称",
key: "goodsName",
minWidth: 120,
},
{
title: "商品价格",
key: "price",
width: 120,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.price || 0,color:this.$mainColor}} );
},
},
{
title: "创建时间",
key: "createTime",
minWidth: 120,
},
{
title: "操作",
key: "action",
align: "center",
width: 150,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
marginRight: "5px",
},
on: {
click: () => {
this.editGoods(params.row);
},
},
},
"编辑"
),
h(
"span",
{
style: { margin: "0 8px", color: "#dcdee2" },
},
"|"
),
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.removeDraft(params.row.id);
},
},
},
"删除"
),
]);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
data: [],
total: 0,
};
},
methods: {
init() {
// 初始化数据
this.getDataList();
},
// 编辑模板
editGoods(v) {
this.$router.push({
name: "goods-template-operation-edit",
query: { draftId: v.id },
});
},
// 删除模板
removeDraft(id) {
let showType = "模版";
this.$Modal.confirm({
title: "确认审核",
content: "您确认要删除id为 " + id + " 的" + showType + "吗?",
content: "您确认要删除id为 " + id + " 的模版吗?",
loading: true,
onOk: () => {
deleteDraftGoods(id).then((res) => {
@@ -181,34 +111,25 @@ export default {
},
});
},
// 改变页数
changePage(v) {
this.searchForm.pageNumber = v;
changePage() {
this.getDataList();
},
// 改变页码
changePageSize(v) {
this.searchForm.pageSize = v;
changePageSize() {
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.getDataList();
},
// 重置
handleReset() {
this.$refs.searchForm.resetFields();
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
// 重新加载数据
this.getDataList();
},
// 获取列表数据
getDataList() {
this.loading = true;
// 带多条件搜索参数获取表单数据
getDraftGoodsListData(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
@@ -222,11 +143,21 @@ export default {
this.init();
},
watch: {
$route(to, from) {
$route() {
this.init();
},
},
};
</script>
<style lang="scss" scoped>
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.op-split {
margin: 0 8px;
color: #dcdee2;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
<template>
<div class="goods-operation">
<div class="step-list">
<steps :current="activestep" style="height:60px;margin-top: 10px">
<step title="选择商品品类"/>
<step title="填写商品详情"/>
<step title="商品发布成功"/>
</steps>
<el-steps :active="activestep" align-center style="height: 60px; margin-top: 10px">
<el-step title="选择商品品类" />
<el-step title="填写商品详情" />
<el-step title="商品发布成功" />
</el-steps>
</div>
<!-- 第一步 选择分类 -->
<first-step ref='first' v-show="activestep === 0" @change="getFirstData"></first-step>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<!-- 选择商品类型 -->
<Modal v-model="selectGoodsType" width="550" :closable="false">
<el-dialog v-model="selectGoodsType" width="550px" :show-close="false">
<div class="goods-type-list">
<div
class="goods-type-item"
@@ -17,7 +17,13 @@
</div>
</div>
</div>
</Modal>
<template #footer>
<div class="goods-type-actions">
<el-button @click="cancelGoodsType">取消</el-button>
<el-button type="primary" @click="confirmGoodsType">确认</el-button>
</div>
</template>
</el-dialog>
<!-- 商品分类 -->
<div class="content-goods-publish">
<div class="goods-category">
@@ -63,15 +69,17 @@
</div>
<!-- 底部按钮 -->
<div class="footer">
<ButtonGroup>
<Button type="primary" @click="selectGoodsType = true">商品类型</Button>
<Button type="primary" @click="next">下一步</Button>
</ButtonGroup>
<div class="footer-btns">
<el-button type="primary" @click="openGoodsTypeDialog">商品类型</el-button>
<el-button type="primary" @click="next">下一步</el-button>
</div>
</div>
</div>
</template>
<script>
import * as API_GOODS from "@/api/goods";
import goodsType1Img from "@/assets/goodsType1.png";
import goodsType2Img from "@/assets/goodsType2.png";
export default {
data() {
return {
@@ -79,14 +87,14 @@ export default {
goodsTypeWay: [
{
title: "实物商品",
img: require("@/assets/goodsType1.png"),
img: goodsType1Img,
desc: "零售批发,物流配送",
type: "PHYSICAL_GOODS",
check: false,
},
{
title: "虚拟商品",
img: require("@/assets/goodsType2.png"),
img: goodsType2Img,
desc: "虚拟核验,无需物流",
type: "VIRTUAL_GOODS",
check: false,
@@ -100,6 +108,7 @@ export default {
],
// 商品类型
goodsType: "",
pendingGoodsType: "",
/** 1级分类列表*/
categoryListLevel1: [],
/** 2级分类列表*/
@@ -108,15 +117,41 @@ export default {
categoryListLevel3: [],
};
},
watch: {
selectGoodsType(val) {
if (val) {
this.syncGoodsTypeSelection();
}
},
},
methods: {
// 点击商品类型
handleClickGoodsType(val) {
this.goodsTypeWay.map((item) => {
return (item.check = false);
syncGoodsTypeSelection() {
this.pendingGoodsType = this.goodsType;
this.goodsTypeWay.forEach((item) => {
item.check = item.type === this.goodsType;
});
val.check = !val.check;
this.goodsType = val.type;
},
openGoodsTypeDialog() {
this.selectGoodsType = true;
},
// 点击商品类型(仅临时选中,确认后才生效)
handleClickGoodsType(val) {
this.goodsTypeWay.forEach((item) => {
item.check = item.type === val.type;
});
this.pendingGoodsType = val.type;
},
cancelGoodsType() {
this.syncGoodsTypeSelection();
this.selectGoodsType = false;
},
confirmGoodsType() {
if (!this.pendingGoodsType) {
this.$Message.error("请选择商品类型");
return;
}
this.goodsType = this.pendingGoodsType;
this.selectGoodsType = false;
},
/** 选择商城商品分类 */
handleSelectCategory(row, index, level) {
@@ -177,4 +212,22 @@ export default {
</script>
<style lang="scss" scoped>
@import "./addGoods.scss";
.footer {
display: flex;
justify-content: center;
}
.footer-btns {
display: flex;
gap: 12px;
justify-content: center;
}
.goods-type-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
width: 100%;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ h4 {
margin: 20px 0;
font-size: 18px;
}
::v-deep .ivu-icon {
:deep(.el-icon) {
margin-right: 10px;
}
.rate-box {
@@ -36,7 +36,7 @@ h4 {
.shop-box {
flex: 3;
display: flex;
font-size: 12px;
font-size: 16px;
margin-top: 50px;
flex-wrap: wrap;
justify-content: space-between;
@@ -65,12 +65,16 @@ h4 {
flex-wrap: wrap;
}
.detail-title {
position: absolute;
left: 0;
top: -15px;
opacity: 0.3;
color: #999;
font-size: 21px;
flex: 0 0 100%;
width: 100%;
order: -1;
align-self: flex-start;
margin-bottom: 8px;
padding-left: 4px;
opacity: 1;
color: #000;
font-size: 20px;
font-weight: normal;
text-decoration: initial;
transition: 0.35s;
}
@@ -82,22 +86,24 @@ h4 {
font-weight: bold;
width: 286px;
display: flex;
::v-deep span {
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
:deep(span) {
color: $theme_color;
font-size: 18px;
}
align-items: center;
justify-content: center;
padding: 20px;
padding: 16px 12px 20px;
background: #eee;
border-radius: 0.4em;
margin: 10px;
> div {
> div:not(.detail-title) {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 0 20px;
margin: 0 8px;
}
}
.detail-item:hover {
@@ -105,8 +111,6 @@ h4 {
transform: translateY(-4px);
> .detail-title {
opacity: 1;
top: 5px;
}
}

View File

@@ -1,58 +1,72 @@
<template>
<div>
<Modal v-model="noticeFlage" :title="noticesDetail.title">
<div v-if="noticesDetail" class="noticesDetail" v-html="noticesDetail.content">
</div>
</Modal>
<el-dialog v-model="noticeFlage" :title="noticesDetail.title">
<div v-if="noticesDetail" class="noticesDetail" v-html="noticesDetail.content"></div>
</el-dialog>
<div class="box flex">
<div class="box-left">
<div class="card shop flex">
<div>
<h4>Hi,<span style="margin-left:5px;">{{ userData.nickName }}</span></h4>
<img class="shop-logo" :src="userData.storeLogo || require('@/assets/logo1.png')" alt="">
<h4>Hi,<span style="margin-left: 5px">{{ userData.nickName }}</span></h4>
<img
class="shop-logo"
:src="userData.storeLogo || defaultLogo"
alt=""
/>
</div>
<div class="shop-box">
<div class="box-item">
<div>店铺名称{{ userData.storeName || '暂无' }}</div>
<div>店铺名称{{ userData.storeName || "暂无" }}</div>
</div>
<div class="box-item">
<div>店铺状态{{ userData.storeDisable == 'OPEN' ? '开启中' : '关闭' }}</div>
<div>店铺状态{{ userData.storeDisable == "OPEN" ? "开启中" : "关闭" }}</div>
</div>
<div class="box-item" @click="im()">
<Button type="info" :loading='load'>点击登录客服</Button>
<el-button type="warning" :loading="load">点击登录客服</el-button>
</div>
</div>
<div class="rate-box">
<div>
<i-circle :size="120" stroke-color="#fecb89" :trail-width="4" :stroke-width="5"
:percent="(userData.serviceScore * 20)" stroke-linecap="square">
<div class="demo-Circle-custom">
<el-progress
type="circle"
:width="120"
:stroke-width="5"
:percentage="userData.serviceScore * 20"
color="#fecb89"
>
<template #default>
<p class="bold">{{ userData.serviceScore }}</p>
</div>
</i-circle>
</template>
</el-progress>
<h5>服务得分</h5>
</div>
<div>
<i-circle :size="120" stroke-color="#a7c5eb" :trail-width="4" :stroke-width="5"
:percent="(userData.deliveryScore * 20)" stroke-linecap="square">
<div>
<el-progress
type="circle"
:width="120"
:stroke-width="5"
:percentage="userData.deliveryScore * 20"
color="#a7c5eb"
>
<template #default>
<p class="bold">{{ userData.deliveryScore }}</p>
</div>
</i-circle>
</template>
</el-progress>
<h5>交货得分</h5>
</div>
<div>
<i-circle :size="120" stroke-color="#848ccf" :trail-width="4" :stroke-width="5"
:percent="(userData.descriptionScore * 20)" stroke-linecap="square">
<div>
<el-progress
type="circle"
:width="120"
:stroke-width="5"
:percentage="userData.descriptionScore * 20"
color="#848ccf"
>
<template #default>
<p class="bold">{{ userData.descriptionScore }}</p>
</div>
</i-circle>
</template>
</el-progress>
<h5>评价得分</h5>
</div>
</div>
@@ -66,9 +80,7 @@
<span>{{ homeData.unPaidOrder || 0 }}</span>
<div>待付款</div>
</div>
<div class="detail-title">
交易前
</div>
<div class="detail-title">交易前</div>
</div>
<div class="detail-item" @click="navigateTo('orderList')">
@@ -80,9 +92,7 @@
<span>{{ homeData.deliveredOrder || 0 }}</span>
<div>待收货</div>
</div>
<div class="detail-title">
交易中
</div>
<div class="detail-title">交易中</div>
</div>
<div class="detail-item">
<div @click="navigateTo('returnMoneyOrder')">
@@ -97,21 +107,16 @@
<span>{{ homeData.memberEvaluation || 0 }}</span>
<div>待评价</div>
</div>
<div class="detail-title">
交易后
</div>
<div class="detail-title">交易后</div>
</div>
<div class="detail-item" @click="navigateTo('orderComplaint')">
<div>
<span>{{ homeData.complaint || 0 }}</span>
<div>待处理</div>
</div>
<div class="detail-title">
投诉
</div>
<div class="detail-title">投诉</div>
</div>
<div class="detail-item" >
<div class="detail-item">
<div @click="navigateTo('alert-goods-quantity')">
<span>{{ homeData.alertQuantityNum || 0 }}</span>
<div>库存预警</div>
@@ -120,9 +125,7 @@
<span>{{ homeData.waitAuth || 0 }}</span>
<div>审核中</div>
</div>
<div class="detail-title">
商品
</div>
<div class="detail-title">商品</div>
</div>
<div class="detail-item">
@@ -134,15 +137,11 @@
<span>{{ homeData.waitPayBill || 0 }}</span>
<div>等待对账</div>
</div>
<div class="detail-title">
其他
</div>
<div class="detail-title">其他</div>
</div>
</div>
</div>
</div>
<!-- 公告 -->
<div class="card box-right">
<h4>平台公告</h4>
<div>
@@ -153,49 +152,46 @@
</div>
</div>
<div class="card ">
<div class="card">
<h4>统计数据</h4>
<div class="count-list flex">
<div class="count-item" @click="navigateTo('goods')">
<div>
<Icon class="icon" size="31" type="md-photos" />
<el-icon class="icon" :size="31"><Picture /></el-icon>
</div>
<div>
<div class="counts">{{ homeData.goodsNum || 0 }}</div>
<div>上架商品数量</div>
</div>
</div>
<div class="count-item" @click="navigateTo('orderStatistics')">
<div>
<Icon class="icon" size="31" type="ios-card" />
<el-icon class="icon" :size="31"><CreditCard /></el-icon>
</div>
<div>
<div class="counts">{{ homeData.orderPrice || 0 | unitPrice('¥') }}</div>
<div class="counts">
{{ $filters.unitPrice(homeData.orderPrice || 0, "¥") }}
</div>
<div>今日订单总额</div>
</div>
</div>
<div class="count-item" @click="navigateTo('orderList')">
<div>
<Icon class="icon" size="31" type="md-list" />
<el-icon class="icon" :size="31"><List /></el-icon>
</div>
<div>
<div class="counts">{{ homeData.orderNum || 0 }}</div>
<div>今日订单数量</div>
</div>
</div>
<div class="count-item" @click="navigateTo('trafficStatistics')">
<div>
<Icon class="icon" size="31" type="md-person" />
<el-icon class="icon" :size="31"><User /></el-icon>
</div>
<div>
<div class="counts">{{ homeData.storeUV || 0 }}</div>
<div>今日访客数量</div>
</div>
</div>
</div>
</div>
@@ -203,99 +199,92 @@
</template>
<script>
import defaultLogo from "@/assets/logo1.png";
import { Picture, CreditCard, List, User } from "@element-plus/icons-vue";
import { getSellerHomeData, getHomeNotice } from "@/api/index";
import { getIMDetail } from "@/api/common"
import { getIMDetail } from "@/api/common";
import { seeArticle } from "@/api/pages";
import Cookies from "js-cookie";
import { userMsg } from "@/api/index";
export default {
name: "home",
data () {
components: {
Picture,
CreditCard,
List,
User,
},
data() {
return {
noticeFlage: false, // 控制平台公告显隐
homeData: {}, // 首页数据
userData: "", // 店铺信息
notices: "", // 平台公告列表
noticesDetail: { // 平台公告详情
defaultLogo,
noticeFlage: false,
homeData: {},
userData: "",
notices: "",
noticesDetail: {
title: "",
},
IMLink: "",
load:false, //加载Im
load: false,
};
},
methods: {
// 跳转页面
navigateTo (name) {
this.$router.push({
name,
});
navigateTo(name) {
this.$router.push({ name });
},
// 初始化数据
async init () {
let userInfo = JSON.parse(Cookies.get("userInfoSeller"));
async init() {
const userInfo = JSON.parse(Cookies.get("userInfoSeller"));
this.userData = userInfo;
let res = await getHomeNotice();
const res = await getHomeNotice();
if (res.success) {
this.notices = res.result.records;
}
},
// 跳转文章页
async clickLinkNotices (val) {
let res = await seeArticle(val.id);
async clickLinkNotices(val) {
const res = await seeArticle(val.id);
if (res.success) {
this.noticesDetail = res.result;
this.noticeFlage = true;
}
},
/**
* 点击登录im的时候需要去判断一下当前店铺信息是否失效
* 失效的话重新请求刷新token保证最新的token去访问im
*/
async im () {
// 获取访问Token
let accessToken = this.getStore("accessToken");
this.load = true
async im() {
const accessToken = this.getStore("accessToken");
this.load = true;
await this.getIMDetailMethods();
const userInfo = await userMsg();
this.load = false
this.load = false;
if (userInfo.success && this.IMLink) {
window.open(`${this.IMLink}?token=` + accessToken);
}
else{
} else {
this.$Message.error("请登录后再联系客服");
}
},
// 获取im信息
async getIMDetailMethods () {
let res = await getIMDetail();
async getIMDetailMethods() {
const res = await getIMDetail();
if (res.success) {
this.IMLink = res.result;
}
},
// 获取首页数据
async getHomeData () {
let res = await getSellerHomeData();
async getHomeData() {
const res = await getSellerHomeData();
if (res.success) {
this.homeData = res.result;
}
},
},
mounted () {
mounted() {
this.init();
this.getHomeData();
},
};
</script>
<style lang="scss" scoped>
@import "./home.scss";
.noticesDetail{
::v-deep img{
.noticesDetail {
:deep(img) {
max-width: 100%;
max-height: 200px;
}

View File

@@ -1,89 +1,85 @@
<template>
<div>
<div class="breadcrumb">
<span @click="clickBreadcrumb(item, index)" :class="{ 'active': item.selected }" v-for="(item, index) in dateList"
:key="index"> {{ item.title }}</span>
<span
v-for="(item, index) in dateList"
:key="index"
:class="{ active: item.selected }"
@click="clickBreadcrumb(item)"
>
{{ item.title }}
</span>
<div class="date-picker">
<Select @on-change="changeSelect($event, selectedWay)" :value="month" placeholder="年月查询"
style="width:200px;margin-left:10px;">
<Option v-for="(item, index) in dates" :value="item.year + '-' + item.month" :key="index">{{
item.year + '' + item.month + '' }}</Option>
</Select>
<el-select
v-model="month"
placeholder="年月查询"
clearable
style="width: 200px; margin-left: 10px"
@change="changeSelect"
>
<el-option
v-for="(item, i) in dates"
:key="i"
:label="item.year + '年' + item.month + '月'"
:value="item.year + '-' + item.month"
/>
</el-select>
</div>
<div v-if="!closeShop" class="shop-list">
<el-select
v-model="storeId"
placeholder="店铺查询"
clearable
filterable
style="width: 200px; margin-left: 10px"
@change="changeshop"
>
<el-option
v-for="(item, index) in shopsData"
:key="index"
:label="item.storeName"
:value="item.id"
/>
</el-select>
</div>
</div>
</div>
</template>
<script>
import { getShopListData } from "@/api/shops.js";
export default {
props: ["closeShop"],
data() {
return {
month: "", // 月份
month: "",
selectedWay: {
// 可选时间项
title: "过去7天",
selected: true,
searchType: "LAST_SEVEN",
},
storeId: "", // 店铺id
dates: [], // 日期列表
storeId: "",
dates: [],
params: {
// 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 100,
storeName: "",
},
dateList: [
// 筛选条件
{
title: "天",
selected: false,
searchType: "TODAY",
},
{
title: "昨天",
selected: false,
searchType: "YESTERDAY",
},
{
title: "过去7天",
selected: true,
searchType: "LAST_SEVEN",
},
{
title: "过去30天",
selected: false,
searchType: "LAST_THIRTY",
},
{ title: "今天", selected: false, searchType: "TODAY" },
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
],
originDateList: [
// 筛选条件
{
title: "天",
selected: false,
searchType: "TODAY",
},
{
title: "昨天",
selected: false,
searchType: "YESTERDAY",
},
{
title: "过去7天",
selected: true,
searchType: "LAST_SEVEN",
},
{
title: "过去30天",
selected: false,
searchType: "LAST_THIRTY",
},
{ title: "今天", selected: false, searchType: "TODAY" },
{ title: "昨天", selected: false, searchType: "YESTERDAY" },
{ title: "过去7天", selected: true, searchType: "LAST_SEVEN" },
{ title: "过去30天", selected: false, searchType: "LAST_THIRTY" },
],
shopTotal: "", // 店铺总数
shopsData: [], // 店铺数据
shopTotal: 0,
shopsData: [],
};
},
mounted() {
@@ -91,113 +87,87 @@ export default {
this.getShopList();
},
methods: {
// 页面触底
handleReachBottom() {
setTimeout(() => {
if (this.params.pageNumber * this.params.pageSize <= this.shopTotal) {
this.params.pageNumber++;
this.getShopList();
}
}, 1500);
},
// 查询店铺列表
getShopList() {
getShopListData(this.params).then((res) => {
if (res.success) {
/**
* 解决数据请求中,滚动栏会一直上下跳动
*/
this.shopTotal = res.result.total;
this.shopsData.push(...res.result.records);
this.shopsData = res.result.records || [];
}
});
},
// 变更店铺
changeshop(val) {
changeshop() {
this.selectedWay.storeId = this.storeId;
this.$emit("selected", this.selectedWay);
},
// 获取近5年 年月
getFiveYears() {
let getYear = new Date().getFullYear();
let lastFiveYear = getYear - 5;
let maxMonth = new Date().getMonth() + 1;
let dates = [];
// 循环出过去5年
const getYear = new Date().getFullYear();
const lastFiveYear = getYear - 5;
const maxMonth = new Date().getMonth() + 1;
const dates = [];
for (let year = lastFiveYear; year <= getYear; year++) {
for (let month = 1; month <= 12; month++) {
if (year == getYear && month > maxMonth) {
} else {
dates.push({
year: year,
month: month,
});
if (year === getYear && month > maxMonth) {
continue;
}
dates.push({ year, month });
}
}
this.dates = dates.reverse();
},
// 改变已选店铺
changeSelect(e) {
this.month = e
this.month = e;
if (this.month) {
this.dateList.forEach((res) => {
res.selected = false;
});
this.selectedWay.year = this.month.split("-")[0];
this.selectedWay.month = this.month.split("-")[1];
this.selectedWay.searchType = "";
const parts = String(this.month).split("-");
this.selectedWay = {
...this.selectedWay,
year: parts[0],
month: parts[1],
searchType: "",
storeId: this.storeId,
};
this.$emit("selected", this.selectedWay);
} else {
const current = this.dateList.find(item => { return item.selected })
this.selectedWay = current
this.clickBreadcrumb(current)
this.$emit("selected", this.selectedWay);
const current =
this.dateList.find((item) => item.selected) ||
this.dateList.find((item) => item.title === this.selectedWay.title) ||
this.dateList.find((item) => item.searchType === "LAST_SEVEN");
this.clickBreadcrumb(current);
}
},
// 变更时间
clickBreadcrumb(item) {
if (!item) return;
let currentIndex;
this.dateList.forEach((res,index) => {
this.dateList.forEach((res, index) => {
res.selected = false;
if(res.title === item.title){
currentIndex = index
if (res.title === item.title) {
currentIndex = index;
}
});
item.selected = true;
item.storeId = this.storeId;
this.month = "";
if (item.searchType == "") {
let currentDate = this.originDateList[currentIndex].searchType
if (currentDate) {
item.searchType = currentDate
} else {
item.searchType = "LAST_SEVEN";
}
if (item.searchType === "") {
const origin = this.originDateList[currentIndex];
item.searchType = (origin && origin.searchType) || "LAST_SEVEN";
}
this.selectedWay = item;
this.selectedWay.year = new Date().getFullYear();
this.selectedWay.month = "";
this.$emit("selected", this.selectedWay);
},
},
};
</script>
<style lang="scss" scoped>
.breadcrumb {
display: flex;
align-items: center;
>span {
> span {
margin-right: 15px;
cursor: pointer;
}
@@ -208,8 +178,6 @@ export default {
position: relative;
}
.date-picker {}
.active:before {
content: "";
position: absolute;

View File

@@ -16,6 +16,10 @@ export default {
components:{uploadImage},
name: "Tinymce",
props: {
modelValue: {
type: String,
default: "",
},
value: {
type: String,
default: "",
@@ -25,6 +29,7 @@ export default {
default:'500px'
}
},
emits: ["update:modelValue", "input"],
data() {
return {
// 引入编辑器的配置
@@ -41,14 +46,19 @@ export default {
created() {
this.init();
},
computed: {
bindValue() {
return this.modelValue ?? this.value ?? "";
},
},
watch: {
value: {
bindValue: {
handler(val) {
if (!this.hasChange && this.hasInit) {
// 当内容有更改且编辑器已初始化时,更新编辑器的内容
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || "")
);
const editor = window.tinymce.get(this.tinymceId);
if (editor) {
this.$nextTick(() => editor.setContent(val || ""));
}
}
},
deep: true,
@@ -69,19 +79,15 @@ export default {
selector: `#${this.tinymceId}`,
convert_urls: false,
init_instance_callback: (editor) => {
if (_this.value) {
// 如果有初始值,则设置编辑器的内容为初始值
this.$nextTick(() => editor.setContent(_this.value));
if (_this.bindValue) {
_this.$nextTick(() => editor.setContent(_this.bindValue));
}
_this.hasInit = true;
// 监听编辑器内容的变化
editor.on("NodeChange Change KeyUp SetContent", (event) => {
if (_this.value) {
// 内容发生更改
this.hasChange = true;
}
// 通过 input 事件将编辑器的内容传递给父组件
this.$emit("input", editor.getContent());
editor.on("NodeChange Change KeyUp SetContent", () => {
_this.hasChange = true;
const content = editor.getContent();
_this.$emit("update:modelValue", content);
_this.$emit("input", content);
});
},
setup(editor) {
@@ -96,8 +102,15 @@ export default {
});
},
setContent(value) {
// 设置编辑器的内容
window.tinymce.get(this.tinymceId).setContent(value);
const editor = window.tinymce.get(this.tinymceId);
if (!editor) {
return;
}
const content = value || "";
this.hasChange = false;
editor.setContent(content);
this.$emit("update:modelValue", content);
this.$emit("input", content);
},
getContent() {
// 获取编辑器的内容
@@ -123,7 +136,7 @@ export default {
deactivated() {
this.destroyTinymce();
},
destroyed() {
unmounted() {
this.destroyTinymce();
},
};

View File

@@ -1,4 +1,4 @@
const plugins = [
'advlist', 'anchor', 'autolink', 'autosave', 'code', 'codesample', 'directionality', 'emoticons', 'fullscreen', 'image', 'importcss', 'insertdatetime', 'link', 'lists', 'media', 'nonbreaking', 'pagebreak', 'preview', 'save', 'searchreplace', 'table', 'template', 'visualblocks', 'visualchars', 'wordcount'
'advlist', 'anchor', 'autolink', 'autosave', 'charmap', 'code', 'codesample', 'directionality', 'emoticons', 'fullscreen', 'image', 'importcss', 'insertdatetime', 'link', 'lists', 'media', 'nonbreaking', 'pagebreak', 'preview', 'save', 'searchreplace', 'table', 'template', 'visualblocks', 'visualchars', 'wordcount'
]
export default plugins

View File

@@ -1,86 +1,114 @@
<template>
<div class="wrapper">
<Button @click="handleClickUploadImage">上传图片</Button>
<Modal v-model="show" width="850" @on-ok="callback" title="上传图片">
<div class="import-oss" @click="importOSS">
从资源库中导入
</div>
<div style="display: flex; flex-wrap: wrap">
<el-button @click="handleClickUploadImage">上传图片</el-button>
<el-dialog v-model="show" width="850px" title="上传图片" append-to-body :z-index="3500">
<div class="import-oss" @click="importOSS">从资源库中导入</div>
<div class="upload-images-wrap">
<vuedraggable
v-model="images"
:animation="200"
:list="images"
:item-key="draggableItemKey"
class="upload-images-draggable"
>
<div
v-for="(item, __index) in images"
:key="__index"
class="upload-list"
>
<template>
<img alt="image" :src="item.url"/>
<template #item="{ element, index }">
<div class="upload-list">
<img alt="image" :src="element.url" />
<div class="upload-list-cover">
<div>
<Icon
size="30"
type="md-search"
@click.native="$previewImage(item.url)"
></Icon>
<Icon
size="30"
type="md-trash"
@click.native="handleRemoveGoodsPicture(__index)"
></Icon>
<el-icon class="action-icon" :size="30" @click="handleView(element.url)">
<ZoomIn />
</el-icon>
<el-icon
class="action-icon"
:size="30"
@click="handleRemoveGoodsPicture(index)"
>
<Delete />
</el-icon>
</div>
</div>
</template>
</div>
</div>
</template>
</vuedraggable>
<div class="upload-box">
<Upload
<el-upload
ref="upload"
:action="uploadFileUrl"
:format="['jpg', 'jpeg', 'png']"
:headers="{ ...accessToken }"
:max-size="10240"
:on-exceeded-size="handleMaxSize"
:on-format-error="handleFormatError"
:on-success="handleSuccessGoodsPicture"
:show-upload-list="false"
:headers="accessToken"
:show-file-list="false"
accept=".jpg,.jpeg,.png"
drag
multiple
type="drag"
:before-upload="handleBeforeUpload"
:on-success="handleSuccessGoodsPicture"
:on-error="handleUploadError"
>
<div style="width: 148px; height: 148px; line-height: 148px">
<Icon size="20" type="md-add"></Icon>
<div class="upload-trigger">
<el-icon :size="20"><Plus /></el-icon>
</div>
</Upload>
</el-upload>
</div>
</div>
</Modal>
<template #footer>
<el-button @click="show = false">取消</el-button>
<el-button type="primary" @click="callback">确定</el-button>
</template>
</el-dialog>
<Modal width="1000" v-model="showOssManager" @on-ok="confirmUrls">
<OssManage ref="ossManage" :isComponent="true" :initialize="showOssManager" @selected="(list)=>{ selectedImage = list}" @callback="handleCallback" />
</Modal>
<el-dialog
v-model="showOssManager"
width="1000px"
append-to-body
:z-index="3600"
destroy-on-close
@closed="resetOssSelection"
>
<OssManage
ref="ossManage"
:is-component="true"
:initialize="showOssManager"
@selected="handleOssSelected"
/>
<template #footer>
<el-button @click="cancelOssImport">取消</el-button>
<el-button type="primary" @click="confirmUrls">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="viewImage" title="图片预览" width="520px" append-to-body :z-index="3700">
<img :src="previewUrl" alt="预览" style="width: 100%; display: block; margin: 0 auto" />
<template #footer>
<el-button @click="viewImage = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { Delete, Plus, ZoomIn } from "@element-plus/icons-vue";
import vuedraggable from "vuedraggable";
import {uploadFile} from "@/libs/axios";
// import OssManage from "@/views/sys/oss-manage/ossManage";
import { uploadFile } from "@/libs/axios";
import OssManage from "@/views/shop/ossManage";
export default {
name: "upload-image",
components: {
OssManage,
vuedraggable,
Delete,
Plus,
ZoomIn,
},
data() {
return {
show: false, // 是否显示弹窗
uploadFileUrl: uploadFile, // 上传地址
accessToken:"",
showOssManager:false, // 是否显示oss管理弹窗
images:[],
selectedImage:[]
}
show: false,
uploadFileUrl: uploadFile,
accessToken: {},
showOssManager: false,
images: [],
selectedImage: [],
viewImage: false,
previewUrl: "",
};
},
mounted() {
this.accessToken = {
@@ -88,124 +116,186 @@ export default {
};
},
methods: {
handleClickUploadImage(){
draggableItemKey(item) {
return item?.url || item;
},
handleClickUploadImage() {
this.show = true;
},
// 回调给父级
callback() {
// 先给数据做一下处理 然后将数据传给父级
const formatImages = this.images.map((item) => item.url);
this.$emit('callback',formatImages)
handleView(url) {
this.previewUrl = url;
this.viewImage = true;
},
callback() {
const formatImages = this.images.map((item) => item.url);
this.$emit("callback", formatImages);
this.show = false;
},
// 移除商品图片
handleRemoveGoodsPicture(__index) {
this.images.splice(__index, 1);
},
// 图片大小不正确
handleMaxSize(file) {
this.$Notice.warning({
title: "超过文件大小限制",
desc: "图片大小不能超过10MB",
});
handleBeforeUpload(file) {
const okType = ["image/jpeg", "image/png", "image/jpg"].includes(file.type);
if (!okType) {
this.$Message.warning("文件 " + file.name + " 的格式不正确,请选择 jpg/jpeg/png");
return false;
}
if (file.size / 1024 / 1024 > 10) {
this.$Message.warning("图片大小不能超过10MB");
return false;
}
return true;
},
// 图片格式不正确
handleFormatError(file) {
this.$Notice.warning({
title: "文件格式不正确",
desc: "文件 " + file.name + " 的格式不正确",
});
},
// sku图片上传成功
handleSuccessGoodsPicture(res, file) {
if (file.response) {
file.url = file.response.result;
this.images.push(file);
handleSuccessGoodsPicture(res) {
const url = res?.result ?? res?.data?.result;
if (url) {
this.images.push({ url });
} else {
this.$Message.error(res?.message || "上传失败");
}
},
confirmUrls(){
this.selectedImage.length ? this.selectedImage.forEach(element => {
this.images.push({ url: element.url })
}):''
this.showOssManager = false
handleUploadError(err) {
this.$Message.error(err?.message || String(err));
},
handleCallback(val){
this.$Message.success("导入成功")
this.images.push({url:val.url})
confirmUrls() {
this.applySelectedImages();
this.showOssManager = false;
},
// 从资源库中导入图片
importOSS(){
cancelOssImport() {
this.selectedImage = [];
this.showOssManager = false;
},
resetOssSelection() {
this.selectedImage = [];
},
handleOssSelected(list) {
this.selectedImage = Array.isArray(list) ? list : [];
},
parseOssSelectionUrl(item) {
if (!item) {
return "";
}
if (typeof item === "string") {
const index = item.indexOf(",");
return index >= 0 ? item.slice(index + 1) : item;
}
return item.url || "";
},
applySelectedImages() {
(this.selectedImage || []).forEach((item) => {
const url = this.parseOssSelectionUrl(item);
if (url) {
this.images.push({ url });
}
});
},
importOSS() {
this.selectedImage = [];
this.showOssManager = true;
this.$refs.ossManage.selectImage = true;
}
}
}
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
},
};
</script>
<style scoped lang="scss">
.import-oss{
.import-oss {
margin-bottom: 10px;
text-align: right;
color: $theme_color;
cursor: pointer;
}
.wrapper{
.wrapper {
margin: 10px 0;
}
$upload-item-size: 150px;
.upload-images-wrap {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
}
.upload-images-draggable {
display: flex;
flex-wrap: wrap;
}
.upload-list {
width: 150px;
height: 150px;
width: $upload-item-size;
height: $upload-item-size;
text-align: center;
border: 1px solid transparent;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: inline-block;
background: #fff;
position: relative;
margin-right: 4px;
vertical-align: bottom;
margin: 0 4px 4px 0;
overflow: hidden;
flex-shrink: 0;
}
.upload-box {
width: $upload-item-size;
height: $upload-item-size;
margin: 0 4px 4px 0;
display: inline-block;
flex-shrink: 0;
.upload-box{
margin: 10px 0;
:deep(.el-upload) {
width: $upload-item-size;
height: $upload-item-size;
display: block;
}
:deep(.el-upload-dragger) {
width: $upload-item-size;
height: $upload-item-size;
min-height: $upload-item-size;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
box-sizing: border-box;
}
}
.upload-trigger {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.upload-list img {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-list-cover {
display: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
justify-content: space-between;
align-items: center;
flex-direction: column;
}
.upload-list:hover .upload-list-cover {
display: flex;
}
.upload-list-cover div {
margin-top: 50px;
width: 100%;
>i {
width: 50%;
margin-top: 8px;
color: #fff;
font-size: 20px;
cursor: pointer;
}
display: flex;
justify-content: center;
gap: 8px;
}
.action-icon {
color: #fff;
cursor: pointer;
}
</style>

View File

@@ -1,57 +1,66 @@
<template>
<Modal :mask-closable="false" :value="switched" v-model="switched" title="选择地址" @on-ok="submit" @on-cancel="cancel">
<div class="flex">
<Spin size="large" fix v-if="spinShow"></Spin>
<Tree ref="tree" class="tree" :data="data" expand-node show-checkbox multiple></Tree>
<el-dialog
v-model="switched"
title="选择地址"
width="600px"
:close-on-click-modal="false"
destroy-on-close
@close="cancel"
>
<div v-loading="spinShow" class="flex">
<el-tree
ref="tree"
class="tree"
:data="data"
:props="treeProps"
node-key="id"
show-checkbox
default-expand-all
/>
</div>
</Modal>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submit">确定</el-button>
</template>
</el-dialog>
</template>
<script>
import { getAllCity } from "@/api/index";
export default {
data() {
return {
switched: false, // 控制模态框显隐
spinShow: false, // 加载loading
data: [], // 地区数据
selectedWay: [], // 选择的地区
callBackData: "", // 打开组件的回显数据
switched: false,
spinShow: false,
data: [],
selectedWay: [],
callBackData: "",
treeProps: {
label: "title",
children: "children",
},
};
},
mounted() {
this.init();
},
methods: {
clear() { // 引用该组件的父组件会调用
clear() {
this.data = [];
this.selectedWay = [];
this.init();
},
/**
* 关闭
*/
cancel() {
this.switched = false;
// 关闭的时候所有数据设置成disabled为true
this.data.forEach((item) => {
this.$set(item, "disabled", false);
item.disabled = false;
item.children.forEach((child) => {
this.$set(child, "disabled", false);
child.disabled = false;
});
});
},
/**
* 打开地图选择器
* @param {val} 回调的数据
* @param {index} 当前操作的运费模板的索引
*/
open(val, index) {
if (val) {
//已选中的地址
let checkedData = this.$store.state.shipTemplate;
let checkData = [];
let disabledData = checkedData.filter((item, i) => {
if (i != index) {
@@ -60,140 +69,127 @@ export default {
checkData.push(item);
}
});
// 选中
checkData.forEach((check) => {
// 循环出已经选中的地址id
check.areaId.split(",").forEach((ids) => {
this.data.forEach((item) => {
// 如果当前省份下市区全部选中则选中该省份
if (check.selectedAll) {
check.area.split(",").forEach((area) => {
if (area == item.name) {
this.$set(item, "checked", true);
item.checked = true;
}
});
}
// 将市区继续循环
item.children.forEach((child, childIndex) => {
// 判断当前市区是否是已选中状态
item.children.forEach((child) => {
if (item.checked) {
this.$set(child, "checked", true);
child.checked = true;
}
if (child.id == ids) {
this.$set(child, "checked", true);
child.checked = true;
}
});
});
});
});
// 禁用
disabledData.forEach((dis) => {
// 循环出已经选中的地址id
dis.areaId.split(",").forEach((ids) => {
// 循环出省份
this.data.forEach((item) => {
// 如果当前省份下市区全部选中则禁用该省份
if (dis.selectedAll) {
dis.area.split(",").forEach((area) => {
if (area == item.name) {
this.$set(item, "disabled", true);
item.disabled = true;
}
});
}
// 将市区继续循环
item.children.forEach((child, childIndex) => {
// 判断当前市区是否是已禁用状态
item.children.forEach((child) => {
if (item.disabled) {
this.$set(child, "disabled", true);
} else {
if (child.id == ids) {
this.$set(child, "disabled", true);
}
child.disabled = true;
} else if (child.id == ids) {
child.disabled = true;
}
});
});
});
});
this.syncTreeCheckedKeys();
}
this.switched ? (this.switched = true) : (this.switched = true);
this.switched = true;
},
syncTreeCheckedKeys() {
this.$nextTick(() => {
const keys = [];
this.data.forEach((item) => {
if (item.checked) {
keys.push(item.id);
}
item.children.forEach((child) => {
if (child.checked) {
keys.push(child.id);
}
});
});
if (this.$refs.tree) {
this.$refs.tree.setCheckedKeys(keys);
}
});
},
/**
* 提交并筛选出省市
*/
submit() {
// 筛选出省市
let list = this.$refs.tree.getCheckedAndIndeterminateNodes();
const checkedNodes = this.$refs.tree.getCheckedNodes(false, true);
const halfCheckedNodes = this.$refs.tree.getHalfCheckedNodes();
let list = [...checkedNodes, ...halfCheckedNodes];
let sort = [];
list.forEach((item, i) => {
list.forEach((item) => {
item.selectedList = [];
item.selectedAll = false;
// 筛选出当前的省份
if (item.level == "province" && !item.disabled) {
sort.push({
...item,
});
sort.push({ ...item });
}
// 筛选出当前选中的市
sort.forEach((sortItem, sortIndex) => {
if (
item.level != "province" &&
sortItem.id == item.parentId &&
!item.disabled
) {
sortItem.selectedList.push({
...item,
});
sort.forEach((sortItem) => {
if (item.level != "province" && sortItem.id == item.parentId && !item.disabled) {
sortItem.selectedList.push({ ...item });
}
});
});
// 判断如果当前省是否全选
this.data.forEach((whether) => {
sort.forEach((item) => {
// 如果当前省匹配
if (
item.id == whether.id &&
item.selectedList.length == whether.children.length
) {
// 给一个全选子级的标识符
item.selectedList.forEach((child) => {
this.$set(child, "selectedAll", true);
child.selectedAll = true;
});
this.$set(item, "selectedAll", true);
item.selectedAll = true;
}
});
});
this.$emit("selected", sort);
this.cancel();
},
// 初始化地区数据
init() {
getAllCity().then((res) => {
if (res.result) {
res.result.forEach((item) => {
item.children.forEach((child) => {
child.title = child.name;
this.spinShow = true;
getAllCity()
.then((res) => {
if (res.result) {
this.data = [];
res.result.forEach((item) => {
item.children.forEach((child) => {
child.title = child.name;
});
this.data.push({
title: item.name,
...item,
});
this.selectedWay.push({ name: item.name, id: item.id });
});
let data = {
title: item.name,
...item,
};
this.data.push(data);
this.selectedWay.push({ name: data.title, id: data.id });
});
this.$store.state.regions = this.data;
}
});
this.$store.state.regions = this.data;
}
})
.finally(() => {
this.spinShow = false;
});
},
},
};
@@ -202,21 +198,14 @@ export default {
.flex {
display: flex;
position: relative;
min-height: 400px;
}
.tree {
flex: 2;
flex: 1;
width: 100%;
}
.form {
flex: 8;
}
.button-list {
margin-left: 80px;
> * {
margin: 0 4px;
}
}
::v-deep .ivu-modal-body {
height: 400px !important;
:deep(.el-dialog__body) {
max-height: 450px;
overflow: auto;
}
</style>

View File

@@ -4,37 +4,69 @@
<div class="query-wrapper">
<div class="query-item">
<div>搜索范围</div>
<Input placeholder="商品名称" @on-clear="goodsData=[]; goodsParams.goodsName=''; goodsParams.pageNumber = 1; getQueryGoodsList()" @on-enter="()=>{goodsData=[]; goodsParams.pageNumber = 1; getQueryGoodsList();}" clearable style="width: 150px"
v-model="goodsParams.goodsName" />
<el-input
v-model="goodsParams.goodsName"
placeholder="商品名称"
clearable
style="width: 150px"
@clear="onSearchGoods"
@keyup.enter="onSearchGoods"
/>
</div>
<div class="query-item">
<Cascader v-model="category" placeholder="请选择商品分类" style="width: 150px" :data="cateList"></Cascader>
<el-cascader
v-model="category"
:options="skuList"
placeholder="请选择商品分类"
popper-class="goods-dialog-cascader-popper"
style="width: 250px"
clearable
/>
</div>
<div class="query-item">
<Button type="primary" @click="goodsData=[]; goodsParams.pageNumber = 1; getQueryGoodsList();" icon="ios-search">搜索</Button>
<el-button type="primary" @click="onSearchGoods">搜索</el-button>
</div>
</div>
<div >
<Scroll class="wap-content-list" :on-reach-bottom="handleReachBottom" :distance-to-edge="[3,3]">
<div class="wap-content-item" :class="{ active: item.selected }" @click="checkedGoods(item, index)" v-for="(item, index) in goodsData" :key="index">
<div>
<div class="wap-content-list">
<div
class="wap-content-item"
:class="{ active: item.selected }"
@click="checkedGoods(item, index)"
v-for="(item, index) in goodsData"
:key="index"
>
<div>
<img :src="item.thumbnail" alt="" />
</div>
<div class="wap-content-desc">
<div class="wap-content-desc-title">{{ item.goodsName }}</div>
<div class="wap-sku">{{ item.goodsUnit }}</div>
<div class="wap-sku"><Tag :color="item.salesModel === 'RETAIL' ? 'default' : 'geekblue'">{{item.salesModel === "RETAIL" ? "零售型" : "批发型"}}</Tag></div>
<div class="wap-sku">
{{ item.goodsUnit }}
<el-tag
style="margin-left: 10px"
:type="item.salesModel === 'RETAIL' ? 'info' : 'primary'"
>
{{ item.salesModel === "RETAIL" ? "零售型" : "批发型" }}
</el-tag>
</div>
<div class="wap-content-desc-bottom">
<div>¥{{ item.price | unitPrice }}</div>
<div>{{ $filters.unitPrice(item.price) }}</div>
</div>
</div>
</div>
<Spin size="large" fix v-if="loading"></Spin>
<div v-if="loading" v-loading="loading" class="loading-mask" />
<div v-if="empty" class="empty">暂无商品信息</div>
</Scroll>
</div>
<el-pagination
v-model:current-page="goodsParams.pageNumber"
class="pageration"
:total="total"
:page-size="goodsParams.pageSize"
layout="total, prev, pager, next"
size="small"
@current-change="changePageSize"
/>
</div>
</div>
</div>
@@ -45,50 +77,48 @@ export default {
props: {
selectedWay: {
type: Array,
default: () => {
return [];
},
default: () => [],
},
},
data() {
return {
type: "multiple", //单选或者多选 single multiple
cateList: [], // 商品分类列表
total: "", // 商品总数
type: "multiple",
selectedList: [],
skuList: [],
total: 0,
goodsParams: {
// 请求商品列表参数
pageNumber: 1,
pageSize: 18,
pageSize: 15,
order: "desc",
goodsName: "",
sn: "",
categoryPath: "",
marketEnable: "UPPER",
authFlag: "PASS",
sort:"createTime"
sort: "createTime",
},
category: [], // 选中的商品分类
goodsData: [], // 商品列表
empty: false, // 是否空数据
loading: false, // 商品加载loading
category: [],
goodsData: [],
empty: false,
loading: false,
};
},
watch: {
category(val) {
this.goodsParams.categoryPath = val[2];
this.goodsParams.categoryPath = val && val.length ? val.join(",") : "";
},
selectedWay: {
handler(val) {
this.$emit("selected", this.selectedWay);
this.selectedList = Array.isArray(val) ? val.slice() : [];
},
deep: true,
immediate: true,
},
"goodsParams.categoryPath": {
handler: function () {
handler() {
this.goodsData = [];
(this.goodsParams.pageNumber = 0), this.getQueryGoodsList();
this.goodsParams.pageNumber = 1;
this.getQueryGoodsList();
},
deep: true,
},
@@ -97,128 +127,101 @@ export default {
this.init();
},
methods: {
handleReachBottom() {
// 页面触底触发加载
setTimeout(() => {
if (
this.goodsParams.pageNumber * this.goodsParams.pageSize <=
this.total
) {
this.goodsParams.pageNumber++;
this.getQueryGoodsList();
}
}, 1500);
onSearchGoods() {
this.goodsData = [];
this.goodsParams.pageNumber = 1;
this.getQueryGoodsList();
},
changePageSize(v) {
this.goodsParams.pageNumber = v;
this.getQueryGoodsList();
},
getQueryGoodsList() {
// 根据商品分类筛选商品
API_Goods.getGoodsSkuData(this.goodsParams).then((res) => {
this.initGoods(res);
});
this.loading = true;
API_Goods.getGoodsSkuData(this.goodsParams)
.then((res) => {
this.initGoods(res);
})
.finally(() => {
this.loading = false;
});
},
initGoods(res) {
// 获取商品列表
if (res.result.records.length != 0) {
let data = res.result.records;
data.forEach((item) => {
const records = res?.result?.records || [];
if (records.length) {
records.forEach((item) => {
item.selected = false;
item.___type = "goods"; //设置为goods让pc wap知道标识
this.selectedWay.forEach((e) => {
if (e.id === item.id) {
item.___type = "goods";
this.selectedList.forEach((e) => {
if (e.id && e.id === item.id) {
item.selected = true;
}
});
});
/**
* 解决数据请求中,滚动栏会一直上下跳动
*/
this.total = res.result.total;
this.goodsData.push(...res.result.records);
this.total = res.result.total || 0;
this.goodsData = records;
this.empty = false;
} else {
this.goodsData = [];
this.empty = true;
}
},
// 查询商品
emitSelected(list) {
this.selectedList = list;
this.$emit("selected", this.selectedList);
},
init() {
Promise.all([
API_Goods.getGoodsSkuData(this.goodsParams),
API_Goods.getGoodsCategoryAll(0),
]).then((res) => {
// 商品
this.initGoods(res[0]);
// 分类
if (res[1].result) {
this.deepGroup(res[1].result);
API_Goods.getGoodsSkuData(this.goodsParams).then((res) => {
this.initGoods(res);
});
API_Goods.getGoodsCategoryAll(0).then((res) => {
if (res.result) {
this.deepGroup(res.result);
}
});
},
deepGroup(val) {
val.forEach((item) => {
let childWay = []; //第二级
// 第二层
let childWay = [];
if (item.children) {
item.children.forEach((child) => {
// // 第三层
if (child.children) {
child.children.forEach((grandson, index, arr) => {
arr[index] = {
value: grandson.id,
label: grandson.name,
children: "",
};
});
}
let children = {
childWay.push({
value: child.id,
label: child.name,
children: child.children,
};
childWay.push(children);
});
});
}
// 第一层
let way = {
this.skuList.push({
value: item.id,
label: item.name,
children: childWay,
};
this.cateList.push(way);
});
});
},
/**
* 点击商品
*/
checkedGoods(val, index) {
// 如果单选的话
if (this.type != "multiple") {
checkedGoods(val) {
if (this.type !== "multiple") {
this.goodsData.forEach((item) => {
item.selected = false;
});
this.selectedWay = [];
val.selected = true;
this.selectedWay.push(val);
return false;
this.emitSelected([val]);
return;
}
if (val.selected == false) {
if (!val.selected) {
val.selected = true;
this.selectedWay.push(val);
this.emitSelected([...this.selectedList, val]);
} else {
val.selected = false;
for (let i = 0; i < this.selectedWay.length; i++) {
if (this.selectedWay[i].id === val.id) {
this.selectedWay.splice(i, 1);
break;
}
}
this.emitSelected(this.selectedList.filter((item) => item.id !== val.id));
}
},
},
@@ -239,17 +242,32 @@ export default {
padding: 0;
}
.wap-content-list {
display: flex;
position: relative;
flex-wrap: wrap;
height: 340px;
}
.wap-content-item {
width: 210px;
margin: 10px 7px;
padding: 6px 0;
}
.active {
background: url("../../assets/selected.png") no-repeat;
background-position: right;
background-size: 10%;
}
.loading-mask {
position: absolute;
inset: 0;
}
.pageration {
margin-top: 12px;
justify-content: flex-end;
}
</style>
<style lang="scss">
.goods-dialog-cascader-popper {
z-index: 10001 !important;
}
</style>

View File

@@ -1,26 +1,40 @@
<template>
<Modal
:title="title"
:styles="{ top: '120px' }"
width="750"
@on-cancel="clickClose"
@on-ok="clickOK"
<el-dialog
v-model="flag"
:mask-closable="false"
scrollable
width="1160px"
top="120px"
:z-index="10000"
:close-on-click-modal="false"
append-to-body
destroy-on-close
@close="clickClose"
>
<goodsDialog
@selected="(val) => {goodsData = val;}"
:selectedWay='goodsData'
ref="goodsDialog"
v-if="goodsFlag"
/>
<linkDialog
@selectedLink="(val) => {linkData = val;}"
v-else
class="linkDialog"
/>
</Modal>
<template v-if="flag">
<goodsDialog
@selected="
(val) => {
goodsData = val;
}
"
v-if="goodsFlag"
ref="goodsDialog"
:selectedWay="goodsData"
/>
<linkDialog
@selectedLink="
(val) => {
linkData = val;
}
"
v-else
class="linkDialog"
/>
</template>
<template #footer>
<el-button @click="clickClose">取消</el-button>
<el-button type="primary" @click="clickOK">确定</el-button>
</template>
</el-dialog>
</template>
<script>
import goodsDialog from "./goods-dialog";
@@ -32,31 +46,30 @@ export default {
},
data() {
return {
title: "选择", // 模态框标题
goodsFlag: false, // 是否商品选择器
goodsData: [], //选择的商品
linkData: "", //选择的链接
flag: false, // 控制模态框显隐
goodsFlag: false,
goodsData: [],
linkData: "",
flag: false,
};
},
methods: {
// 关闭弹窗
clickClose() {
this.$emit("closeFlag", false);
this.goodsFlag = false;
clearGoodsSelected() {
this.goodsData = [];
},
// 单选商品
singleGoods(){
clickClose() {
this.flag = false;
this.goodsFlag = false;
this.$emit("closeFlag", false);
},
singleGoods() {
var timer = setInterval(() => {
if (this.$refs.goodsDialog) {
this.$refs.goodsDialog.type = "single";
clearInterval(timer);
clearInterval(timer);
}
}, 100);
},
clickOK() { // 确定按钮回调,
clickOK() {
if (this.goodsFlag) {
this.$emit("selectedGoodsData", this.goodsData);
} else {
@@ -64,27 +77,25 @@ export default {
}
this.clickClose();
},
open (type) { // 父组件通过ref调用打开商品选择器
open(type, mutiple) {
this.flag = true;
if(type == 'goods'){
if (type == "goods") {
this.goodsFlag = true;
if (mutiple) {
this.singleGoods();
}
} else {
this.goodsFlag = false
this.goodsFlag = false;
}
},
close(){ // 关闭组件
close() {
this.flag = false;
}
},
},
};
</script>
<style scoped lang="scss">
::v-deep .ivu-modal {
overflow: hidden;
height: 650px !important;
}
::v-deep .ivu-modal-body {
:deep(.el-dialog__body) {
width: 100%;
height: 500px;
overflow: hidden;

View File

@@ -1,17 +1,23 @@
<template>
<div class="wrapper">
<Tabs :value="wap[0].title" class="tabs">
<TabPane :label="item.title" :name="item.title" @click="clickTag(item, i)" v-for="(item, i) in wap" :key="i">
<component ref="lili-component" :is="templateWay[item.name]" @selected="
(val) => {
changed = val;
}
" />
</TabPane>
</Tabs>
<el-tabs v-model="activeTab" class="tabs">
<el-tab-pane
:label="item.title"
:name="item.title"
v-for="(item, i) in wap"
:key="i"
>
<component
ref="lili-component"
:is="templateWay[item.name]"
@selected="
(val) => {
changed = val;
}
"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
@@ -22,33 +28,38 @@ export default {
components: {
goodsDialog,
},
setup() {
return { templateWay };
},
data() {
return {
templateWay, // 模板数据
changed: "", // 变更模板
selected: 0, // 已选数据
selectedLink: "", //选中的链接
wap // tab标签
changed: "",
selected: 0,
selectedLink: "",
wap,
activeTab: wap[0]?.title || "",
};
},
watch: {
changed: {
handler(val) {
this.$emit("selectedLink", val[0]); //因为是单选,所以直接返回第一个
this.$emit("selectedLink", val[0]);
},
deep: true,
},
},
mounted() {
this.$nextTick(() => {
this.$refs["lili-component"][0].type = "single"; //商品页面设置成为单选
if (this.$refs["lili-component"]?.[0]) {
this.$refs["lili-component"][0].type = "single";
}
});
this.wap.forEach((item) => {
item.selected = false;
if (item) {
item.selected = false;
}
});
},
methods: {},
};
</script>
<style scoped lang="scss">
@@ -63,14 +74,8 @@ export default {
.tabs {
width: 100%;
}
::v-deep .ivu-modal {
overflow: hidden;
height: 650px !important;
}
::v-deep .ivu-modal-body {
width: 100%;
:deep(.el-tabs__content) {
height: 500px;
overflow: hidden;
overflow: auto;
}
</style>

View File

@@ -1,101 +1,101 @@
.wrapper {
width: 100%;
height: 100%;
display: flex;
.wap-list {
flex: 2;
text-align: center;
overflow-y: auto;
height: 100%;
}
> .wap-list,
.wap-content {
padding: 8px;
}
.wap-content {
flex: 8;
}
}
.wap-sku {
font-size: 12px;
color: #999;
overflow: hidden;
width: 100%;
height: 100%;
display: flex;
text-overflow: ellipsis;
white-space: nowrap;
}
.query-wrapper {
display: flex;
margin: 8px 0;
> .query-item {
display: flex;
align-items: center;
> * {
margin: 0 4px;
}
}
}
::v-deep .ivu-scroll-container {
width: 100% !important;
height: 400px !important;
}
::v-deep .ivu-scroll-content {
/* */
display: flex;
flex-wrap: wrap;
}
.wap-content-list {
.wap-list {
flex: 2;
text-align: center;
overflow-y: auto;
}
.wap-item {
padding: 10px 0;
cursor: pointer;
}
.wap-item:hover {
background: #ededed;
}
.active{
background: #ededed;
}
.active {
border: 1px solid #ededed;
}
.wap-content-item {
cursor: pointer;
height: 100%;
}
> .wap-list,
.wap-content {
flex: 8;
}
}
.wap-sku {
font-size: 12px;
color: #999;
overflow: hidden;
display: flex;
align-items: center;
text-overflow: ellipsis;
white-space: nowrap;
}
.query-wrapper {
display: flex;
margin: 8px 0;
> .query-item {
display: flex;
height: 100px;
padding: 2px;
overflow: hidden;
align-items: center;
margin: 10px;
::v-deep img {
width: 60px;
height: 60px;
text-align: center;
}
.wap-content-desc {
width: 180px;
padding: 8px;
> .wap-content-desc-title {
display: -webkit-box;
font-size: 12px;
color: #666;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 2;
}
> .wap-content-desc-bottom {
font-size: 12px;
padding: 4px 0;
color: #999;
display: flex;
justify-content: space-between;
> div:nth-of-type(1) {
color: $theme_color;
}
}
> * {
margin: 0 4px;
}
}
}
.wap-content-list {
overflow-y: auto;
}
.wap-item {
padding: 10px 0;
cursor: pointer;
}
.wap-item:hover {
background: #ededed;
}
.pageration {
text-align: right;
padding-right: 20px;
}
.wap-content-item {
cursor: pointer;
display: flex;
height: 90px;
padding: 2px;
overflow: hidden;
align-items: center;
margin: 10px;
:deep(img) {
width: 60px;
height: 60px;
text-align: center;
}
.wap-content-desc {
width: 180px;
padding: 8px;
> .wap-content-desc-title {
display: -webkit-box;
font-size: 12px;
color: #666;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 2;
}
> .wap-content-desc-bottom {
font-size: 12px;
padding: 4px 0;
color: #999;
display: flex;
justify-content: space-between;
> div:nth-of-type(1) {
color: $theme_color;
}
}
}
}

View File

@@ -23,6 +23,7 @@
</template>
<script>
import { getCategoryTree } from "@/api/goods.js";
export default {
data() {
@@ -40,10 +41,32 @@ export default {
this.init();
},
methods: {
loadCategoryFromCache() {
try {
const raw = localStorage.getItem("category");
if (!raw) {
return null;
}
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : null;
} catch {
return null;
}
},
applyCategoryList(category) {
if (!Array.isArray(category)) {
this.categoryList = [];
return;
}
category.forEach((item) => {
item.___type = "category";
});
this.categoryList = category;
},
// 点击一级
handleClickChild(item, index) {
this.parentIndex = index;
this.secondLevel = item.children;
this.secondLevel = item.children || [];
item.___type = "category";
item.allId = item.id;
@@ -51,8 +74,6 @@ export default {
this.thirdIndex = '';
this.thirdLevel = []
this.$emit("selected", [item]);
// 点击第一级的时候默认显示第二级第一个
// this.handleClickSecondChild(item.children, 0);
},
// 点击二级
handleClickSecondChild(second, index) {
@@ -60,10 +81,9 @@ export default {
second.allId = `${second.parentId},${second.id}`
this.secondIndex = index;
this.thirdLevel = second.children;
this.thirdLevel = second.children || [];
this.thirdIndex = '';
this.$emit("selected", [second]);
// this.handleClickthirdChild(second.children[0], 0);
},
// 点击三级
handleClickthirdChild(item, index) {
@@ -73,26 +93,17 @@ export default {
this.thirdIndex = index;
},
init() {
let category = JSON.parse(localStorage.getItem('category'))
if (category) {
category.forEach((item) => {
item.___type = "category";
});
this.categoryList = category;
// this.handleClickChild(category[0], 0);
} else {
setTimeout(() => {
category = JSON.parse(localStorage.getItem('category'))
category.forEach((item) => {
item.___type = "category";
});
this.categoryList = category;
// this.handleClickChild(category[0], 0);
},3000)
const cached = this.loadCategoryFromCache();
if (cached) {
this.applyCategoryList(cached);
return;
}
getCategoryTree().then((res) => {
if (res.success && Array.isArray(res.result)) {
localStorage.setItem("category", JSON.stringify(res.result));
this.applyCategoryList(res.result);
}
});
},
},
};
@@ -117,5 +128,6 @@ export default {
}
.wrapper {
overflow: hidden;
display: flex;
}
</style>

View File

@@ -1,15 +1,14 @@
import { markRaw } from "vue";
import category from "./category.vue";
import pages from "./pages.vue";
import goods from "../goods-dialog.vue";
import other from "./other.vue";
import shops from "./shops.vue";
import category from './category.vue'
import shops from './shops.vue'
import pages from './pages.vue'
import goods from '../goods-dialog.vue'
import other from './other.vue'
export default {
// pages,
// shops,
category,
goods,
other,
}
category: markRaw(category),
goods: markRaw(goods),
other: markRaw(other),
pages: markRaw(pages),
shops: markRaw(shops),
};

View File

@@ -1,116 +1,108 @@
<template>
<div>
<Row :gutter="30">
<Col span="6" v-for="(item,index) in linkList" :key="index" v-if="(item.title !== '拼团频道' && item.title !== '签到') || $route.name !== 'renovation'">
<div class="card" :class="{'active':selectedIndex == index}" @click="handleLink(item,index)">
<Icon size="24" :type="item.icon" />
<p>{{item.title}}</p>
</div>
</Col>
<!-- 外部链接只有pc端跳转 -->
<Col span="6" v-if="$route.name === 'renovation'">
<div class="card" :class="{'active':selectedIndex == linkList.length}" @click="handleLink(linkItem,linkList.length)">
<Poptip v-model="linkVisible">
<Icon size="24" :type="linkItem.icon" />
<p>{{linkItem.title}}</p>
<div slot="title">链接地址</div>
<div slot="content">
<Input type="text" @keyup="handleLink(linkItem,linkList.length)" v-model="linkItem.url" placeholder="https://"></Input>
<el-row :gutter="30">
<template v-for="(item, index) in linkList" :key="index">
<el-col v-if="showLinkItem(item)" :span="6">
<div
class="card"
:class="{ active: selectedIndex == index }"
@click="handleLink(item, index)"
>
<el-icon :size="24">
<component :is="item.icon" />
</el-icon>
<p>{{ item.title }}</p>
</div>
</el-col>
</template>
<el-col v-if="linkVisible" :span="6">
<div
class="card"
:class="{ active: selectedIndex == linkList.length }"
@click="handleLink(linkItem, linkList.length)"
>
<el-popover v-model:visible="linkPopoverVisible" trigger="click" placement="top" :width="280">
<template #reference>
<div class="link-card-inner">
<el-icon :size="24">
<component :is="linkItem.icon" />
</el-icon>
<p>{{ linkItem.title }}</p>
</div>
</Poptip>
</template>
<div>
<div style="margin-bottom: 8px">链接地址</div>
<el-input
v-model="linkItem.url"
placeholder="https://"
@keyup.enter="handleLink(linkItem, linkList.length)"
/>
</div>
</el-popover>
</div>
</Col>
</Row>
</el-col>
</el-row>
</div>
</template>
<script>
import { markRaw } from "vue";
import {
House,
ShoppingCart,
Star,
Document,
User,
Promotion,
PriceTag,
Sunny,
Share,
ShoppingBag,
Link,
} from "@element-plus/icons-vue";
export default {
data() {
return {
linkList: [ // 链接列表
{
title: "首页",
icon: "md-home",
___type: "home",
},
{
title: "购物车",
icon: "md-cart",
___type: "cart",
},
{
title: "收藏商品",
icon: "md-heart",
___type: "collection",
},
{
title: "我的订单",
icon: "md-document",
___type: "order",
},
{
title: "个人中心",
icon: "md-person",
___type: "user",
},
{
title: "拼团频道",
icon: "md-flame",
___type: "group",
},
{
title: "秒杀频道",
icon: "md-flame",
___type: "seckill",
},
{
title: "领券中心",
icon: "md-pricetag",
___type: "coupon",
},
{
title: "签到",
icon: "md-happy",
___type: "sign",
},
// {
// title: "小程序直播",
// icon: "ios-videocam",
// ___type: "live",
// },
{
title: "砍价",
icon: "md-share-alt",
___type: "kanjia",
},
{
title: "积分商城",
icon: "ios-basket",
___type: "point",
},
linkList: [
{ title: "首页", icon: markRaw(House), ___type: "home" },
{ title: "购物车", icon: markRaw(ShoppingCart), ___type: "cart" },
{ title: "收藏商品", icon: markRaw(Star), ___type: "collection" },
{ title: "我的订单", icon: markRaw(Document), ___type: "order" },
{ title: "个人中心", icon: markRaw(User), ___type: "user" },
{ title: "拼团频道", icon: markRaw(Promotion), ___type: "group" },
{ title: "秒杀频道", icon: markRaw(Promotion), ___type: "seckill" },
{ title: "领券中心", icon: markRaw(PriceTag), ___type: "coupon" },
{ title: "签到", icon: markRaw(Sunny), ___type: "sign" },
{ title: "砍价", icon: markRaw(Share), ___type: "kanjia" },
{ title: "积分商城", icon: markRaw(ShoppingBag), ___type: "point" },
],
linkItem: {
title: "外部链接",
icon: "ios-link",
icon: markRaw(Link),
___type: "link",
url: ''
url: "",
},
linkVisible: false, // 是否显示外部链接
selectedIndex: 9999999, // 已选index
linkVisible: false,
linkPopoverVisible: false,
selectedIndex: 9999999,
};
},
methods: {
showLinkItem(item) {
return (
(item.title !== "拼团频道" && item.title !== "签到") ||
this.$route.name !== "renovation"
);
},
handleLink(val, index) {
val = {...val,___type:'other'}
val = { ...val, ___type: "other" };
this.selectedIndex = index;
if (index === this.linkList.length) {
this.linkVisible = true
this.linkPopoverVisible = true;
} else {
this.linkVisible = false
this.linkPopoverVisible = false;
}
this.$emit("selected",[val])
this.$emit("selected", [val]);
},
},
};
@@ -127,13 +119,19 @@ export default {
text-align: center;
transition: 0.35s;
cursor: pointer;
::v-deep p {
border: 1px solid #ededed;
:deep(p) {
margin: 10px 0;
}
border: 1px solid #ededed;
}
.card:hover{
background: #ededed;
.link-card-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.card:hover {
background: #ededed;
}
.active {
background: #ededed;

View File

@@ -33,13 +33,10 @@ export default {
};
</script>
<style lang="scss" scoped>
::v-deep .ivu-card-body {
:deep(.el-card__body) {
height: 414px;
overflow: auto;
}
.ivu-table-wrapper ivu-table-wrapper-with-border {
height: 300px !important;
}
.list {
margin: 0 1.5%;
height: 400px;
@@ -69,11 +66,11 @@ export default {
height: 416px;
overflow: hidden;
}
::v-deep .ivu-table {
:deep(.el-table) {
height: 300px !important;
overflow: auto;
}
::v-deep .ivu-card-body {
:deep(.el-card__body) {
padding: 0;
height: auto;
}

View File

@@ -4,29 +4,54 @@
<div class="query-wrapper">
<div class="query-item">
<div>店铺名称</div>
<Input placeholder="请输入店铺名称" @on-clear="shopsData=[]; params.storeName=''; params.pageNumber =1; init()" @on-enter="()=>{shopsData=[]; params.pageNumber =1; init();}" icon="ios-search" clearable style="width: 150px"
v-model="params.storeName" />
<el-input
v-model="params.storeName"
placeholder="请输入店铺名称"
clearable
style="width: 150px"
@clear="resetSearch"
@keyup.enter="resetSearch"
/>
</div>
<div class="query-item">
<Button type="primary" @click="shopsData=[];params.pageNumber =1; init();" icon="ios-search">搜索</Button>
<el-button type="primary" @click="resetSearch">搜索</el-button>
</div>
</div>
<div>
<Scroll class="wap-content-list" :on-reach-bottom="handleReachBottom" :distance-to-edge="23">
<div class="wap-content-item" @click="clickShop(item,index)" :class="{ active:selected == index }" v-for="(item, index) in shopsData" :key="index">
<div v-loading="loading" class="wap-content-list">
<div
v-for="(item, index) in shopsData"
:key="index"
class="wap-content-item"
:class="{ active: selected == index }"
@click="clickShop(item, index)"
>
<div>
<img class="shop-logo" :src="item.storeLogo" alt="" />
</div>
<div class="wap-content-desc">
<div class="wap-content-desc-title">{{ item.storeName }}</div>
<div class="self-operated" :class="{'theme_color':item.selfOperated }">{{ item.selfOperated ? '自营' : '非自营' }}</div>
<div class="wap-sku" :class="{'theme_color':(item.storeDisable === 'OPEN' ? true : false) }">{{ item.storeDisable === 'OPEN' ? '开启中' : '未开启' }}</div>
<div class="self-operated" :class="{ theme_color: item.selfOperated }">
{{ item.selfOperated ? "自营" : "非自营" }}
</div>
<div
class="wap-sku"
:class="{ theme_color: item.storeDisable === 'OPEN' }"
>
{{ item.storeDisable === "OPEN" ? "开启中" : "未开启" }}
</div>
</div>
</div>
<Spin size="large" fix v-if="loading"></Spin>
</Scroll>
</div>
<el-pagination
v-model:current-page="params.pageNumber"
class="pageration"
size="small"
layout="total, prev, pager, next, jumper"
:total="total"
:page-size="params.pageSize"
@current-change="changePageSize"
/>
</div>
</div>
</div>
@@ -36,45 +61,39 @@ import { getShopListData } from "@/api/shops.js";
export default {
data() {
return {
loading: false, // 加载状态
total: "", // 总数
params: { // 请求参数
loading: false,
total: 0,
params: {
pageNumber: 1,
pageSize: 10,
pageSize: 12,
storeDisable: "OPEN",
storeName: "",
},
shopsData: [], // 店铺数据
selected: 9999999999, //设置一个不可能选中的index
shopsData: [],
selected: 9999999999,
};
},
watch: {},
created() {
this.init();
},
methods: {
handleReachBottom() {
setTimeout(() => {
if (this.params.pageNumber * this.params.pageSize <= this.total) {
this.params.pageNumber++;
this.init();
}
}, 1500);
resetSearch() {
this.shopsData = [];
this.params.pageNumber = 1;
this.init();
},
changePageSize(v) {
this.params.pageNumber = v;
this.init();
},
init() {
this.loading = true;
getShopListData(this.params).then((res) => {
if (res.success) {
/**
* 解决数据请求中,滚动栏会一直上下跳动
*/
this.total = res.result.total;
this.shopsData.push(...res.result.records);
this.loading = false;
this.shopsData = res.result.records;
}
this.loading = false;
});
},
clickShop(val, i) {
@@ -95,16 +114,21 @@ export default {
color: #999;
}
.wap-content-list {
display: flex;
flex-wrap: wrap;
height: 340px;
min-height: 120px;
}
.shop-logo {
object-fit: cover;
}
.wap-content-item {
}
.active {
background: url("../../../assets/selected.png") no-repeat;
background-position: right;
background-size: 10%;
}
.pageration {
margin-top: 12px;
justify-content: flex-end;
}
</style>

View File

@@ -5,36 +5,34 @@ export default [
openGoods: true,
name: "goods"
},
// {
// title: "分类",
// url: "1",
// name: "category"
// },
// {
// title: "活动",
// url: "3",
// name: "marketing"
// },
// {
// title: "页面",
// url: "3",
// name: "pages"
// },
// {
// title: "活动",
// url: "3",
// name: "marketing"
// },
// {
// title: "页面",
// url: "3",
// name: "pages"
// },
{
title: "分类",
url: "1",
name: "category"
},
{
title: "店铺",
url: "2",
name: "shops"
},
{
title: "活动",
url: "3",
name: "marketing"
},
{
title: "文章",
url: "3",
name: "pages"
},
{
title: "专题",
url: "4",
name: "special"
},
{
title: "其他",
url: "3",
name: "other"
}
},
];

View File

@@ -1,65 +1,125 @@
<template>
<div class="login" @click="$refs.verify.show = false">
<Row type="flex" @keydown.enter.native="submitLogin">
<Col style="width: 368px">
<Header />
<Row style="flex-direction: column">
<Tabs v-model="loginType">
<Tab-pane label="账号密码登录" name="passwordLogin">
<!--账号密码登录-->
<Form ref="usernameLoginForm" :model="form" :rules="rules" class="form">
<FormItem prop="username">
<Input v-model="form.username" prefix="ios-contact" clearable placeholder="请输入用户名"
autocomplete="off" />
</FormItem>
<FormItem prop="password">
<Input type="password" v-model="form.password" prefix="ios-lock" password
placeholder="请输入密码" autocomplete="off" />
</FormItem>
</Form>
<div class="register">
<a @click="$router.push('forgetPassword')">忘记密码</a>
</div>
</Tab-pane>
<Tab-pane label="验证码登录" name="mobileLogin">
<!-- 验证码登录 -->
<Form ref="formSms" :model="formSms" :rules="ruleInline" @click.self='$refs.verify.show = false'>
<FormItem prop="mobile">
<i-input type="text" maxlength="11" v-model="formSms.mobile" clearable placeholder="手机号">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="code">
<i-input type="text" v-model="formSms.code" placeholder="手机验证码">
<Icon type="ios-text-outline" style="font-weight: bold" slot="prepend" />
<Button slot="append" @click="sendCode" :loading="sendCodeLoading">{{ codeMsg }}</Button>
</i-input>
</FormItem>
<FormItem>
<!-- <Button @click.stop="verifyBtnClick" long :type="verifyStatus ? 'success' : 'default'">{{ verifyStatus ?
'验证通过' :
'点击完成安全验证' }}
</Button> -->
</FormItem>
</Form>
</Tab-pane>
</Tabs>
<Row>
<div class="login-btn" type="primary" size="large" :loading="loading" @click="submitLogin" long>
<span v-if="!loading">登录</span>
<span v-else>登录中</span>
</div>
</Row>
</Row>
<Footer />
<!-- 拼图验证码 -->
<verify ref="verify" class="verify-con" verifyType="LOGIN" @change="verifyChange"></verify>
</Col>
</Row>
<div class="login-hero">
<img class="login-bg" :src="loginBg" alt="" />
<div class="login-panel" @keyup.enter="submitLogin">
<div class="login-wrap">
<img class="login-logo" :src="loginLogo" alt="LILISHOP" />
<Header class="login-header-init" />
<el-tabs v-model="loginType" class="login-tabs">
<el-tab-pane label="账号登录" name="passwordLogin">
<el-form
ref="usernameLoginForm"
:model="form"
:rules="rules"
class="form"
>
<el-form-item prop="username">
<el-input
v-model="form.username"
size="large"
clearable
placeholder="请输入用户名"
autocomplete="off"
>
<template #prefix>
<el-icon><User /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="form.password"
type="password"
size="large"
show-password
placeholder="请输入密码"
autocomplete="off"
>
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="手机登录" name="mobileLogin">
<el-form
ref="formSms"
:model="formSms"
:rules="ruleInline"
class="form"
@click.self="$refs.verify.show = false"
>
<el-form-item prop="mobile">
<el-input
v-model="formSms.mobile"
size="large"
maxlength="11"
clearable
placeholder="请输入手机号"
>
<template #prefix>
<el-icon><Iphone /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<div class="code-field">
<el-input
v-model="formSms.code"
size="large"
placeholder="请输入验证码"
class="code-input"
>
<template #prefix>
<el-icon><Message /></el-icon>
</template>
</el-input>
<el-button
class="send-code-btn"
link
:disabled="!isMobileValid || time !== 60"
:loading="sendCodeLoading"
@click="sendCode"
>
{{ codeMsg }}
</el-button>
</div>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<el-button
class="login-btn"
type="primary"
size="large"
:loading="loading"
:disabled="!canSubmitLogin"
@click="submitLogin"
>
<span v-if="!loading">登录</span>
<span v-else>登录中</span>
</el-button>
<verify
ref="verify"
class="verify-con"
verifyType="LOGIN"
@change="verifyChange"
/>
</div>
</div>
</div>
<Footer class="login-footer" />
</div>
</template>
<script>
import { User, Lock, Iphone, Message } from "@element-plus/icons-vue";
import * as RegExp from "@/utils/regular.js";
import { sendSms } from "@/api/common.js";
import { login, storeSmsLogin, userMsg } from "@/api/index";
import util from "@/libs/util.js";
@@ -67,55 +127,43 @@ import Footer from "@/views/main-components/footer";
import Header from "@/views/main-components/header";
import verify from "@/views/my-components/verify";
import Cookies from "js-cookie";
export default {
components: {
Header,
Footer,
verify,
User,
Lock,
Iphone,
Message,
},
data() {
return {
saveLogin: true, // 保存登录状态
sendCodeLoading:false,
loading: false, // 加载状态
verifyStatus: false, // 是否图片验证通过
time: 60, // 倒计时
loginType: 'passwordLogin', //登陆类型
loginBg: require("@/assets/login-bg.png"),
loginLogo: require("@/assets/logo-lilishop.png"),
saveLogin: true,
sendCodeLoading: false,
loading: false,
verifyStatus: false,
smsCodeSent: false,
time: 60,
loginType: "passwordLogin",
form: {
// 表单数据
username: "",
password: "",
mobile: "",
code: "",
},
formSms: {
mobile: '',
code: '',
mobile: "",
code: "",
},
rules: {
// 验证规则
username: [
{
required: true,
message: "账号不能为空",
trigger: "blur",
},
],
password: [
{
required: true,
message: "密码不能为空",
trigger: "blur",
},
],
username: [{ required: true, message: "账号不能为空", trigger: "blur" }],
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
},
ruleInline: {
// 验证规则
username: [{ required: true, message: "请输入用户名" }],
password: [
{ required: true, message: "请输入密码" },
{ type: "string", min: 6, message: "密码不能少于6位" },
],
mobile: [
{ required: true, message: "请输入手机号码" },
{
@@ -125,28 +173,40 @@ export default {
],
code: [{ required: true, message: "请输入手机验证码" }],
},
codeMsg: "发送验证码", // 验证码文字
codeMsg: "发送验证码",
};
},
computed: {
isMobileValid() {
return RegExp.mobile.test(this.formSms.mobile);
},
canSubmitLogin() {
if (this.loginType === "passwordLogin") {
return !!(this.form.username.trim() && this.form.password.trim());
}
return this.smsCodeSent;
},
},
watch: {
"formSms.mobile"() {
this.smsCodeSent = false;
this.verifyStatus = false;
this.formSms.code = "";
},
},
created() {
window.localStorage.setItem("menuData", "");
},
methods: {
afterLogin(res) {
let accessToken = res.result.accessToken;
const accessToken = res.result.accessToken;
this.setStore("accessToken", accessToken);
this.setStore("refreshToken", res.result.refreshToken);
// 获取用户信息
userMsg().then((res) => {
if (res.success) {
// location.reload();
// this.$router.go(0);
this.setStore("saveLogin", this.saveLogin);
if (this.saveLogin) {
// 保存7天
Cookies.set("userInfoSeller", JSON.stringify(res.result), {
expires: 7,
});
@@ -158,92 +218,89 @@ export default {
this.$store.commit("setAvatarPath", res.result.storeLogo);
const redirectRouter = this.$route.query.redirect;
// 加载菜单
const push = {
this.$router.push({
path: redirectRouter || "/home",
}
this.$router.push(push);
});
} else {
this.loading = false;
}
});
},
// 发送手机验证码
sendCode() {
if (this.formSms.mobile === "") {
this.$Message.warning("请先填写手机号");
if (!this.isMobileValid) {
this.$Message.warning("请输入正确的手机号");
return;
}
if (!this.verifyStatus) {
this.$refs.verify.init();
return
return;
}
if (this.time === 60) {
this.sendCodeLoading = true
let params = {
this.sendCodeLoading = true;
const params = {
mobile: this.formSms.mobile,
verificationEnums: "LOGIN",
};
sendSms(params).then((res) => {
if (res.success) {
this.$Message.success("验证码发送成功");
let that = this;
this.interval = setInterval(() => {
// this.sendCodeLoading = false
that.time--;
if (that.time === 0) {
this.sendCodeLoading = false
that.time = 60;
that.codeMsg = "重新发送";
that.verifyStatus = false;
clearInterval(that.interval);
} else {
that.codeMsg = that.time;
}
}, 1000);
} else {
this.$Message.warning(res.message);
}
}).catch(() => {
this.sendCodeLoading = false
});
sendSms(params)
.then((res) => {
if (res.success) {
this.smsCodeSent = true;
this.$Message.success("验证码发送成功");
const that = this;
this.interval = setInterval(() => {
that.time--;
if (that.time === 0) {
this.sendCodeLoading = false;
that.time = 60;
that.codeMsg = "重新发送";
that.verifyStatus = false;
clearInterval(that.interval);
} else {
that.codeMsg = that.time;
}
}, 1000);
} else {
this.$Message.warning(res.message);
}
})
.catch(() => {
this.sendCodeLoading = false;
});
}
},
submitLogin() {
// 登录提交
if (this.loginType == 'passwordLogin') {
if (!this.canSubmitLogin) return;
if (this.loginType === "passwordLogin") {
this.$refs.usernameLoginForm.validate((valid) => {
if (valid) {
this.$refs.verify.init();
}
});
} else if (this.loginType == 'mobileLogin') {
this.$refs['formSms'].validate((valid) => {
} else if (this.loginType === "mobileLogin") {
this.$refs.formSms.validate((valid) => {
if (valid) {
this.loading = true;
storeSmsLogin(this.formSms).then(res => {
this.loading = false;
if (res.success) {
this.afterLogin(res)
}
}).catch(() => {
this.loading = false;
})
storeSmsLogin(this.formSms)
.then((res) => {
this.loading = false;
if (res.success) {
this.afterLogin(res);
}
})
.catch(() => {
this.loading = false;
});
}
})
});
}
},
verifyChange(con) {
// 拼图验证码回显
if (!con.status) return;
if (this.loginType == 'passwordLogin') {
if (this.loginType === "passwordLogin") {
this.loading = true;
let fd = new FormData();
const fd = new FormData();
fd.append("username", this.form.username);
fd.append("password", this.md5(this.form.password));
login(fd)
@@ -258,63 +315,288 @@ export default {
});
} else {
this.verifyStatus = true;
this.sendCode()
this.sendCode();
}
this.$refs.verify.show = false;
},
// 开启滑块验证
verifyBtnClick() {
if (!this.verifyStatus) {
this.$refs.verify.init();
}
},
},
};
</script>
<style lang="scss" scoped>
.login {
height: 100%;
background: url("../assets/background.png") no-repeat;
background-size: 100%;
background-position-y: bottom;
position: relative;
min-height: 100vh;
width: 100%;
background-color: #fff;
overflow: hidden;
}
.login-hero {
position: relative;
width: 100%;
aspect-ratio: 1024 / 426;
}
.login-bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: fill;
pointer-events: none;
user-select: none;
z-index: 0;
}
.login-panel {
position: absolute;
inset: 0;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-end;
padding: 0 12vw 0 0;
background: transparent;
}
.verify-con {
position: absolute;
top: 126px;
z-index: 10;
left: 20px;
}
.login-wrap {
position: relative;
width: 320px;
padding: 40px 42px 32px;
background: #fff;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
}
.form {
padding-top: 1vh;
}
.login-logo {
display: block;
width: 168px;
height: auto;
margin: 0 auto;
}
.login-btn {
background: linear-gradient(135deg, $theme_color 0%, $warning_color 100%);
height: 40px;
cursor: pointer;
border-radius: 4px;
.login-header-init {
display: none;
}
.login-tabs {
margin-top: 28px;
:deep(.el-tabs__header) {
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #fff;
width: 100%;
text-align: center;
transition: 0.35s;
}
.login-btn:hover {
:deep(.el-tabs__nav-wrap) {
flex: none;
}
:deep(.el-tabs__nav-wrap::after) {
display: none;
}
:deep(.el-tabs__nav) {
float: none;
display: flex;
justify-content: center;
}
:deep(.el-tabs__item) {
padding: 0 0 12px;
margin-right: 32px;
height: auto;
font-size: 15px;
color: #8a9199;
transition: color 0.2s ease, font-size 0.2s ease;
&:last-child {
margin-right: 0;
}
&.is-active {
font-size: 18px;
font-weight: 600;
color: #1f2329;
}
&:hover {
color: $theme_color;
}
}
:deep(.el-tabs__active-bar) {
height: 2px;
background-color: $theme_color;
border-radius: 1px;
}
}
:deep(.el-input__wrapper) {
background-color: #fff !important;
box-shadow: 0 0 0 1px #e5e7eb inset !important;
border-radius: 8px;
padding: 4px 14px;
transition: box-shadow 0.2s ease;
&.is-focus {
box-shadow: 0 0 0 1px $theme_color inset,
0 0 0 3px rgba($theme_color, 0.12) !important;
}
}
:deep(.el-input-group__prepend),
:deep(.el-input-group__append) {
background-color: transparent;
box-shadow: none;
border: none;
padding: 0;
}
:deep(.el-input-group__prepend) {
padding-right: 8px;
}
:deep(.el-input-group__append) {
padding-left: 8px;
}
:deep(.el-input__prefix) {
color: #a0a7b0;
}
:deep(.el-input__inner) {
background-color: transparent !important;
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 1000px #fff inset !important;
box-shadow: 0 0 0 1000px #fff inset !important;
-webkit-text-fill-color: #606266 !important;
caret-color: #606266;
transition: background-color 99999s ease-out;
}
}
:deep(.el-form-item) {
margin-bottom: 22px;
}
.send-code-btn {
flex-shrink: 0;
min-width: 90px;
padding: 0 4px;
font-size: 14px;
white-space: nowrap;
color: $theme_color !important;
&:not(.is-disabled):hover {
opacity: 0.85;
}
&.is-disabled {
color: #c0c4cc !important;
}
}
.code-field {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
.code-input {
flex: 1;
min-width: 0;
}
}
.verify-con {
position: absolute;
top: 150px;
z-index: 10;
left: 20px;
}
.form {
padding-top: 2px;
}
.login-btn {
width: 100%;
height: 44px;
margin-top: 4px;
border: none !important;
border-radius: 8px;
font-size: 15px;
letter-spacing: 2px;
color: #fff !important;
background: linear-gradient(135deg, $theme_color 0%, $warning_color 100%) !important;
box-shadow: none;
transition: opacity 0.2s ease;
&:not(.is-disabled):hover {
opacity: 0.9;
border-radius: 10px;
}
&.is-disabled,
&.is-disabled:hover,
&.is-disabled:focus {
color: #fff !important;
border-color: transparent !important;
background: linear-gradient(
135deg,
rgba($theme_color, 0.45) 0%,
rgba($warning_color, 0.45) 100%
) !important;
opacity: 1;
cursor: not-allowed;
}
}
:deep(.login-footer.foot) {
position: absolute;
bottom: 24px;
left: 0;
right: 0;
z-index: 2;
width: 100%;
margin-top: 0;
text-align: center;
color: #000;
}
:deep(.login-footer .information) {
justify-content: center;
}
:deep(.login-footer .copyright p) {
color: #000;
}
:deep(.login-footer a) {
color: #000 !important;
&:hover {
color: #333 !important;
}
}
@media (max-width: 992px) {
.login-panel {
justify-content: center;
padding: 0 20px;
}
.login-wrap {
width: 100%;
max-width: 340px;
padding: 32px 24px 24px;
}
}
</style>

View File

@@ -1,64 +1,55 @@
<template>
<div>
<Drawer width="300px" title="页面配置" v-model="drawer">
<!-- 内容 -->
<h3>
内容设置
</h3>
<el-drawer v-model="drawer" title="页面配置" size="300px">
<h3>内容设置</h3>
<div class="config-item flex flex-a-c flex-j-sb">
<div>
<Tooltip theme="light" placement="bottom-end" max-width="100" content="关闭之后部分页面点击'查看''详情'等按钮将跳到新页面展示" >
<div>
多标签Tab页内嵌模式
</div>
</Tooltip>
<el-tooltip
placement="bottom-end"
content="关闭之后部分页面点击'查看''详情'等按钮将跳到新页面展示"
>
<div>多标签Tab页内嵌模式</div>
</el-tooltip>
</div>
<i-switch v-model="setting.isUseTabsRouter"></i-switch>
<el-switch v-model="setting.isUseTabsRouter" />
</div>
</Drawer>
</el-drawer>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { mapState } from "vuex";
export default {
name: "configDrawer",
data() {
return {
drawer: false,
};
},
computed: {
...mapState({
setting: state => {
return state.setting.setting
}
})
setting: (state) => state.setting.setting,
}),
},
watch: {
setting: {
handler(val) {
this.setStore('setting', val)
this.$store.commit('updateSetting', val);
this.setStore("setting", val);
this.$store.commit("updateSetting", val);
},
deep: true
}
},
mounted() {
deep: true,
},
},
methods: {
open() {
this.drawer = true
this.drawer = true;
},
close() {
this.drawer = false
this.drawer = false;
},
toggle() {
this.drawer != this.drawer
},
}
}
},
};
</script>
<style lang="scss" scoped>

View File

@@ -1,31 +1,46 @@
<template>
<div class="foot">
<Row type="flex" justify="space-around" class="help">
<a class="item" :href="config.website" target="_blank">帮助</a>
<a class="item" :href="config.website" target="_blank">隐私</a>
<a class="item" :href="config.website" target="_blank">条款</a>
</Row>
<Row type="flex" justify="center" class="copyright">
Copyright © {{ year }} - Present
<a
:href="config.website"
class="href"
target="_blank"
style="margin: 0 5px"
>{{ config.title }}</a
>
</Row>
<div class="information footer-bottom">
<div class="icp">
<li v-if="config.icpCard">
<a
:href="
'https://tsm.miit.gov.cn/dxxzsp/xkz/xkzgl/resource/qiyesearch.jsp?num=' +
config.icpCard +
'&type=xuke'
"
target="_blank"
>
{{ config.icpCard }}
</a>
</li>
<li v-if="config.icpMessage" class="footer-bottom">
<a href="https://beian.miit.gov.cn/" target="_blank">
{{ config.icpMessage }}
</a>
</li>
</div>
<a class="company-link" :href="config.company.href" target="_blank">
<img class="zhizhao" :src="zhizhaoImg" alt="" />
{{ config.company.name }}
</a>
</div>
<div class="copyright">
<p>Copyright © {{ year }} {{ config.title }}</p>
</div>
</div>
</template>
<script>
const config = require("@/config/index");
export default {
// name: "footer",
data() {
return {
config,
year: new Date().getFullYear(), // 年
zhizhaoImg: require("@/assets/images/zhizhao.jpg"),
year: new Date().getFullYear(),
};
},
};
@@ -33,21 +48,71 @@ export default {
<style lang="scss" scoped>
.foot {
position: fixed;
bottom: 4vh;
width: 368px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
.help {
margin: 0 auto;
margin-bottom: 1vh;
width: 60%;
.item {
color: rgba(0, 0, 0, 0.45);
}
:hover {
color: rgba(0, 0, 0, 0.65);
}
width: 100%;
margin-top: 0;
color: #000;
font-size: 12px;
}
.information {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 8px 30px;
margin-bottom: 8px;
}
.icp {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
list-style: none;
margin: 0;
padding: 0;
li {
margin: 0 4px;
list-style: none;
}
}
.company-link {
display: flex;
align-items: center;
color: #000;
text-decoration: none;
&:hover {
opacity: 0.85;
}
}
.zhizhao {
width: 20px;
height: 20px;
margin-right: 4px;
border-radius: 50%;
}
.copyright {
text-align: center;
p {
margin: 0;
font-size: 12px;
line-height: 1.4;
color: #000;
}
}
a {
color: #000;
text-decoration: none;
&:hover {
opacity: 0.85;
}
}
</style>

View File

@@ -1,29 +1,40 @@
<template>
<div @click="handleChange" v-if="showFullScreenBtn" class="full-screen-btn-con">
<Tooltip :content="value ? '退出全屏' : '全屏'" placement="bottom">
<Icon :type="value ? 'ios-contract' : 'ios-expand'" :size="24"></Icon>
</Tooltip>
</div>
<div v-if="showFullScreenBtn" @click="handleChange" class="full-screen-btn-con">
<el-tooltip :content="modelValue ? '退出全屏' : '全屏'" placement="bottom">
<el-icon :size="24">
<ScaleToOriginal v-if="modelValue" />
<FullScreen v-else />
</el-icon>
</el-tooltip>
</div>
</template>
<script>
import { FullScreen, ScaleToOriginal } from "@element-plus/icons-vue";
export default {
name: "fullScreen",
components: { FullScreen, ScaleToOriginal },
props: {
modelValue: {
type: Boolean,
default: false,
},
value: {
type: Boolean,
default: false
}
default: false,
},
},
emits: ["update:modelValue", "input", "on-change"],
computed: {
showFullScreenBtn() {
return window.navigator.userAgent.indexOf("MSIE") < 0;
}
},
},
methods: {
handleFullscreen() {
let main = document.body;
if (this.value) {
const main = document.body;
if (this.modelValue) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
@@ -47,34 +58,44 @@ export default {
},
handleChange() {
this.handleFullscreen();
}
},
emitFullscreenState(isFullscreen) {
this.$emit("update:modelValue", isFullscreen);
this.$emit("input", isFullscreen);
this.$emit("on-change", isFullscreen);
},
onFullscreenChange() {
const isFullscreen = !!(
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen
);
this.emitFullscreenState(isFullscreen);
},
},
created() {
let isFullscreen =
const isFullscreen = !!(
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen;
isFullscreen = !!isFullscreen;
document.addEventListener("fullscreenchange", () => {
this.$emit("input", !this.value);
this.$emit("on-change", !this.value);
});
document.addEventListener("mozfullscreenchange", () => {
this.$emit("input", !this.value);
this.$emit("on-change", !this.value);
});
document.addEventListener("webkitfullscreenchange", () => {
this.$emit("input", !this.value);
this.$emit("on-change", !this.value);
});
document.addEventListener("msfullscreenchange", () => {
this.$emit("input", !this.value);
this.$emit("on-change", !this.value);
});
this.$emit("input", isFullscreen);
}
document.webkitIsFullScreen
);
this.emitFullscreenState(isFullscreen);
document.addEventListener("fullscreenchange", this.onFullscreenChange);
document.addEventListener("mozfullscreenchange", this.onFullscreenChange);
document.addEventListener("webkitfullscreenchange", this.onFullscreenChange);
document.addEventListener("msfullscreenchange", this.onFullscreenChange);
},
beforeUnmount() {
document.removeEventListener("fullscreenchange", this.onFullscreenChange);
document.removeEventListener("mozfullscreenchange", this.onFullscreenChange);
document.removeEventListener("webkitfullscreenchange", this.onFullscreenChange);
document.removeEventListener("msfullscreenchange", this.onFullscreenChange);
},
};
</script>

View File

@@ -1,18 +1,19 @@
<template>
<div>
<Row class="header">
<el-row class="header">
<img class="logo" :src="storeSideLogo" />
</Row>
</el-row>
</div>
</template>
<script>
import defaultLogo from "@/assets/logo.png";
import { getBaseSite } from "@/api/common.js";
export default {
// name: "header",
data() {
return {
storeSideLogo: require("@/assets/logo.png"),
storeSideLogo: require("@/assets/logo-lilishop.png"),
};
},
methods: {
@@ -24,53 +25,39 @@ export default {
!localStorage.getItem("seller_expiration_time")
) {
this.getSite();
} else if (new Date() > localStorage.getItem("seller_expiration_time")) {
this.getSite();
} else {
// 如果缓存过期,则获取最新的信息
if (new Date() > localStorage.getItem("seller_expiration_time")) {
this.getSite();
return;
} else {
this.storeSideLogo = localStorage.getItem("sellerlogoImg");
window.document.title = localStorage.getItem("sellersiteName");
//动态获取icon
let link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = localStorage.getItem("sellerIconImg");
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
}
this.storeSideLogo = localStorage.getItem("sellerlogoImg");
window.document.title = localStorage.getItem("sellersiteName");
this.applyFavicon(localStorage.getItem("sellerIconImg"));
}
},
applyFavicon(href) {
const link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = href;
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
},
getSite() {
//获取基本站点信息
getBaseSite().then((res) => {
if (res.success && res.result.settingValue) {
let data = JSON.parse(res.result.settingValue);
// 过期时间
var expirationTime = new Date().setHours(new Date().getHours() + 1);
// 存放过期时间
const data = JSON.parse(res.result.settingValue);
const expirationTime = new Date().setHours(new Date().getHours() + 1);
localStorage.setItem("seller_expiration_time", expirationTime);
// 存放信息
localStorage.setItem("sellersiteName", data.siteName);
localStorage.setItem("sellerlogoImg", data.storeSideLogo);
localStorage.setItem("sellerIconImg", data.storeSideIcon);
console.log(data, "datadadada");
this.storeSideLogo = data.storeSideLogo;
window.document.title = data.siteName;
//动态获取icon
let link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = data.storeSideIcon;
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
this.applyFavicon(data.storeSideIcon);
}
});
},
},
mounted() {
this.init();
},
@@ -79,13 +66,13 @@ export default {
<style lang="scss" scoped>
.header {
margin-bottom: 6vh;
margin-bottom: 0;
align-items: center;
display: flex;
justify-content: center !important;
}
.logo {
width: 440px;
height: 158px;
width: 168px;
height: auto;
}
</style>

View File

@@ -1,34 +1,41 @@
<template>
<div @click="showMessage" class="message-con">
<Tooltip :always="value>0" :content="value > 0 ? '有' + value + message : '无未读消息'" placement="bottom">
<Badge :count="value" dot>
<Icon type="md-notifications" :size="22" />
</Badge>
</Tooltip>
<el-tooltip
:visible="value > 0 ? undefined : false"
:content="value > 0 ? '有' + value + message : '无未读消息'"
placement="bottom"
>
<el-badge :value="value" :hidden="value <= 0" is-dot>
<el-icon :size="22"><Bell /></el-icon>
</el-badge>
</el-tooltip>
</div>
</template>
<script>
import { Bell } from "@element-plus/icons-vue";
import util from "@/libs/util.js";
export default {
name: "messageTip",
components: { Bell },
props: {
value: { // 未读消息数量
value: {
type: Number,
default: 0
default: 0,
},
message:{ // 消息展示内容
message: {
type: String,
default: ""
}
default: "",
},
},
methods: {
showMessage() {
util.openNewPage(this, "message_index");
this.$router.push({
name: "message_index"
name: "message_index",
});
}
}
},
},
};
</script>

View File

@@ -1,79 +1,130 @@
<style lang="scss" scoped>
@import "./styles/menu.scss";
</style>
<template>
<div class="ivu-shrinkable-menu">
<!-- 一级菜单 -->
<Menu ref="sideMenu" width="80px" theme="dark" :active-name="currNav" @on-select="selectNav">
<MenuItem v-for="(item, i) in navList" :key="i" :name="item.name">
{{item.title}}
</MenuItem>
</Menu>
<!-- 二级菜单 -->
<Menu
ref="childrenMenu"
:active-name="$route.name"
width="100px"
@on-select="changeMenu"
<div class="shrinkable-menu">
<el-menu
ref="sideMenu"
class="nav-menu-dark"
:default-active="currNav"
@select="selectNav"
>
<template v-for="item in menuList">
<MenuGroup :title="item.title" :key="item.id" style="padding-left:0;">
<MenuItem :name="menu.name" v-for="menu in item.children" :key="menu.name">
{{menu.title}}
</MenuItem>
</MenuGroup>
<el-menu-item v-for="(item, i) in navList" :key="i" :index="item.name">
{{ item.title }}
</el-menu-item>
</el-menu>
<el-menu
ref="childrenMenu"
:key="currNav"
class="sub-menu"
:default-active="$route.name"
@select="changeMenu"
>
<template v-for="item in menuList" :key="item.id">
<el-menu-item-group :title="item.title">
<el-menu-item
v-for="menu in item.children"
:key="menu.name"
:index="menu.name"
>
{{ menu.title }}
</el-menu-item>
</el-menu-item-group>
</template>
</Menu>
</el-menu>
</div>
</template>
<script>
import util from "@/libs/util.js";
export default {
name: "shrinkableMenu",
computed: {
// 二级菜单列表
menuList() {
return this.$store.state.app.menuList;
},
// 一级菜单
navList() {
return this.$store.state.app.navList;
},
// 当前一级菜单
currNav() {
return this.$store.state.app.currNav;
}
},
},
watch: {
// 监听路由变化
$route: {
handler: function (val, oldVal) {
if (val.meta.firstRouterName && val.meta.firstRouterName !== this.currNav) {
this.selectNav(val.meta.firstRouterName)
}
$route(val) {
if (
val.meta.firstRouterName &&
val.meta.firstRouterName !== this.currNav
) {
this.selectNav(val.meta.firstRouterName);
}
}
},
},
methods: {
changeMenu(name) { //二级路由点击
this.$router.push({
name: name
});
changeMenu(name) {
if (!name) return;
this.$router.push({ name });
},
selectNav(name) { // 一级路由点击
this.$store.commit("childrenMenu",this.$refs.childrenMenu)
selectNav(name) {
this.$store.commit("childrenMenu", this.$refs.childrenMenu);
this.$store.commit("setCurrNav", name);
this.setStore("currNav", name);
util.initRouter(this);
},
}
},
};
</script>
<style lang="scss" scoped>
.ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu), .ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title-active:not(.ivu-menu-submenu){
color: $theme_color;
.shrinkable-menu {
height: calc(100% - 60px);
width: 180px;
display: flex;
}
.nav-menu-dark {
width: 80px;
background-color: #191a23;
border-right: none;
overflow-y: auto;
:deep(.el-menu-item) {
color: rgba(255, 255, 255, 0.7);
justify-content: center;
padding: 0 8px !important;
text-align: center;
line-height: 1.3;
height: auto;
min-height: 56px;
white-space: normal;
transition: background-color 0.2s ease, color 0.2s ease;
}
:deep(.el-menu-item:not(.is-active):hover),
:deep(.el-menu-item:not(.is-active):focus) {
background-color: #43444d !important;
color: #fff !important;
}
:deep(.el-menu-item.is-active) {
background-color: #fff !important;
color: $theme_color !important;
}
:deep(.el-menu-item.is-active:hover),
:deep(.el-menu-item.is-active:focus) {
background-color: #fff !important;
color: $theme_color !important;
}
}
.sub-menu {
width: 100px;
overflow-y: auto;
border-right: none;
:deep(.el-menu-item-group__title) {
height: 40px;
line-height: 40px;
padding-left: 20px;
}
}
</style>

View File

@@ -1,25 +1,10 @@
.ivu-shrinkable-menu{
.shrinkable-menu {
height: calc(100% - 60px);
width: 180px;
display: flex;
}
.ivu-btn-text:hover {
background-color: rgba(255,255,255,.2) !important;
}
.ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu), .ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title-active:not(.ivu-menu-submenu){
background-color: #fff;
&:hover{
background-color: #fff;
}
}
.ivu-menu-vertical{
overflow-y: auto;
}
.ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu), .ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title-active:not(.ivu-menu-submenu){
color: #ed3f14;
}
::v-deep .ivu-menu-vertical .ivu-menu-item-group-title{
:deep(.el-menu-item-group__title) {
height: 40px;
line-height: 40px;
padding-left: 20px;

View File

@@ -1,33 +1,42 @@
<style lang="scss" scoped>
@import "../main.scss";
</style>
<template>
<div
ref="scrollCon"
@DOMMouseScroll="handlescroll"
@mousewheel="handlescroll"
class="tags-outer-scroll-con"
>
<ul v-show="visible" :style="{left: contextMenuLeft + 'px', top: contextMenuTop + 'px'}" class="contextmenu">
<li v-for="(item, key) of actionList" @click="handleTagsOption(key)" :key="key">{{item}}</li>
<ul
v-show="visible"
:style="{ left: contextMenuLeft + 'px', top: contextMenuTop + 'px' }"
class="contextmenu"
>
<li
v-for="(item, key) of actionList"
:key="key"
@click="handleTagsOption(key)"
>
{{ item }}
</li>
</ul>
<div ref="scrollBody" class="tags-inner-scroll-body" :style="{left: tagBodyLeft + 'px'}">
<transition-group name="taglist-moving-animation">
<Tag
type="dot"
v-for="item in pageTagsList"
ref="tagsPageOpened"
:key="item.name"
:name="item.name"
@on-close="closePage"
@click.native="linkTo(item)"
:closable="item.name=='home_index'?false:true"
:color="item.children?(item.children[0].name==currentPageName?'primary':'default'):(item.name==currentPageName?'primary':'default')"
@contextmenu.prevent.native="contextMenu(item, $event)"
>{{ itemTitle(item) }}</Tag>
</transition-group>
<div
ref="scrollBody"
class="tags-inner-scroll-body"
:style="{ left: tagBodyLeft + 'px' }"
>
<el-tag
v-for="item in pageTagsList"
:key="item.name"
:closable="item.name !== 'home_index'"
:type="tagType(item)"
:effect="isActive(item) ? 'dark' : 'plain'"
class="page-tag"
size="large"
@close="closePage($event, item.name)"
@click="linkTo(item)"
@contextmenu.prevent="contextMenu(item, $event)"
>
{{ itemTitle(item) }}
</el-tag>
</div>
</div>
</template>
@@ -35,218 +44,149 @@
<script>
export default {
name: "tagsPageOpened",
data() {
return {
currentPageName: this.$route.name, // 当前路由名称
tagBodyLeft: 0, // 标签左偏移量
visible: false, // 显示操作按钮
contextMenuLeft: 0, // 内容左偏移量
contextMenuTop: 0, // 内容上偏移量
actionList: { // 右键菜单
others: '关闭其他',
clearAll: '关闭所有'
},
refsTag: [], // 所有已打开标签
tagsCount: 1 // 标签数量
};
},
props: {
pageTagsList: Array,
beforePush: {
type: Function,
default: item => {
return true;
}
}
default: () => true,
},
},
data() {
return {
currentPageName: this.$route.name,
tagBodyLeft: 0,
visible: false,
contextMenuLeft: 0,
contextMenuTop: 0,
actionList: {
others: "关闭其他",
clearAll: "关闭所有",
},
};
},
computed: {
title() {
return this.$store.state.app.currentTitle;
},
tagsList() {
return this.$store.state.app.storeOpenedList;
}
},
},
methods: {
// 格式化标签名
isActive(item) {
return item.children
? item.children[0].name === this.currentPageName
: item.name === this.currentPageName;
},
tagType(item) {
return this.isActive(item) ? "primary" : "info";
},
itemTitle(item) {
if (typeof item.title == "object") {
return item.title;
} else {
if (typeof item.title === "object") {
return item.title;
}
return item.title;
},
// 关闭页面
closePage(event, name) {
let storeOpenedList = this.$store.state.app.storeOpenedList;
let lastPageObj = storeOpenedList[0];
if (this.currentPageName == name) {
let len = storeOpenedList.length;
if (this.currentPageName === name) {
const len = storeOpenedList.length;
for (let i = 1; i < len; i++) {
if (storeOpenedList[i].name == name) {
if (i < len - 1) {
lastPageObj = storeOpenedList[i + 1];
} else {
lastPageObj = storeOpenedList[i - 1];
}
if (storeOpenedList[i].name === name) {
lastPageObj =
i < len - 1 ? storeOpenedList[i + 1] : storeOpenedList[i - 1];
break;
}
}
} else {
let tagWidth = event.target.parentNode.offsetWidth;
} else if (event && event.target) {
const tagWidth = event.target.parentNode?.offsetWidth || 0;
this.tagBodyLeft = Math.min(this.tagBodyLeft + tagWidth, 0);
}
this.$store.commit("removeTag", name);
this.$store.commit("closePage", name);
storeOpenedList = this.$store.state.app.storeOpenedList;
localStorage.storeOpenedList = JSON.stringify(storeOpenedList);
if (this.currentPageName == name) {
if (this.currentPageName === name) {
this.linkTo(lastPageObj);
}
},
// 跳转
linkTo(item) {
if (this.$route.name == item.name) {
return;
}
let routerObj = {};
routerObj.name = item.name;
if (item.argu) {
routerObj.params = item.argu;
}
if (item.query) {
routerObj.query = item.query;
}
if (this.$route.name === item.name) return;
const routerObj = { name: item.name };
if (item.argu) routerObj.params = item.argu;
if (item.query) routerObj.query = item.query;
if (this.beforePush(item)) {
this.$router.push(routerObj);
}
},
// 页签栏滚动
handlescroll(e) {
var type = e.type;
const type = e.type;
let delta = 0;
if (type == "DOMMouseScroll" || type == "mousewheel") {
if (type === "DOMMouseScroll" || type === "mousewheel") {
delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40;
}
let left = 0;
if (delta > 0) {
left = Math.min(0, this.tagBodyLeft + delta);
} else {
} else if (
this.$refs.scrollCon.offsetWidth - 100 <
this.$refs.scrollBody.offsetWidth
) {
if (
this.$refs.scrollCon.offsetWidth - 100 <
this.$refs.scrollBody.offsetWidth
this.tagBodyLeft <
-(this.$refs.scrollBody.offsetWidth - this.$refs.scrollCon.offsetWidth + 100)
) {
if (
this.tagBodyLeft <
-(
this.$refs.scrollBody.offsetWidth -
this.$refs.scrollCon.offsetWidth +
100
)
) {
left = this.tagBodyLeft;
} else {
left = Math.max(
this.tagBodyLeft + delta,
this.$refs.scrollCon.offsetWidth -
this.$refs.scrollBody.offsetWidth -
100
);
}
left = this.tagBodyLeft;
} else {
this.tagBodyLeft = 0;
left = Math.max(
this.tagBodyLeft + delta,
this.$refs.scrollCon.offsetWidth -
this.$refs.scrollBody.offsetWidth -
100
);
}
} else {
this.tagBodyLeft = 0;
}
this.tagBodyLeft = left;
},
// 标签右键操作
handleTagsOption(type) {
if (type == "clearAll") {
if (type === "clearAll") {
this.$store.commit("clearAllTags");
this.$router.push({
name: "home_index"
});
this.$router.push({ name: "home_index" });
} else {
this.$store.commit("clearOtherTags", this);
}
this.tagBodyLeft = 0;
},
// 标签栏滚动
moveToView(tag) {
if (tag.offsetLeft < -this.tagBodyLeft) {
// 标签在可视区域左侧
this.tagBodyLeft = -tag.offsetLeft + 10;
} else if (
tag.offsetLeft + 10 > -this.tagBodyLeft &&
tag.offsetLeft + tag.offsetWidth <
-this.tagBodyLeft + this.$refs.scrollCon.offsetWidth - 100
) {
// 标签在可视区域
this.tagBodyLeft = Math.min(
0,
this.$refs.scrollCon.offsetWidth -
100 -
tag.offsetWidth -
tag.offsetLeft -
20
);
} else {
// 标签在可视区域右侧
this.tagBodyLeft = -(
tag.offsetLeft -
(this.$refs.scrollCon.offsetWidth - 100 - tag.offsetWidth) +
20
);
}
contextMenu(item, e) {
this.visible = true;
const offsetLeft = this.$el.getBoundingClientRect().left;
this.contextMenuLeft = e.clientX - offsetLeft + 10;
this.contextMenuTop = e.clientY - 64;
},
// 显示操作按钮
contextMenu (item, e) {
this.visible = true
const offsetLeft = this.$el.getBoundingClientRect().left
this.contextMenuLeft = e.clientX - offsetLeft + 10
this.contextMenuTop = e.clientY - 64
closeMenu() {
this.visible = false;
},
// 关闭右侧菜单
closeMenu () {
this.visible = false
}
},
mounted() {
this.refsTag = this.$refs.tagsPageOpened;
setTimeout(() => {
this.refsTag.forEach((item, index) => {
if (this.$route.name == item.name) {
let tag = this.refsTag[index].$el;
this.moveToView(tag);
}
});
}, 1); // 这里不设定时器就会有偏移bug
this.tagsCount = this.tagsList.length;
},
watch: {
$route(to) {
this.currentPageName = to.name;
this.$nextTick(() => {
this.refsTag.forEach((item, index) => {
if (to.name == item.name) {
let tag = this.refsTag[index].$el;
this.moveToView(tag);
}
});
});
this.tagsCount = this.tagsList.length;
},
visible (value) {
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
document.body.addEventListener("click", this.closeMenu);
} else {
document.body.removeEventListener('click', this.closeMenu)
document.body.removeEventListener("click", this.closeMenu);
}
}
}
},
},
beforeUnmount() {
document.body.removeEventListener("click", this.closeMenu);
},
};
</script>
<style lang="scss" scoped>
<style lang="scss">
@import "@/views/main.scss";
.contextmenu {
position: absolute;
margin: 0;
@@ -255,21 +195,50 @@ export default {
z-index: 11000;
list-style-type: none;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
li {
margin: 0;
padding: 5px 15px;
cursor: pointer;
&:hover {
background: rgba($color: $theme_color, $alpha: .1);
background: rgba($color: $theme_color, $alpha: 0.1);
}
}
}
.page-tag {
margin-right: 8px;
cursor: pointer;
height: 28px;
padding: 0 12px;
border-radius: 4px;
font-size: 13px;
transition: all 0.2s ease;
.ivu-tag-primary{
::v-deep .ivu-tag-dot-inner{
background: $theme_color !important;
&.el-tag--info.el-tag--plain {
color: #606266;
border-color: #dcdfe6;
background-color: #fff;
}
&.el-tag--info.el-tag--plain .el-tag__close {
color: #909399;
&:hover {
color: #606266;
background-color: transparent;
}
}
& + .page-tag {
margin-left: 0;
}
&:hover {
opacity: 0.85;
}
.el-tag__close {
margin-left: 6px;
}
}
</style>

View File

@@ -54,7 +54,7 @@
display: block;
padding-left: 180px;
width: 100%;
height: 100px;
height: 106px;
z-index: 20;
box-shadow: 0 2px 1px 1px rgba(100, 100, 100, 0.1);
transition: padding 0.3s;
@@ -81,9 +81,11 @@
}
.tags-con {
height: 40px;
height: 46px;
z-index: -1;
background: #f0f0f0;
display: flex;
align-items: center;
.tags-outer-scroll-con {
position: relative;
@@ -94,7 +96,11 @@
.tags-inner-scroll-body {
position: absolute;
padding: 2px 10px;
top: 0;
bottom: 0;
display: flex;
align-items: center;
padding: 0 10px;
overflow: visible;
white-space: nowrap;
transition: left 0.3s ease;
@@ -151,17 +157,6 @@
height: 100%;
.options {
.ivu-select-dropdown {
transform-origin: center top 0px;
position: absolute;
top: 45px !important;
left: -2px;
will-change: top, left;
}
}
.message-con {
display: inline-block;
@@ -233,10 +228,10 @@
min-width: 740px;
position: relative;
left: 180px;
top: 100px;
top: 106px;
right: 0;
bottom: 0;
height: calc(100% - 110px);
height: calc(100% - 116px);
width: calc(100% - 180px);
overflow: auto;
background-color: #f0f0f0;

View File

@@ -1,64 +1,128 @@
<template>
<div class="search">
<Card>
<Form ref="searchForm" :model="searchForm" @keydown.enter.native="handleSearch" inline :label-width="70" class="search-form">
<Form-item label="会员名称" prop="memberName">
<Input type="text" v-model="searchForm.memberName" clearable placeholder="请输入会员名称" style="width: 240px" />
</Form-item>
<Form-item label="商品名称" prop="goodsName">
<Input type="text" v-model="searchForm.goodsName" clearable placeholder="请输入商品名" style="width: 240px" />
</Form-item>
<Form-item label="评价" prop="orderStatus">
<Select v-model="searchForm.grade" placeholder="请选择" clearable style="width: 240px">
<Option value="GOOD">好评</Option>
<Option value="MODERATE">中评</Option>
<Option value="WORSE">差评</Option>
</Select>
</Form-item>
<Form-item label="评论日期">
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd HH:mm:ss" clearable @on-change="selectDateRange" placeholder="选择起始时间" style="width: 240px"></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Card>
<Card>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" class="mt_10"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small"
show-total show-elevator show-sizer></Page>
</Row>
</Card>
<Modal :title="modalTitle" v-model="modalVisible" :mask-closable="false" :width="500">
<Form ref="form" :model="form" :label-width="100" :rules="formValidate">
<FormItem label="评价内容">
<span v-if="!content">暂无评价</span>
<span v-else>
<div>
<Input v-model="content" type="textarea" maxlength="200" disabled :rows="4" clearable style="width:90%" />
</div>
</span>
</FormItem>
<FormItem label="评价图片" style="padding-top: 10px" v-if="detailInfo.haveImage == 1">
<upload-pic-thumb v-model="image" :disable="true" :remove="false" :isView="true"></upload-pic-thumb>
</FormItem>
<FormItem label="回复内容" prop="reply">
<Input v-if="replyStatus == false" v-model="form.reply" type="textarea" maxlength="200" :rows="4" clearable style="width:90%" />
<span v-else>
<Input v-model="form.reply" type="textarea" maxlength="200" disabled :rows="4" clearable style="width:90%" />
</span>
</FormItem>
<FormItem label="回复图片" prop="replyImage" style="padding-top: 18px" v-if="detailInfo.haveReplyImage == 1 || replyStatus == false">
<upload-pic-thumb v-if="replyStatus == false" v-model="form.replyImage" :limit="5"></upload-pic-thumb>
<upload-pic-thumb v-else v-model="form.replyImage" :disable="true" :remove="false"></upload-pic-thumb>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="modalVisible = false">取消</Button>
<Button v-if="replyStatus == false" type="primary" :loading="submitLoading" @click="handleSubmit">回复
</Button>
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="会员名称" prop="memberName">
<el-input v-model="searchForm.memberName" clearable placeholder="请输入会员名称" style="width: 240px" />
</el-form-item>
<el-form-item label="商品名称" prop="goodsName">
<el-input v-model="searchForm.goodsName" clearable placeholder="请输入商品名" style="width: 240px" />
</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-select>
</el-form-item>
<el-form-item label="评论日期">
<el-date-picker
v-model="selectDate"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 360px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-button class="search-btn" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<el-table v-loading="loading" border :data="data" ref="table" 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">
<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>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<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">
<template #default="{ row }">
<el-tag v-if="row.replyStatus" type="success">已回复</el-tag>
<el-tag v-else type="primary">未回复</el-tag>
</template>
</el-table-column>
<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>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</Modal>
</el-card>
<el-dialog v-model="modalVisible" :title="modalTitle" width="500px" :close-on-click-modal="false">
<el-form ref="form" :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>
<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>
</div>
</template>
@@ -68,175 +132,74 @@ import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
export default {
name: "memberComment",
components: {
uploadPicThumb,
},
components: { uploadPicThumb },
data() {
return {
detailInfo: {}, // 详情信息
image: [], //评价图片
replyStatus: false, //回复状态
modalVisible: false, // 添加或编辑显示
modalTitle: "", // 添加或编辑标题
loading: true, // 表单加载状态
content: "", //评价内容
detailInfo: {},
image: [],
replyStatus: false,
modalVisible: false,
modalTitle: "",
loading: true,
content: "",
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startTime: "", // 起始时间
endTime: "", // 终止时间
pageNumber: 1,
pageSize: 10,
sort: "createTime",
order: "desc",
startTime: "",
endTime: "",
},
selectDate: null,
form: {
replyImage: [],
reply: "",
},
// 表单验证规则
formValidate: {
reply: [{ required: true, message: "请输入回复内容", trigger: "blur" }],
},
submitLoading: false, // 添加或编辑提交状态
columns: [
// 表头
{
title: "会员名称",
key: "memberName",
minWidth: 150,
tooltip: true,
},
{
title: "商品名称",
key: "goodsName",
minWidth: 150,
tooltip: true,
},
{
title: "评价内容",
key: "content",
minWidth: 300,
tooltip: true,
},
{
title: "评价",
key: "grade",
width: 100,
render: (h, params) => {
if (params.row.grade == "GOOD") {
return h("Tag", { props: { color: "green" } }, "好评");
} else if (params.row.grade == "MODERATE") {
return h("Tag", { props: { color: "orange" } }, "中评");
} else {
return h("Tag", { props: { color: "red" } }, "差评");
}
},
},
{
title: "状态",
key: "status",
width: 100,
render: (h, params) => {
if (params.row.status === "OPEN") {
return h("Tag", { props: { color: "green" } }, "展示");
} else {
return h("Tag", { props: { color: "red" } }, "隐藏");
}
},
},
{
title: "回复状态",
key: "replyStatus",
width: 110,
render: (h, params) => {
if (params.row.replyStatus) {
return h("Tag", { props: { color: "green" } }, "已回复");
} else {
return h("Tag", { props: { color: "blue" } }, "未回复");
}
},
},
{
title: "创建日期",
key: "createTime",
width: 170,
},
{
title: "操作",
key: "action",
align: "center",
fixed: 'right',
width: 200,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"详细"
),
]);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
submitLoading: false,
data: [],
total: 0,
};
},
methods: {
init() {
// 初始化数据
this.getDataList();
},
// 改变页数
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.clearSelectAll();
},
// 改变页码
changePageSize(v) {
this.searchForm.pageSize = v;
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.getDataList();
},
// 重置
handleReset() {
this.searchForm = {};
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm = {
pageNumber: 1,
pageSize: 10,
sort: "createTime",
order: "desc",
startTime: "",
endTime: "",
};
this.selectDate = null;
this.getDataList();
},
// 清除选中状态
clearSelectAll() {
this.$refs.table.selectAll(false);
},
// 选择日期回调
selectDateRange(v) {
if (v) {
if (v && v.length === 2) {
this.searchForm.startTime = v[0];
this.searchForm.endTime = v[1];
} else {
this.searchForm.startTime = "";
this.searchForm.endTime = "";
}
},
// 获取列表数据
getDataList() {
this.loading = true;
API_Member.getMemberReview(this.searchForm).then((res) => {
@@ -247,7 +210,6 @@ export default {
}
});
},
//回复
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
@@ -262,26 +224,19 @@ export default {
}
});
},
// 获取详情
detail(v) {
this.form.replyImage = [];
this.loading = true;
API_Member.getMemberInfoReview(v.id).then((res) => {
this.loading = false;
if (res.success) {
//赋值
this.form.id = res.result.id;
this.content = res.result.content;
this.form.reply = res.result.reply;
this.replyStatus = res.result.replyStatus;
if (res.result.images) {
this.image = (res.result.images || "").split(",");
}
if (res.result.replyImage) {
this.form.replyImage = (res.result.replyImage || "").split(",");
}
this.image = res.result.images ? (res.result.images || "").split(",") : [];
this.form.replyImage = res.result.replyImage ? (res.result.replyImage || "").split(",") : [];
this.detailInfo = res.result;
//弹出框
this.modalVisible = true;
this.modalTitle = "详细";
}
@@ -293,7 +248,12 @@ export default {
},
};
</script>
<style lang="scss">
// 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss";
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
</style>

View File

@@ -1,81 +1,102 @@
<template>
<div class="message-main-con">
<div class="message-mainlist-con">
<div>
<Button @click="setCurrentMesType('unread')" size="large" long type="text">
<el-button size="large" text style="width: 100%" @click="setCurrentMesType('unread')">
<div class="mes-wrap">
<transition name="mes-current-type-btn">
<Icon v-show="currentMessageType == 'unread'" type="md-checkmark"></Icon>
<el-icon v-show="currentMessageType == 'unread'"><Check /></el-icon>
</transition>
<span class="mes-type-btn-text">未读消息</span>
<Badge
class="message-count-badge-outer"
class-name="message-count-badge-red"
:count="unReadCount"
></Badge>
<el-badge :value="unReadCount" class="message-count-badge-outer" />
</div>
</Button>
</el-button>
</div>
<div>
<Button @click="setCurrentMesType('read')" size="large" long type="text">
<el-button size="large" text style="width: 100%" @click="setCurrentMesType('read')">
<div class="mes-wrap">
<transition name="mes-current-type-btn">
<Icon v-show="currentMessageType == 'read'" type="md-checkmark"></Icon>
<el-icon v-show="currentMessageType == 'read'"><Check /></el-icon>
</transition>
<span class="mes-type-btn-text">已读消息</span>
</div>
</Button>
</el-button>
</div>
<div>
<Button @click="setCurrentMesType('recycleBin')" size="large" long type="text">
<el-button size="large" text style="width: 100%" @click="setCurrentMesType('recycleBin')">
<div class="mes-wrap">
<transition name="mes-current-type-btn">
<Icon v-show="currentMessageType == 'recycleBin'" type="md-checkmark"></Icon>
<el-icon v-show="currentMessageType == 'recycleBin'"><Check /></el-icon>
</transition>
<span class="mes-type-btn-text">回收站</span>
</div>
</Button>
</el-button>
</div>
</div>
<div class="message-content-con">
<transition name="view-message">
<div v-if="showMesTitleList" class="message-title-list-con">
<Table
class="mt_10"
<el-table
ref="messageList"
:loading="loading"
:columns="mesTitleColumns"
v-loading="loading"
class="mt_10"
:data="currentMesList"
:no-data-text="noDataText"
></Table>
<Page
:current="params.pageNumber"
:total="total"
:page-size="params.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[5,10]"
size="small"
show-total
show-elevator
show-sizer
class="page-fix"
></Page>
:empty-text="noDataText"
style="width: 100%"
>
<el-table-column label=" " min-width="300">
<template #default="{ row }">
<a class="link-text mes-title-link" @click="openMessage(row)">{{ row.title }}</a>
</template>
</el-table-column>
<el-table-column label=" " width="190" align="center">
<template #default="{ row }">
<el-icon style="margin-right: 5px; vertical-align: middle"><Clock /></el-icon>
<span>{{ row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label=" " width="210" align="center">
<template #default="{ row }">
<template v-if="currentMessageType == 'unread'">
<a class="link-text" @click="markAsRead(row)">标为已读</a>
</template>
<template v-else-if="currentMessageType == 'read'">
<a class="link-text" @click="deleteMes(row)">删除</a>
</template>
<template v-else>
<a class="link-text" @click="restoreMes(row)">还原</a>
<span class="op-split">|</span>
<a class="link-text" @click="deleteReal(row)">彻底删除</a>
</template>
</template>
</el-table-column>
</el-table>
<div class="page-fix mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:page-sizes="[5, 10]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</div>
</transition>
<transition name="back-message-list">
<div v-if="!showMesTitleList" class="message-view-content-con">
<div class="message-content-top-bar">
<span class="mes-back-btn-con">
<Button type="text" @click="backMesTitleList">
<Icon type="ios-arrow-back"></Icon>&nbsp;&nbsp;返回
</Button>
<el-button type="primary" link @click="backMesTitleList">
<el-icon><ArrowLeft /></el-icon>&nbsp;&nbsp;返回
</el-button>
</span>
<h3 class="mes-title">{{ mes.title }}</h3>
</div>
<p class="mes-time-con">
<Icon type="android-time"></Icon>
<el-icon><Clock /></el-icon>
&nbsp;&nbsp;{{ mes.time }}
</p>
<div class="message-content-body">
@@ -88,308 +109,145 @@
</template>
<script>
import * as API_Index from "@/api/index";
import { Check, Clock, ArrowLeft } from "@element-plus/icons-vue";
import * as API_Index from "@/api/index";
export default {
name: "message_index",
data() {
const markAsReadBtn = (h, params) => {
return h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
let v = params.row;
this.loading = true;
API_Index.read(v.id).then(res => {
this.loading = false;
if (res.success) {
this.getAll();
}
});
},
},
},
"标为已读"
);
};
const deleteMesBtn = (h, params) => {
return h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
let v = params.row;
this.loading = true;
API_Index.deleteMessage(v.id).then(res => {
this.loading = false;
if (res.success) {
this.refreshMessage();
}
});
},
},
},
"删除"
);
};
const restoreBtn = (h, params) => {
return h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
let v = params.row;
API_Index.reductionMessage(v.id).then(res => {
this.loading = false;
if (res.success) {
this.refreshMessage();
}
});
},
},
},
"还原"
);
};
const deleteRealBtn = (h, params) => {
return h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
let v = params.row;
this.loading = true;
API_Index.clearMessage(v.id).then(res => {
this.loading = false;
if (res.success) {
this.refreshMessage();
}
});
},
},
},
"彻底删除"
);
};
return {
loading: true, // 列表加载的loading
params: { // 请求消息列表参数
status: "UN_READY",
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc" // 默认排序方式
},
total: 0, // 消息列表总数
totalUnread: 0, // 未读总数
totalRead: 0, // 已读总数
totalRemove: 0, // 回收站消息数
currentMesList: [], // 当前状态消息
unreadMesList: [], // 未读消息
hasReadMesList: [], // 已读消息
recyclebinList: [], // 回收站消息
currentMessageType: "unread", // 当前列表消息状态
showMesTitleList: true, // 是否展示消息状态列表
unReadCount: 0, // 未读消息数量
hasReadCount: 0, // 已读消息数量
recycleBinCount: 0, // 回收站消息数量
noDataText: "暂无未读消息",
mes: { // 展示消息详情
title: "",
time: "",
content: ""
},
mesTitleColumns: [ // 表格表头
{
title: " ",
key: "title",
align: "left",
ellipsis: true,
render: (h, params) => {
return h("span", [
h(
"a",
{
style: {
margin: "0 30px 0 0"
},
on: {
click: () => {
this.showMesTitleList = false;
this.mes.title = params.row.title;
this.mes.time = params.row.createTime;
this.getContent(params.row);
}
}
},
params.row.title
)
]);
}
},
{
title: " ",
key: "time",
align: "center",
width: 190,
render: (h, params) => {
return h("span", [
h("Icon", {
props: {
type: "md-time",
size: 16
},
style: {
margin: "0 5px 3px 0"
}
}),
h("span", params.row.createTime)
]);
}
},
{
title: " ",
key: "asread",
align: "center",
width: 210,
render: (h, params) => {
if (this.currentMessageType == "unread") {
return h("div", [markAsReadBtn(h, params)]);
} else if (this.currentMessageType == "read") {
return h("div", [deleteMesBtn(h, params)]);
} else {
return h("div", [
restoreBtn(h, params),
h("span", { style: { margin: "0 8px", color: "#dcdee2" } }, "|"),
deleteRealBtn(h, params)
]);
}
}
}
]
};
export default {
name: "message_index",
components: { Check, Clock, ArrowLeft },
data() {
return {
loading: true,
params: {
status: "UN_READY",
pageNumber: 1,
pageSize: 10,
sort: "createTime",
order: "desc",
},
total: 0,
currentMesList: [],
currentMessageType: "unread",
showMesTitleList: true,
unReadCount: 0,
noDataText: "暂无未读消息",
mes: {
title: "",
time: "",
content: "",
},
};
},
methods: {
changePage() {
this.refreshMessage();
},
methods: {
// 改变页数
changePage(v) {
this.params.pageNumber = v;
this.refreshMessage();
},
// 改变页码
changePageSize(v) {
this.params.pageSize = v;
this.refreshMessage();
},
// 刷新消息
refreshMessage() {
let status = "UN_READY";
let type = this.currentMessageType;
if (type == "unread") {
status = "UN_READY";
} else if (type == "read") {
status = "ALREADY_READY";
} else {
status = "ALREADY_REMOVE";
changePageSize() {
this.refreshMessage();
},
refreshMessage() {
let status = "UN_READY";
const type = this.currentMessageType;
if (type == "unread") status = "UN_READY";
else if (type == "read") status = "ALREADY_READY";
else status = "ALREADY_REMOVE";
this.params.status = status;
this.loading = true;
API_Index.getMessageSendData(this.params).then((res) => {
this.loading = false;
if (res.success) {
this.currentMesList = res.result.records;
this.total = res.result.total;
}
this.params.status = status;
this.loading = true;
API_Index.getMessageSendData(this.params).then(res => {
this.loading = false;
if (res.success) {
this.currentMesList = res.result.records;
this.total = res.result.total;
}
});
},
//获取全部数据
getAll() {
API_Index.getAllMessage(this.params).then(res => {
this.loading = false;
if (res.success) {
//未读消息
this.unReadCount = res.result.UN_READY.total;
this.currentMesList = res.result.UN_READY.records;
//已读消息
this.hasReadCount = res.result.ALREADY_READY.total;
//回收站
this.recycleBinCount = res.result.ALREADY_REMOVE.total;
}
});
},
// 删除消息
deleteMessage(id) {
API_Index.deleteMessage(id).then(res => {
if (res.success) {
this.$Message.success("删除成功");
}
});
},
backMesTitleList() {
});
},
getAll() {
API_Index.getAllMessage(this.params).then((res) => {
this.loading = false;
if (res.success) {
this.unReadCount = res.result.UN_READY.total;
this.currentMesList = res.result.UN_READY.records;
}
});
},
backMesTitleList() {
this.showMesTitleList = true;
},
setCurrentMesType(type) {
if (this.currentMessageType !== type) {
this.showMesTitleList = true;
},
// 设置当前消息分类
setCurrentMesType(type) {
if (this.currentMessageType !== type) {
this.showMesTitleList = true;
}
this.currentMessageType = type;
if (type == "unread") {
this.noDataText = "暂无未读消息";
} else if (type == "read") {
this.noDataText = "暂无已读消息";
} else {
this.noDataText = "回收站无消息";
}
this.params.pageNumber = 1;
this.refreshMessage();
},
getContent(v) {
this.mes.content = v.content;
API_Index.read(v.id).then(res => {
this.loading = false;
if (res.success) {
this.getAll()();
}
});
}
this.currentMessageType = type;
if (type == "unread") this.noDataText = "暂无未读消息";
else if (type == "read") this.noDataText = "暂无已读消息";
else this.noDataText = "回收站无消息";
this.params.pageNumber = 1;
this.refreshMessage();
},
mounted() {
this.getAll();
openMessage(row) {
this.showMesTitleList = false;
this.mes.title = row.title;
this.mes.time = row.createTime;
this.getContent(row);
},
watch: {
// 监听路由变化通过id获取数据
$route(to, from) {
if (to.name == "message_index") {
this.getAll();
}
}
}
};
markAsRead(v) {
this.loading = true;
API_Index.read(v.id).then((res) => {
this.loading = false;
if (res.success) this.getAll();
});
},
deleteMes(v) {
this.loading = true;
API_Index.deleteMessage(v.id).then((res) => {
this.loading = false;
if (res.success) this.refreshMessage();
});
},
restoreMes(v) {
API_Index.reductionMessage(v.id).then((res) => {
this.loading = false;
if (res.success) this.refreshMessage();
});
},
deleteReal(v) {
this.loading = true;
API_Index.clearMessage(v.id).then((res) => {
this.loading = false;
if (res.success) this.refreshMessage();
});
},
getContent(v) {
this.mes.content = v.content;
API_Index.read(v.id).then((res) => {
this.loading = false;
if (res.success) this.getAll();
});
},
},
mounted() {
this.getAll();
},
watch: {
$route(to) {
if (to.name == "message_index") this.getAll();
},
},
};
</script>
<style lang="scss" scoped>
@import "./message.scss";
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.op-split {
margin: 0 8px;
color: #dcdee2;
}
.mes-title-link {
margin-right: 30px;
}
</style>

View File

@@ -1,12 +1,17 @@
<template>
<div style="display: inline-block;">
<Icon type="ios-loading" size="18" color="#2d8cf0" class="spin-icon-load"></Icon>
<div style="display: inline-block">
<el-icon class="spin-icon-load" :size="18" color="#ff5c58">
<Loading />
</el-icon>
</div>
</template>
<script>
import { Loading } from "@element-plus/icons-vue";
export default {
name: "circleLoading"
name: "circleLoading",
components: { Loading },
};
</script>
@@ -14,5 +19,12 @@ export default {
.spin-icon-load {
animation: ani-demo-spin 1s linear infinite;
}
@keyframes ani-demo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,72 +1,68 @@
<template>
<div>
<Cascader
<el-cascader
v-model="selectDep"
:data="department"
@on-change="handleChangeDep"
change-on-select
:options="department"
:props="cascaderProps"
filterable
clearable
placeholder="请选择"
></Cascader>
style="width: 100%"
@change="handleChangeDep"
/>
</div>
</template>
<script>
import { initDepartment } from "@/api/index";
export default {
name: "departmentChoose",
props: {
},
data() {
return {
selectDep: [], // 已选数据
department: [] // 列表
selectDep: [],
department: [],
cascaderProps: {
value: "value",
label: "label",
children: "children",
checkStrictly: true,
emitPath: true,
},
};
},
methods: {
// 获取部门数据
initDepartmentData() {
initDepartment().then(res => {
initDepartment().then((res) => {
if (res.success) {
const arr = res.result;
this.filterData(arr)
this.department = arr
this.filterData(arr);
this.department = arr;
}
});
},
handleChangeDep(value, selectedData) {
handleChangeDep(value) {
let departmentId = "";
// 获取最后一个值
if (value && value.length > 0) {
departmentId = value[value.length - 1];
}
this.$emit("on-change", departmentId);
},
// 清空已选列表
clearSelect() {
this.selectDep = [];
},
// 处理部门数据
filterData (data) {
data.forEach(e => {
filterData(data) {
data.forEach((e) => {
e.value = e.id;
e.label = e.title;
if (e.children) {
this.filterData(e.children)
} else {
return
this.filterData(e.children);
}
})
}
});
},
},
created() {
this.initDepartmentData();
}
},
};
</script>
<style lang="scss">
</style>

View File

@@ -1,112 +1,123 @@
<template>
<div>
<div style="display:flex;">
<Input
<div style="display: flex">
<el-input
v-model="departmentTitle"
readonly
style="margin-right:10px;"
style="margin-right: 10px; flex: 1"
:placeholder="placeholder"
:clearable="clearable"
@on-clear="clearSelect"
@clear="clearSelect"
/>
<Poptip transfer trigger="click" placement="right" title="选择部门" width="250">
<Button icon="md-list">选择部门</Button>
<div slot="content">
<Input
v-model="searchKey"
suffix="ios-search"
@on-change="searchDep"
placeholder="输入部门名搜索"
clearable
<el-popover trigger="click" placement="right" title="选择部门" :width="280">
<template #reference>
<el-button>选择部门</el-button>
</template>
<el-input
v-model="searchKey"
placeholder="输入部门名搜索"
clearable
style="margin-bottom: 8px"
@input="searchDep"
/>
<div v-loading="depLoading" class="dep-tree-bar">
<el-tree
:data="dataDep"
:props="treeProps"
node-key="id"
highlight-current
default-expand-all
@node-click="selectTree"
/>
<div class="dep-tree-bar">
<Tree
:data="dataDep"
@on-select-change="selectTree"
></Tree>
<Spin size="large" fix v-if="depLoading"></Spin>
</div>
</div>
</Poptip>
</el-popover>
</div>
</div>
</template>
<script>
import {initDepartment, searchDepartment} from "@/api/index";
import { initDepartment, searchDepartment } from "@/api/index";
export default {
name: "departmentTreeChoose",
props: {
multiple: {
type: Boolean,
default: false
default: false,
},
clearable: {
type: Boolean,
default: true
default: true,
},
placeholder: {
type: String,
default: "点击选择部门"
}
default: "点击选择部门",
},
},
data() {
return {
depLoading: false, // 加载状态
departmentTitle: "", // modal标题
searchKey: "", // 搜索关键词
dataDep: [], // 部门列表
selectDep: [], // 已选部门
departmentId: [] // 部门id
depLoading: false,
departmentTitle: "",
searchKey: "",
dataDep: [],
cloneDep: [],
departmentId: [],
treeProps: {
label: "title",
children: "children",
},
};
},
methods: {
// 获取部门数据
initDepartmentData() {
initDepartment().then(res => {
if (res.success) {
this.dataDep = res.result;
}
});
this.depLoading = true;
initDepartment()
.then((res) => {
if (res.success) {
this.dataDep = res.result;
this.cloneDep = JSON.parse(JSON.stringify(this.dataDep));
}
})
.finally(() => {
this.depLoading = false;
});
},
searchDep() {
// 搜索部门
if (this.searchKey) {
this.depLoading = true;
searchDepartment({title: this.searchKey}).then(res => {
this.depLoading = false;
if (res.success) {
res.result.forEach(function (e) {
if (e.status == -1) {
e.title = "[已禁用] " + e.title;
e.disabled = true;
}
});
this.dataDep = res.result;
}
});
searchDepartment({ title: this.searchKey })
.then((res) => {
if (res.success) {
res.result.forEach((e) => {
if (e.status == -1) {
e.title = "[已禁用] " + e.title;
e.disabled = true;
}
});
this.dataDep = res.result;
}
})
.finally(() => {
this.depLoading = false;
});
} else {
this.initDepartmentData();
this.dataDep = JSON.parse(JSON.stringify(this.cloneDep));
}
},
// 选择回调
selectTree(v) {
if (v.length === 0) {
selectTree(node) {
if (!node) {
this.$emit("on-change", null);
this.departmentId = "";
this.departmentTitle = "";
return
return;
}
this.departmentId = v[0].id;
this.departmentTitle = v[0].title;
let department = {
this.departmentId = node.id;
this.departmentTitle = node.title;
this.$emit("on-change", {
departmentId: this.departmentId,
departmentTitle: this.departmentTitle
}
this.$emit("on-change", department);
departmentTitle: this.departmentTitle,
});
},
// 清除选中方法
clearSelect() {
this.departmentId = [];
this.departmentTitle = "";
@@ -118,7 +129,6 @@ export default {
}
this.$emit("on-clear");
},
// 设置数据 回显用
setData(ids, title) {
this.departmentTitle = title;
if (this.multiple) {
@@ -127,11 +137,11 @@ export default {
this.departmentId = [];
this.departmentId.push(ids);
}
}
},
},
created() {
this.initDepartmentData();
}
},
};
</script>
@@ -151,9 +161,6 @@ export default {
.dep-tree-bar::-webkit-scrollbar-thumb {
border-radius: 4px;
-webkit-box-shadow: inset 0 0 2px #d1d1d1;
background: #e4e4e4;
}
</style>

View File

@@ -1,30 +1,31 @@
<template>
<div class="set-password">
<Poptip transfer trigger="focus" placement="right" width="250">
<Input
type="password"
password
style="width:350px;"
:maxlength="maxlength"
v-model="currentValue"
@on-change="handleChange"
:size="size"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
/>
<div :class="tipStyle" slot="content">
<div class="words">强度 : {{strength}}</div>
<Progress
:percent="strengthValue"
:status="progressStatus"
hide-info
style="margin: 13px 0;"
<el-popover trigger="focus" placement="right" :width="250">
<template #reference>
<el-input
v-model="currentValue"
type="password"
show-password
style="width: 350px"
:maxlength="maxlength"
:size="size"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
@input="handleChange"
/>
<br />请至少输入 6 个字符请不要使
<br />用容易被猜到的密码
</template>
<div :class="tipStyle">
<div class="words">强度 : {{ strength }}</div>
<el-progress
:percentage="strengthValue"
:status="progressStatus"
:show-text="false"
style="margin: 13px 0"
/>
<br />请至少输入 6 个字符请不要使用容易被猜到的密码
</div>
</Poptip>
</el-popover>
</div>
</template>
@@ -32,58 +33,56 @@
export default {
name: "setPassword",
props: {
modelValue: String,
value: String,
size: String,
placeholder: {
type: String,
default: "请输入密码长度为6-20个字符"
default: "请输入密码长度为6-20个字符",
},
disabled: {
type: Boolean,
default: false
default: false,
},
readonly: {
type: Boolean,
default: false
default: false,
},
maxlength: {
type: Number,
default: 20
}
default: 20,
},
},
emits: ["update:modelValue", "input", "on-change"],
data() {
return {
currentValue: this.value, // 当前密码
tipStyle: "password-tip-none", // 提示样式
strengthValue: 0, // 密码强度
progressStatus: "normal", // 进度条状态
strength: "无", // 密码长度
grade: 0 // 强度等级
currentValue: this.modelValue ?? this.value ?? "",
tipStyle: "password-tip-none",
strengthValue: 0,
progressStatus: "",
strength: "无",
grade: 0,
};
},
watch: {
modelValue(val) {
this.setCurrentValue(val);
},
value(val) {
this.setCurrentValue(val);
},
},
methods: {
checkStrengthValue(v) {
// 评级制判断密码强度 最高5
let grade = 0;
if (/\d/.test(v)) {
grade++; //数字
}
if (/[a-z]/.test(v)) {
grade++; //小写
}
if (/[A-Z]/.test(v)) {
grade++; //大写
}
if (/\W/.test(v)) {
grade++; //特殊字符
}
if (v.length >= 10) {
grade++;
}
if (/\d/.test(v)) grade++;
if (/[a-z]/.test(v)) grade++;
if (/[A-Z]/.test(v)) grade++;
if (/\W/.test(v)) grade++;
if (v.length >= 10) grade++;
this.grade = grade;
return grade;
},
// 强度验证方法
strengthChange() {
if (!this.currentValue) {
this.tipStyle = "password-tip-none";
@@ -91,14 +90,14 @@ export default {
this.strengthValue = 0;
return;
}
let grade = this.checkStrengthValue(this.currentValue);
const grade = this.checkStrengthValue(this.currentValue);
if (grade <= 1) {
this.progressStatus = "wrong";
this.progressStatus = "exception";
this.tipStyle = "password-tip-weak";
this.strength = "弱";
this.strengthValue = 33;
} else if (grade >= 2 && grade <= 4) {
this.progressStatus = "normal";
this.progressStatus = "";
this.tipStyle = "password-tip-middle";
this.strength = "中";
this.strengthValue = 66;
@@ -109,61 +108,33 @@ export default {
this.strengthValue = 100;
}
},
// 输入框change事件
handleChange(v) {
handleChange() {
this.strengthChange();
this.$emit("update:modelValue", this.currentValue);
this.$emit("input", this.currentValue);
this.$emit("on-change", this.currentValue, this.grade, this.strength);
},
// 回显当前密码
setCurrentValue(value) {
if (value === this.currentValue) {
return;
}
this.currentValue = value;
if (value === this.currentValue) return;
this.currentValue = value ?? "";
this.strengthChange();
this.$emit("on-change", this.currentValue, this.grade, this.strength);
}
},
},
watch: {
value(val) {
this.setCurrentValue(val);
}
}
};
</script>
<style lang="scss" scoped>
.set-password .ivu-poptip,
.set-password .ivu-poptip-rel {
display: block;
}
.password-tip-none {
padding: 1vh 0;
}
.password-tip-weak {
padding: 1vh 0;
.words {
color: #ed3f14;
}
.password-tip-weak .words {
color: #ed3f14;
}
.password-tip-middle {
padding: 1vh 0;
.words {
color: #2d8cf0;
}
.password-tip-middle .words {
color: #2d8cf0;
}
.password-tip-strong {
padding: 1vh 0;
.words {
color: #52c41a;
}
.password-tip-strong .words {
color: #52c41a;
}
</style>

View File

@@ -1,173 +1,190 @@
<template>
<div>
<div style="display:flex;">
<Input
<div style="display: flex; gap: 10px; align-items: center; width: 100%">
<el-input
v-if="showInput"
v-model="currentValue"
@on-change="handleChange"
v-show="showInput"
:placeholder="placeholder"
:size="size"
:disabled="disabled"
:readonly="readonly"
:maxlength="maxlength"
style="flex: 1"
@input="handleChange"
>
<Poptip slot="append" transfer trigger="hover" title="图片预览" placement="right">
<Icon type="md-eye" class="see-icon" />
<div slot="content">
<img :src="currentValue" alt="该资源不存在" style="width: 100%;margin: 0 auto;display: block;" />
<a @click="viewImage=true" style="margin-top:5px;text-align:right;display:block">查看大图</a>
</div>
</Poptip>
</Input>
<Upload
:action="uploadFileUrl"
:headers="accessToken"
:on-success="handleSuccess"
:on-error="handleError"
:format="['jpg','jpeg','png','gif','bmp']"
accept=".jpg, .jpeg, .png, .gif, .bmp"
:max-size="1024"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
:before-upload="beforeUpload"
:show-upload-list="false"
ref="up"
class="upload"
>
<Button :loading="loading" :size="size" :disabled="disabled">上传图片</Button>
</Upload>
<template #append>
<el-popover trigger="hover" placement="right" :width="320" title="图片预览">
<template #reference>
<el-button class="see-icon">
<el-icon><View /></el-icon>
</el-button>
</template>
<img
v-if="currentValue"
:src="currentValue"
alt="该资源不存在"
style="max-width: 280px; display: block; margin: 0 auto"
/>
<el-button
v-if="currentValue"
type="primary"
link
style="margin-top: 8px; display: block; text-align: right"
@click="viewImage = true"
>
查看大图
</el-button>
</el-popover>
</template>
</el-input>
<el-button @click="handleCLickImg('storeLogo')">选择图片</el-button>
</div>
<Modal title="图片预览" v-model="viewImage" :styles="{top: '30px'}" draggable>
<img :src="currentValue" alt="该资源不存在" style="width: 100%;margin: 0 auto;display: block;" />
<div slot="footer">
<Button @click="viewImage=false">关闭</Button>
</div>
</Modal>
<el-dialog v-model="viewImage" title="图片预览" width="480px" append-to-body :z-index="3500">
<img
:src="currentValue"
alt="该资源不存在"
style="max-width: 100%; margin: 0 auto; display: block"
/>
<template #footer>
<el-button @click="viewImage = false">关闭</el-button>
</template>
</el-dialog>
<el-dialog v-model="picModalFlag" width="1200px" append-to-body :z-index="3500" destroy-on-close>
<ossManage
ref="ossManage"
:is-component="true"
:initialize="picModalFlag"
:max-select="1"
@callback="callbackSelected"
@selected="handleOssSelected"
/>
<template #footer>
<el-button @click="picModalFlag = false">取消</el-button>
<el-button type="primary" @click="confirmSelectedImage">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { View } from "@element-plus/icons-vue";
import { uploadFile } from "@/libs/axios";
import ossManage from "@/views/shop/ossManages";
export default {
name: "uploadPicInput",
props: {
value: String,
size: String,
placeholder: { // input提示信息
type: String,
default: "图片链接"
},
showInput: { // 显示图片链接
type: Boolean,
default: true
},
disabled: { // 是否不可选中
type: Boolean,
default: false
},
readonly: { // 是否只读
type: Boolean,
default: false
},
maxlength: Number, // 最大长度
icon: { // 上传按钮图标
type: String,
default: "ios-cloud-upload-outline"
}
components: {
ossManage,
View,
},
props: {
modelValue: String,
value: String,
size: {
default: "default",
type: String,
},
placeholder: {
type: String,
default: "图片链接",
},
showInput: {
type: Boolean,
default: true,
},
disabled: {
type: Boolean,
default: false,
},
readonly: {
type: Boolean,
default: false,
},
maxlength: Number,
},
emits: ["update:modelValue", "input", "on-change"],
data() {
return {
accessToken: {}, // 验证token
currentValue: this.value, // 当前值
loading: false, // 加载状态
viewImage: false, // 是否预览图片
uploadFileUrl: uploadFile // 上传路径
accessToken: {},
currentValue: this.modelValue ?? this.value ?? "",
viewImage: false,
uploadFileUrl: uploadFile,
picModalFlag: false,
selectedFormBtnName: "",
picIndex: "",
selectedImage: [],
};
},
methods: {
// 初始化
init() {
this.accessToken = {
accessToken: this.getStore("accessToken")
};
},
// 格式校验
handleFormatError(file) {
this.loading = false;
this.$Notice.warning({
title: "不支持的文件格式",
desc:
"所选文件‘ " +
file.name +
" ’格式不正确, 请选择 .jpg .jpeg .png .gif .bmp格式文件"
handleCLickImg(val, index) {
this.picModalFlag = true;
this.selectedFormBtnName = val;
this.picIndex = index;
this.selectedImage = [];
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
// 大小校验
handleMaxSize(file) {
this.loading = false;
this.$Notice.warning({
title: "文件大小过大",
desc: "所选文件大小过大, 不得超过1M."
});
},
// 上传前
beforeUpload() {
this.loading = true;
return true;
},
// 上传成功
handleSuccess(res, file) {
this.loading = false;
if (res.success) {
this.currentValue = res.result;
this.$emit("input", this.currentValue);
this.$emit("on-change", this.currentValue);
} else {
this.$Message.error(res.message);
callbackSelected(val) {
if (val?.url) {
this.selectedImage = [val.url];
}
},
// 上传失败
handleError(error, file, fileList) {
this.loading = false;
this.$Message.error(error.toString());
handleOssSelected(list) {
this.selectedImage = (list || []).map((item) => item.split(",")[1]).filter(Boolean);
},
// 上传成功回显
handleChange(v) {
this.$emit("input", this.currentValue);
this.$emit("on-change", this.currentValue);
this.$attrs.rollback && this.$attrs.rollback()
confirmSelectedImage() {
if (!this.selectedImage.length) {
this.$Message.warning("请选择图片");
return;
}
this.currentValue = this.selectedImage[this.selectedImage.length - 1];
this.picModalFlag = false;
this.picIndex = "";
this.emitValue(this.currentValue);
},
init() {
this.accessToken = {
accessToken: this.getStore("accessToken"),
};
},
emitValue(val) {
this.$emit("update:modelValue", val);
this.$emit("input", val);
this.$emit("on-change", val);
},
handleChange() {
this.emitValue(this.currentValue);
this.$attrs.rollback && this.$attrs.rollback();
},
// 初始值
setCurrentValue(value) {
if (value === this.currentValue) {
return;
}
this.currentValue = value;
this.$emit("on-change", this.currentValue);
}
this.currentValue = value ?? "";
this.emitValue(this.currentValue);
},
},
watch: {
modelValue(val) {
this.setCurrentValue(val);
},
value(val) {
this.setCurrentValue(val);
}
},
},
created() {
this.init();
}
},
};
</script>
<style lang="scss" scoped>
.see-icon {
font-size: 16px;
cursor: pointer;
}
.upload {
display: inline-block;
margin-left: 10px;
padding: 8px;
}
</style>

View File

@@ -1,146 +1,201 @@
<template>
<div>
<div class="upload-pic-thumb">
<vuedraggable
:list="uploadList"
:disabled="!draggable||!multiple"
:animation="200"
class="list-group"
ghost-class="thumb-ghost"
@end="onEnd"
>
<div class="upload-list" v-for="(item, index) in uploadList" :key="index">
<div v-if="item.status == 'finished'">
<img :src="item.url" />
<div v-if="disable || isView" class="list-group">
<div v-for="item in uploadList" :key="item.url" class="upload-list">
<div v-if="item.status == 'finished'" style="height: 60px">
<img :src="item.url" alt="" />
<div class="upload-list-cover">
<Icon type="ios-eye-outline" @click="handleView(item.url)"></Icon>
<Icon v-if="remove" type="ios-trash-outline" @click="handleRemove(item)"></Icon>
<el-icon class="action-icon" @click="handleView(item.url)"><View /></el-icon>
<el-icon v-if="remove" class="action-icon" @click="handleRemove(item)"><Delete /></el-icon>
</div>
</div>
<div v-else>
<Progress v-if="item.showProgress" :percent="item.percentage" hide-info></Progress>
<el-progress
v-if="item.showProgress"
:percentage="item.percentage"
:show-text="false"
/>
</div>
</div>
</div>
<vuedraggable
v-else
v-model="uploadList"
:disabled="disable || !draggable || !multiple"
:animation="200"
class="list-group"
ghost-class="thumb-ghost"
item-key="url"
@end="onEnd"
>
<template #item="{ element: item }">
<div class="upload-list">
<div v-if="item.status == 'finished'" style="height: 60px">
<img :src="item.url" alt="" />
<div class="upload-list-cover">
<el-icon class="action-icon" @click="handleView(item.url)"><View /></el-icon>
<el-icon v-if="remove" class="action-icon" @click="handleRemove(item)"><Delete /></el-icon>
</div>
</div>
<div v-else>
<el-progress
v-if="item.showProgress"
:percentage="item.percentage"
:show-text="false"
/>
</div>
</div>
</template>
</vuedraggable>
<div style="display: inline-block; width: 60px; height: 60px;border: 1px dashed #dcdee2;border-radius: 4px;line-height: 60px;text-align: center;"
@click="handleCLickImg('uploadList')">
<Icon size="20" type="md-camera"></Icon>
<div
v-if="!isView"
class="upload-trigger-box"
@click="handleCLickImg('uploadList')"
>
<el-icon :size="20"><Camera /></el-icon>
</div>
<!--<Upload-->
<!--:disabled="disable"-->
<!--ref="upload"-->
<!--:multiple="multiple"-->
<!--:show-upload-list="false"-->
<!--:on-success="handleSuccess"-->
<!--:on-error="handleError"-->
<!--:format="['jpg','jpeg','png','gif']"-->
<!--:max-size="1024"-->
<!--:on-format-error="handleFormatError"-->
<!--:on-exceeded-size="handleMaxSize"-->
<!--:before-upload="handleBeforeUpload"-->
<!--type="drag"-->
<!--:action="uploadFileUrl"-->
<!--:headers="accessToken"-->
<!--style="display: inline-block;width:58px;"-->
<!--v-if="!isView"-->
<!--&gt;-->
<!--<div style="width: 58px;height:58px;line-height: 58px;">-->
<!--<Icon type="md-camera" size="20"></Icon>-->
<!--</div>-->
<!--</Upload>-->
</div>
<Modal title="图片预览" v-model="viewImage" :styles="{top: '30px'}" draggable>
<img :src="imgUrl" alt="无效的图片链接" style="width: 100%;margin: 0 auto;display: block;" />
<div slot="footer">
<Button @click="viewImage=false">关闭</Button>
</div>
</Modal>
<Modal width="1200px" v-model="picModelFlag" @on-ok="confirmUrls">
<ossManage @callback="callbackSelected" :isComponent="true" :initialize="picModelFlag" @selected="(list)=>{ selectedImage = list}" ref="ossManage" />
</Modal>
<el-dialog v-model="viewImage" title="图片预览" width="520px" append-to-body>
<img :src="imgUrl" alt="无效的图片链接" style="width: 100%; display: block; margin: 0 auto" />
<template #footer>
<el-button @click="viewImage = false">关闭</el-button>
</template>
</el-dialog>
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage
ref="ossManage"
:is-component="true"
:initialize="picModelFlag"
@callback="callbackSelected"
@selected="(list) => { selectedImage = list }"
/>
<template #footer>
<el-button @click="picModelFlag = false">取消</el-button>
<el-button type="primary" @click="confirmUrls">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { Camera, Delete, View } from "@element-plus/icons-vue";
import { uploadFile } from "@/libs/axios";
import vuedraggable from "vuedraggable";
import ossManage from "@/views/shop/ossManages";
export default {
name: "uploadPicThumb",
components: {
vuedraggable,
ossManage
ossManage,
Camera,
Delete,
View,
},
props: {
value: { // 默认值
type:null
},
draggable: { // 是否可拖拽改变位置
modelValue: { type: null },
value: { type: null },
draggable: {
type: Boolean,
default: true
default: true,
},
multiple: { // 多选
multiple: {
type: Boolean,
default: true
default: true,
},
disable:{ // 禁止上传
disable: {
type: Boolean,
default: false
default: false,
},
remove:{ // 移除图片
remove: {
type: Boolean,
default: true
default: true,
},
limit: { // 上传总数限制
limit: {
type: Number,
default: 10
default: 10,
},
isView: { // 显示上传按钮
isView: {
type: Boolean,
default: false
}
default: false,
},
},
emits: ["update:modelValue", "input", "on-change", "uploadchange"],
data() {
return {
accessToken: {}, // 验证token
uploadFileUrl: uploadFile, // 上传文件
uploadList: [], // 上传文件列表
viewImage: false, // 是否预览图片
imgUrl: "", // 图片地址
picModelFlag: false, // 图片选择器
selectedFormBtnName: "", // 点击图片绑定form
accessToken: {},
uploadFileUrl: uploadFile,
uploadList: [],
viewImage: false,
imgUrl: "",
picModelFlag: false,
selectedFormBtnName: "",
selectedImage: [],
};
},
computed: {
bindValue() {
return this.modelValue !== undefined ? this.modelValue : this.value;
},
},
methods: {
// 选择图片modal
handleCLickImg(val, index) {
this.$refs.ossManage.selectImage = true;
handleCLickImg(val) {
this.picModelFlag = true;
this.selectedFormBtnName = val;
this.selectedImage = [];
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
parseOssSelectionUrl(item) {
if (!item) {
return "";
}
if (typeof item === "string") {
const index = item.indexOf(",");
return index >= 0 ? item.slice(index + 1) : item;
}
return item.url || "";
},
// 图片选择后回调
callbackSelected(val) {
if (this.multiple) {
return;
}
if (!val?.url) {
return;
}
this.picModelFlag = false;
if (!this.multiple && this.uploadList && this.uploadList.length > 0) {
// 删除第一张
if (this.uploadList.length > 0) {
this.uploadList.splice(0, 1);
}
this.uploadList.push(val);
// 返回组件值
this.uploadList.push({ ...val, status: "finished" });
this.returnValue();
},
confirmUrls(){
confirmUrls() {
if (this.selectedImage.length) {
this.selectedImage.forEach((element) => {
const url = this.parseOssSelectionUrl(element);
if (url) {
this.uploadList.push({ url, status: "finished" });
}
});
}
this.selectedImage = [];
this.picModelFlag = false;
this.returnValue();
},
onEnd() {
this.returnValue();
},
init() {
this.setData(this.value, true);
this.setData(this.bindValue, true);
this.accessToken = {
accessToken: this.getStore("accessToken")
accessToken: this.getStore("accessToken"),
};
},
handleView(imgUrl) {
@@ -148,79 +203,28 @@ export default {
this.viewImage = true;
},
handleRemove(file) {
const uploadList = this.uploadList;
this.uploadList.splice(uploadList.indexOf(file), 1);
this.uploadList = this.uploadList.filter((i) => i.url !== file.url);
this.returnValue();
},
handleSuccess(res, file) {
if (res.success) {
file.url = res.result;
// 单张图片处理
if (!this.multiple && this.uploadList.length > 0) {
// 删除第一张
this.uploadList.splice(0, 1);
}
this.uploadList.push(file);
// 返回组件值
this.returnValue();
} else {
this.$Message.error(res.message);
}
},
handleError(error, file, fileList) {
this.$Message.error(error.toString());
},
handleFormatError(file) {
this.$Notice.warning({
title: "不支持的文件格式",
desc:
"所选文件‘ " +
file.name +
" ’格式不正确, 请选择 .jpg .jpeg .png .gif图片格式文件"
});
},
handleMaxSize(file) {
this.$Notice.warning({
title: "文件大小过大",
desc:
"所选文件大小过大, 不得超过1M."
});
},
handleBeforeUpload() {
if (this.multiple && this.uploadList.length >= this.limit) {
this.$Message.warning("最多只能上传" + this.limit + "张图片");
return false;
}
return true;
emitValue(val) {
this.$emit("update:modelValue", val);
this.$emit("input", val);
this.$emit("on-change", val);
},
returnValue() {
if (!this.uploadList || this.uploadList.length < 1) {
if (!this.multiple) {
this.$emit("input", "");
this.$emit("on-change", "");
} else {
this.$emit("input", []);
this.$emit("on-change", []);
}
const empty = this.multiple ? [] : "";
this.emitValue(empty);
return;
}
if (!this.multiple) {
// 单张
let v = this.uploadList[0].url;
this.$emit("input", v);
this.$emit("on-change", v);
this.emitValue(this.uploadList[0].url);
} else {
let v = [];
this.uploadList.forEach(e => {
v.push(e.url);
});
this.$emit("input", v);
this.$emit("on-change", v);
this.emitValue(this.uploadList.map((e) => e.url));
}
},
setData(v, init) {
if (typeof v == "string") {
// 单张
if (this.multiple) {
this.$Message.warning("多张上传仅支持数组数据类型");
return;
@@ -228,62 +232,53 @@ export default {
if (!v) {
return;
}
this.uploadList = [];
let item = {
url: v,
status: "finished"
};
this.uploadList.push(item);
this.$emit("on-change", v);
} else if (typeof v == "object") {
// 多张
this.uploadList = [{ url: v, status: "finished" }];
this.$emit("uploadchange", v);
this.emitValue(v);
} else if (typeof v == "object" && v) {
if (!this.multiple) {
this.$Message.warning("单张上传仅支持字符串数据类型");
return;
}
this.uploadList = [];
const list = v.length > this.limit ? v.slice(0, this.limit) : v;
if (v.length > this.limit) {
for (let i = 0; i < this.limit; i++) {
let item = {
url: v[i],
status: "finished"
};
this.uploadList.push(item);
}
this.$emit("on-change", v.slice(0, this.limit));
if (init) {
this.$emit("input", v.slice(0, this.limit));
}
this.$Message.warning("最多只能上传" + this.limit + "张图片");
} else {
v.forEach(e => {
let item = {
url: e,
status: "finished"
};
this.uploadList.push(item);
}
list.forEach((e) => {
this.uploadList.push({
status: "finished",
...(typeof e === "string" ? { url: e } : e),
});
this.$emit("on-change", v);
});
if (init) {
this.emitValue(list);
} else {
this.$emit("on-change", list);
}
}
}
},
},
watch: {
modelValue(val) {
this.setData(val);
},
value(val) {
this.setData(val);
}
},
},
mounted() {
this.init();
}
},
};
</script>
<style lang="scss" scoped>
.upload-pic-thumb{
.upload-pic-thumb {
display: flex;
}
.upload-list {
display: inline-flex;
display: inline-block;
width: 60px;
height: 60px;
text-align: center;
@@ -294,12 +289,12 @@ export default {
background: #fff;
position: relative;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
margin-right: 5px;
vertical-align: middle;
}
.upload-list img {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-list-cover {
display: none;
@@ -309,22 +304,35 @@ export default {
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
align-items: center;
justify-content: center;
gap: 6px;
}
.upload-list:hover .upload-list-cover {
display: block;
display: flex;
}
.upload-list-cover i {
.action-icon {
color: #fff;
font-size: 20px;
cursor: pointer;
margin: 0 2px;
}
.list-group {
display: inline-block;
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.thumb-ghost {
opacity: 0.5;
background: #c8ebfb;
}
.upload-trigger-box {
display: inline-block;
width: 58px;
height: 58px;
border: 1px dashed #dcdfe6;
border-radius: 4px;
line-height: 58px;
text-align: center;
cursor: pointer;
}
</style>

View File

@@ -1,11 +1,10 @@
<template>
<div class="map">
<div class="address">{{ addrContent.address }}</div>
<div id="map-container"></div>
<div class="search-con">
<Input placeholder="输入关键字搜索" id="input-map" v-model="mapSearch" />
<el-input id="input-map" v-model="mapSearch" placeholder="输入关键字搜索" clearable />
<ul>
<li v-for="(tip, index) in tips" :key="index" @click="selectAddr(tip.location)">
<p>{{ tip.name }}</p>
@@ -13,42 +12,38 @@
</li>
</ul>
</div>
<div slot="footer" class="footer">
<Button type="primary" :loading="loading" @click="ok">确定</Button>
<div class="footer">
<el-button type="primary" :loading="loading" @click="ok">确定</el-button>
</div>
</div>
</template>
<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import { getRegion } from "@/api/common.js";
const config = require('@/config/index')
import config from "@/config/index";
export default {
name: "map",
data() {
return {
config,
showMap: false, // 地图显隐
mapSearch: "", // 地图搜索
map: null, // 初始化地图
autoComplete: null, // 初始化搜索方法
geocoder: null, // 初始化地理、坐标转化
positionPicker: null, // 地图拖拽选点
tips: [], //搜索关键字列表
addrContent: {}, // 回显地址信息
loading: false, // 加载状态
showMap: false,
mapSearch: "",
map: null,
autoComplete: null,
geocoder: null,
positionPicker: null,
tips: [],
addrContent: {},
loading: false,
};
},
watch: {
mapSearch: function (val) {
mapSearch(val) {
this.searchOfMap(val);
},
},
methods: {
ok() {
if (this.addrContent && this.addrContent.regeocode) {
const params = {
cityCode: this.addrContent.regeocode.addressComponent.citycode,
@@ -59,30 +54,27 @@ export default {
this.addrContent.addr = res.result.name.replace(/,/g, " ");
this.addrContent.addrId = res.result.id;
this.loading = false;
this.$emit("getAddress", this.addrContent);
}
});
} else {
this.$Message.error('未获取到坐标信息请查看高德API配置是否正确')
this.$Message.error("未获取到坐标信息请查看高德API配置是否正确");
}
},
init() {
AMapLoader.load({
key: this.config.aMapKey, // 申请好的Web端开发者Key首次调用 load 时必填
version: "", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
key: this.config.aMapKey,
version: "",
plugins: [
"AMap.ToolBar",
"AMap.Autocomplete",
"AMap.PlaceSearch",
"AMap.Geolocation",
"AMap.Geocoder",
], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
],
AMapUI: {
// 是否加载 AMapUI缺省不加载
version: "1.1", // AMapUI 缺省 1.1
plugins: ["misc/PositionPicker"], // 需要加载的 AMapUI ui插件
version: "1.1",
plugins: ["misc/PositionPicker"],
},
})
.then((AMap) => {
@@ -95,36 +87,26 @@ export default {
that.map.addControl(new AMap.PlaceSearch());
that.map.addControl(new AMap.Geocoder());
// 实例化Autocomplete
let autoOptions = {
city: "全国",
};
that.autoComplete = new AMap.Autocomplete(autoOptions); // 搜索
that.autoComplete = new AMap.Autocomplete(autoOptions);
that.geocoder = new AMap.Geocoder(autoOptions);
that.positionPicker = new AMapUI.PositionPicker({
// 拖拽选点
mode: "dragMap",
map: that.map,
});
that.positionPicker.start();
/**
*
* 所有回显数据都在positionResult里面
* 需要字段可以查找
*
*/
that.positionPicker.on("success", function (positionResult) {
that.addrContent = positionResult;
});
})
.catch((e) => { });
.catch(() => {});
},
searchOfMap(val) {
// 地图搜索
let that = this;
this.autoComplete.search(val, function (status, result) {
// 搜索成功时result即是对应的匹配数据
if (status == "complete" && result.info == "OK") {
that.tips = result.tips;
} else {
@@ -133,7 +115,6 @@ export default {
});
},
selectAddr(location) {
// 选择坐标
if (!location) {
this.$Message.warning("请选择正确点位");
return false;
@@ -182,7 +163,6 @@ export default {
.address {
margin-bottom: 10px;
// color: $theme_color;
font-weight: bold;
}

View File

@@ -1,38 +1,50 @@
<template>
<Modal width="800" footer-hide v-model="enableMap">
<RadioGroup @on-change="changeMap" v-model="mapDefault" type="button">
<Radio label="select">级联选择</Radio>
<Radio label="map" v-if="aMapSwitch">高德地图</Radio>
</RadioGroup>
<el-dialog v-model="enableMap" width="800px" :show-close="true" destroy-on-close>
<el-radio-group v-model="mapDefault" @change="changeMap">
<el-radio-button value="select">级联选择</el-radio-button>
<el-radio-button v-if="aMapSwitch" value="map">高德地图</el-radio-button>
</el-radio-group>
<div>
<div v-if="mapDefault === 'select'">
<div class="selector">
<div class="selector-item" v-for="(plant, plantIndex) in Object.keys(data)" :key="plantIndex">
<div :class="{ 'active': chiosend[plantIndex].id == item.id }" v-for="(item, index) in data[plant]"
<div
v-for="(plant, plantIndex) in Object.keys(data)"
:key="plantIndex"
class="selector-item"
>
<div
v-for="(item, index) in data[plant]"
:key="index"
@click="init(item, plantIndex != Object.keys(data).length - 1 ? Object.keys(data)[plantIndex + 1] : 0, plantIndex)"
class="map-item">
:class="{ active: chiosend[plantIndex] && chiosend[plantIndex].id == item.id }"
class="map-item"
@click="
init(
item,
plantIndex != Object.keys(data).length - 1
? Object.keys(data)[plantIndex + 1]
: 0,
plantIndex
)
"
>
{{ item.name }}
</div>
</div>
</div>
<div class="footer">
<Button type="primary" @click="finished">确定</Button>
<el-button type="primary" @click="finished">确定</el-button>
</div>
</div>
<mapping v-if="mapDefault === 'map'" ref="map" @getAddress="getAddress" />
</div>
</Modal>
</el-dialog>
</template>
<script>
import { aMapSwitch } from '@/config/index'
import { aMapSwitch } from "@/config/index";
import mapping from "@/views/my-components/map/index.vue";
import * as API_Setup from "@/api/common.js";
export default {
components: { mapping },
data() {
@@ -41,87 +53,76 @@ export default {
enableMap: false,
mapDefault: "select",
data: {
province: [], //省
city: [], //市
area: [], //区
street: [], //街道
province: [],
city: [],
area: [],
street: [],
},
chiosend: [],
};
},
mounted() {
this.chiosend = new Array(4).fill("");
},
methods: {
open() {
this.enableMap = true
this.init({ id: 0 }, 'province');
this.enableMap = true;
this.init({ id: 0 }, "province");
},
changeMap(val) {
this.mapDefault = val
this.mapDefault = val;
},
init(val, level = 'province', index) {
init(val, level = "province", index) {
if (level == 0) {
// 说明选择到了街道将街道id存入数组
this.chiosend.splice(3, 1, val);
}
else {
} else {
API_Setup.getChildRegion(val.id).then((res) => {
if (res.result.length && val.id !== 0) {
this.chiosend[index] = val
}
else if(!res.result.length){
this.chiosend[index] = val
this.chiosend[index] = val;
} else if (!res.result.length) {
this.chiosend[index] = val;
}
this.data[level] = res.result;
if (level == 'city') {
this.data.area = []
this.data.street = []
this.chiosend.splice(1, 3, "","","");
if (level == "city") {
this.data.area = [];
this.data.street = [];
this.chiosend.splice(1, 3, "", "", "");
}
if (level == 'area') {
this.data.street = []
this.chiosend.splice(2, 2, "","");
if (level == "area") {
this.data.street = [];
this.chiosend.splice(2, 2, "", "");
}
if (level == 'street') {
if (level == "street") {
this.chiosend.splice(3, 1, "");
}
});
}
},
getAddress(center) {
this.$emit('callback', {
this.$emit("callback", {
type: this.mapDefault,
data: center
})
data: center,
});
this.enableMap = false;
},
// 选择完成
finished() {
if(!this.chiosend[0]){
this.$Message.error("请选择地址")
return
if (!this.chiosend[0]) {
this.$Message.error("请选择地址");
return;
}
const params = this.chiosend.filter((item) => item!=="" && item.value !== "");
const params = this.chiosend.filter((item) => item !== "" && item.value !== "");
this.enableMap = false;
this.$emit('callback', {
this.$emit("callback", {
type: this.mapDefault,
data: params
})
data: params,
});
},
},
}
};
</script>
<style lang="scss" scoped>
.selector {
height: 400px;
padding: 10px 0;
display: flex;

View File

@@ -55,4 +55,4 @@
};
</script>
<style lang="less" src="./Checkbox.less"></style>
<style lang="less" scoped src="./Checkbox.less"></style>

View File

@@ -2,8 +2,8 @@
<div
v-if="columns.length > 0"
ref="table"
v-loading="loading"
:class="[prefixCls, `${prefixCls}-${size}`, tableClass]">
<Spin fix v-if="loading"></Spin>
<div
v-show="showHeader"
ref="header-wrapper"
@@ -214,11 +214,8 @@ export default {
default: () => []
},
size: {
default() {
return !this.$IVIEW || this.$IVIEW.size === ""
? "default"
: this.$IVIEW.size;
}
type: String,
default: "default",
},
loading: {
type: Boolean,
@@ -297,6 +294,11 @@ export default {
cellStyle: [Object, Function],
expandKey: String
},
provide() {
return {
treeTableRoot: this,
};
},
data() {
return {
computedWidth: "",
@@ -387,7 +389,7 @@ export default {
this.measure();
window.addEventListener("resize", this.measure);
},
beforeDestroy() {
beforeUnmount() {
window.removeEventListener("resize", this.measure);
}
};

View File

@@ -1,13 +1,13 @@
import Checkbox from '../Checkbox/Checkbox'; // eslint-disable-line
// import Radio from '../Radio/Radio'; // eslint-disable-line
import { mixins } from './utils';
import { Radio } from 'view-design'; // eslint-disable-line
/* eslint-disable no-underscore-dangle */
export default {
name: 'TreeTable__body',
mixins: [mixins],
components: { Radio },
inject: {
treeTableRoot: { default: null },
},
data() {
return {
radioSelectedIndex: -1,
@@ -15,6 +15,12 @@ export default {
},
computed: {
table() {
if (this.treeTableRoot) return this.treeTableRoot;
let parent = this.$parent;
while (parent) {
if (parent.$options && parent.$options.name === 'TreeTable') return parent;
parent = parent.$parent;
}
return this.$parent;
},
},
@@ -110,15 +116,6 @@ export default {
},
},
render() {
// key
// function getKey(row, rowIndex) {
// const rowKey = this.table.rowKey;
// if (rowKey) {
// return rowKey.call(null, row, rowIndex);
// }
// return rowIndex;
// }
// style
function getStyle(type, row, rowIndex, column, columnIndex) {
const certainType = this.validateType(type, ['cell', 'row'], 'getStyle');
@@ -185,8 +182,21 @@ export default {
return classList.join(' ');
}
// Vue 3scoped slot 合并到 $slots
function renderTemplateSlot(table, slotName, scope) {
if (!table || !slotName) return '';
const slots = table.$slots || {};
let slot = slots[slotName];
if (!slot && table.$scopedSlots && table.$scopedSlots[slotName]) {
slot = table.$scopedSlots[slotName];
}
if (!slot) return '';
return slot(scope);
}
// 根据type渲染单元格Cell
function renderCell(row, rowIndex, column, columnIndex) {
if (!row || !column) return '';
// ExpandType
if (this.isExpandCell(this.table, columnIndex)) {
return <i class='zk-icon zk-icon-angle-right'></i>;
@@ -219,13 +229,16 @@ export default {
}
}
}
// res = <Checkbox
// indeterminate={indeterminate}
// value={allCheck}
// onOn-change={isChecked => this.handleEvent(null, 'checkbox', { row, rowIndex, column, columnIndex }, { isChecked })}>
// </Checkbox>;
} else {
res = <Radio value={this.radioSelectedIndex === rowIndex} on-on-change={() => this.handleEvent(null, 'radio', { row, rowIndex, column, columnIndex })}></Radio>;
res = (
<input
type="radio"
checked={this.radioSelectedIndex === rowIndex}
onChange={() =>
this.handleEvent(null, 'radio', { row, rowIndex, column, columnIndex })
}
/>
);
}
return res;
}
@@ -253,9 +266,12 @@ export default {
if (column.type === undefined || column.type === 'custom') {
return row[column.key];
} else if (column.type === 'template') {
return this.table.$scopedSlots[column.template]
? this.table.$scopedSlots[column.template]({ row, rowIndex, column, columnIndex })
: '';
return renderTemplateSlot.call(this, this.table, column.template, {
row,
rowIndex,
column,
columnIndex,
});
}
return '';
}
@@ -263,11 +279,6 @@ export default {
// Template
return (
<table cellspacing="0" cellpadding="0" border="0" class={`${this.prefixCls}__body`}>
{/* <colgroup>
{this.table.tableColumns.map(column =>
<col width={column.computedWidth || column.minWidth || column.width}></col>)
}
</colgroup> */}
<tbody>
{ this.table.bodyData.length > 0
? this.table.bodyData.map((row, rowIndex) =>
@@ -304,10 +315,7 @@ export default {
<td
class={`${this.prefixCls}--expand-content`}
colspan={this.table.tableColumns.length}>
{this.table.$scopedSlots.expand
? this.table.$scopedSlots.expand({ row, rowIndex })
: ''
}
{renderTemplateSlot.call(this, this.table, 'expand', { row, rowIndex })}
</td>
</tr>,
])

View File

@@ -1,23 +1,21 @@
import Vue from 'vue';
let scrollBarWidth;
export default function () {
if (Vue.prototype.$isServer) return 0;
export default function getScrollBarWidth() {
if (typeof document === "undefined") return 0;
if (scrollBarWidth !== undefined) return scrollBarWidth;
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.width = '100px';
outer.style.position = 'absolute';
outer.style.top = '-9999px';
const outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.position = "absolute";
outer.style.top = "-9999px";
document.body.appendChild(outer);
const widthNoScroll = outer.offsetWidth;
outer.style.overflow = 'scroll';
outer.style.overflow = "scroll";
const inner = document.createElement('div');
inner.style.width = '100%';
const inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
const widthWithScroll = inner.offsetWidth;

View File

@@ -1,59 +1,81 @@
<template>
<div class="verify-content" v-if="show" @mousemove="mouseMove" @mouseup="mouseUp" @click.stop>
<div class="imgBox" :style="{width:data.originalWidth+'px',height:data.originalHeight + 'px'}">
<img :src="data.backImage" style="width:100%;height:100%" alt="">
<img class="slider" :src="data.slidingImage" :style="{left:distance+'px',top:data.randomY+'px'}" :width="data.sliderWidth" :height="data.sliderHeight" alt="">
<Icon type="md-refresh" class="refresh" @click="init" />
<div
class="verify-content"
v-if="show"
@mousemove="mouseMove"
@mouseup="mouseUp"
@click.stop
>
<div
class="imgBox"
:style="{
width: data.originalWidth + 'px',
height: data.originalHeight + 'px',
}"
>
<img :src="data.backImage" style="width: 100%; height: 100%" alt="" />
<img
class="slider"
:src="data.slidingImage"
:style="{ left: distance + 'px', top: data.randomY + 'px' }"
:width="data.sliderWidth"
:height="data.sliderHeight"
alt=""
/>
<el-icon class="refresh" @click="init"><Refresh /></el-icon>
</div>
<div class="handle" :style="{width:data.originalWidth+'px'}">
<span class="bgcolor" :style="{width:distance + 'px',background:bgColor}"></span>
<span class="swiper" :style="{left:distance + 'px'}" @mousedown="mouseDown">
<Icon type="md-arrow-round-forward" />
<div class="handle" :style="{ width: data.originalWidth + 'px' }">
<span
class="bgcolor"
:style="{ width: distance + 'px', background: bgColor }"
></span>
<span class="swiper" :style="{ left: distance + 'px' }" @mousedown="mouseDown">
<el-icon><DArrowRight /></el-icon>
</span>
<span class="text">{{verifyText}}</span>
<span class="text">{{ verifyText }}</span>
</div>
</div>
</template>
<script>
import { getVerifyImg, postVerifyImg } from './verify.js';
import { Refresh, DArrowRight } from "@element-plus/icons-vue";
import { getVerifyImg, postVerifyImg } from "./verify.js";
export default {
components: { Refresh, DArrowRight },
props: {
// 传入数据,判断是登录、注册、修改密码
verifyType: {
defalut: 'LOGIN',
type: String
}
default: "LOGIN",
type: String,
},
},
data () {
data() {
return {
show: false, // 验证码显隐
type: 'LOGIN', // 请求类型
data: { // 验证码数据
backImage: '',
slidingImage: '',
show: false,
type: "LOGIN",
data: {
backImage: "",
slidingImage: "",
originalHeight: 150,
originalWidth: 300,
sliderWidth: 60,
sliderHeight: 60
sliderHeight: 60,
},
distance: 0, // 拼图移动距离
flag: false, // 判断滑块是否按下
downX: 0, // 鼠标按下位置
bgColor: '#04ad11', // 滑动背景颜色
verifyText: '拖动滑块解锁' // 文字提示
distance: 0,
flag: false,
downX: 0,
bgColor: "#04ad11",
verifyText: "拖动滑块解锁",
};
},
methods: {
// 鼠标按下事件,开始拖动滑块
mouseDown (e) {
mouseDown(e) {
this.downX = e.clientX;
this.flag = true;
},
// 鼠标移动事件,计算距离
mouseMove (e) {
mouseMove(e) {
if (this.flag) {
let offset = e.clientX - this.downX;
const offset = e.clientX - this.downX;
if (offset > this.data.originalWidth - 43) {
this.distance = this.data.originalWidth - 43;
} else if (offset < 0) {
@@ -63,65 +85,63 @@ export default {
}
}
},
// 鼠标抬起事件,验证是否正确
mouseUp () {
mouseUp() {
if (!this.flag) return false;
this.flag = false;
let params = {
const params = {
verificationEnums: this.type,
xPos: this.distance
xPos: this.distance,
};
postVerifyImg(params).then(res => {
if (res.success) {
if (res.result) {
this.bgColor = 'green';
this.verifyText = '解锁成功';
this.$emit('change', { status: true, distance: this.distance });
postVerifyImg(params)
.then((res) => {
if (res.success) {
if (res.result) {
this.bgColor = "green";
this.verifyText = "解锁成功";
this.$emit("change", { status: true, distance: this.distance });
} else {
this.bgColor = "red";
this.verifyText = "解锁失败";
setTimeout(() => this.init(), 1000);
this.$emit("change", { status: false, distance: this.distance });
}
} else {
this.bgColor = 'red';
this.verifyText = '解锁失败';
let that = this;
setTimeout(() => {
that.init();
}, 1000);
this.$emit('change', { status: false, distance: this.distance });
this.init();
}
} else {
this.init()
}
}).catch(()=>{
this.init()
});
})
.catch(() => {
this.init();
});
},
init () { // 初始化数据
init() {
this.flag = false;
this.downX = 0;
this.distance = 0;
this.bgColor = '#04ad11';
this.verifyText = '拖动滑块解锁';
getVerifyImg(this.type).then(res => {
this.bgColor = "#04ad11";
this.verifyText = "拖动滑块解锁";
getVerifyImg(this.type).then((res) => {
if (res.result) {
this.data = res.result;
this.show = true;
} else {
this.$Message.warning('请求失败请重试!')
this.$Message.warning("请求失败请重试!");
}
});
}
},
},
watch: {
verifyType: {
immediate: true,
handler: function (v) {
handler(v) {
this.type = v;
}
}
}
},
},
},
};
</script>
<style lang="scss" scoped>
.verify-content{
.verify-content {
padding: 10px;
background: #fff;
border: 1px solid #eee;
@@ -174,9 +194,7 @@ export default {
display: flex;
align-items: center;
justify-content: center;
.ivu-icon {
font-size: 20px;
}
font-size: 20px;
}
.text {

View File

@@ -1,258 +1,212 @@
<template>
<div class="search">
<Card>
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="会员名称" prop="memberName">
<Input
type="text"
v-model="searchForm.memberName"
clearable
placeholder="请输入会员名称"
style="width: 240px"
/>
</Form-item>
<Form-item label="订单号" prop="orderSn">
<Input
type="text"
v-model="searchForm.orderSn"
clearable
placeholder="请输入订单号"
style="width: 240px"
/>
</Form-item>
<Form-item label="状态" prop="status">
<Select v-model="searchForm.status" placeholder="请选择订单状态" clearable style="width: 240px">
<Option value="NEW">新投诉</Option>
<Option value="CANCEL">已撤销</Option>
<Option value="WAIT_APPEAL">待申诉</Option>
<Option value="COMMUNICATION">对话中</Option>
<Option value="WAIT_ARBITRATION">等待仲裁</Option>
<Option value="COMPLETE">已完成</Option>
</Select>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Row>
</Card>
<Card>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
class="mt_10"
ref="table"
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<template slot-scope="{row}" slot="goodsName">
<a class="mr_10" @click="linkTo(row.goodsId,row.skuId)">{{row.goodsName}}</a>
<Poptip trigger="hover" title="扫码在手机中查看" transfer>
<div slot="content">
<vue-qr :text="wapLinkTo(row.goodsId,row.skuId)" :margin="0" colorDark="#000" colorLight="#fff" :size="150"></vue-qr>
</div>
<img src="../../../assets/qrcode.svg" style="vertical-align:bottom;" class="hover-pointer" width="20" height="20" alt="">
</Poptip>
</template>
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
<el-form-item label="订单编号" prop="orderSn">
<el-input
v-model="searchForm.orderSn"
placeholder="请输入订单编号"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="会员名称" prop="memberName">
<el-input
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="searchForm.status" placeholder="请选择" clearable style="width: 240px">
<el-option label="新投诉" value="NEW" />
<el-option label="已撤销" value="CANCEL" />
<el-option label="待申诉" value="WAIT_APPEAL" />
<el-option label="对话中" value="COMMUNICATION" />
<el-option label="等待仲裁" value="WAIT_ARBITRATION" />
<el-option label="已完成" value="COMPLETE" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<el-table v-loading="loading" border :data="data" ref="table" class="mt_10" style="width: 100%">
<el-table-column prop="memberName" label="会员名称" width="200" />
<el-table-column prop="orderSn" label="订单编号" min-width="120" show-overflow-tooltip />
<el-table-column label="商品名称" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<template v-if="row">
<a class="link-text mr_10" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
<el-popover trigger="hover" title="扫码在手机中查看" placement="top" width="180">
<template #reference>
<img
src="../../../assets/qrcode.svg"
class="hover-pointer"
width="20"
height="20"
alt="qrcode"
/>
</template>
<vue-qr
:text="wapLinkTo(row.goodsId, row.skuId)"
:margin="0"
color-dark="#000"
color-light="#fff"
:size="150"
/>
</el-popover>
</template>
</template>
</el-table-column>
<el-table-column prop="complainTopic" label="投诉主题" min-width="160" show-overflow-tooltip />
<el-table-column prop="createTime" label="投诉时间" width="180" />
<el-table-column label="投诉状态" width="110">
<template #default="{ row }">
<el-tag v-if="row" :type="complainStatusTagType(row.complainStatus)">
{{ complainStatusText(row.complainStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<a v-if="row" class="link-text" @click="detail(row)">
{{ row.complainStatus === "COMPLETE" ? "详情" : "处理" }}
</a>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import * as API_Member from "@/api/member";
import * as API_Order from "@/api/order";
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
import * as API_Order from "@/api/order";
import vueQr from "vue-qr";
export default {
name: "orderComplaint",
components: {
uploadPicThumb
export default {
name: "orderComplaint",
components: { vueQr },
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
},
data: [],
total: 0,
};
},
watch: {
$route() {
this.getDataList();
},
data() {
return {
loading: true, // 表单加载状态
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
},
columns: [
// 表头
{
title: "会员名称",
key: "memberName",
sortable: false,
},
{
title: "订单编号",
key: "orderSn",
},
{
title: "商品名称",
slot: "goodsName",
},
{
title: "投诉主题",
key: "complainTopic",
},
{
title: "投诉时间",
key: "createTime",
},
{
title: "投诉状态",
key: "complainStatus",
render: (h, params) => {
if (params.row.complainStatus == "NEW") {
return h('div', [h('tag',{props: {color: "purple"}}, '新投诉'),]);
} else if (params.row.complainStatus == "CANCEL") {
return h('div', [h('tag', {props: {color: "cyan"}}, '已撤销'),]);
} else if (params.row.complainStatus == "WAIT_APPEAL") {
return h('div', [h('tag', {props: {color: "volcano"}}, '待申诉'),]);
} else if (params.row.complainStatus == "COMMUNICATION") {
return h('div', [h('tag', {props: {color: "orange"}}, '对话中'),]);
}else if (params.row.complainStatus == "WAIT_ARBITRATION") {
return h('div', [h('tag', {props: {color: "blue"}}, '等待仲裁'),]);
}else if (params.row.complainStatus == "COMPLETE") {
return h('div', [h('tag', {props: {color: "green"}}, '已完成'),]);
}
}
},
{
title: "操作",
key: "action",
align: "center",
fixed: 'right',
width: 200,
render: (h, params) => {
if (params.row.complainStatus === "COMPLETE") {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"详情"
),
]);
} else {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"处理"
),
]);
}
},
},
],
data: [], // 表格数据
total: 0, // 表单数据总数
},
methods: {
complainStatusText(v) {
const map = {
NEW: "新投诉",
CANCEL: "已撤销",
WAIT_APPEAL: "待申诉",
COMMUNICATION: "对话中",
WAIT_ARBITRATION: "等待仲裁",
COMPLETE: "已完成",
};
return map[v] || v || "-";
},
methods: {
// 初始化数据
init() {
this.getDataList();
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 重置
handleReset() {
this.searchForm = {}
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 获取数据
getDataList() {
this.loading = true;
API_Order.getComplainPage(this.searchForm).then((res) => {
complainStatusTagType(v) {
const map = {
NEW: "primary",
CANCEL: "info",
WAIT_APPEAL: "warning",
COMMUNICATION: "warning",
WAIT_ARBITRATION: "",
COMPLETE: "success",
};
return map[v] || "info";
},
init() {
this.getDataList();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
getDataList() {
this.loading = true;
API_Order.getOrderComplain(this.searchForm)
.then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
this.total = this.data.length;
this.loading = false;
},
//投诉详情
detail(v) {
let id = v.id;
this.$options.filters.customRouterPush({
name: "order-complaint-detail",
query: { id: id },
})
},
.catch(() => {
this.loading = false;
});
},
mounted() {
this.init();
detail(v) {
this.$filters.customRouterPush({
name: "order-complaint-detail",
query: { id: v.id },
});
},
// 页面缓存处理从该页面离开时修改KeepAlive为false保证进入该页面是刷新
beforeRouteLeave(to, from, next) {
from.meta.keepAlive = false
next()
}
};
},
mounted() {
this.init();
},
};
</script>
<style lang="scss">
// 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss";
<style scoped>
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.mr_10 {
margin-right: 10px;
}
.hover-pointer {
cursor: pointer;
vertical-align: bottom;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="search">
<Card>
<div class="main-content">
<div class="search" v-loading="loading">
<el-card>
<div class="main-content" v-if="complaintInfo.id">
<div class="div-flow-left">
<div class="div-form-default">
<h3>投诉信息</h3>
@@ -9,20 +9,15 @@
<dt>投诉商品</dt>
<dd>
<div>
<img :src="complaintInfo.goodsImage" style="height: 60px">
<img :src="complaintInfo.goodsImage" style="height: 60px" alt="" />
</div>
<a>{{ complaintInfo.goodsName }}</a><br>
<span>{{ complaintInfo.goodsPrice | unitPrice }} * {{ complaintInfo.num }}(数量)</span>
<a class="link-text">{{ complaintInfo.goodsName }}</a><br />
<span>{{ complaintInfo.num }}(数量)</span>
</dd>
</dl>
<dl>
<dt>投诉状态</dt>
<dd v-if="complaintInfo.complainStatus =='NEW'">新投诉</dd>
<dd v-if="complaintInfo.complainStatus =='CANCEL'">已撤销</dd>
<dd v-if="complaintInfo.complainStatus =='WAIT_APPEAL'">待申诉</dd>
<dd v-if="complaintInfo.complainStatus =='COMMUNICATION'">对话中</dd>
<dd v-if="complaintInfo.complainStatus =='WAIT_ARBITRATION'">等待仲裁</dd>
<dd v-if="complaintInfo.complainStatus =='COMPLETE'">已完成</dd>
<dd>{{ complainStatusText(complaintInfo.complainStatus) }}</dd>
</dl>
<dl>
<dt>投诉时间</dt>
@@ -38,17 +33,45 @@
</dl>
<dl>
<dt>投诉凭证</dt>
<dd v-if="images === ''">
暂无投诉凭证
</dd>
<dd v-if="!images.length">暂无投诉凭证</dd>
<dd v-else>
<div class="div-img" v-for="(item, index) in images" :key="index">
<img class="complain-img" :src=item>
<img class="complain-img" :src="item" alt="" />
</div>
</dd>
</dl>
</div>
<div class="div-form-default" v-if="complaintInfo.complainStatus !== 'WAIT_APPEAL'">
<div class="div-form-default" v-if="complaintInfo.complainStatus === 'WAIT_APPEAL'">
<h3>商家申诉</h3>
<dl>
<dt>申诉内容</dt>
<dd>
<el-input
v-model="appeal.appealContent"
type="textarea"
maxlength="200"
:rows="4"
clearable
style="width: 260px"
/>
</dd>
</dl>
<dl>
<dt>申诉凭证</dt>
<dd>
<upload-pic-thumb v-model="appeal.appealImages" :limit="5" />
</dd>
</dl>
<dl>
<dt></dt>
<dd>
<el-button type="primary" :loading="submitLoading" @click="appealSubmit">提交申诉</el-button>
</dd>
</dl>
</div>
<div class="div-form-default" v-else-if="complaintInfo.appealContent">
<h3>商家申诉信息</h3>
<dl>
<dt>申诉时间</dt>
@@ -60,198 +83,136 @@
</dl>
<dl>
<dt>申诉凭证</dt>
<dd v-if="appealImages == ''">
暂无申诉凭证
</dd>
<dd v-if="!appealImages.length">暂无申诉凭证</dd>
<dd v-else>
<div class="div-img" v-for="(item, index) in appealImages" :key="index">
<img class="complain-img" :src=item>
<img class="complain-img" :src="item" alt="" />
</div>
</dd>
</dl>
</div>
<div class="div-form-default" v-if="complaintInfo.complainStatus === 'WAIT_APPEAL'">
<h3>商家申诉</h3>
<dl>
<dt>申诉内容</dt>
<dd>
<Input v-model="appeal.appealContent" type="textarea" maxlength="200" :rows="4" clearable style="width:260px" />
</dd>
</dl>
<dl>
<dt>申诉凭证</dt>
<dd>
<div class="complain-upload-list" :key="index" v-for="(item,index) in appeal.appealImages">
<template v-if="item.status === 'finished'">
<img class="complain-img" :src="item.url">
<div class="complain-upload-list-cover">
<Icon type="ios-eye-outline" @click.native="handleView(item.url)"></Icon>
<Icon type="ios-trash-outline" @click.native="handleRemove(item)"></Icon>
</div>
</template>
<template v-else>
<Progress v-if="item.showProgress" :percent="item.percentage" hide-info></Progress>
</template>
</div>
<Upload ref="upload" :show-upload-list="false" :on-format-error="handleFormatError" :action="uploadFileUrl" :headers="accessToken" :on-success="handleSuccessGoodsPicture"
:format="['jpg','jpeg','png']" :max-size="1024" :on-exceeded-size="handleMaxSize" :before-upload="handleBeforeUpload" multiple type="drag"
style="display: inline-block;width:58px;">
<div style="width: 58px;height:58px;line-height: 58px;">
<Icon type="ios-camera" size="20"></Icon>
</div>
</Upload>
<Modal title="View Image" v-model="visible">
<img :src="imgName" v-if="visible" style="width: 100%">
</Modal>
</dd>
</dl>
<dl>
<dt></dt>
<dd>
<Button type="primary" :loading="submitLoading" @click="appealSubmit()" style="margin-left: 5px">
提交申诉
</Button>
</dd>
</dl>
</div>
<div class="div-form-default">
<h3>对话详情</h3>
<dl>
<dt>对话记录</dt>
<dd>
<div class="div-content">
<p v-for="(item, index) in complaintInfo.orderComplaintCommunications" :key="index">
<span v-if="item.owner === 'STORE'">商家[{{ item.createTime }}]</span>
<span v-if="item.owner === 'BUYER'">买家[{{ item.createTime }}]</span>
<span v-if="item.owner === 'PLATFORM'">平台[{{ item.createTime }}]</span>
<p
v-for="(item, index) in complaintInfo.orderComplaintCommunications || []"
:key="index"
>
<span v-if="item.owner == 'STORE'">商家[{{ item.createTime }}]</span>
<span v-else-if="item.owner == 'BUYER'">买家[{{ item.createTime }}]</span>
<span v-else-if="item.owner == 'PLATFORM'">平台[{{ item.createTime }}]</span>
{{ item.content }}
</p>
</div>
</dd>
</dl>
<dl v-if="complaintInfo.complainStatus!='COMPLETE'">
<dl v-if="complaintInfo.complainStatus != 'COMPLETE'">
<dt>发送对话</dt>
<dd>
<Input v-model="params.content" type="textarea" maxlength="200" :rows="4" clearable style="width:260px" />
<el-input
v-model="params.content"
type="textarea"
maxlength="200"
:rows="4"
clearable
style="width: 260px"
/>
</dd>
</dl>
<dl>
<dl v-if="complaintInfo.complainStatus != 'COMPLETE'">
<dt></dt>
<dd v-if="complaintInfo.complainStatus != 'COMPLETE'">
<div style="text-align: right;width: 45%;margin-top: 10px">
<Button type="primary" :loading="submitLoading" @click="handleSubmit" style="margin-left: 5px">
<dd>
<div style="text-align: left; margin-top: 10px">
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
回复
</Button>
<Button type="default" :loading="submitLoading" @click="returnDataList" style="margin-left: 5px">
</el-button>
<el-button type="primary" :loading="submitLoading" @click="returnDataList" style="margin-left: 5px">
返回列表
</Button>
</el-button>
</div>
</dd>
</dl>
</div>
<div class="div-form-default" v-if="complaintInfo.complainStatus === 'COMPLETE'">
<div class="div-form-default" v-if="complaintInfo.complainStatus == 'COMPLETE'">
<h3>仲裁结果</h3>
<dl>
<dt>仲裁意见</dt>
<dd>
{{ complaintInfo.arbitrationResult }}
</dd>
<dd>{{ complaintInfo.arbitrationResult }}</dd>
</dl>
</div>
</div>
<div class="div-flow-center">
</div>
<div class="div-flow-center"></div>
<div class="div-flow-right">
<div class="div-form-default">
<h3>订单相关信息</h3>
<dl>
<dt>
订单编号
</dt>
<dd>
{{ complaintInfo.orderSn }}
</dd>
<dt>订单编号</dt>
<dd>{{ complaintInfo.orderSn }}</dd>
</dl>
<dl>
<dt>
下单时间
</dt>
<dd>
{{ complaintInfo.orderTime }}
</dd>
<dt>下单时间</dt>
<dd>{{ complaintInfo.orderTime || complaintInfo.createTime }}</dd>
</dl>
<dl>
<dt>
订单金额
</dt>
<dt>订单金额</dt>
<dd>
<priceColorScheme :value="complaintInfo.orderPrice" :color="$mainColor"></priceColorScheme>
<span class="price-text">{{ $filters.unitPrice(complaintInfo.orderPrice, "¥") }}</span>
</dd>
</dl>
</div>
<div class="div-form-default">
<h3>收件人信息</h3>
<dl>
<dt>
收货人
</dt>
<dd>
{{ complaintInfo.consigneeName }}
</dd>
<dt>收货人</dt>
<dd>{{ complaintInfo.consigneeName }}</dd>
</dl>
<dl>
<dt>
收货地址
</dt>
<dd>
{{ complaintInfo.consigneeAddressPath }}
</dd>
<dt>收货地址</dt>
<dd>{{ complaintInfo.consigneeAddressPath }}</dd>
</dl>
<dl>
<dt>
收货人手机
</dt>
<dd>
{{ complaintInfo.consigneeMobile }}
</dd>
<dt>收货人手机</dt>
<dd>{{ complaintInfo.consigneeMobile }}</dd>
</dl>
</div>
</div>
</div>
</Card>
</el-card>
</div>
</template>
<script>
import * as API_Order from "@/api/order";
import { uploadFile } from "@/libs/axios";
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
const emptyComplaint = () => ({
id: "",
orderComplaintCommunications: [],
});
export default {
name: "orderComplaint",
name: "orderComplaintDetail",
components: {
uploadPicThumb,
},
data() {
return {
//展示图片层
visible: false,
//上传图片路径
uploadFileUrl: uploadFile,
accessToken: "", // 验证token
id: 0, // 投诉单id
complaintInfo: "", // 投诉信息
images: [], //会员申诉图片
appealImages: [], //商家申诉的图片
applyAppealImages: [], //商家申诉表单填写的图片
submitLoading: false, // 添加或编辑提交状态
//商家回复内容
loading: false,
id: "",
complaintInfo: emptyComplaint(),
images: [],
appealImages: [],
submitLoading: false,
params: {
content: "",
complainId: "",
},
//投诉
appeal: {
orderComplaintId: "",
appealContent: "",
@@ -259,169 +220,105 @@ export default {
},
};
},
watch: {
$route() {
this.getDetail();
},
},
methods: {
// 预览图片
handleView(name) {
this.imgName = name;
this.visible = true;
complainStatusText(v) {
const map = {
NEW: "新投诉",
CANCEL: "已撤销",
WAIT_APPEAL: "待申诉",
COMMUNICATION: "对话中",
WAIT_ARBITRATION: "等待仲裁",
COMPLETE: "已完成",
};
return map[v] || v || "-";
},
// 移除回复图片
handleRemove(file) {
this.appeal.appealImages = this.appeal.appealImages.filter(
(i) => i.url !== file.url
);
},
// 上传成功回调
handleSuccessGoodsPicture(res, file) {
if (file.response) {
file.url = file.response.result;
this.appeal.appealImages.push(file);
}
},
// 上传之前钩子
handleBeforeUpload() {
const check =
this.images.images !== undefined && this.images.images.length > 5;
if (check) {
this.$Notice.warning({
title: "Up to five pictures can be uploaded.",
});
}
return !check;
},
// 上传格式错误
handleFormatError(file) {
this.$Notice.warning({
title: "图片格式不正确",
desc:
"File format of " +
file.name +
" is incorrect, please select jpg or png.",
});
},
// 上传大小限制
handleMaxSize(file) {
this.$Notice.warning({
title: "超过文件大小限制",
desc: "图片不能超过1mb",
});
},
// 获取详情
getDetail() {
if (!this.id) return;
this.loading = true;
API_Order.getComplainDetail(this.id).then((res) => {
this.loading = false;
if (res.success) {
this.complaintInfo = res.result;
this.images = (res.result.images || "").split(",");
this.appealImages = (res.result.appealImages || "").split(",");
}
});
API_Order.getOrderComplainDetail(this.id)
.then((res) => {
this.loading = false;
if (res.success) {
this.complaintInfo = res.result || emptyComplaint();
this.images = (res.result.images || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
this.appealImages = (res.result.appealImages || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}
})
.catch(() => {
this.loading = false;
});
},
//返回列表
returnDataList() {
this.$router.push({
name: "orderComplaint",
});
this.$router.push({ name: "orderComplaint" });
},
appealSubmit() {
if (!this.appeal.appealContent) {
this.$Message.error("请填写申诉内容");
return;
}
this.submitLoading = true;
const appealImages = Array.isArray(this.appeal.appealImages)
? this.appeal.appealImages
: [];
API_Order.appeal({
orderComplaintId: this.id,
appealContent: this.appeal.appealContent,
appealImages,
})
.then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("申诉成功");
this.appeal.appealContent = "";
this.appeal.appealImages = [];
this.getDetail();
}
})
.catch(() => {
this.submitLoading = false;
});
},
//回复
handleSubmit() {
if (this.params.content === "") {
if (!this.params.content) {
this.$Message.error("请填写对话内容");
return;
}
this.submitLoading = true;
this.params.complainId = this.id;
API_Order.addOrderComplaint(this.params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("对话成功");
this.params.content = "";
this.getDetail();
}
});
},
//申诉
appealSubmit() {
if (this.appeal.appealContent === "") {
this.$Message.error("请填写内容");
return;
}
this.appeal.appealImages = this.appeal.appealImages.map(item=> item.url)
this.appeal.orderComplaintId = this.id;
API_Order.appeal(this.appeal).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("申诉成功");
this.getDetail();
}
});
API_Order.addOrderCommunication(this.params)
.then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("对话成功");
this.params.content = "";
this.getDetail();
}
})
.catch(() => {
this.submitLoading = false;
});
},
},
mounted () {
mounted() {
this.id = this.$route.query.id;
this.getDetail();
this.accessToken = {
accessToken: this.getStore("accessToken"),
};
},
// 如果是从详情页返回列表页修改列表页keepAlive为true确保不刷新页面
beforeRouteLeave(to, from, next){
if(to.name === 'orderComplaint') {
to.meta.keepAlive = true
}
next()
}
watch: {
"$route.query.id"(val) {
this.id = val;
this.getDetail();
},
},
};
</script>
<style lang="scss" scoped>
::v-deep .ivu-col {
width: 100% !important;
}
.complain-upload-list {
display: inline-block;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
border: 1px solid transparent;
border-radius: 4px;
overflow: hidden;
background: #fff;
position: relative;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
margin-right: 4px;
}
.complain-upload-list img {
width: 100%;
height: 100%;
}
.complain-upload-list-cover {
display: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
}
.complain-upload-list:hover .complain-upload-list-cover {
display: block;
}
.complain-upload-list-cover i {
color: #fff;
font-size: 20px;
cursor: pointer;
margin: 0 2px;
}
.main-content {
min-height: 600px;
padding: 10px;
@@ -545,5 +442,31 @@ export default {
overflow-y: auto;
overflow-x: auto;
height: 150px;
scrollbar-width: auto;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: rgba(50, 50, 50, 0.45);
}
&::-webkit-scrollbar-track {
border-radius: 4px;
background-color: rgba(50, 50, 50, 0.12);
}
}
.link-text {
color: #409eff;
cursor: pointer;
}
.price-text {
color: #ff5c58;
font-weight: 600;
}
</style>

View File

@@ -1,369 +1,345 @@
<template>
<div class="search">
<Card>
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="商品" prop="goodsName">
<Input
type="text"
v-model="searchForm.goodsName"
clearable
placeholder="请输入商品名称"
style="width: 240px"
/>
</Form-item>
<Form-item label="会员名称" prop="memberName">
<Input
type="text"
v-model="searchForm.memberName"
clearable
placeholder="请输入会员名称"
style="width: 240px"
/>
</Form-item>
<Form-item label="订单编号" prop="orderSn">
<Input
type="text"
v-model="searchForm.orderSn"
clearable
placeholder="请输入订单编号"
style="width: 240px"
/>
</Form-item>
<Form-item label="申请时间">
<DatePicker
v-model="selectDate"
type="datetimerange"
format="yyyy-MM-dd HH:mm:ss"
clearable
@on-change="selectDateRange"
placeholder="选择起始时间"
style="width: 240px"
></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Row>
</Card>
<Card>
<div class="order-tab">
<Tabs v-model="currentStatus" @on-click="serviceStatusClick">
<TabPane v-for="item in serviceStatusWithCount" :key="item.value" :label="item.title" :name="item.value"/>
</Tabs>
</div>
<Table
:loading="loading"
class="mt_10"
:columns="columns"
:data="data"
ref="table"
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="关键字" prop="keywords">
<el-input
v-model="searchForm.keywords"
placeholder="请输入商品名称、订单编号搜索"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="订单编号" prop="orderSn">
<el-input
v-model="searchForm.orderSn"
placeholder="请输入订单编号"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="售后单号" prop="sn">
<el-input
v-model="searchForm.sn"
placeholder="请输入售后单号"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="申请时间">
<el-date-picker
v-model="selectDate"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 360px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item label="商家名称" prop="storeName">
<el-input
v-model="searchForm.storeName"
placeholder="请输入商家名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="会员名称" prop="memberName">
<el-input
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 商品栏目格式化 -->
<template slot="goodsSlot" slot-scope="{row}">
<div style="margin-top: 5px;height: 60px; display: flex;">
<div style="">
<img :src="row.goodsImage" style="height: 50px;margin-top: 3px">
</div>
<el-card>
<div class="order-tab">
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
<el-tab-pane
v-for="item in serviceStatusWithCount"
:key="item.value"
:label="item.title"
:name="item.value"
/>
</el-tabs>
</div>
<div style="margin-left: 13px;">
<div class="div-zoom">
<a @click="linkTo(row.goodsId,row.skuId)">{{row.goodsName}}</a>
<el-table
ref="table"
v-loading="loading"
:data="data"
class="mt_10"
style="width: 100%"
>
<el-table-column prop="sn" label="售后服务单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="orderSn" label="订单编号" min-width="120" show-overflow-tooltip />
<el-table-column label="商品" min-width="300">
<template #default="{ row }">
<div v-if="row" style="margin-top: 5px; height: 80px; display: flex">
<div>
<img
:src="row.goodsImage"
style="width: 60px; height: 60px; margin-top: 3px; object-fit: cover; border-radius: 4px"
alt=""
/>
</div>
<div style="margin-left: 13px">
<div class="div-zoom">
<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 }}
</div>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="memberId" label="会员ID" min-width="120" show-overflow-tooltip />
<el-table-column prop="memberName" label="会员名称" width="140" />
<el-table-column prop="storeName" label="店铺名称" width="100" show-overflow-tooltip />
<el-table-column label="售后金额" width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.applyRefundPrice, "") }}</span>
</template>
</el-table-column>
<el-table-column label="售后状态" width="180">
<template #default="{ row }">
<el-tag
v-if="row"
:type="serviceStatusTagType(row.serviceStatus)"
effect="plain"
>
{{ serviceStatusText(row.serviceStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="申请时间" width="180" />
<el-table-column label="操作" fixed="right" align="center" width="100">
<template #default="{ row }">
<a v-if="row" class="link-text" @click="detail(row)">查看</a>
</template>
</el-table-column>
</el-table>
</template>
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import * as API_Order from "@/api/order";
import * as API_Order from "@/api/order";
export default {
name: "returnGoodsOrder",
components: {},
data() {
return {
loading: true, // 表单加载状态
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
// serviceType:"RETURN_GOODS",
orderSn:"",
memberName:"",
goodsName:""
export default {
name: "after-sale-order",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
serviceType: "RETURN_GOODS",
orderSn: "",
memberName: "",
serviceStatus: "",
storeName: "",
sn: "",
keywords: "",
},
selectDate: null,
data: [],
total: 0,
currentStatus: "",
afterSaleNumData: {},
};
},
computed: {
serviceStatusWithCount() {
return [
{ title: "全部", value: "" },
{
title: `申请售后${this.afterSaleNumData.applyNum ? "(" + this.afterSaleNumData.applyNum + ")" : ""}`,
value: "APPLY",
},
selectDate: null,
columns: [
{
title: "售后单号",
key: "sn",
minWidth: 150,
},
{
title: "订单号",
key: "orderSn",
minWidth: 150,
},
{
title: "商品",
key: "sn",
minWidth: 200,
slot: "goodsSlot",
},
{
title: "申请退款金额",
key: "applyRefundPrice",
width: 130,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.applyRefundPrice,color:this.$mainColor}} );
},
},
{
title: "会员名称",
key: "memberName",
minWidth: 120,
},
{
title: "会员ID",
key: "memberId",
minWidth: 120,
},
{
title: "状态",
align: "center",
key: "serviceStatus",
width: 200,
render: (h, params) => {
if (params.row.serviceStatus == "APPLY") {
return h('div', [h('tag', {props: {color: "blue"}}, '申请中'),]);
} else if (params.row.serviceStatus == "PASS") {
return h('div', [h('tag', {props: {color: "cyan"}}, '通过售后'),]);
} else if (params.row.serviceStatus == "REFUSE") {
return h('div', [h('tag', {props: {color: "volcano"}}, '拒绝售后'),]);
} else if (params.row.serviceStatus == "BUYER_RETURN") {
return h('div', [h('tag', {props: {color: "orange"}}, '买家退货,待卖家收货'),]);
} else if (params.row.serviceStatus == "SELLER_CONFIRM") {
return h('div', [h('tag', {props: {color: "gold"}}, '卖家确认收货'),]);
} else if (params.row.serviceStatus == "SELLER_TERMINATION") {
return h('div', [h('tag', {props: {color: "lime"}}, '卖家终止售后'),]);
} else if (params.row.serviceStatus == "BUYER_CANCEL") {
return h('div', [h('tag', {props: {color: "purple"}}, '买家取消售后'),]);
} else if (params.row.serviceStatus == "COMPLETE") {
return h('div', [h('tag', {props: {color: "green"}}, '完成售后'),]);
}else if (params.row.serviceStatus == "WAIT_REFUND") {
return h('div', [h('tag', {props: {color: "geekblue"}}, '待平台退款'),]);
}
}
},
{
title: "申请时间",
key: "createTime",
width: 170
},
{
title: "操作",
key: "action",
align: "center",
fixed: 'right',
width: 200,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"查看"
),
]);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
serviceStatus: [
{title: '全部', value: ''},
{title: '申请售后', value: 'APPLY'},
{title: '通过售后', value: 'PASS'},
{title: '拒绝售后', value: 'REFUSE'},
{title: '待收货', value: 'BUYER_RETURN'},
{title: '确认收货', value: 'SELLER_CONFIRM'},
{title: '完成售后', value: 'COMPLETE'},
{title: '卖家终止售后', value: 'SELLER_TERMINATION'},
{title: '买家取消售后', value: 'BUYER_CANCEL'},
{title: '等待平台退款', value: 'WAIT_REFUND'}
],
currentStatus: '',
afterSaleNumData: {} // 售后数量统计数据
{
title: `通过售后${this.afterSaleNumData.passNum ? "(" + this.afterSaleNumData.passNum + ")" : ""}`,
value: "PASS",
},
{
title: `拒绝售后${this.afterSaleNumData.refuseNum ? "(" + this.afterSaleNumData.refuseNum + ")" : ""}`,
value: "REFUSE",
},
{
title: `待收货${this.afterSaleNumData.buyerReturnNum ? "(" + this.afterSaleNumData.buyerReturnNum + ")" : ""}`,
value: "BUYER_RETURN",
},
{
title: `确认收货${this.afterSaleNumData.sellerConfirmNum ? "(" + this.afterSaleNumData.sellerConfirmNum + ")" : ""}`,
value: "SELLER_CONFIRM",
},
{
title: `完成售后${this.afterSaleNumData.completeNum ? "(" + this.afterSaleNumData.completeNum + ")" : ""}`,
value: "COMPLETE",
},
{
title: `卖家终止售后${this.afterSaleNumData.sellerTerminationNum ? "(" + this.afterSaleNumData.sellerTerminationNum + ")" : ""}`,
value: "SELLER_TERMINATION",
},
{
title: `买家取消售后${this.afterSaleNumData.buyerCancelNum ? "(" + this.afterSaleNumData.buyerCancelNum + ")" : ""}`,
value: "BUYER_CANCEL",
},
{
title: `等待平台退款${this.afterSaleNumData.waitRefundNum ? "(" + this.afterSaleNumData.waitRefundNum + ")" : ""}`,
value: "WAIT_REFUND",
},
];
},
},
methods: {
serviceStatusText(status) {
const map = {
APPLY: "申请中",
PASS: "通过售后",
REFUSE: "拒绝售后",
BUYER_RETURN: "买家退货,待卖家收货",
SELLER_CONFIRM: "卖家确认收货",
SELLER_TERMINATION: "卖家终止售后",
BUYER_CANCEL: "买家取消售后",
COMPLETE: "完成售后",
WAIT_REFUND: "待平台退款",
};
return map[status] || status || "-";
},
methods: {
// 初始化数据
init() {
this.getDataList();
this.getAfterSaleNumData();
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
this.getAfterSaleNumData();
},
// 重置
handleReset() {
const defaultForm = {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
// serviceType:"RETURN_GOODS",
orderSn:"",
memberName:"",
goodsName:""
}
this.searchForm = defaultForm;
this.selectDate = ''
this.getDataList();
this.getAfterSaleNumData();
},
// 范围时间选择格式化
selectDateRange(v) {
if (v) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
}
},
// 获取列表数据
getDataList() {
this.loading = true;
API_Order.afterSaleOrderPage(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
this.total = this.data.length;
this.loading = false;
},
// 获取售后数量统计
getAfterSaleNumData() {
const { serviceStatus, ...searchParams } = this.searchForm;
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
if (res.success) {
this.afterSaleNumData = res.result;
}
});
},
// 退货订单详情
detail(v) {
let sn = v.sn;
this.$options.filters.customRouterPush({
name: "return-goods-order-detail",
query: { sn: sn },
})
},
// 售后筛选
serviceStatusClick(item) {
this.currentStatus = item;
// 如果是全部空字符串则删除serviceStatus字段
if (item === 0) {
delete this.searchForm.serviceStatus;
} else {
this.searchForm.serviceStatus = item;
}
this.getDataList();
this.getAfterSaleNumData();
},
serviceStatusTagType(status) {
const map = {
APPLY: "primary",
PASS: "info",
REFUSE: "warning",
BUYER_RETURN: "warning",
SELLER_CONFIRM: "",
SELLER_TERMINATION: "success",
BUYER_CANCEL: "danger",
COMPLETE: "success",
WAIT_REFUND: "primary",
};
return map[status] || "info";
},
mounted () {
this.init();
init() {
this.getDataList();
this.getAfterSaleNumData();
},
computed: {
// 带数量的售后状态
serviceStatusWithCount() {
return [
{title: '全部', value: ''},
{title: `申请售后${this.afterSaleNumData.applyNum ? '(' + this.afterSaleNumData.applyNum + ')' : ''}`, value: 'APPLY'},
{title: `通过售后${this.afterSaleNumData.passNum ? '(' + this.afterSaleNumData.passNum + ')' : ''}`, value: 'PASS'},
{title: `拒绝售后${this.afterSaleNumData.refuseNum ? '(' + this.afterSaleNumData.refuseNum + ')' : ''}`, value: 'REFUSE'},
{title: `待收货${this.afterSaleNumData.buyerReturnNum ? '(' + this.afterSaleNumData.buyerReturnNum + ')' : ''}`, value: 'BUYER_RETURN'},
{title: `确认收货${this.afterSaleNumData.sellerConfirmNum ? '(' + this.afterSaleNumData.sellerConfirmNum + ')' : ''}`, value: 'SELLER_CONFIRM'},
{title: `完成售后${this.afterSaleNumData.completeNum ? '(' + this.afterSaleNumData.completeNum + ')' : ''}`, value: 'COMPLETE'},
{title: `卖家终止售后${this.afterSaleNumData.sellerTerminationNum ? '(' + this.afterSaleNumData.sellerTerminationNum + ')' : ''}`, value: 'SELLER_TERMINATION'},
{title: `买家取消售后${this.afterSaleNumData.buyerCancelNum ? '(' + this.afterSaleNumData.buyerCancelNum + ')' : ''}`, value: 'BUYER_CANCEL'},
{title: `等待平台退款${this.afterSaleNumData.waitRefundNum ? '(' + this.afterSaleNumData.waitRefundNum + ')' : ''}`, value: 'WAIT_REFUND'}
];
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
this.getAfterSaleNumData();
},
selectDateRange(v) {
if (v) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
} else {
this.searchForm.startDate = "";
this.searchForm.endDate = "";
}
},
// 页面缓存处理从该页面离开时修改KeepAlive为false保证进入该页面是刷新
beforeRouteLeave(to, from, next) {
from.meta.keepAlive = false
next()
}
};
getDataList() {
this.loading = true;
API_Order.getAfterSaleOrderPage(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
},
getAfterSaleNumData() {
const { serviceStatus, ...searchParams } = this.searchForm;
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
if (res.success) {
this.afterSaleNumData = res.result;
}
});
},
detail(v) {
const sn = v.sn;
this.$filters.customRouterPush({
name: "return-goods-order-detail",
query: { sn: sn },
});
},
onStatusTabClick(tab) {
this.serviceStatusClick(tab.paneName);
},
serviceStatusClick(item) {
this.currentStatus = item;
if (item === "" || item === undefined) {
delete this.searchForm.serviceStatus;
} else {
this.searchForm.serviceStatus = item;
}
this.getDataList();
this.getAfterSaleNumData();
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss">
// 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss";
// Tab组件样式
.order-tab {
::v-deep .ivu-tabs-tab {
font-size: 14px;
}
<style lang="scss" scoped>
.order-tab {
:deep(.el-tabs__item) {
font-size: 14px;
}
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.mt_10 {
margin-top: 10px;
}
</style>

View File

@@ -1,298 +1,339 @@
<template>
<div class="search">
<Card>
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="商品" prop="goodsName">
<Input
type="text"
v-model="searchForm.goodsName"
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="关键字" prop="keywords">
<el-input
v-model="searchForm.keywords"
placeholder="请输入商品名称、订单编号搜索"
clearable
placeholder="请输入商品名称"
style="width: 240px"
/>
</Form-item>
<Form-item label="会员名称" prop="memberName">
<Input
type="text"
v-model="searchForm.memberName"
clearable
placeholder="请输入会员名称"
style="width: 240px"
/>
</Form-item>
<Form-item label="订单编号" prop="orderSn">
<Input
type="text"
</el-form-item>
<el-form-item label="订单编号" prop="orderSn">
<el-input
v-model="searchForm.orderSn"
clearable
placeholder="请输入订单编号"
clearable
style="width: 240px"
/>
</Form-item>
<Form-item label="申请时间">
<DatePicker
</el-form-item>
<el-form-item label="售后单号" prop="sn">
<el-input
v-model="searchForm.sn"
placeholder="请输入售后单号"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="会员名称" prop="memberName">
<el-input
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="申请时间">
<el-date-picker
v-model="selectDate"
type="datetimerange"
format="yyyy-MM-dd HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
@on-change="selectDateRange"
placeholder="选择起始时间"
style="width: 240px"
></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Card>
<Card>
<Table
:loading="loading"
border
class="mt_10"
:columns="columns"
:data="data"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 360px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-button class="search-btn" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<div class="order-tab">
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
<el-tab-pane
v-for="item in serviceStatusWithCount"
:key="item.value"
:label="item.title"
:name="item.value"
/>
</el-tabs>
</div>
<el-table
ref="table"
v-loading="loading"
:data="data"
class="mt_10"
style="width: 100%"
>
<!-- 商品栏目格式化 -->
<template slot="goodsSlot" slot-scope="{row}">
<div style="margin-top: 5px;height: 90px; display: flex;">
<div style="">
<img :src="row.goodsImage" style="height: 80px;margin-top: 3px">
</div>
<div style="margin-left: 13px;">
<div class="div-zoom">
<a @click="linkTo(row.goodsId,row.skuId)">{{row.goodsName}}</a>
<el-table-column prop="sn" label="售后服务单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="orderSn" label="订单编号" min-width="120" show-overflow-tooltip />
<el-table-column label="商品" min-width="300">
<template #default="{ row }">
<div v-if="row" style="margin-top: 5px; height: 80px; display: flex">
<div>
<img
:src="row.goodsImage"
style="width: 60px; height: 60px; margin-top: 3px; object-fit: cover; border-radius: 4px"
alt=""
/>
</div>
<Poptip trigger="hover" title="扫码在手机中查看" transfer>
<div slot="content">
<vue-qr :text="wapLinkTo(row.goodsId,row.skuId)" :margin="0" colorDark="#000" colorLight="#fff" :size="150"></vue-qr>
<div style="margin-left: 13px">
<div class="div-zoom">
<a class="link-text" @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
</div>
<img src="../../../assets/qrcode.svg" class="hover-pointer" width="20" height="20" alt="">
</Poptip>
<div style="color: #999; font-size: 12px; margin-top: 5px">
商品ID: {{ row.goodsId }}
</div>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="memberId" label="会员ID" min-width="120" show-overflow-tooltip />
<el-table-column prop="memberName" label="会员名称" width="140" />
<el-table-column label="售后金额" width="110">
<template #default="{ row }">
<span v-if="row" :style="{ color: $mainColor }">
{{ $filters.unitPrice(row.applyRefundPrice, "") }}</span>
</template>
</el-table-column>
<el-table-column label="售后状态" width="180">
<template #default="{ row }">
<el-tag
v-if="row"
:type="serviceStatusTagType(row.serviceStatus)"
effect="plain"
>
{{ serviceStatusText(row.serviceStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="申请时间" width="180" />
<el-table-column label="操作" fixed="right" align="center" width="100">
<template #default="{ row }">
<a v-if="row" class="link-text" @click="detail(row)">查看</a>
</template>
</el-table-column>
</el-table>
</template>
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import * as API_Order from "@/api/order";
import * as API_Order from "@/api/order";
export default {
name: "returnMoneyOrder",
data() {
return {
loading: true, // 表单加载状态
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
serviceType:"RETURN_MONEY",
orderSn:"",
memberName:"",
goodsName:""
const createDefaultSearchForm = () => ({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
serviceType: "RETURN_MONEY",
orderSn: "",
memberName: "",
serviceStatus: "",
sn: "",
keywords: "",
});
export default {
name: "returnMoneyOrder",
data() {
return {
loading: true,
searchForm: createDefaultSearchForm(),
selectDate: null,
data: [],
total: 0,
currentStatus: "",
afterSaleNumData: {},
};
},
computed: {
serviceStatusWithCount() {
return [
{ title: "全部", value: "" },
{
title: `申请售后${this.afterSaleNumData.applyNum ? "(" + this.afterSaleNumData.applyNum + ")" : ""}`,
value: "APPLY",
},
selectDate: null,
submitLoading: false, // 添加或编辑提交状态
columns: [
// 表头
{
title: "退款编号",
key: "sn",
minWidth: 150,
},
{
title: "订单号",
key: "orderSn",
minWidth: 150,
},
{
title: "商品",
key: "sn",
minWidth: 250,
sortable: false,
slot: "goodsSlot",
},
{
title: "申请退款金额",
key: "applyRefundPrice",
width: 130,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.applyRefundPrice,color:this.$mainColor}} );
},
},
{
title: "会员",
key: "memberName",
minWidth: 130,
tooltip: true
},
{
title: "申请时间",
key: "createTime",
width: 170
},
{
title: "售后状态",
key: "serviceStatus",
minWidth: 120,
render: (h, params) => {
if (params.row.serviceStatus == "APPLY") {
return h('div', [h('tag', {props: {color: "blue"}}, '申请中'),]);
} else if (params.row.serviceStatus == "PASS") {
return h('div', [h('tag', {props: {color: "cyan"}}, '通过售后'),]);
} else if (params.row.serviceStatus == "REFUSE") {
return h('div', [h('tag', {props: {color: "volcano"}}, '拒绝售后'),]);
} else if (params.row.serviceStatus == "BUYER_CANCEL") {
return h('div', [h('tag', {props: {color: "purple"}}, '买家取消售后'),]);
} else if (params.row.serviceStatus == "COMPLETE") {
return h('div', [h('tag', {props: {color: "green"}}, '完成售后'),]);
}else if (params.row.serviceStatus == "WAIT_REFUND") {
return h('div', [h('tag', {props: {color: "geekblue"}}, '待平台退款'),]);
}
}
},
{
title: "操作",
key: "action",
align: "center",
fixed: 'right',
width: 200,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"查看"
),
]);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
};
{
title: `通过售后${this.afterSaleNumData.passNum ? "(" + this.afterSaleNumData.passNum + ")" : ""}`,
value: "PASS",
},
{
title: `拒绝售后${this.afterSaleNumData.refuseNum ? "(" + this.afterSaleNumData.refuseNum + ")" : ""}`,
value: "REFUSE",
},
{
title: `完成售后${this.afterSaleNumData.completeNum ? "(" + this.afterSaleNumData.completeNum + ")" : ""}`,
value: "COMPLETE",
},
{
title: `卖家终止售后${this.afterSaleNumData.sellerTerminationNum ? "(" + this.afterSaleNumData.sellerTerminationNum + ")" : ""}`,
value: "SELLER_TERMINATION",
},
{
title: `买家取消售后${this.afterSaleNumData.buyerCancelNum ? "(" + this.afterSaleNumData.buyerCancelNum + ")" : ""}`,
value: "BUYER_CANCEL",
},
{
title: `等待平台退款${this.afterSaleNumData.waitRefundNum ? "(" + this.afterSaleNumData.waitRefundNum + ")" : ""}`,
value: "WAIT_REFUND",
},
];
},
methods: {
// 初始化数据
init() {
this.getDataList();
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 重置
handleReset() {
this.searchForm = {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
serviceType:"RETURN_MONEY",
orderSn:"",
memberName:"",
goodsName:""
}
this.selectDate = ''
this.getDataList();
},
// 范围时间重新赋值
selectDateRange(v) {
if (v) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
}
},
// 获取列表数据
getDataList() {
this.loading = true;
API_Order.afterSaleOrderPage(this.searchForm).then((res) => {
this.loading = false;
},
methods: {
serviceStatusText(status) {
const map = {
APPLY: "申请中",
PASS: "通过售后",
REFUSE: "拒绝售后",
BUYER_RETURN: "买家退货,待卖家收货",
SELLER_CONFIRM: "卖家确认收货",
SELLER_TERMINATION: "卖家终止售后",
BUYER_CANCEL: "买家取消售后",
COMPLETE: "完成售后",
WAIT_REFUND: "待平台退款",
};
return map[status] || status || "-";
},
serviceStatusTagType(status) {
const map = {
APPLY: "primary",
PASS: "info",
REFUSE: "warning",
BUYER_RETURN: "warning",
SELLER_CONFIRM: "",
SELLER_TERMINATION: "success",
BUYER_CANCEL: "danger",
COMPLETE: "success",
WAIT_REFUND: "primary",
};
return map[status] || "info";
},
init() {
this.getDataList();
this.getAfterSaleNumData();
},
changePage() {
this.getDataList();
},
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
this.getAfterSaleNumData();
},
handleReset() {
this.searchForm = createDefaultSearchForm();
this.selectDate = null;
this.currentStatus = "";
this.getDataList();
this.getAfterSaleNumData();
},
selectDateRange(v) {
if (v) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
} else {
this.searchForm.startDate = "";
this.searchForm.endDate = "";
}
},
getDataList() {
this.loading = true;
API_Order.getAfterSaleOrderPage(this.searchForm)
.then((res) => {
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
this.total = this.data.length;
this.loading = false;
},
// 退款订单详情
detail(v) {
let sn = v.sn;
this.$options.filters.customRouterPush({
name: "return-goods-order-detail",
query: { sn: sn },
})
},
.finally(() => {
this.loading = false;
});
},
mounted() {
this.init();
getAfterSaleNumData() {
const { serviceStatus, ...searchParams } = this.searchForm;
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
if (res.success) {
this.afterSaleNumData = res.result;
}
});
},
// 页面缓存处理从该页面离开时修改KeepAlive为false保证进入该页面是刷新
beforeRouteLeave(to, from, next) {
from.meta.keepAlive = false
next()
}
};
detail(v) {
this.$filters.customRouterPush({
name: "return-goods-order-detail",
query: { sn: v.sn },
});
},
onStatusTabClick(tab) {
this.serviceStatusClick(tab.paneName);
},
serviceStatusClick(item) {
this.currentStatus = item;
if (item === "" || item === undefined) {
delete this.searchForm.serviceStatus;
} else {
this.searchForm.serviceStatus = item;
}
this.getDataList();
this.getAfterSaleNumData();
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss">
// 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss";
<style lang="scss" scoped>
.order-tab {
:deep(.el-tabs__item) {
font-size: 14px;
}
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.mt_10 {
margin-top: 10px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +1,78 @@
<template>
<Card>
<el-card>
<div class="step-list">
<div class="step-item" @click="handleCheckStep(item)" :class="{'active':item.checked}" v-for="(item,index) in stepList" :key="index">
<img class="img" :src="item.img" alt="">
<div
v-for="(item, index) in stepList"
:key="index"
class="step-item"
:class="{ active: item.checked }"
@click="handleCheckStep(item)"
>
<img class="img" :src="item.img" alt="" />
<div>
<h2>{{item.title}}</h2>
<h2>{{ item.title }}</h2>
</div>
</div>
</div>
<div v-for="(item,index) in stepList" :key="index">
<!-- 下载 -->
<div v-if="item.checked && index ==0" class="tpl">
<Button @click="downLoad">下载导入模板</Button>
<div v-for="(item, index) in stepList" :key="'step-' + index">
<div v-if="item.checked && index === 0" class="tpl">
<el-button @click="downLoad">下载导入模板</el-button>
</div>
<!-- 上传 -->
<div v-if="item.checked && index ==1" class="tpl">
<Upload :before-upload="handleUpload" name="files" style="width:50%; height:400px;" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
multiple type="drag" :action="action" :headers="accessToken">
<div style="padding: 50px 0">
<Icon type="ios-cloud-upload" size="102" style="color: #3399ff"></Icon>
<h2>选择或拖拽文件上传</h2>
</div>
</Upload>
<div v-if="item.checked && index === 1" class="tpl">
<el-upload
drag
name="files"
style="width: 50%; height: 400px"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:action="action"
:headers="accessToken"
:before-upload="handleUpload"
:show-file-list="false"
>
<el-icon :size="102" style="color: #3399ff"><UploadFilled /></el-icon>
<h2>选择或拖拽文件上传</h2>
</el-upload>
</div>
<!-- 上传 -->
<div v-if="item.checked && index ==2" class="tpl success">
<div v-if="item.checked && index === 2" class="tpl success">
<h1>发货完成</h1>
<div>
<Button class="btn" @click="close">关闭页面</Button>
<Button class="btn" type="primary" @click="navigationToGoodsOrder">商品订单</Button>
<el-button class="btn" @click="close">关闭页面</el-button>
<el-button class="btn" type="primary" @click="navigationToGoodsOrder">商品订单</el-button>
</div>
</div>
</div>
</Card>
</el-card>
</template>
<script>
import JsonExcel from "vue-json-excel";
import { UploadFilled } from "@element-plus/icons-vue";
import { downLoadDeliverExcel, uploadDeliverExcel } from "@/api/order.js";
import { baseUrl } from "@/libs/axios.js";
import downloadImg from "@/assets/download.png";
import uploadImg from "@/assets/upload.png";
import successImg from "@/assets/success.png";
export default {
components: {
"download-excel": JsonExcel,
},
components: { UploadFilled },
data() {
return {
file: "",
action: baseUrl + "/order/order/batchDeliver", // 上传接口
accessToken: {}, // 验证token
// 步骤集合
action: baseUrl + "/order/order/batchDeliver",
accessToken: {},
stepList: [
{
img: require("@/assets/download.png"),
img: downloadImg,
title: "1.下载批量发货导入模板",
checked: true,
},
{
img: require("@/assets/upload.png"),
img: uploadImg,
title: "2.上传数据",
checked: false,
},
{
img: require("@/assets/success.png"),
img: successImg,
title: "3.完成",
checked: false,
},
@@ -78,75 +83,55 @@ export default {
this.accessToken.accessToken = this.getStore("accessToken");
},
methods: {
// 点击选择步骤
handleCheckStep(val) {
if (val.title.search("3") == -1) {
this.stepList.map((item) => {
if (val.title.search("3") === -1) {
this.stepList.forEach((item) => {
item.checked = false;
});
val.checked = true;
}
},
// 上传数据
handleUpload(file) {
this.file = file;
this.upload();
return false;
},
// 跳转订单列表
navigationToGoodsOrder() {
this.$router.push({
path: "/order/orderList",
});
this.$router.push({ path: "/order/orderList" });
},
// 关闭页面
close() {
this.$store.commit("removeTag", "export-order-deliver");
localStorage.storeOpenedList = JSON.stringify(
this.$store.state.app.storeOpenedList
);
localStorage.storeOpenedList = JSON.stringify(this.$store.state.app.storeOpenedList);
this.$router.go(-1);
},
/**
* 上传文件
*/
async upload() {
let fd = new FormData();
const fd = new FormData();
fd.append("files", this.file);
let res = await uploadDeliverExcel(fd);
const res = await uploadDeliverExcel(fd);
if (res.success) {
this.stepList.map((item) => {
this.stepList.forEach((item) => {
item.checked = false;
});
this.stepList[2].checked = true;
}
},
/**
* 下载excel
*/
downLoad() {
downLoadDeliverExcel()
.then((res) => {
const blob = new Blob([res], {
type: "application/vnd.ms-excel;charset=utf-8",
});
//对于<a>标签,只有 Firefox 和 Chrome内核 支持 download 属性
//IE10以上支持blob但是依然不支持download
if ("download" in document.createElement("a")) {
//支持a标签download的浏览器
const link = document.createElement("a"); //创建a标签
link.download = "批量发货导入模板.xls"; //a标签添加属性
const link = document.createElement("a");
link.download = "批量发货导入模板.xls";
link.style.display = "none";
link.href = URL.createObjectURL(blob);
document.body.appendChild(link);
link.click(); //执行下载
URL.revokeObjectURL(link.href); //释放url
document.body.removeChild(link); //释放标签
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
} else {
navigator.msSaveBlob(blob, fileName);
navigator.msSaveBlob(blob, "批量发货导入模板.xls");
}
})
.catch((err) => {
@@ -201,7 +186,7 @@ img {
font-size: 28px;
margin: 10px;
}
::v-deep .btn {
:deep(.btn) {
margin: 10px;
}
}

View File

@@ -1,543 +1,415 @@
<template>
<div class="search">
<Card>
<Form
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
:label-width="70"
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<Form-item label="关键字" prop="keywords" style="display: block; width: 100%;">
<Input
type="text"
<el-form-item label="关键字" prop="keywords" style="display: block; width: 100%">
<el-input
v-model="searchForm.keywords"
placeholder="请输入商品名称/收货人/收货人手机号/店铺名称"
clearable
style="width: 500px"
/>
</Form-item>
<Form-item label="订单号" prop="orderSn">
<Input
type="text"
v-model="searchForm.orderSn"
clearable
placeholder="请输入订单编号"
style="width: 240px"
/>
</Form-item>
<Form-item label="会员名称" prop="buyerName">
<Input
type="text"
v-model="searchForm.buyerName"
clearable
placeholder="请输入会员名称"
style="width: 240px"
/>
</Form-item>
<Form-item label="收货人" prop="shipName">
<Input
type="text"
v-model="searchForm.shipName"
placeholder="请输入收货人姓名"
clearable
style="width: 240px"
/>
</Form-item>
<Form-item label="订单类型" prop="orderType">
<Select
v-model="searchForm.orderPromotionType"
placeholder="请选择"
clearable
style="width: 240px"
>
<Option value="NORMAL">普通订单</Option>
<Option value="PINTUAN">拼团订单</Option>
<Option value="GIFT">赠品订单</Option>
<Option value="POINTS">积分订单</Option>
<Option value="KANJIA">砍价订单</Option>
</Select>
</Form-item>
<Form-item label="下单时间">
<DatePicker
</el-form-item>
<el-form-item label="订单号" prop="orderSn">
<el-input v-model="searchForm.orderSn" placeholder="请输入订单号" clearable style="width: 240px" />
</el-form-item>
<el-form-item label="会员名称" prop="buyerName">
<el-input v-model="searchForm.buyerName" placeholder="请输入会员名称" clearable style="width: 240px" />
</el-form-item>
<el-form-item label="商品名称" prop="goodsName">
<el-input v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 240px" />
</el-form-item>
<el-form-item label="收货人" prop="shipName">
<el-input v-model="searchForm.shipName" placeholder="请输入收货人姓名" clearable style="width: 240px" />
</el-form-item>
<el-form-item label="订单类型" prop="orderType">
<el-select v-model="searchForm.orderPromotionType" placeholder="请选择" clearable style="width: 240px">
<el-option label="普通订单" value="NORMAL" />
<el-option label="拼团订单" value="PINTUAN" />
<el-option label="赠品订单" value="GIFT" />
<el-option label="积分订单" value="POINTS" />
<el-option label="砍价订单" value="KANJIA" />
</el-select>
</el-form-item>
<el-form-item label="支付方式" prop="paymentMethod">
<el-select v-model="searchForm.paymentMethod" placeholder="请选择支付方式" clearable style="width: 240px">
<el-option label="微信支付" value="WECHAT" />
<el-option label="支付宝" value="ALIPAY" />
<el-option label="余额支付" value="WALLET" />
<el-option label="线下转账" value="BANK_TRANSFER" />
</el-select>
</el-form-item>
<el-form-item label="下单时间">
<el-date-picker
v-model="selectDate"
type="datetimerange"
format="yyyy-MM-dd"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
@on-change="selectDateRange"
placeholder="选择起始时间"
style="width: 240px"
></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn"
>搜索</Button
>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Card>
<Card>
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 360px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<div class="order-tab">
<Tabs v-model="currentStatus" @on-click="orderStatusClick">
<TabPane v-for="(item,index) in orderStatusWithCount" :key="index" :label="item.title" :name="item.value">
</TabPane>
</Tabs>
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
<el-tab-pane
v-for="(item, index) in orderStatusWithCount"
:key="index"
:label="item.title"
:name="item.value"
/>
</el-tabs>
</div>
<div class="export">
<Button type="primary" class="mr_10" @click="expressOrderDeliver">批量发货</Button>
<Button @click="exportOrder" type="info" class="export">导出订单</Button>
<Poptip @keydown.enter.native="orderVerification" placement="bottom-start" width="400">
<Button class="export">
核验订单
</Button>
<div class="api" slot="content">
<h2>核验码</h2>
<div style="margin:10px 0;">
<Input v-model="orderCode" style="width:300px; margin-right:10px;" />
<Button style="primary" @click="orderVerification">核验</Button>
</div>
</div>
</Poptip>
<div>
<el-button type="info" class="export" @click="exportOrder">导出订单</el-button>
</div>
<Table
:loading="loading"
:columns="columns"
:data="data"
ref="table"
></Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
<el-table v-loading="loading" :data="data" ref="table" class="mt_10" style="width: 100%">
<el-table-column prop="sn" label="订单号" min-width="240" show-overflow-tooltip />
<el-table-column label="订单来源" width="120">
<template #default="{ row }">{{ clientTypeText(row.clientType) }}</template>
</el-table-column>
<el-table-column label="订单类型" width="120">
<template #default="{ row }">
<el-tag :type="orderPromotionTagType(row.orderPromotionType)">
{{ orderPromotionText(row.orderPromotionType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="memberName" label="买家名称" min-width="130" show-overflow-tooltip />
<el-table-column prop="memberId" label="会员ID" min-width="120" show-overflow-tooltip />
<el-table-column prop="storeName" label="店铺名称" min-width="150" show-overflow-tooltip />
<el-table-column label="订单金额" min-width="100">
<template #default="{ row }">
<span :style="{ color: $mainColor }">{{ $filters.unitPrice(row.flowPrice, '¥') }}</span>
</template>
</el-table-column>
<el-table-column label="支付方式" width="120">
<template #default="{ row }">{{ paymentMethodText(row.paymentMethod) }}</template>
</el-table-column>
<el-table-column label="订单状态" min-width="100">
<template #default="{ row }">
<el-tag :type="orderStatusTagType(row.orderStatus)">{{ orderStatusText(row.orderStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="下单时间" width="170" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<a class="link-text" @click="detail(row)">查看</a>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import * as API_Order from "@/api/order";
import JsonExcel from "vue-json-excel";
import Cookies from "js-cookie";
import {verificationCode} from "@/api/order";
export default {
name: "orderList",
components: {
"download-excel": JsonExcel,
},
data() {
return {
loading: true, // 表单加载状态
fields: {
订单编号: "sn",
下单时间: "createTime",
客户名称: "memberName",
支付方式: {
field: "clientType",
callback: (value) => {
if (value == "H5") return "移动端";
if (value == "PC") return "PC端";
if (value == "WECHAT_MP") return "小程序端";
if (value == "APP") return "移动应用端";
return value;
},
},
商品数量: "groupNum",
付款状态: {
field: "payStatus",
callback: (value) =>
value == "UNPAID" ? "未付款" : value == "PAID" ? "已付款" : "",
},
店铺: "storeName",
},
loading: true,
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "", // 默认排序字段
order: "", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
orderType: "",
orderSn: "",
keywords: "",
buyerName: "",
goodsName: "",
shipName: "",
orderStatus: "",
orderType: "NORMAL",
paymentMethod: "",
orderPromotionType: "",
},
selectDate: null,
columns: [
{
title: "订单号",
key: "sn",
minWidth: 200,
tooltip: true,
render: (h, params) => {
if (params.row.sellerRemark) {
return h("div", {}, params.row.sellerRemark + " ("+params.row.sn+")");
} else {
return h("div", {}, params.row.sn);
}
},
},
{
title: "订单来源",
key: "clientType",
width: 120,
render: (h, params) => {
if (params.row.clientType == "H5") {
return h("div", {}, "移动端");
} else if (params.row.clientType == "PC") {
return h("div", {}, "PC端");
} else if (params.row.clientType == "WECHAT_MP") {
return h("div", {}, "小程序端");
} else if (params.row.clientType == "APP") {
return h("div", {}, "APP端");
} else {
return h("div", {}, params.row.clientType);
}
},
},
{
title: "订单类型",
key: "orderPromotionType",
width: 120,
render: (h, params) => {
if (params.row.orderPromotionType == "NORMAL") {
return h("div", [
h("tag", { props: { color: "blue" } }, "普通订单"),
]);
} else if (params.row.orderPromotionType == "PINTUAN") {
return h("div", [
h("tag", { props: { color: "volcano" } }, "拼团订单"),
]);
} else if (params.row.orderPromotionType == "GIFT") {
return h("div", [
h("tag", { props: { color: "green" } }, "赠品订单"),
]);
} else if (params.row.orderPromotionType == "POINTS") {
return h("div", [
h("tag", { props: { color: "geekblue" } }, "积分订单"),
]);
} else if (params.row.orderPromotionType == "KANJIA") {
return h("div", [
h("tag", { props: { color: "pink" } }, "砍价订单"),
]);
}
},
},
{
title: "买家名称",
key: "memberName",
minWidth: 130,
tooltip: true,
},
{
title: "会员ID",
key: "memberId",
minWidth: 120,
tooltip: true,
},
{
title: "订单金额",
key: "flowPrice",
minWidth: 100,
tooltip: true,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.flowPrice,color:this.$mainColor}} );
},
},
{
title: "订单状态",
key: "orderStatus",
minWidth: 100,
render: (h, params) => {
if (params.row.orderStatus == "UNPAID") {
return h("div", [
h("tag", { props: { color: "magenta" } }, "未付款"),
]);
} else if (params.row.orderStatus == "PAID") {
return h("div", [
h("tag", { props: { color: "blue" } }, "已付款"),
]);
} else if (params.row.orderStatus == "UNDELIVERED") {
return h("div", [
h("tag", { props: { color: "geekblue" } }, "待发货"),
]);
} else if (params.row.orderStatus == "STAY_PICKED_UP") {
return h("div", [
h("tag", { props: { color: "geekblue" } }, "待自提"),
]);
} else if (params.row.orderStatus == "PARTS_DELIVERED") {
return h("div", [
h("tag", { props: { color: "cyan" } }, "部分发货"),
]);
} else if (params.row.orderStatus == "DELIVERED") {
return h("div", [
h("tag", { props: { color: "cyan" } }, "已发货"),
]);
} else if (params.row.orderStatus == "COMPLETED") {
return h("div", [
h("tag", { props: { color: "green" } }, "已完成"),
]);
} else if (params.row.orderStatus == "TAKE") {
return h("div", [
h("tag", { props: { color: "volcano" } }, "待核验"),
]);
} else if (params.row.orderStatus == "CANCELLED") {
return h("div", [
h("tag", { props: { color: "red" } }, "已取消"),
]);
}
},
},
{
title: "支付方式",
key: "paymentMethod",
width: 120,
render: (h, params) => {
if (params.row.paymentMethod == "NOT_ACTUALLY_PAID") {
return h("div", {}, "-");
} else if (params.row.paymentMethod == "WECHAT") {
return h("div", {}, "微信支付");
} else if (params.row.paymentMethod == "ALIPAY") {
return h("div", {}, "支付宝");
} else if (params.row.paymentMethod == "WALLET") {
return h("div", {}, "余额支付");
} else if (params.row.paymentMethod == "BANK_TRANSFER") {
return h("div", {}, "线下转账");
} else {
return h("div", {}, params.row.paymentMethod || "-");
}
},
},
{
title: "下单时间",
key: "createTime",
width: 170,
},
{
title: "操作",
key: "action",
align: "center",
fixed: "right",
width: 200,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"查看"
),
]);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
orderNumData: {}, // 新增:订单数量统计数据
excelColumns: {
// 导出excel的参数
编号: "index",
订单号: "sn",
收货人: "consigneeName",
收货人联系电话: "consigneeMobile",
收货地址: "consigneeAddress",
商品名称: "goodsName",
商品价格: "goodsPrice",
订单金额: "flowPrice",
商品数量: "num",
店铺名称: "storeName",
创建时间: "createTime",
},
orderStatus: [
{title: '全部', value: ''},
{title: '未付款', value: 'UNPAID'},
{title: '已付款', value: 'PAID'},
{title: '待发货', value: 'UNDELIVERED'},
{title: '部分发货', value: 'PARTS_DELIVERED'},
{title: '已发货', value: 'DELIVERED'},
{title: '待核验', value: 'TAKE'},
{title: '待自提', value: 'STAY_PICKED_UP'},
{title: '已完成', value: 'COMPLETED'},
{title: '已关闭', value: 'CANCELLED'},
],
currentStatus: ''
data: [],
total: 0,
orderNumData: {},
currentStatus: "ALL",
};
},
computed: {
// 新增:带数量的订单状态选项
orderStatusWithCount() {
return [
{title: '全部', value: ''},
{title: `未付款${this.orderNumData.waitPayNum ? '(' + this.orderNumData.waitPayNum + ')' : ''}`, value: 'UNPAID'},
{title: `付款${this.orderNumData.waitDeliveryNum ? '(' + this.orderNumData.waitDeliveryNum + ')' : ''}`, value: 'PAID'},
{title: `待发货${this.orderNumData.waitShipNum ? '(' + this.orderNumData.waitShipNum + ')' : ''}`, value: 'UNDELIVERED'},
{title: `部分发货${this.orderNumData.partsDeliveredNumNum ? '(' + this.orderNumData.partsDeliveredNumNum + ')' : ''}`, value: 'PARTS_DELIVERED'},
{title: `已发货${this.orderNumData.deliveredNum ? '(' + this.orderNumData.deliveredNum + ')' : ''}`, value: 'DELIVERED'},
{title: `待核验${this.orderNumData.waitCheckNum ? '(' + this.orderNumData.waitCheckNum + ')' : ''}`, value: 'TAKE'},
{title: `待自提${this.orderNumData.waitSelfPickNum ? '(' + this.orderNumData.waitSelfPickNum + ')' : ''}`, value: 'STAY_PICKED_UP'},
{title: `已完成${this.orderNumData.finishNum ? '(' + this.orderNumData.finishNum + ')' : ''}`, value: 'COMPLETED'},
{title: `已关闭${this.orderNumData.closeNum ? '(' + this.orderNumData.closeNum + ')' : ''}`, value: 'CANCELLED'},
{ title: "全部", value: "ALL" },
{
title: `付款${this.orderNumData.waitPayNum ? "(" + this.orderNumData.waitPayNum + ")" : ""}`,
value: "UNPAID",
},
{
title: `已付款${this.orderNumData.waitDeliveryNum ? "(" + this.orderNumData.waitDeliveryNum + ")" : ""}`,
value: "PAID",
},
{
title: `待发货${this.orderNumData.waitShipNum ? "(" + this.orderNumData.waitShipNum + ")" : ""}`,
value: "UNDELIVERED",
},
{
title: `部分发货${this.orderNumData.partsDeliveredNumNum ? "(" + this.orderNumData.partsDeliveredNumNum + ")" : ""}`,
value: "PARTS_DELIVERED",
},
{
title: `待收货${this.orderNumData.deliveredNum ? "(" + this.orderNumData.deliveredNum + ")" : ""}`,
value: "DELIVERED",
},
{
title: `待核验${this.orderNumData.waitCheckNum ? "(" + this.orderNumData.waitCheckNum + ")" : ""}`,
value: "TAKE",
},
{
title: `待自提${this.orderNumData.waitSelfPickNum ? "(" + this.orderNumData.waitSelfPickNum + ")" : ""}`,
value: "STAY_PICKED_UP",
},
{
title: `已完成${this.orderNumData.finishNum ? "(" + this.orderNumData.finishNum + ")" : ""}`,
value: "COMPLETED",
},
{
title: `已关闭${this.orderNumData.closeNum ? "(" + this.orderNumData.closeNum + ")" : ""}`,
value: "CANCELLED",
},
];
}
},
},
methods: {
/**
* 核验订单
*/
async orderVerification() {
let result = await verificationCode(this.orderCode);
if (result.success) {
this.$router.push({
name: "order-detail",
query: { sn: result.result.sn || this.orderCode },
});
}
onStatusTabClick(tab) {
this.orderStatusClick(tab.paneName);
},
/**
* 批量发货
*/
expressOrderDeliver() {
this.$router.push({
path: "/export-order-deliver",
});
clientTypeText(v) {
const map = { H5: "移动端", PC: "PC端", WECHAT_MP: "小程序端", APP: "移动应用端" };
return map[v] || v || "-";
},
orderPromotionText(v) {
const map = {
NORMAL: "普通订单",
PINTUAN: "拼团订单",
GIFT: "赠品订单",
POINTS: "积分订单",
KANJIA: "砍价订单",
};
return map[v] || v || "-";
},
orderPromotionTagType(v) {
const map = {
NORMAL: "primary",
PINTUAN: "danger",
GIFT: "success",
POINTS: "info",
KANJIA: "warning",
};
return map[v] || "info";
},
paymentMethodText(v) {
const map = {
NOT_ACTUALLY_PAID: "-",
WECHAT: "微信支付",
ALIPAY: "支付宝",
WALLET: "余额支付",
BANK_TRANSFER: "线下转账",
};
return map[v] || v || "-";
},
orderStatusText(v) {
const map = {
UNPAID: "未付款",
PAID: "已付款",
UNDELIVERED: "待发货",
STAY_PICKED_UP: "待自提",
PARTS_DELIVERED: "部分发货",
DELIVERED: "已发货",
COMPLETED: "已完成",
TAKE: "待核验",
CANCELLED: "已关闭",
};
return map[v] || v || "-";
},
orderStatusTagType(v) {
const map = {
UNPAID: "danger",
PAID: "primary",
UNDELIVERED: "info",
STAY_PICKED_UP: "info",
PARTS_DELIVERED: "warning",
DELIVERED: "warning",
COMPLETED: "success",
TAKE: "warning",
CANCELLED: "danger",
};
return map[v] || "info";
},
// 初始化数据
init() {
this.getDataList();
this.getOrderNumData(); // 新增:获取订单数量统计
this.getOrderNumData();
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getDataList();
},
// 搜索订单
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
this.getOrderNumData(); // 新增:搜索时也更新数量统计
this.getOrderNumData();
},
// 重置
handleReset() {
this.searchForm = {};
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.selectDate = null;
this.searchForm.startDate = "";
this.searchForm.endDate = "";
this.searchForm.orderType = "NORMAL",
// 重新加载数据
this.getDataList();
},
// 起始时间处理
selectDateRange(v) {
if (v) {
if (v && v.length === 2) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
} else {
this.searchForm.startDate = "";
this.searchForm.endDate = "";
}
},
// 获取表格数据
getDataList() {
this.loading = true;
API_Order.getOrderList(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
API_Order.getOrderList(this.searchForm)
.then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
})
.catch(() => {
this.loading = false;
});
},
detail(v) {
this.$filters.customRouterPush({
name: "order-detail",
query: { sn: v.sn },
});
},
// 导出订单
async exportOrder() {
if(this.searchForm.startDate==""||this.searchForm.endDate==""){
if (!this.searchForm.startDate || !this.searchForm.endDate) {
this.$Message.error("必须选择时间范围,搜索后进行导出!");
}else{
API_Order.exportOrder(this.searchForm)
.then((res) => {
const blob = new Blob([res], {
type: "application/vnd.ms-excel;charset=utf-8",
});
//对于<a>标签,只有 Firefox 和 Chrome内核 支持 download 属性
//IE10以上支持blob但是依然不支持download
if ("download" in document.createElement("a")) {
//支持a标签download的浏览器
const link = document.createElement("a"); //创建a标签
link.download = "订单列表.xlsx"; //a标签添加属性
link.style.display = "none";
link.href = URL.createObjectURL(blob);
document.body.appendChild(link);
link.click(); //执行下载
URL.revokeObjectURL(link.href); //释放url
document.body.removeChild(link); //释放标签
} else {
navigator.msSaveBlob(blob, fileName);
}
})
.catch((err) => {
console.log(err);
return;
}
API_Order.exportOrder(this.searchForm)
.then((res) => {
const blob = new Blob([res], {
type: "application/vnd.ms-excel;charset=utf-8",
});
}
if ("download" in document.createElement("a")) {
const link = document.createElement("a");
link.download = "订单列表.xlsx";
link.style.display = "none";
link.href = URL.createObjectURL(blob);
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
} else {
navigator.msSaveBlob(blob, "订单列表.xlsx");
}
})
.catch((err) => {
console.log(err);
});
},
// 查看订单详情
detail(v) {
let sn = v.sn;
this.$options.filters.customRouterPush({
name: "order-detail",
query: { sn: sn },
})
},
// 订单筛选
orderStatusClick(name) {
if (name === 0) {
// 点击"全部"时设置为空字符串在getDataList中会被过滤掉
this.searchForm.orderStatus = '';
if (name === "ALL" || name === "" || name === undefined) {
this.searchForm.orderStatus = "";
this.currentStatus = "ALL";
} else {
// 其他状态正常赋值
this.searchForm.orderStatus = name;
this.currentStatus = name;
}
this.currentStatus = name;
this.getDataList();
},
getOrderNumData() {
// 创建一个不包含orderStatus字段的搜索参数
const { orderStatus, ...searchParams } = this.searchForm;
API_Order.getOrderNum(searchParams).then((res) => {
if (res.success) {
this.orderNumData = res.result;
}
}).catch((err) => {
console.error('获取订单数量统计失败:', err);
});
// orderNum 接口仅查 li_order不含 order_item 关联keywords/goodsName 会引用 oi 字段导致 SQL 报错
const { orderStatus, keywords, goodsName, ...searchParams } = this.searchForm;
API_Order.getOrderNum(searchParams)
.then((res) => {
if (res.success) {
this.orderNumData = res.result;
}
})
.catch((err) => {
console.error("获取订单数量统计失败:", err);
});
},
},
mounted() {
this.init();
},
// 页面缓存处理从该页面离开时修改KeepAlive为false保证进入该页面是刷新
beforeRouteLeave(to, from, next) {
from.meta.keepAlive = false;
next();
},
};
</script>
<style lang="scss">
// 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss";
<style lang="scss" scoped>
.export {
margin: 10px 20px 10px 0;
}
// Tab组件样式
.order-tab {
::v-deep .ivu-tabs-tab {
:deep(.el-tabs__item) {
font-size: 14px;
}
}
.link-text {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
</style>

View File

@@ -1,245 +1,191 @@
<template>
<div class="search">
<Card>
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="订单编号" prop="orderSn">
<Input type="text" v-model="searchForm.orderSn" clearable placeholder="请输入订单编号" style="width: 240px" />
</Form-item>
<Form-item label="会员名称" prop="buyerName">
<Input type="text" v-model="searchForm.buyerName" clearable placeholder="请输入会员名称" style="width: 240px" />
</Form-item>
<Form-item label="订单状态" prop="orderStatus">
<Select v-model="searchForm.orderStatus" placeholder="请选择" clearable style="width: 240px">
<Option value="UNPAID">未付款</Option>
<Option value="PAID">已付款</Option>
<Option value="COMPLETED">已完成</Option>
<Option value="TAKE">待核验</Option>
<Option value="CANCELLED">已取消</Option>
</Select>
</Form-item>
<Form-item label="下单时间">
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd" clearable @on-change="selectDateRange" placeholder="选择起始时间" style="width: 240px"></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Card>
<Card>
<div>
<Poptip @keydown.enter.native="orderVerification" placement="bottom-start" width="400">
<Button class="export">
核验订单
</Button>
<div class="api" slot="content">
<h2>核验码</h2>
<div style="margin:10px 0;">
<Input v-model="orderCode" style="width:300px; margin-right:10px;" />
<Button style="primary" @click="orderVerification">核验</Button>
</div>
</div>
</Poptip>
<el-card>
<el-form
ref="searchForm"
:model="searchForm"
inline
label-width="70px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="订单号" prop="orderSn">
<el-input
v-model="searchForm.orderSn"
placeholder="请输入订单号"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="会员名称" prop="buyerName">
<el-input
v-model="searchForm.buyerName"
placeholder="请输入会员名称"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="下单时间">
<el-date-picker
v-model="selectDate"
type="datetimerange"
value-format="YYYY-MM-DD"
clearable
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择起始时间"
style="width: 240px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<div class="order-tab">
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
<el-tab-pane
v-for="(item, index) in orderStatus"
:key="index"
:label="item.title"
:name="item.value"
/>
</el-tabs>
</div>
<Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table" sortable="custom" @on-sort-change="changeSort"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]" size="small"
show-total show-elevator show-sizer></Page>
</Row>
</Card>
<el-table
ref="table"
v-loading="loading"
border
:data="data"
class="mt_10"
style="width: 100%"
@sort-change="changeSort"
>
<el-table-column prop="sn" label="订单号" min-width="230" show-overflow-tooltip />
<el-table-column prop="createTime" label="下单时间" width="200" />
<el-table-column label="订单来源" width="95">
<template #default="{ row }">
<span v-if="row">
<span v-if="row.clientType == 'H5'">移动端</span>
<span v-else-if="row.clientType == 'PC'">PC端</span>
<span v-else-if="row.clientType == 'WECHAT_MP'">小程序端</span>
<span v-else-if="row.clientType == 'APP'">移动应用端</span>
<span v-else>{{ row.clientType }}</span>
</span>
</template>
</el-table-column>
<el-table-column prop="memberName" label="会员名称" width="130" />
<el-table-column label="订单金额" prop="flowPrice" min-width="120" sortable="custom">
<template #default="{ row }">
<priceColorScheme v-if="row" :value="row.flowPrice" :color="$mainColor" />
</template>
</el-table-column>
<el-table-column label="订单状态" width="95">
<template #default="{ row }">
<template v-if="row">
<el-tag v-if="row.orderStatus == 'UNPAID'" type="danger">未付款</el-tag>
<el-tag v-else-if="row.orderStatus == 'PAID'" type="primary">已付款</el-tag>
<el-tag v-else-if="row.orderStatus == 'COMPLETED'" type="success">已完成</el-tag>
<el-tag v-else-if="row.orderStatus == 'TAKE'" type="warning">待核验</el-tag>
<el-tag v-else-if="row.orderStatus == 'CANCELLED'" type="info">已关闭</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="{ row }">
<template v-if="row">
<a
class="link-text"
:class="{ disabled: row.orderStatus != 'UNPAID' }"
@click="row.orderStatus == 'UNPAID' && confirmPrice(row)"
>收款</a>
<span class="op-split">|</span>
<a class="link-text" @click="detail(row)">查看</a>
</template>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
</el-card>
</div>
</template>
<script>
import * as API_Order from "@/api/order";
import { verificationCode } from "@/api/order";
export default {
name: "virtualOrderList",
name: "fictitiousOrderList",
data() {
return {
orderCode: "", // 核验码
loading: true, // 表单加载状态
loading: true,
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
pageNumber: 1,
pageSize: 20,
sort: "",
order: "",
startDate: "",
endDate: "",
orderType: "VIRTUAL",
orderSn: "",
buyerName: "",
orderStatus: "",
orderType: "VIRTUAL",
},
selectDate: null,
columns: [
{
title: "订单号",
key: "sn",
minWidth: 240,
tooltip: true,
},
{
title: "订单来源",
key: "clientType",
width: 120,
render: (h, params) => {
if (params.row.clientType == "H5") {
return h("div", {}, "移动端");
} else if (params.row.clientType == "PC") {
return h("div", {}, "PC端");
} else if (params.row.clientType == "WECHAT_MP") {
return h("div", {}, "小程序端");
} else if (params.row.clientType == "APP") {
return h("div", {}, "移动应用端");
} else {
return h("div", {}, params.row.clientType);
}
},
},
{
title: "买家名称",
key: "memberName",
minWidth: 130,
tooltip: true,
},
{
title: "订单金额",
key: "flowPrice",
minWidth: 100,
tooltip: true,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.flowPrice,color:this.$mainColor}} );
},
},
{
title: "订单状态",
key: "orderStatus",
minWidth: 100,
render: (h, params) => {
if (params.row.orderStatus == "UNPAID") {
return h("div", [h("tag", {props: {color: "magenta"}}, "未付款")]);
} else if (params.row.orderStatus == "PAID") {
return h("div", [h("tag", {props: {color: "blue"}}, "已付款")]);
} else if (params.row.orderStatus == "UNDELIVERED") {
return h("div", [h("tag", {props: {color: "geekblue"}}, "待发货")]);
} else if (params.row.orderStatus == "DELIVERED") {
return h("div", [h("tag", {props: {color: "cyan"}}, "已发货")]);
} else if (params.row.orderStatus == "COMPLETED") {
return h("div", [h("tag", {props: {color: "green"}}, "已完成")]);
} else if (params.row.orderStatus == "TAKE") {
return h("div", [h("tag", {props: {color: "volcano"}}, "待核验")]);
} else if (params.row.orderStatus == "CANCELLED") {
return h("div", [h("tag", {props: {color: "red"}}, "已取消")]);
}
},
},
{
title: "下单时间",
key: "createTime",
width: 170,
sortable: true,
sortType: "desc",
},
{
title: "操作",
key: "action",
align: "center",
width: 100,
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
marginRight: "5px",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"查看"
),
]);
},
},
data: [],
total: 0,
orderStatus: [
{ title: "全部", value: "" },
{ title: "未付款", value: "UNPAID" },
{ title: "已付款", value: "PAID" },
{ title: "待核验", value: "TAKE" },
{ title: "已完成", value: "COMPLETED" },
{ title: "已关闭", value: "CANCELLED" },
],
data: [], // 表单数据
total: 0, // 表单数据总数
currentStatus: "",
};
},
methods: {
/**
* 核验订单
*/
async orderVerification() {
let result = await verificationCode(this.orderCode);
if (result.success) {
this.$router.push({
name: "order-detail",
query: { sn: result.result.sn || this.orderCode },
});
}
},
// 初始化数据
init() {
this.getDataList();
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
changePage() {
this.getDataList();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
changePageSize() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 重置
handleReset() {
this.searchForm = {};
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.orderType = "VIRTUAL";
this.selectDate = null;
this.searchForm.startDate = "";
this.searchForm.endDate = "";
// 重新加载数据
this.getDataList();
},
// 表格排序
changeSort(e) {
this.searchForm.sort = e.key;
this.searchForm.order = e.order;
if (e.order === "normal") {
this.searchForm.order = "";
}
this.searchForm.sort = e.prop;
this.searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
this.getDataList();
},
// 时间段重新赋值
selectDateRange(v) {
if (v) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
}
},
// 获取商品列表
getDataList() {
this.loading = true;
API_Order.getOrderList(this.searchForm).then((res) => {
@@ -250,29 +196,44 @@ export default {
}
});
},
// 跳转详情
confirmPrice(v) {
this.$Modal.confirm({
title: "确认收款",
content: "您确定要收款吗?",
loading: true,
onOk: () => {
API_Order.orderPay(v.sn).then((res) => {
if (res.success) {
this.$Message.success("收款成功");
this.getDataList();
}
this.$Modal.remove();
});
},
});
},
detail(v) {
let sn = v.sn;
this.$options.filters.customRouterPush({
this.$filters.customRouterPush({
name: "order-detail",
query: { sn: sn },
})
query: { sn: sn, orderType: v.orderType },
});
},
onStatusTabClick(tab) {
const item = tab.paneName;
this.currentStatus = item;
this.searchForm.orderStatus = item;
this.getDataList();
},
},
mounted() {
this.init();
},
// 页面缓存处理从该页面离开时修改KeepAlive为false保证进入该页面是刷新
beforeRouteLeave(to, from, next) {
from.meta.keepAlive = false
next()
}
};
</script>
<style lang="scss">
// 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss";
.export {
margin: 10px 20px 10px 0;
<style lang="scss" scoped>
.order-tab {
margin-top: 20px;
}
</style>

View File

@@ -1,52 +1,95 @@
<template>
<div class="search">
<Card>
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="订单编号" prop="orderSn">
<Input type="text" v-model="searchForm.orderSn" clearable placeholder="请输入订单编号" style="width: 240px" />
</Form-item>
<Form-item label="会员名称" prop="memberName">
<Input type="text" v-model="searchForm.memberName" clearable placeholder="请输入会员名称" style="width: 240px" />
</Form-item>
<Form-item label="发票抬头" prop="receiptTitle">
<Input type="text" v-model="searchForm.receiptTitle" clearable placeholder="请输入发票抬头" style="width: 240px" />
</Form-item>
<Form-item label="状态" prop="receiptStatus">
<Select v-model="searchForm.receiptStatus" placeholder="请选择" clearable style="width: 240px">
<Option :value="0">未开票</Option>
<Option :value="1">已开票</Option>
</Select>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
<Button @click="handleReset" class="search-btn">重置</Button>
</Form>
</Card>
<el-card>
<el-form ref="searchForm" :model="searchForm" inline label-width="70px" class="search-form">
<el-form-item label="订单编号" prop="orderSn">
<el-input v-model="searchForm.orderSn" clearable placeholder="请输入订单编号" style="width: 240px" />
</el-form-item>
<el-form-item label="会员名称" prop="memberName">
<el-input v-model="searchForm.memberName" clearable placeholder="请输入会员名称" style="width: 240px" />
</el-form-item>
<el-form-item label="发票抬头" prop="receiptTitle">
<el-input v-model="searchForm.receiptTitle" clearable placeholder="请输入发票抬头" style="width: 240px" />
</el-form-item>
<el-form-item label="状态" prop="receiptStatus">
<el-select v-model="searchForm.receiptStatus" placeholder="请选择" clearable style="width: 240px">
<el-option label="未开票" :value="0" />
<el-option label="已开票" :value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-button class="search-btn" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<Card>
<div class="receipt-tip">
订单状态为已发货/已完成可开票
<el-card>
<div class="receipt-tip">订单状态为已发货/已完成可开票</div>
<el-table ref="table" v-loading="loading" border :data="data" class="mt_10" style="width: 100%">
<el-table-column label="订单号" min-width="120">
<template #default="{ row }">
<a class="link-text" @click="$router.push({ name: 'order-detail', query: { sn: row.orderSn } })">
{{ row.orderSn }}
</a>
</template>
</el-table-column>
<el-table-column prop="memberName" label="会员名称" min-width="90" show-overflow-tooltip />
<el-table-column label="发票抬头" min-width="90" show-overflow-tooltip>
<template #default="{ row }">{{ row.receiptTitle || "暂未填写" }}</template>
</el-table-column>
<el-table-column label="纳税人识别号" min-width="100" show-overflow-tooltip>
<template #default="{ row }">{{ row.taxpayerId || "暂未填写" }}</template>
</el-table-column>
<el-table-column label="发票内容" min-width="90" show-overflow-tooltip>
<template #default="{ row }">{{ row.receiptContent || "暂未填写" }}</template>
</el-table-column>
<el-table-column label="发票金额" width="150">
<template #default="{ row }">
<priceColorScheme :value="row.receiptPrice" :color="$mainColor" />
</template>
</el-table-column>
<el-table-column label="发票状态" width="100">
<template #default="{ row }">
<el-tag :type="Number(row.receiptStatus) === 0 ? 'warning' : 'success'">
{{ Number(row.receiptStatus) === 0 ? "未开票" : "已开票" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="订单状态" width="100">
<template #default="{ row }">
<el-tag :type="orderStatusTagType(row.orderStatus)">{{ orderStatusText(row.orderStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="{ row }">
<a class="link-text" style="margin-right: 12px" @click="openReceiptModal(row, 'detail')">详情</a>
<a
class="link-text"
:class="{ disabled: !canInvoicing(row) }"
@click="canInvoicing(row) && openReceiptModal(row, 'invoicing')"
>
开票
</a>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
size="small"
@current-change="changePage"
@size-change="changePageSize"
/>
</div>
<Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table">
<!-- 订单详情格式化 -->
<template slot="orderSlot" slot-scope="scope">
<a @click="$router.push({name: 'order-detail',query: {sn: scope.row.orderSn}})">{{scope.row.orderSn}}</a>
</template>
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small"
show-total show-elevator show-sizer></Page>
</Row>
</Card>
<Modal
v-model="receiptModalVisible"
title="发票信息"
:mask-closable="false"
width="680"
>
<div v-if="receiptDetailLoading" class="receipt-modal-loading">
<Spin size="large" />
</div>
<div v-else class="receipt-modal-content">
</el-card>
<el-dialog v-model="receiptModalVisible" title="发票信息" :close-on-click-modal="false" width="680px">
<div v-loading="receiptDetailLoading" class="receipt-modal-content">
<div v-if="hasValue(currentReceipt.orderSn)" class="receipt-item">
<span class="receipt-label">订单号</span>
<span class="receipt-value">{{ currentReceipt.orderSn }}</span>
@@ -110,39 +153,31 @@
<div v-if="hasValue(getInvoiceAddress(currentReceipt))" class="receipt-item">
<span class="receipt-label">发票附件</span>
<span class="receipt-value">
<a @click="viewInvoiceFile(getInvoiceAddress(currentReceipt))">查看附件</a>
<a class="link-text" @click="viewInvoiceFile(getInvoiceAddress(currentReceipt))">查看附件</a>
</span>
</div>
</div>
<div slot="footer">
<template #footer>
<template v-if="receiptModalMode === 'invoicing'">
<Upload
<el-upload
:action="uploadFileUrl"
:data="receiptUploadData"
:headers="{ ...accessToken }"
:format="['jpg', 'jpeg', 'png', 'pdf']"
:max-size="10240"
:accept="'.jpg,.jpeg,.png,.pdf'"
:show-file-list="false"
:on-success="handleInvoiceUploadSuccess"
:on-error="handleInvoiceUploadError"
:on-format-error="handleInvoiceFormatError"
:on-exceeded-size="handleInvoiceMaxSize"
:show-upload-list="false"
:before-upload="beforeInvoiceUpload"
style="display: inline-block; margin-right: 8px"
>
<Button :disabled="receiptDetailLoading">上传发票</Button>
</Upload>
<Button @click="receiptModalVisible = false">取消</Button>
<Button
type="primary"
:loading="invoiceSubmitting"
@click="submitInvoicing"
>
确认开票
</Button>
<el-button :disabled="receiptDetailLoading">上传发票</el-button>
</el-upload>
<el-button @click="receiptModalVisible = false">取消</el-button>
<el-button type="primary" :loading="invoiceSubmitting" @click="submitInvoicing">确认开票</el-button>
</template>
<Button v-else @click="receiptModalVisible = false">关闭</Button>
</div>
</Modal>
<el-button v-else @click="receiptModalVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -154,178 +189,61 @@ export default {
name: "receipt",
data() {
return {
loading: true, // 表单加载状态
loading: true,
receiptModalVisible: false,
receiptDetailLoading: false,
invoiceSubmitting: false,
receiptModalMode: "detail",
uploadFileUrl: uploadFile,
accessToken: {},
receiptUploadData: {
directoryPath: "receipt"
},
receiptUploadData: { directoryPath: "receipt" },
currentReceipt: {},
selectedReceiptRow: null,
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
receiptStatus: "", // 发票状态
pageNumber: 1,
pageSize: 10,
sort: "createTime",
order: "desc",
receiptStatus: "",
},
columns: [
{
title: "订单号",
key: "orderSn",
minWidth: 120,
slot: "orderSlot",
},
{
title: "会员名称",
key: "memberName",
minWidth: 90,
tooltip: true,
},
{
title: "发票抬头",
key: "receiptTitle",
minWidth: 90,
tooltip: true,
render: (h, params) => {
return h("div", params.row.receiptTitle || "暂未填写");
},
},
{
title: "纳税人识别号",
key: "taxpayerId",
minWidth: 100,
tooltip: true,
render: (h, params) => {
return h("div", params.row.taxpayerId || "暂未填写");
},
},
{
title: "发票内容",
key: "receiptContent",
minWidth: 90,
tooltip: true,
render: (h, params) => {
return h("div", params.row.receiptContent || "暂未填写");
},
},
{
title: "发票金额",
key: "billPrice",
width: 150,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.receiptPrice,color:this.$mainColor}} );
},
},
{
title: "发票状态",
key: "receiptStatus",
width: 100,
tooltip: true,
render: (h, params) => {
if (Number(params.row.receiptStatus) === 0) {
return h("div", [
h("tag", { props: { color: "volcano" } }, "未开票"),
]);
} else {
return h("div", [
h("tag", { props: { color: "green" } }, "已开票"),
]);
}
},
},
{
title: "订单状态",
key: "orderStatus",
width: 100,
render: (h, params) => {
if (params.row.orderStatus == "UNPAID") {
return h("div", [
h("tag", { props: { color: "magenta" } }, "未付款"),
]);
} else if (params.row.orderStatus == "PAID") {
return h("div", [
h("tag", { props: { color: "blue" } }, "已付款"),
]);
} else if (params.row.orderStatus == "UNDELIVERED") {
return h("div", [
h("tag", { props: { color: "geekblue" } }, "待发货"),
]);
} else if (params.row.orderStatus == "DELIVERED") {
return h("div", [
h("tag", { props: { color: "cyan" } }, "已发货"),
]);
} else if (params.row.orderStatus == "COMPLETED") {
return h("div", [
h("tag", { props: { color: "green" } }, "已完成"),
]);
} else if (params.row.orderStatus == "TAKE") {
return h("div", [
h("tag", { props: { color: "volcano" } }, "待核验"),
]);
} else if (params.row.orderStatus == "CANCELLED") {
return h("div", [
h("tag", { props: { color: "red" } }, "已取消"),
]);
}
},
},
{
title: "操作",
key: "action",
align: "center",
fixed: 'right',
width: 200,
render: (h, params) => {
const disabled = !this.canInvoicing(params.row);
const detailStyle = { color: "#2d8cf0", cursor: "pointer", textDecoration: "none", marginRight: "12px" };
const style = disabled
? { color: "#c5c8ce", cursor: "not-allowed", textDecoration: "none" }
: { color: "#2d8cf0", cursor: "pointer", textDecoration: "none" };
const on = disabled ? {} : { click: () => { this.openReceiptModal(params.row, "invoicing"); } };
return h("div", [
h(
"a",
{
style: detailStyle,
on: { click: () => { this.openReceiptModal(params.row, "detail"); } },
},
"详情"
),
h(
"a",
{
style,
on,
},
"开票"
),
]);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
data: [],
total: 0,
};
},
methods: {
orderStatusText(status) {
const map = {
UNPAID: "未付款",
PAID: "已付款",
UNDELIVERED: "待发货",
DELIVERED: "已发货",
COMPLETED: "已完成",
TAKE: "待核验",
CANCELLED: "已取消",
};
return map[status] || status;
},
orderStatusTagType(status) {
const map = {
UNPAID: "danger",
PAID: "primary",
UNDELIVERED: "",
DELIVERED: "info",
COMPLETED: "success",
TAKE: "warning",
CANCELLED: "danger",
};
return map[status] || "";
},
canInvoicing(row) {
if (!row) return false;
const orderStatus = row.orderStatus;
const receiptStatus = Number(row.receiptStatus);
return (orderStatus === "COMPLETED" || orderStatus === "DELIVERED") && receiptStatus === 0;
return (
(row.orderStatus === "COMPLETED" || row.orderStatus === "DELIVERED") &&
Number(row.receiptStatus) === 0
);
},
initUploadAccessToken() {
this.accessToken = {
accessToken: this.getStore("accessToken")
};
this.accessToken = { accessToken: this.getStore("accessToken") };
},
hasValue(value) {
if (value === null || value === undefined) return false;
@@ -334,11 +252,6 @@ export default {
hasPrice(value) {
return value !== null && value !== undefined && value !== "";
},
formatValue(value) {
if (value === null || value === undefined) return "暂无";
const text = String(value).trim();
return text ? text : "暂无";
},
formatPrice(value) {
if (value === null || value === undefined || value === "") return "暂无";
return `${value}`;
@@ -354,26 +267,6 @@ export default {
if (receiptType === "电子普通发票" || receiptType === "增值税专用发票") return receiptType;
return this.isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
},
formatReceiptHeaderType(receipt) {
if (!receipt) return "暂无";
if (this.isVatSpecialReceipt(receipt)) return "单位";
if (receipt.companyName) return "单位";
if (receipt.personalName) return "个人";
const receiptTitle = receipt.receiptTitle != null ? String(receipt.receiptTitle).trim() : "";
if (receiptTitle === "单位" || receiptTitle === "个人") return receiptTitle;
return receipt.taxpayerId ? "单位" : "个人";
},
getReceiptTitleLabel(receipt) {
return this.formatReceiptHeaderType(receipt) === "单位" ? "单位名称" : "个人名称";
},
getReceiptTitleName(receipt) {
if (!receipt) return "";
if (receipt.companyName) return receipt.companyName;
if (receipt.personalName) return receipt.personalName;
const receiptTitle = receipt.receiptTitle != null ? String(receipt.receiptTitle).trim() : "";
if (receiptTitle === "单位" || receiptTitle === "个人") return "";
return receiptTitle;
},
getInvoiceAddress(receipt) {
if (!receipt) return "";
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
@@ -382,11 +275,24 @@ export default {
const invoiceAddress = this.getInvoiceAddress(this.currentReceipt);
return invoiceAddress ? { invoiceAddress } : {};
},
beforeInvoiceUpload(file) {
const allowed = ["image/jpeg", "image/jpg", "image/png", "application/pdf"];
const okType = allowed.includes(file.type) || /\.(jpg|jpeg|png|pdf)$/i.test(file.name);
if (!okType) {
this.$Message.warning("请上传 jpg、jpeg、png 或 pdf 格式文件");
return false;
}
if (file.size / 1024 / 1024 > 10) {
this.$Message.warning("发票附件不能超过 10MB");
return false;
}
return true;
},
handleInvoiceUploadSuccess(res) {
if (res && res.success && res.result) {
this.$set(this.currentReceipt, "invoiceAddress", res.result);
this.currentReceipt.invoiceAddress = res.result;
if (this.selectedReceiptRow) {
this.$set(this.selectedReceiptRow, "invoiceAddress", res.result);
this.selectedReceiptRow.invoiceAddress = res.result;
}
this.$Message.success("发票上传成功");
} else {
@@ -396,57 +302,35 @@ export default {
handleInvoiceUploadError() {
this.$Message.error("发票上传失败");
},
handleInvoiceFormatError() {
this.$Notice.warning({
title: "文件格式不正确",
desc: "请上传 jpg、jpeg、png 或 pdf 格式文件"
});
},
handleInvoiceMaxSize() {
this.$Notice.warning({
title: "超过文件大小限制",
desc: "发票附件不能超过 10MB"
});
},
viewInvoiceFile(url) {
if (!url) return;
window.open(url, "_blank");
},
// 初始化数据
init() {
this.getData();
},
// 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
// 改变页数
changePageSize(v) {
this.searchForm.pageSize = v;
this.getData();
},
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.getData();
},
// 重置搜索条件
handleReset() {
this.searchForm = {};
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm = {
pageNumber: 1,
pageSize: 10,
sort: "createTime",
order: "desc",
receiptStatus: "",
};
this.getData();
},
// 时间段从新赋值
selectDateRange(v) {
if (v) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
}
},
// 获取数据
getData() {
this.loading = true;
API_Order.getReceiptPage(this.searchForm).then((res) => {
@@ -456,8 +340,6 @@ export default {
this.total = res.result.total;
}
});
this.total = this.data.length;
this.loading = false;
},
async openReceiptModal(row, mode = "detail") {
if (!row) return;
@@ -512,16 +394,8 @@ export default {
};
</script>
<style lang="scss">
// 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss";
.receipt-modal-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 240px;
}
.receipt-modal-content {
max-height: 460px;
overflow-y: auto;
@@ -554,4 +428,14 @@ export default {
color: #17233d;
word-break: break-all;
}
.link-text {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.link-text.disabled {
color: #c5c8ce;
cursor: not-allowed;
}
</style>

Some files were not shown because too many files have changed in this diff Show More