291 changed files with 20 additions and 35344 deletions
@ -1,28 +1,16 @@ |
|||
<template> |
|||
<a-config-provider :locale="locale"> |
|||
<div id="app"> |
|||
<router-view/> |
|||
</div> |
|||
</a-config-provider> |
|||
<div id="app"> |
|||
<router-view /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { domTitle, setDocumentTitle } from '@/utils/domUtil' |
|||
import { i18nRender } from '@/locales' |
|||
|
|||
export default { |
|||
data () { |
|||
return { |
|||
} |
|||
data() { |
|||
return {} |
|||
}, |
|||
computed: { |
|||
locale () { |
|||
// 只是为了切换语言时,更新标题 |
|||
// const { title } = this.$route.meta |
|||
// title && (setDocumentTitle(`${i18nRender(title)} - ${domTitle}`)) |
|||
|
|||
return this.$i18n.getLocaleMessage(this.$store.getters.lang).antLocale |
|||
} |
|||
} |
|||
// do nothing |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:优惠券适用范围 |
|||
* ApplyRangeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'ALL', name: '全部商品', value: 10 }, |
|||
{ key: 'SOME_GOODS', name: '指定商品', value: 20 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:优惠券类型 |
|||
* CouponTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'FULL_DISCOUNT', name: '满减券', value: 10 }, |
|||
{ key: 'DISCOUNT', name: '折扣券', value: 20 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:优惠券到期类型 |
|||
* ExpireTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'RECEIVE', name: '领取后', value: 10 }, |
|||
{ key: 'FIXED_TIME', name: '固定时间', value: 20 } |
|||
]) |
|||
@ -1,5 +0,0 @@ |
|||
import ApplyRangeEnum from './ApplyRange' |
|||
import ExpireTypeEnum from './ExpireType' |
|||
import CouponTypeEnum from './CouponType' |
|||
|
|||
export { ApplyRangeEnum, CouponTypeEnum, ExpireTypeEnum } |
|||
@ -1,84 +0,0 @@ |
|||
/** |
|||
* 枚举类 |
|||
* Enum.IMAGE.name => "图片" |
|||
* Enum.getNameByKey('IMAGE') => "图片" |
|||
* Enum.getValueByKey('IMAGE') => 10 |
|||
* Enum.getNameByValue(10) => "图片" |
|||
* Enum.getData() => [{key: "IMAGE", name: "图片", value: 10}] |
|||
*/ |
|||
class Enum { |
|||
constructor (param) { |
|||
const keyArr = [] |
|||
const valueArr = [] |
|||
|
|||
if (!Array.isArray(param)) { |
|||
throw new Error('param is not an array!') |
|||
} |
|||
|
|||
param.map(element => { |
|||
if (!element.key || !element.name) { |
|||
return |
|||
} |
|||
// 保存key值组成的数组,方便A.getName(name)类型的调用
|
|||
keyArr.push(element.key) |
|||
valueArr.push(element.value) |
|||
// 根据key生成不同属性值,以便A.B.name类型的调用
|
|||
this[element.key] = element |
|||
if (element.key !== element.value) { |
|||
this[element.value] = element |
|||
} |
|||
}) |
|||
|
|||
// 保存源数组
|
|||
this.data = param |
|||
this.keyArr = keyArr |
|||
this.valueArr = valueArr |
|||
|
|||
// 防止被修改
|
|||
Object.freeze(this) |
|||
} |
|||
|
|||
// 根据key得到对象
|
|||
keyOf (key) { |
|||
return this.data[this.keyArr.indexOf(key)] |
|||
} |
|||
|
|||
// 根据key得到对象
|
|||
valueOf (key) { |
|||
return this.data[this.valueArr.indexOf(key)] |
|||
} |
|||
|
|||
// 根据key获取name值
|
|||
getNameByKey (key) { |
|||
const prop = this.keyOf(key) |
|||
if (!prop) { |
|||
throw new Error('No enum constant' + key) |
|||
} |
|||
return prop.name |
|||
} |
|||
|
|||
// 根据value获取name值
|
|||
getNameByValue (value) { |
|||
const prop = this.valueOf(value) |
|||
if (!prop) { |
|||
throw new Error('No enum constant' + value) |
|||
} |
|||
return prop.name |
|||
} |
|||
|
|||
// 根据key获取value值
|
|||
getValueByKey (key) { |
|||
const prop = this.keyOf(key) |
|||
if (!prop) { |
|||
throw new Error('No enum constant' + key) |
|||
} |
|||
return prop.value |
|||
} |
|||
|
|||
// 返回源数组
|
|||
getData () { |
|||
return this.data |
|||
} |
|||
} |
|||
|
|||
export default Enum |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:文件上传来源 |
|||
* ChannelEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'STORE', name: '商户后台', value: 10 }, |
|||
{ key: 'CLIENT', name: '用户端', value: 20 } |
|||
]) |
|||
@ -1,11 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:文件存储方式 |
|||
* FileTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'IMAGE', name: '图片', value: 10 }, |
|||
{ key: 'ANNEX', name: '附件', value: 20 }, |
|||
{ key: 'VIDEO', name: '视频', value: 30 } |
|||
]) |
|||
@ -1,12 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:文件存储方式 |
|||
* StorageEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'LOCAL', name: '本地', value: 'local' }, |
|||
{ key: 'QINIU', name: '七牛云', value: 'qiniu' }, |
|||
{ key: 'ALIYUN', name: '阿里云', value: 'aliyun' }, |
|||
{ key: 'QCLOUD', name: '腾讯云', value: 'qcloud' } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单发货状态 |
|||
* DeliveryStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'NOT_DELIVERED', name: '未发货', value: 10 }, |
|||
{ key: 'DELIVERED', name: '已发货', value: 20 } |
|||
]) |
|||
@ -1,9 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:配送方式 |
|||
* DeliveryTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'EXPRESS', name: '快递配送', value: 10 } |
|||
]) |
|||
@ -1,9 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单来源 |
|||
* OrderSourceEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'MASTER', name: '普通订单', value: 10 } |
|||
]) |
|||
@ -1,12 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单状态 |
|||
* OrderStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'NORMAL', name: '进行中', value: 10 }, |
|||
{ key: 'CANCELLED', name: '已取消', value: 20 }, |
|||
{ key: 'APPLY_CANCEL', name: '待取消', value: 21 }, |
|||
{ key: 'COMPLETED', name: '已完成', value: 30 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单支付状态 |
|||
* PayStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'PENDING', name: '待支付', value: 10 }, |
|||
{ key: 'SUCCESS', name: '已支付', value: 20 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单支付方式 |
|||
* PayTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'BALANCE', name: '余额支付', value: 10 }, |
|||
{ key: 'WECHAT', name: '微信支付', value: 20 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单收货状态 |
|||
* ReceiptStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'NOT_RECEIVED', name: '未收货', value: 10 }, |
|||
{ key: 'RECEIVED', name: '已收货', value: 20 } |
|||
]) |
|||
@ -1,11 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单导出状态 |
|||
* ExportStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'NORMAL', name: '进行中', value: 10 }, |
|||
{ key: 'COMPLETED', name: '已完成', value: 20 }, |
|||
{ key: 'FAIL', name: '失败', value: 30 } |
|||
]) |
|||
@ -1,3 +0,0 @@ |
|||
import ExportStatusEnum from './ExportStatus' |
|||
|
|||
export { ExportStatusEnum } |
|||
@ -1,17 +0,0 @@ |
|||
import DeliveryStatusEnum from './DeliveryStatus' |
|||
import DeliveryTypeEnum from './DeliveryType' |
|||
import OrderSourceEnum from './OrderSource' |
|||
import OrderStatusEnum from './OrderStatus' |
|||
import PayStatusEnum from './PayStatus' |
|||
import PayTypeEnum from './PayType' |
|||
import ReceiptStatusEnum from './ReceiptStatus' |
|||
|
|||
export { |
|||
DeliveryStatusEnum, |
|||
DeliveryTypeEnum, |
|||
OrderSourceEnum, |
|||
OrderStatusEnum, |
|||
PayStatusEnum, |
|||
PayTypeEnum, |
|||
ReceiptStatusEnum |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:商家审核状态 |
|||
* AuditStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'WAIT', name: '待审核', value: 0 }, |
|||
{ key: 'REVIEWED', name: '已同意', value: 10 }, |
|||
{ key: 'REJECTED', name: '已拒绝', value: 20 } |
|||
]) |
|||
@ -1,12 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:售后单状态 |
|||
* RefundStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'NORMAL', name: '进行中', value: 0 }, |
|||
{ key: 'REJECTED', name: '已拒绝', value: 10 }, |
|||
{ key: 'COMPLETED', name: '已完成', value: 20 }, |
|||
{ key: 'CANCELLED', name: '已取消', value: 30 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:售后类型 |
|||
* RefundTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'RETURN', name: '退货退款', value: 10 }, |
|||
{ key: 'EXCHANGE', name: '换货', value: 20 } |
|||
]) |
|||
@ -1,9 +0,0 @@ |
|||
import AuditStatusEnum from './AuditStatus' |
|||
import RefundStatusEnum from './RefundStatus' |
|||
import RefundTypeEnum from './RefundType' |
|||
|
|||
export { |
|||
AuditStatusEnum, |
|||
RefundStatusEnum, |
|||
RefundTypeEnum |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:订单支付方式 |
|||
* PageTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'HOME', name: '首页', value: 10 }, |
|||
{ key: 'CUSTOM', name: '自定义页', value: 20 } |
|||
]) |
|||
@ -1,3 +0,0 @@ |
|||
import PageTypeEnum from './PageType' |
|||
|
|||
export { PageTypeEnum } |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:用户充值订单--支付状态 |
|||
* PayStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'PENDING', name: '待支付', value: 10 }, |
|||
{ key: 'SUCCESS', name: '支付成功', value: 20 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:用户充值订单--充值方式 |
|||
* RechargeTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'CUSTOM', name: '自定义金额', value: 10 }, |
|||
{ key: 'PLAN', name: '套餐充值', value: 20 } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:短信发送的场景 |
|||
* SettingSmsSceneEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'CAPTCHA', name: '短信验证码', value: 'captcha' }, |
|||
{ key: 'ORDER_PAY', name: '新付款订单', value: 'order_pay' } |
|||
]) |
|||
@ -1,15 +0,0 @@ |
|||
import Enum from '../enum' |
|||
|
|||
/** |
|||
* 枚举类:商城设置项 |
|||
* SettingEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'DELIVERY', name: '配送设置', value: 'delivery' }, |
|||
{ key: 'TRADE', name: '交易设置', value: 'trade' }, |
|||
{ key: 'STORAGE', name: '上传设置', value: 'storage' }, |
|||
{ key: 'FULL_FREE', name: '满额包邮设置', value: 'full_free' }, |
|||
{ key: 'RECHARGE', name: '充值设置', value: 'recharge' }, |
|||
{ key: 'POINTS', name: '积分设置', value: 'points' }, |
|||
{ key: 'PAGE_CATEGORY_TEMPLATE', name: '分类页模板', value: 'page_category_template' } |
|||
]) |
|||
@ -1,10 +0,0 @@ |
|||
import Enum from '../../enum' |
|||
|
|||
/** |
|||
* 枚举类:地址类型 |
|||
* AddressTypeEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'DELIVERY', name: '发货地址', value: 10 }, |
|||
{ key: 'RETURN', name: '退货地址', value: 20 } |
|||
]) |
|||
@ -1,3 +0,0 @@ |
|||
import AddressTypeEnum from './Type' |
|||
|
|||
export { AddressTypeEnum } |
|||
@ -1,3 +0,0 @@ |
|||
import SettingEnum from './Setting' |
|||
|
|||
export { SettingEnum } |
|||
@ -1,12 +0,0 @@ |
|||
import Enum from '../../../enum' |
|||
|
|||
/** |
|||
* 枚举类:地址类型 |
|||
* PageCategoryStyleEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'COMMODITY', name: '分类+商品', value: 30 }, |
|||
{ key: 'ONE_LEVEL_BIG', name: '一级分类[大图]', value: 10 }, |
|||
{ key: 'ONE_LEVEL_SMALL', name: '一级分类[小图]', value: 11 }, |
|||
{ key: 'TWO_LEVEL', name: '二级分类', value: 20 } |
|||
]) |
|||
@ -1,3 +0,0 @@ |
|||
import PageCategoryStyleEnum from './Style' |
|||
|
|||
export { PageCategoryStyleEnum } |
|||
@ -1,12 +0,0 @@ |
|||
import Enum from '../../../enum' |
|||
|
|||
/** |
|||
* 枚举类:余额变动场景 |
|||
* PayStatusEnum |
|||
*/ |
|||
export default new Enum([ |
|||
{ key: 'RECHARGE', name: '用户充值', value: 10 }, |
|||
{ key: 'CONSUME', name: '用户消费', value: 20 }, |
|||
{ key: 'ADMIN', name: '管理员操作', value: 30 }, |
|||
{ key: 'REFUND', name: '订单退款', value: 40 }, |
|||
]) |
|||
@ -1,69 +0,0 @@ |
|||
import * as Api from '@/api/category' |
|||
|
|||
/** |
|||
* 商品分类 model类 |
|||
* CategoryModel |
|||
*/ |
|||
export default { |
|||
|
|||
// 获取商品分类列表 (用于添加商品的form)
|
|||
getCategoryTreeSelect () { |
|||
return new Promise((resolve, reject) => { |
|||
Api.list() |
|||
.then(result => { |
|||
const categoryList = result.data.list |
|||
// 格式化分类列表
|
|||
const treeData = this.formatTreeData(categoryList) |
|||
resolve(treeData) |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
// 获取商品分类列表 (用于筛选select)
|
|||
getListFromScreen () { |
|||
return new Promise((resolve, reject) => { |
|||
Api.list().then(result => { |
|||
// 格式化分类列表
|
|||
const resultList = result.data.list |
|||
// 格式化为 select列表数据
|
|||
const selectList = [{ |
|||
title: '全部分类', |
|||
key: 0, |
|||
value: 0 |
|||
}].concat(this.formatTreeData(resultList)) |
|||
resolve(selectList) |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 格式化分类列表 |
|||
* @param {*} list 分类数据源 |
|||
* @param {*} disabled |
|||
*/ |
|||
formatTreeData (list, disabledParentId = null, disabled = false) { |
|||
const data = [] |
|||
list.forEach(item => { |
|||
// 新的元素
|
|||
const netItem = { |
|||
title: item.name, |
|||
key: item.category_id, |
|||
value: item.category_id |
|||
} |
|||
// 禁用的分类
|
|||
if ( |
|||
[item.category_id, item.parent_id].includes(disabledParentId) || |
|||
disabled === true |
|||
) { |
|||
netItem.disabled = true |
|||
} |
|||
// 递归整理子集
|
|||
if (item.children && item.children.length) { |
|||
netItem['children'] = this.formatTreeData(item['children'], disabledParentId, netItem.disabled) |
|||
} |
|||
data.push(netItem) |
|||
}) |
|||
return data |
|||
} |
|||
|
|||
} |
|||
@ -1,266 +0,0 @@ |
|||
|
|||
// 常量:链接类型 - 页面
|
|||
const LINK_TYPE_PAGE = 'PAGE' |
|||
|
|||
// 基础页面
|
|||
const basics = { |
|||
title: '基础页面', |
|||
key: 'basics', |
|||
data: [ |
|||
{ |
|||
id: 'cb344ba', |
|||
title: '商城首页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/index/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: 'c37c2ee', |
|||
title: '分类页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/category/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: 'bb2f7f1', |
|||
title: '购物车', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/cart/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: 'a013c9e', |
|||
title: '个人中心', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/user/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: '593fe1f', |
|||
title: '会员登录页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/login/index' |
|||
} |
|||
} |
|||
] |
|||
} |
|||
|
|||
// 商城页面
|
|||
const store = { |
|||
title: '商城页面', |
|||
key: 'store', |
|||
data: [ |
|||
{ |
|||
id: '995bf1c', |
|||
title: '商品列表页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/goods/list', |
|||
query: {} |
|||
}, |
|||
form: [ |
|||
{ |
|||
key: 'query.categoryId', |
|||
lable: '分类ID', |
|||
tips: '商品管理 -> 商品分类' |
|||
}, |
|||
{ |
|||
key: 'query.search', |
|||
lable: '关键词', |
|||
tips: '搜索的关键词,用于匹配商品名称' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
id: '6wawb10', |
|||
title: '商品详情页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/goods/detail', |
|||
query: {} |
|||
}, |
|||
form: [ |
|||
{ |
|||
key: 'query.goodsId', |
|||
lable: '商品ID', |
|||
required: true, |
|||
// value: '10001', // 默认值
|
|||
tips: '商品管理 - 商品列表' // 字段提示
|
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
id: '88lxeey', |
|||
title: '商品搜索页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/search/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: '56sswhq', |
|||
title: '领券中心', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/coupon/index' |
|||
} |
|||
} |
|||
] |
|||
} |
|||
|
|||
// 个人中心
|
|||
const personal = { |
|||
title: '个人中心', |
|||
key: 'personal', |
|||
data: [ |
|||
{ |
|||
id: '7b345f6', |
|||
title: '我的订单', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/order/index', |
|||
query: {} |
|||
}, |
|||
form: [ |
|||
{ |
|||
key: 'query.dataType', |
|||
lable: '订单类型', |
|||
required: true, |
|||
value: 'all', // 默认值
|
|||
tips: 'all 全部<br>payment 待支付<br>delivery 待发货<br>received 待收货' // 字段提示
|
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
id: 'c4f630d', |
|||
title: '我的钱包页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/wallet/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: '792db19', |
|||
title: '充值中心页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/wallet/recharge/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: '03b9290', |
|||
title: '我的优惠券', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/my-coupon/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: '569b0b0', |
|||
title: '会员积分明细', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/points/log' |
|||
} |
|||
}, |
|||
{ |
|||
id: '0c25051', |
|||
title: '收货地址', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/address/index' |
|||
} |
|||
}, |
|||
{ |
|||
id: '3558c27', |
|||
title: '帮助中心', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/help/index' |
|||
} |
|||
} |
|||
] |
|||
} |
|||
|
|||
// 其他页面
|
|||
const other = { |
|||
title: '其他页面', |
|||
key: 'other', |
|||
data: [ |
|||
{ |
|||
id: '91th4ss', |
|||
title: '自定义页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/custom/index', |
|||
query: {} |
|||
}, |
|||
form: [ |
|||
{ |
|||
key: 'query.pageId', |
|||
lable: '页面ID', |
|||
required: true, |
|||
tips: '店铺管理 -> 店铺页面' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
id: 'ugrauzv', |
|||
title: '资讯列表页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/article/index', |
|||
query: {} |
|||
}, |
|||
form: [ |
|||
{ |
|||
key: 'query.categoryId', |
|||
lable: '分类ID', |
|||
tips: '内容管理 -> 文章分类', |
|||
value: '' |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
id: 'u1v6aux', |
|||
title: '资讯详情页', |
|||
type: LINK_TYPE_PAGE, |
|||
param: { |
|||
path: 'pages/article/detail', |
|||
query: {} |
|||
}, |
|||
form: [ |
|||
{ |
|||
key: 'query.articleId', |
|||
lable: '文章ID', |
|||
required: true, |
|||
tips: '内容管理 -> 文章列表' |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
|
|||
// // 门店 - 拨打电话 - 暂未实现
|
|||
// const callPhone = {
|
|||
// id: 'cb344ba',
|
|||
// title: '拨打电话',
|
|||
// type: 'callPhone',
|
|||
// param: {
|
|||
// phoneNumber: '13212341234'
|
|||
// },
|
|||
// form: [
|
|||
// {
|
|||
// key: 'phoneNumber',
|
|||
// lable: '电话号码',
|
|||
// tips: ''
|
|||
// }
|
|||
// ]
|
|||
// }
|
|||
|
|||
export const linkList = [basics, store, personal, other] |
|||
@ -1,57 +0,0 @@ |
|||
import * as Api from '@/api/region' |
|||
import storage from 'store' |
|||
|
|||
const REGION_TREE = 'region_tree' |
|||
|
|||
/** |
|||
* 商品分类 model类 |
|||
* RegionModel |
|||
*/ |
|||
export default { |
|||
|
|||
// 从服务端获取全部地区数据(树状)
|
|||
getTreeDataFromApi () { |
|||
return new Promise((resolve, reject) => { |
|||
Api.tree().then(result => resolve(result.data.list)) |
|||
}) |
|||
}, |
|||
|
|||
// 获取所有地区(树状)
|
|||
getTreeData () { |
|||
return new Promise((resolve, reject) => { |
|||
// 判断缓存中是否存在
|
|||
const data = storage.get(REGION_TREE) |
|||
// 从服务端获取全部地区数据
|
|||
if (data) { |
|||
resolve(data) |
|||
} else { |
|||
this.getTreeDataFromApi().then(list => { |
|||
// 缓存24小时
|
|||
storage.set(REGION_TREE, list, 1 * 24 * 60 * 60 * 1000) |
|||
resolve(list) |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 获取所有地区的总数
|
|||
getCitysCount () { |
|||
return new Promise((resolve, reject) => { |
|||
// 获取所有地区(树状)
|
|||
this.getTreeData().then(data => { |
|||
const cityIds = [] |
|||
// 遍历省份
|
|||
for (const pidx in data) { |
|||
const province = data[pidx] |
|||
// 遍历城市
|
|||
for (const cidx in province.city) { |
|||
const cityItem = province.city[cidx] |
|||
cityIds.push(cityItem.id) |
|||
} |
|||
} |
|||
resolve(cityIds.length) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
import * as CategoryApi from '@/api/content/article/category' |
|||
|
|||
/** |
|||
* 文章分类模型类 |
|||
* CategoryModel |
|||
*/ |
|||
export default { |
|||
|
|||
/** |
|||
* 向服务端获取分类列表并格式化 |
|||
*/ |
|||
getCategorySelect () { |
|||
return new Promise((resolve, reject) => { |
|||
CategoryApi.list() |
|||
.then(result => { |
|||
const categoryList = result.data.list |
|||
console.log(categoryList) |
|||
|
|||
// 格式化分类列表
|
|||
const selectList = this.formatData(categoryList) |
|||
resolve(selectList) |
|||
}) |
|||
.catch(error => reject(error)) |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 格式化分类列表 |
|||
* @param {*} list 分类数据源 |
|||
* @param {*} disabled |
|||
*/ |
|||
formatData (list, disabledId = null) { |
|||
const data = [] |
|||
list.forEach(item => { |
|||
// 新的元素
|
|||
const netItem = { |
|||
title: item.name, |
|||
key: item.category_id, |
|||
value: item.category_id |
|||
} |
|||
// 禁用的分类
|
|||
// netItem.disabled = item.category_id === disabledId
|
|||
data.push(netItem) |
|||
}) |
|||
return data |
|||
} |
|||
|
|||
} |
|||
@ -1,176 +0,0 @@ |
|||
import _ from 'lodash' |
|||
import CategoryModel from '../Category' |
|||
import * as GoodsApi from '@/api/goods' |
|||
import * as GradeApi from '@/api/user/grade' |
|||
import * as ServiceApi from '@/api/goods/service' |
|||
import * as DeliveryApi from '@/api/setting/delivery' |
|||
|
|||
/** |
|||
* 商品 model类 |
|||
* GoodsModel |
|||
*/ |
|||
export default { |
|||
|
|||
// 当前商品ID
|
|||
goodsId: null, |
|||
|
|||
// 表单数据
|
|||
formData: { |
|||
// 当前商品记录
|
|||
goods: {}, |
|||
// 分类列表
|
|||
categoryList: [], |
|||
// 运费模板
|
|||
deliveryList: [], |
|||
// 服务与承诺
|
|||
serviceList: [], |
|||
defaultServiceIds: [], |
|||
// 会员等级列表
|
|||
userGradeList: [], |
|||
defaultUserGradeValue: {} |
|||
}, |
|||
|
|||
// 获取form所需的数据
|
|||
getFromData (goodsId = null) { |
|||
// 记录商品ID (编辑时)
|
|||
this.goodsId = goodsId |
|||
return new Promise((resolve, reject) => { |
|||
Promise.all([ |
|||
// 获取商品详情信息(编辑时)
|
|||
this.getGoodsDetail(goodsId), |
|||
// 获取分类列表
|
|||
this.getCategoryList(), |
|||
// 获取运费模板列表
|
|||
this.getDeliveryList(), |
|||
// 获取服务与承诺
|
|||
this.getServiceList(), |
|||
// 获取会员等级列表
|
|||
this.getUserGradeList() |
|||
]).then(() => { |
|||
// 设置默认数据
|
|||
this.setDefaultData() |
|||
resolve({ formData: this.formData }) |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
// 获取商品详情
|
|||
getGoodsDetail (goodsId = null) { |
|||
if (!goodsId) return false |
|||
return new Promise((resolve, reject) => { |
|||
GoodsApi.detail({ goodsId }) |
|||
.then(result => { |
|||
this.formData.goods = result.data.goodsInfo |
|||
resolve() |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
// 获取表单默认值(用于form.setFieldsValue的数据)
|
|||
getFieldsValue () { |
|||
// 商品详情信息
|
|||
const goodsInfo = this.formData.goods |
|||
// 格式化categoryIds
|
|||
goodsInfo.categorys = this.formatCategoryIds(goodsInfo.categoryIds) |
|||
// 商品基本数据
|
|||
const goodsFormData = _.pick(goodsInfo, [ |
|||
'goods_name', 'categorys', 'goods_no', 'delivery_type', 'sort', |
|||
'delivery_id', 'status', 'spec_type', 'deduct_stock_type', 'content', |
|||
'selling_point', 'serviceIds', 'sales_initial', 'is_points_gift', |
|||
'is_points_discount', 'is_enable_grade', 'is_alone_grade' |
|||
]) |
|||
// 单规格数据
|
|||
const skuOne = _.pick(goodsInfo.skuList[0], ['goods_price', 'line_price', 'stock_num', 'goods_weight']) |
|||
return { |
|||
...goodsFormData, |
|||
...skuOne |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 格式化categoryIds (用于表单元素选中) |
|||
* @param {*} categoryIds |
|||
*/ |
|||
formatCategoryIds (categoryIds) { |
|||
return categoryIds.map(id => { return { value: id } }) |
|||
}, |
|||
|
|||
// 获取分类列表
|
|||
getCategoryList () { |
|||
return new Promise((resolve, reject) => { |
|||
CategoryModel.getCategoryTreeSelect() |
|||
.then(list => { |
|||
this.formData.categoryList = list |
|||
resolve() |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
// 获取运费模板列表
|
|||
getDeliveryList () { |
|||
return new Promise((resolve, reject) => { |
|||
DeliveryApi.all() |
|||
.then(result => { |
|||
this.formData.deliveryList = result.data.list |
|||
resolve() |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
// 获取服务与承诺
|
|||
getServiceList () { |
|||
return new Promise((resolve, reject) => { |
|||
ServiceApi.all() |
|||
.then(result => { |
|||
this.formData.serviceList = result.data.list |
|||
resolve() |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
// 获取会员等级列表
|
|||
getUserGradeList () { |
|||
return new Promise((resolve, reject) => { |
|||
GradeApi.all({ status: 1 }) |
|||
.then(result => { |
|||
this.formData.userGradeList = result.data.list |
|||
resolve() |
|||
}) |
|||
}) |
|||
}, |
|||
|
|||
// 设置默认的数据(无法用于form.setFieldsValue的数据)
|
|||
setDefaultData () { |
|||
// 默认的商品服务与承诺
|
|||
this.setDefaultServiceIds() |
|||
// 默认的等级折扣
|
|||
this.setDefaultUserGradeValue() |
|||
}, |
|||
|
|||
// 默认的商品服务与承诺
|
|||
setDefaultServiceIds () { |
|||
// 服务与承诺列表
|
|||
const serviceList = this.formData.serviceList |
|||
if (!this.goodsId) { |
|||
// 默认选中的id集
|
|||
const defaultServiceItems = serviceList.filter(item => item.is_default) |
|||
this.formData.defaultServiceIds = defaultServiceItems.map(item => item.service_id) |
|||
} |
|||
}, |
|||
|
|||
// 默认的等级折扣
|
|||
setDefaultUserGradeValue () { |
|||
// 会员等级列表
|
|||
const userGradeList = this.formData.userGradeList |
|||
// 单独设置折扣的配置 (已保存的)
|
|||
const aloneGradeEquity = (this.goodsId && this.formData.goods.alone_grade_equity) |
|||
? this.formData.goods.alone_grade_equity : {} |
|||
// 生成的默认数据
|
|||
const defaultData = {} |
|||
userGradeList.forEach(item => { |
|||
defaultData[item.grade_id] = aloneGradeEquity[item.grade_id] || item.equity.discount |
|||
}) |
|||
this.formData.defaultUserGradeValue = { ...defaultData } |
|||
} |
|||
|
|||
} |
|||
@ -1,446 +0,0 @@ |
|||
|
|||
import _ from 'lodash' |
|||
import { debounce, isEmpty } from '@/utils/util' |
|||
|
|||
// 默认的sku字段属性
|
|||
const defaultColumns = [ |
|||
{ |
|||
title: '预览图', |
|||
dataIndex: 'image', |
|||
width: 90, |
|||
scopedSlots: { customRender: 'image' } |
|||
}, |
|||
{ |
|||
title: '商品价格', |
|||
dataIndex: 'goods_price', |
|||
width: 120, |
|||
scopedSlots: { customRender: 'goods_price' } |
|||
}, |
|||
{ |
|||
title: '划线价格', |
|||
dataIndex: 'line_price', |
|||
width: 120, |
|||
scopedSlots: { customRender: 'line_price' } |
|||
}, |
|||
{ |
|||
title: '库存数量', |
|||
dataIndex: 'stock_num', |
|||
width: 120, |
|||
scopedSlots: { customRender: 'stock_num' } |
|||
}, |
|||
{ |
|||
title: '商品重量 (KG)', |
|||
dataIndex: 'goods_weight', |
|||
width: 120, |
|||
scopedSlots: { customRender: 'goods_weight' } |
|||
}, |
|||
{ |
|||
title: 'SKU编码', |
|||
dataIndex: 'goods_sku_no', |
|||
width: 140, |
|||
scopedSlots: { customRender: 'goods_sku_no' } |
|||
} |
|||
] |
|||
|
|||
// 默认的sku记录值
|
|||
const defaultSkuItemData = { |
|||
image_id: 0, |
|||
image: {}, |
|||
// imageList: [],
|
|||
goods_price: '', |
|||
line_price: '', |
|||
stock_num: '', |
|||
goods_weight: '', |
|||
goods_sku_no: '' |
|||
} |
|||
|
|||
// const demoSpecList = [
|
|||
// {
|
|||
// key: 0,
|
|||
// spec_name: '颜色',
|
|||
// valueList: [
|
|||
// { key: 0, groupKey: 0, /* spec_value_id: 10001, */ spec_value: '红色' },
|
|||
// { key: 1, groupKey: 0, spec_value: '白色' },
|
|||
// { key: 2, groupKey: 0, spec_value: '蓝色' }
|
|||
// ]
|
|||
// },
|
|||
// {
|
|||
// key: 1,
|
|||
// spec_name: '尺码',
|
|||
// valueList: [
|
|||
// { key: 0, groupKey: 1, spec_value: 'XXL' },
|
|||
// { key: 1, groupKey: 1, spec_value: 'XL' }
|
|||
// ]
|
|||
// }
|
|||
// ]
|
|||
|
|||
/** |
|||
* 商品 model类 |
|||
* GoodsModel |
|||
*/ |
|||
export default class MultiSpec { |
|||
// 商品多规格数据
|
|||
multiSpecData = {} |
|||
|
|||
// 错误信息
|
|||
error = ''; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @param {array} specList 规格列表 |
|||
* @param {array} skuList SKU列表 |
|||
*/ |
|||
constructor() { |
|||
this.multiSpecData = { |
|||
// 规格列表
|
|||
specList: [], |
|||
// SKU列表
|
|||
skuList: [], |
|||
// SKU字段
|
|||
skuColumns: _.cloneDeep(defaultColumns), |
|||
// 批量设置sku
|
|||
skuBatchForm: _.cloneDeep(defaultSkuItemData) |
|||
} |
|||
} |
|||
|
|||
// 生成并获取多规格数据
|
|||
getData (specList = [], skuList = []) { |
|||
if (specList.length) { |
|||
this.multiSpecData.specList = _.cloneDeep(specList) |
|||
this.multiSpecData.skuList = _.cloneDeep(skuList) |
|||
} |
|||
// 整理所有的规格组
|
|||
const specGroupArr = this.specGroupArr() |
|||
// sku记录的规格属性集(生成笛卡尔积)
|
|||
const cartesianList = cartesianProductOf(specGroupArr) |
|||
// 合并单元格
|
|||
const rowSpanArr = this.rowSpanArr(specGroupArr, cartesianList) |
|||
// 生成sku字段名
|
|||
this.buildSkuColumns(rowSpanArr) |
|||
// 生成sku列表数据
|
|||
this.buildSkuList(cartesianList) |
|||
// 返回多规格数据
|
|||
return this.multiSpecData |
|||
} |
|||
|
|||
// 数据是否为空
|
|||
isEmpty () { |
|||
return this.multiSpecData.specList.length === 0 |
|||
} |
|||
|
|||
// 返回错误信息
|
|||
getError () { |
|||
return this.error |
|||
} |
|||
|
|||
// 整理所有的规格
|
|||
specGroupArr () { |
|||
const specGroupArr = [] |
|||
this.multiSpecData.specList.forEach(specGroup => { |
|||
const itemArr = [] |
|||
specGroup.valueList.forEach(value => { |
|||
itemArr.push(value) |
|||
}) |
|||
specGroupArr.push(itemArr) |
|||
}) |
|||
return specGroupArr |
|||
} |
|||
|
|||
// 合并单元格
|
|||
rowSpanArr (specGroupArr, cartesianList) { |
|||
const rowSpanArr = [] |
|||
var rowSpan = cartesianList.length |
|||
for (let i = 0; i < specGroupArr.length; i++) { |
|||
rowSpanArr[i] = parseInt(rowSpan / specGroupArr[i].length) |
|||
rowSpan = rowSpanArr[i] |
|||
} |
|||
return rowSpanArr |
|||
} |
|||
|
|||
// 生成skuList
|
|||
buildSkuList (cartesianList) { |
|||
// 生成新的skuList
|
|||
const newSkuList = [] |
|||
for (let i = 0; i < cartesianList.length; i++) { |
|||
const newSkuItem = { |
|||
...defaultSkuItemData, |
|||
key: i, |
|||
// skuKey用于合并旧记录
|
|||
skuKey: cartesianList[i].map(item => item.key).join('_'), |
|||
// skuKeys用于传参给后端
|
|||
skuKeys: cartesianList[i].map(item => { |
|||
return { |
|||
groupKey: item.groupKey, |
|||
valueKey: item.key |
|||
} |
|||
}) |
|||
} |
|||
cartesianList[i].forEach((val, idx) => { |
|||
newSkuItem[`spec_value_${idx}`] = val.spec_value |
|||
}) |
|||
newSkuList.push(newSkuItem) |
|||
} |
|||
// 兼容旧的sku数据
|
|||
this.multiSpecData.skuList = this.oldSkuList(newSkuList) |
|||
} |
|||
|
|||
// 合并已存在的sku数据
|
|||
oldSkuList (newSkuList) { |
|||
// const oldSkuList = _.cloneDeep(this.multiSpecData.skuList)
|
|||
const oldSkuList = this.multiSpecData.skuList.concat() |
|||
if (!oldSkuList.length || !newSkuList.length) { |
|||
return newSkuList |
|||
} |
|||
for (const index in newSkuList) { |
|||
// 查找符合的旧记录
|
|||
let oldSkuItem = {} |
|||
if (oldSkuList.length === newSkuList.length) { |
|||
oldSkuItem = _.cloneDeep(oldSkuList[index]) |
|||
} else { |
|||
oldSkuItem = oldSkuList.find(item => { |
|||
return item.skuKey === newSkuList[index].skuKey |
|||
}) |
|||
} |
|||
// 写入新纪录
|
|||
if (oldSkuItem) { |
|||
newSkuList[index] = { |
|||
...newSkuList[index], |
|||
..._.pick(oldSkuItem, Object.keys(defaultSkuItemData)) |
|||
} |
|||
// console.log(newSkuList[index].image)
|
|||
} |
|||
} |
|||
return newSkuList |
|||
} |
|||
|
|||
// 生成sku表格字段名
|
|||
buildSkuColumns (rowSpanArr) { |
|||
const specList = this.multiSpecData.specList |
|||
const newColumns = defaultColumns.concat() |
|||
// 渲染字段的rowSpan
|
|||
const customRender = (specIndex, value, row, index) => { |
|||
const obj = { |
|||
children: value, |
|||
attrs: {} |
|||
} |
|||
const rowSpan = rowSpanArr[specIndex - 1] |
|||
if ((index % rowSpan) === 0) { |
|||
obj.attrs.rowSpan = rowSpan |
|||
} else { |
|||
obj.attrs.rowSpan = 0 |
|||
} |
|||
return obj |
|||
} |
|||
// 遍历规格组整理字段
|
|||
for (let specIndex = specList.length; specIndex > 0; specIndex--) { |
|||
const specGroupItem = specList[specIndex - 1] |
|||
newColumns.unshift({ |
|||
title: specGroupItem.spec_name, |
|||
dataIndex: `spec_value_${specIndex - 1}`, |
|||
customRender: (value, row, index) => customRender(specIndex, value, row, index) |
|||
}) |
|||
} |
|||
this.multiSpecData.skuColumns = newColumns |
|||
} |
|||
|
|||
// 添加规格组
|
|||
handleAddSpecGroup () { |
|||
const specList = this.multiSpecData.specList |
|||
specList.push({ |
|||
key: specList.length || 0, |
|||
spec_name: '', |
|||
valueList: [] |
|||
}) |
|||
// 默认规格值
|
|||
const groupIndex = specList.length - 1 |
|||
this.handleAddSpecValue(groupIndex) |
|||
} |
|||
|
|||
// 添加规格值
|
|||
handleAddSpecValue (groupIndex) { |
|||
const specGroupItem = this.multiSpecData.specList[groupIndex] |
|||
const specValueList = specGroupItem.valueList |
|||
specValueList.push({ |
|||
key: specValueList.length || 0, |
|||
groupKey: specGroupItem.key, |
|||
spec_value: '' |
|||
}) |
|||
// 刷新规格值的key
|
|||
this.onRefreshSpecValueKey(groupIndex) |
|||
} |
|||
|
|||
// 删除规格组
|
|||
handleDeleteSpecGroup (groupIndex) { |
|||
this.multiSpecData.specList.splice(groupIndex, 1) |
|||
this.onUpdate(false) |
|||
} |
|||
|
|||
// 删除规格值
|
|||
handleDeleteSpecValue (groupIndex, valueIndex) { |
|||
// 将规格值移出
|
|||
this.multiSpecData.specList[groupIndex].valueList.splice(valueIndex, 1) |
|||
// 刷新规格值的key
|
|||
this.onRefreshSpecValueKey(groupIndex) |
|||
this.onUpdate(false) |
|||
} |
|||
|
|||
// 刷新规格值的key
|
|||
onRefreshSpecValueKey (groupIndex) { |
|||
const specGroupItem = this.multiSpecData.specList[groupIndex] |
|||
const specValueList = specGroupItem.valueList |
|||
specValueList.forEach((item, index) => { |
|||
specValueList[index].key = index |
|||
}) |
|||
} |
|||
|
|||
// 批量设置sku事件
|
|||
handleSkuBatch () { |
|||
const skuBatchForm = this.getFilterObject(this.multiSpecData.skuBatchForm) |
|||
const skuList = this.multiSpecData.skuList |
|||
// if (!skuBatchForm.image_id) {
|
|||
// delete skuBatchForm.image
|
|||
// }
|
|||
console.log('skuBatchForm', skuBatchForm) |
|||
for (const index in skuList) { |
|||
skuList[index] = { ...skuList[index], ...skuBatchForm } |
|||
} |
|||
this.onUpdate(false) |
|||
} |
|||
|
|||
/** |
|||
* 过滤对象的空元素 |
|||
* (仅支持一维对象) |
|||
* @param {object} object 源对象 |
|||
* @returns {object} |
|||
*/ |
|||
getFilterObject (object) { |
|||
const newObj = {} |
|||
for (const key in object) { |
|||
const value = object[key] |
|||
// value === 0 可以不过滤image_id为0的情况
|
|||
// if (!isEmpty(value) || value === 0) {
|
|||
// newObj[key] = value
|
|||
// }
|
|||
if (!isEmpty(value)) { |
|||
newObj[key] = value |
|||
} |
|||
} |
|||
return newObj |
|||
} |
|||
|
|||
// 表单验证
|
|||
verifyForm () { |
|||
// 验证规格
|
|||
if (!this.verifySpec()) { |
|||
return false |
|||
} |
|||
// 验证sku
|
|||
if (!this.verifySkuList()) { |
|||
return false |
|||
} |
|||
return true |
|||
} |
|||
|
|||
// 验证sku
|
|||
verifySkuList () { |
|||
const columns = [ |
|||
{ field: 'goods_price', name: '商品价格' }, |
|||
{ field: 'stock_num', name: '库存数量' }, |
|||
{ field: 'goods_weight', name: '商品重量' } |
|||
] |
|||
const skuList = this.multiSpecData.skuList |
|||
for (const skuIndex in skuList) { |
|||
const skuItem = skuList[skuIndex] |
|||
for (const colIndex in columns) { |
|||
const value = skuItem[columns[colIndex].field] |
|||
if (value === '' || value === null) { |
|||
this.error = `${columns[colIndex].name}不能为空` |
|||
return false |
|||
} |
|||
} |
|||
} |
|||
return true |
|||
} |
|||
|
|||
// 验证规格
|
|||
verifySpec () { |
|||
const specList = this.multiSpecData.specList |
|||
if (!specList.length) { |
|||
this.error = '亲,还没有添加规格组~' |
|||
return false |
|||
} |
|||
for (const index in specList) { |
|||
// 验证规格组
|
|||
const specGroup = specList[index] |
|||
if (isEmpty(specGroup.spec_name)) { |
|||
this.error = '规格组名称不能为空~' |
|||
return false |
|||
} |
|||
// 验证规格值
|
|||
const valueList = specGroup.valueList |
|||
if (!valueList.length) { |
|||
this.error = '还没有添加规格值~' |
|||
return false |
|||
} |
|||
for (const i in valueList) { |
|||
if (isEmpty(valueList[i].spec_value)) { |
|||
this.error = '规格值不能为空~' |
|||
return false |
|||
} |
|||
} |
|||
} |
|||
return true |
|||
} |
|||
|
|||
// 获取规格及SKU信息(表单提交)
|
|||
getFromSpecData () { |
|||
const { multiSpecData: { specList, skuList } } = this |
|||
const specData = { |
|||
specList: _.cloneDeep(specList), |
|||
skuList: _.cloneDeep(skuList) |
|||
} |
|||
for (const skuIndex in specData.skuList) { |
|||
const skuItem = specData.skuList[skuIndex] |
|||
delete skuItem.image |
|||
// delete skuItem.imageList
|
|||
delete skuItem.key |
|||
} |
|||
return specData |
|||
} |
|||
|
|||
/** |
|||
* 使用防抖节流方式刷新sku列表 |
|||
* @param {boolean} isDebounce 如果true则使用防抖函数 |
|||
*/ |
|||
onUpdate (isDebounce = true) { |
|||
if (isDebounce) { |
|||
debounce(getDataForDebounce, 200)(this) |
|||
} else { |
|||
getDataForDebounce(this) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// onUpdate调用的逻辑方法
|
|||
const getDataForDebounce = MultiSpecModel => { |
|||
return MultiSpecModel.getData() |
|||
} |
|||
|
|||
/** |
|||
* 生成笛卡尔积数据 |
|||
* cartesianProductOf([arr1, arr2, arr3 ...]) |
|||
*/ |
|||
const cartesianProductOf = arrays => { |
|||
if (!arrays.length) { |
|||
return [] |
|||
} |
|||
return Array.prototype.reduce.call(arrays, (arr1, arr2) => { |
|||
var ret = [] |
|||
arr1.forEach(v1 => { |
|||
arr2.forEach(v2 => { |
|||
ret.push(v1.concat([v2])) |
|||
}) |
|||
}) |
|||
return ret |
|||
}, [[]]) |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
import * as Api from '@/api/setting/delivery' |
|||
|
|||
/** |
|||
* 格式化分类列表 |
|||
*/ |
|||
export default { |
|||
|
|||
/** |
|||
* 向服务端获取分类列表并格式化 |
|||
*/ |
|||
getListSelect () { |
|||
return new Promise((resolve, reject) => { |
|||
|
|||
}) |
|||
} |
|||
|
|||
} |
|||
@ -1,52 +0,0 @@ |
|||
<template> |
|||
<div class="content-header"> |
|||
<div class="widget-head"> |
|||
<div class="widget-title">{{ title }}</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
|
|||
export default { |
|||
name: 'ContentHeader', |
|||
props: { |
|||
title: { |
|||
type: String, |
|||
default: null |
|||
} |
|||
}, |
|||
data () { |
|||
return {} |
|||
}, |
|||
mounted () { |
|||
}, |
|||
methods: { |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
@import '../index.less'; |
|||
|
|||
.widget-head { |
|||
width: 100%; |
|||
padding: 5px 0 14px 20px; |
|||
border-bottom: 1px solid #eef1f5; |
|||
margin-bottom: 20px; |
|||
.widget-title { |
|||
position: relative; |
|||
font-size: 14px; |
|||
&::before { |
|||
content: ''; |
|||
position: absolute; |
|||
width: 4px; |
|||
height: 16px; |
|||
background: @primary-color; |
|||
// background: #46a6ff; |
|||
top: 4px; |
|||
left: -15px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import ContentHeader from './ContentHeader' |
|||
export default ContentHeader |
|||
@ -1,113 +0,0 @@ |
|||
import Modal from 'ant-design-vue/es/modal' |
|||
export default (Vue) => { |
|||
function dialog (component, componentProps, modalProps) { |
|||
const _vm = this |
|||
modalProps = modalProps || {} |
|||
if (!_vm || !_vm._isVue) { |
|||
return |
|||
} |
|||
let dialogDiv = document.querySelector('body>div[type=dialog]') |
|||
if (!dialogDiv) { |
|||
dialogDiv = document.createElement('div') |
|||
dialogDiv.setAttribute('type', 'dialog') |
|||
document.body.appendChild(dialogDiv) |
|||
} |
|||
|
|||
const handle = function (checkFunction, afterHandel) { |
|||
if (checkFunction instanceof Function) { |
|||
const res = checkFunction() |
|||
if (res instanceof Promise) { |
|||
res.then(c => { |
|||
c && afterHandel() |
|||
}) |
|||
} else { |
|||
res && afterHandel() |
|||
} |
|||
} else { |
|||
// checkFunction && afterHandel()
|
|||
checkFunction || afterHandel() |
|||
} |
|||
} |
|||
|
|||
const dialogInstance = new Vue({ |
|||
data () { |
|||
return { |
|||
visible: true |
|||
} |
|||
}, |
|||
router: _vm.$router, |
|||
store: _vm.$store, |
|||
mounted () { |
|||
this.$on('close', (v) => { |
|||
this.handleClose() |
|||
}) |
|||
}, |
|||
methods: { |
|||
handleClose () { |
|||
handle(this.$refs._component.onCancel, () => { |
|||
this.visible = false |
|||
this.$refs._component.$emit('close') |
|||
this.$refs._component.$emit('cancel') |
|||
dialogInstance.$destroy() |
|||
}) |
|||
}, |
|||
handleOk () { |
|||
handle(this.$refs._component.onOK || this.$refs._component.onOk, () => { |
|||
this.visible = false |
|||
this.$refs._component.$emit('close') |
|||
this.$refs._component.$emit('ok') |
|||
dialogInstance.$destroy() |
|||
}) |
|||
} |
|||
}, |
|||
render: function (h) { |
|||
const that = this |
|||
const modalModel = modalProps && modalProps.model |
|||
if (modalModel) { |
|||
delete modalProps.model |
|||
} |
|||
const ModalProps = Object.assign({}, modalModel && { model: modalModel } || {}, { |
|||
attrs: Object.assign({}, { |
|||
...(modalProps.attrs || modalProps) |
|||
}, { |
|||
visible: this.visible |
|||
}), |
|||
on: Object.assign({}, { |
|||
...(modalProps.on || modalProps) |
|||
}, { |
|||
ok: () => { |
|||
that.handleOk() |
|||
}, |
|||
cancel: () => { |
|||
that.handleClose() |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
const componentModel = componentProps && componentProps.model |
|||
if (componentModel) { |
|||
delete componentProps.model |
|||
} |
|||
const ComponentProps = Object.assign({}, componentModel && { model: componentModel } || {}, { |
|||
ref: '_component', |
|||
attrs: Object.assign({}, { |
|||
...((componentProps && componentProps.attrs) || componentProps) |
|||
}), |
|||
on: Object.assign({}, { |
|||
...((componentProps && componentProps.on) || componentProps) |
|||
}) |
|||
}) |
|||
|
|||
return h(Modal, ModalProps, [h(component, ComponentProps)]) |
|||
} |
|||
}).$mount(dialogDiv) |
|||
} |
|||
|
|||
Object.defineProperty(Vue.prototype, '$dialog', { |
|||
get: () => { |
|||
return function () { |
|||
dialog.apply(this, arguments) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
@ -1,44 +0,0 @@ |
|||
<template> |
|||
<iframe id="map" src="static/getpoint/index.html" width="915" height="610"></iframe> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
|
|||
// 省市区级联选择器组件 |
|||
export default { |
|||
name: 'Getpoint', |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
props: { |
|||
// v-model 指定选中项 |
|||
value: PropTypes.array.def() |
|||
}, |
|||
data () { |
|||
return { |
|||
// 选中项 |
|||
sValue: [] |
|||
} |
|||
}, |
|||
watch: { |
|||
value (val) { |
|||
this.sValue = val |
|||
} |
|||
}, |
|||
created () { |
|||
window.setCoordinate = this.setCoordinate |
|||
}, |
|||
methods: { |
|||
|
|||
setCoordinate (coordinate) { |
|||
this.$emit('setCoordinate', coordinate) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import Getpoint from './Getpoint' |
|||
export default Getpoint |
|||
@ -1,46 +0,0 @@ |
|||
<template> |
|||
<global-footer v-if="visibility" class="footer custom-render"> |
|||
<template v-slot:links> |
|||
<!-- <a href="https://www.github.com/vueComponent/pro-layout" target="_blank">Pro Layout</a> |
|||
<a href="https://www.github.com/vueComponent/ant-design-vue-pro" target="_blank">Github</a> |
|||
<a href="https://www.github.com/sendya/" target="_blank">@Sendya</a>--> |
|||
</template> |
|||
<template v-slot:copyright> |
|||
<span style="margin-right: 6px">{{ copyright }}</span> |
|||
<a :href="link.url" target="_blank">{{ link.text }}</a> |
|||
</template> |
|||
</global-footer> |
|||
</template> |
|||
|
|||
<script> |
|||
import { GlobalFooter } from '@/layouts/ProLayout' |
|||
|
|||
const uncompileStr = (code) => { |
|||
code = unescape(code) |
|||
var c = String.fromCharCode(code.charCodeAt(0) - code.length) |
|||
for (var i = 1; i < code.length; i++) { |
|||
c += String.fromCharCode(code.charCodeAt(i) - c.charCodeAt(i - 1)) |
|||
} |
|||
return c |
|||
} |
|||
|
|||
export default { |
|||
name: 'ProGlobalFooter', |
|||
components: { |
|||
GlobalFooter |
|||
}, |
|||
data () { |
|||
return { |
|||
visibility: true, |
|||
copyright: uncompileStr('%5D%B2%DF%E9%EB%DB%D0%CF%DC%94%C9%C9RbbdR%u8444%uF48F%uC5B1%uAD14%u5800%60%5EP%9C'), |
|||
link: { |
|||
text: uncompileStr('b%A2%98%A5%A5%7Dq%92%9C'), |
|||
url: uncompileStr('%7D%DC%E8%E4%E3%ADi%5E%A6%EE%EE%A5%A7%E2%D8%E5%E5%9D%91%D2%DC') |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
@ -1,74 +0,0 @@ |
|||
<template> |
|||
<a-dropdown v-if="currentUser" placement="bottomRight"> |
|||
<span class="ant-pro-account-avatar oneline-hide"> |
|||
<!-- <a-avatar size="small" src="https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png" class="antd-pro-global-header-index-avatar" /> --> |
|||
<a-icon type="user" :style="{ fontSize: '16px', marginRight: '5px' }"></a-icon> |
|||
<span>{{ currentUser.real_name || currentUser.user_name }}</span> |
|||
</span> |
|||
<template v-slot:overlay> |
|||
<a-menu class="ant-pro-drop-down menu" :selected-keys="[]"> |
|||
<a-menu-item v-if="menu" key="settings" @click="handleToSettings"> |
|||
<a-icon type="setting" />账户设置 |
|||
</a-menu-item> |
|||
<!-- <a-menu-divider v-if="menu" /> --> |
|||
<a-menu-item key="logout" @click="handleLogout"> |
|||
<a-icon type="logout" />退出登录 |
|||
</a-menu-item> |
|||
</a-menu> |
|||
</template> |
|||
</a-dropdown> |
|||
<span v-else> |
|||
<a-spin size="small" :style="{ marginLeft: 8, marginRight: 8 }" /> |
|||
</span> |
|||
</template> |
|||
|
|||
<script> |
|||
import { Modal } from 'ant-design-vue' |
|||
|
|||
export default { |
|||
name: 'AvatarDropdown', |
|||
props: { |
|||
currentUser: { |
|||
type: Object, |
|||
default: () => null |
|||
}, |
|||
menu: { |
|||
type: Boolean, |
|||
default: true |
|||
} |
|||
}, |
|||
methods: { |
|||
handleToSettings () { |
|||
this.$router.push({ path: '/manage/renew' }) |
|||
}, |
|||
handleLogout (e) { |
|||
Modal.confirm({ |
|||
title: '友情提示', |
|||
content: '真的要注销登录吗 ?', |
|||
onOk: () => { |
|||
return this.$store.dispatch('Logout') |
|||
.then(() => { |
|||
setTimeout(() => { |
|||
window.location.reload() |
|||
}, 200) |
|||
}) |
|||
}, |
|||
onCancel () { } |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.ant-pro-drop-down { |
|||
/deep/ .action { |
|||
padding: 0px 24px; |
|||
} |
|||
/deep/ .ant-dropdown-menu-item { |
|||
min-width: 130px; |
|||
padding-left: 20px; |
|||
font-size: @font-size-base; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,55 +0,0 @@ |
|||
<template> |
|||
<div :class="wrpCls"> |
|||
<avatar-dropdown :menu="showMenu" :current-user="currentUser" :class="prefixCls" /> |
|||
<!-- <select-lang :class="prefixCls" /> --> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import AvatarDropdown from './AvatarDropdown' |
|||
// import SelectLang from '@/components/SelectLang' |
|||
|
|||
export default { |
|||
name: 'RightContent', |
|||
components: { |
|||
AvatarDropdown |
|||
// SelectLang |
|||
}, |
|||
props: { |
|||
prefixCls: { |
|||
type: String, |
|||
default: 'ant-pro-global-header-index-action' |
|||
}, |
|||
isMobile: { |
|||
type: Boolean, |
|||
default: () => false |
|||
}, |
|||
topMenu: { |
|||
type: Boolean, |
|||
required: true |
|||
}, |
|||
theme: { |
|||
type: String, |
|||
required: true |
|||
} |
|||
}, |
|||
data () { |
|||
const currentUser = this.$store.getters.userInfo |
|||
return { |
|||
showMenu: true, |
|||
currentUser |
|||
} |
|||
}, |
|||
computed: { |
|||
wrpCls () { |
|||
return { |
|||
'ant-pro-global-header-index-right': true, |
|||
[`ant-pro-global-header-index-${(this.isMobile || !this.topMenu) ? 'light' : this.theme}`]: true |
|||
} |
|||
} |
|||
}, |
|||
mounted () { |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
@ -1,63 +0,0 @@ |
|||
<template> |
|||
<div class="input-number-group"> |
|||
<span class="ant-input-group-wrapper"> |
|||
<span class="ant-input-wrapper ant-input-group"> |
|||
<span v-if="addonBefore" class="ant-input-group-addon">{{ addonBefore }}</span> |
|||
<a-input-number v-bind="inputProps" v-model="sValue" @change="onChange" /> |
|||
<span v-if="addonAfter" class="ant-input-group-addon">{{ addonAfter }}</span> |
|||
</span> |
|||
</span> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
|
|||
// 数字输入框组 |
|||
export default { |
|||
name: 'InputNumberGroup', |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
props: { |
|||
// v-model 当前输入框值 |
|||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), |
|||
// 设置前置标签 |
|||
addonBefore: PropTypes.string.def(''), |
|||
// 设置后置标签 |
|||
addonAfter: PropTypes.string.def(''), |
|||
// input-number props属性 |
|||
inputProps: PropTypes.object.def({}) |
|||
}, |
|||
data () { |
|||
return { |
|||
// 当前值 |
|||
sValue: '' |
|||
} |
|||
}, |
|||
watch: { |
|||
value: { |
|||
// 首次加载的时候执行函数 |
|||
immediate: true, |
|||
handler (val) { |
|||
this.sValue = val |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
|
|||
// 触发change事件 |
|||
onChange (value) { |
|||
this.$emit('change', value) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.ant-input-group-wrapper { |
|||
width: auto !important; |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import InputNumberGroup from './InputNumberGroup' |
|||
export default InputNumberGroup |
|||
@ -1,369 +0,0 @@ |
|||
<template> |
|||
<a-modal |
|||
class="noborder" |
|||
:title="title" |
|||
:width="720" |
|||
:visible="visible" |
|||
:isLoading="isLoading" |
|||
:maskClosable="false" |
|||
@ok="handleSubmit" |
|||
@cancel="handleCancel" |
|||
> |
|||
<a-spin :spinning="isLoading"> |
|||
<div class="areas-content clearfix"> |
|||
<div class="areas-left fl-l"> |
|||
<h2 class="areas-title clearfix"> |
|||
<span class="fl-l">地区选择:</span> |
|||
<a |
|||
v-if="Object.keys(unSelected).length > 0" |
|||
class="areas-flip fl-r" |
|||
@click="handleSelectAll" |
|||
>全选</a> |
|||
</h2> |
|||
<div class="areas-list"> |
|||
<ul class="areas-list-body"> |
|||
<li |
|||
class="areas-item" |
|||
:class="{'show-children': !province.isHideChildren}" |
|||
v-for="(province, pidx) in unSelected" |
|||
:key="pidx" |
|||
> |
|||
<div class="text clearfix" @click="handleActive(province)"> |
|||
<a-icon class="icon" type="right" /> |
|||
<span class="item-title fl-l">{{ province.name }}</span> |
|||
<a class="item-flip fl-r" @click="handleSelected($event, 'province', province)">选择</a> |
|||
</div> |
|||
<ul v-show="!province.isHideChildren" class="areas-sublist"> |
|||
<li class="areas-item" v-for="(city, cidx) in province.city" :key="cidx"> |
|||
<div class="text clearfix"> |
|||
<span class="item-title fl-l">{{ city.name }}</span> |
|||
<a class="item-flip fl-r" @click="handleSelected($event, 'city', city)">选择</a> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
<div class="areas-right fl-r"> |
|||
<h2 class="areas-title">已选择:</h2> |
|||
<div class="areas-list"> |
|||
<ul class="areas-list-body"> |
|||
<li |
|||
class="areas-item" |
|||
:class="{'show-children': !province.isHideChildren}" |
|||
v-for="(province, pidx) in selected" |
|||
:key="pidx" |
|||
> |
|||
<div class="text clearfix" @click="handleActive(province)"> |
|||
<a-icon class="icon" type="right" /> |
|||
<span class="item-title fl-l">{{ province.name }}</span> |
|||
<a |
|||
class="item-flip fl-r" |
|||
@click="handleUnSelected($event, 'province', province)" |
|||
>删除</a> |
|||
</div> |
|||
<ul v-show="!province.isHideChildren" class="areas-sublist"> |
|||
<li class="areas-item" v-for="(city, cidx) in province.city" :key="cidx"> |
|||
<div class="text clearfix"> |
|||
<span class="item-title fl-l">{{ city.name }}</span> |
|||
<a class="item-flip fl-r" @click="handleUnSelected($event, 'city', city)">删除</a> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</a-spin> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import _ from 'lodash' |
|||
import RegionModel from '@/common/model/Region' |
|||
import { inArray } from '@/utils/util' |
|||
|
|||
export default { |
|||
name: 'AreasModal', |
|||
data () { |
|||
return { |
|||
// 对话框标题 |
|||
title: '选择地区', |
|||
// 标签布局属性 |
|||
labelCol: { span: 7 }, |
|||
// 输入框布局属性 |
|||
wrapperCol: { span: 13 }, |
|||
// modal(对话框)是否可见 |
|||
visible: false, |
|||
// modal(对话框)确定按钮 loading |
|||
isLoading: false, |
|||
// 全部地区列表 |
|||
regions: [], |
|||
// 城市总数 |
|||
citysCount: null, |
|||
// 自定义参数 |
|||
custom: {}, |
|||
// 已选择的城市id集 |
|||
selectedCityIds: [], |
|||
// 排除的城市id集(用于控制不显示在unSelected中) |
|||
excludedCityIds: [], |
|||
// 未选择的地区 |
|||
unSelected: {}, |
|||
// 已选择的地区 |
|||
selected: {} |
|||
} |
|||
}, |
|||
// 初始化获取地区数据 |
|||
created () { |
|||
// 获取地区数据 |
|||
RegionModel.getTreeData().then(data => { |
|||
this.regions = data |
|||
}) |
|||
// 城市总数 |
|||
RegionModel.getCitysCount().then(count => { |
|||
this.citysCount = count |
|||
}) |
|||
}, |
|||
methods: { |
|||
|
|||
// 显示对话框 |
|||
handle (custom, selectedCityIds = [], excludedCityIds = []) { |
|||
// 显示窗口, |
|||
this.visible = true |
|||
// 自定义参数 |
|||
this.custom = custom |
|||
// 已选择的城市id集 |
|||
this.selectedCityIds = selectedCityIds |
|||
// 排除的城市id集 |
|||
this.excludedCityIds = excludedCityIds |
|||
// 设置默认值 |
|||
this.init() |
|||
}, |
|||
|
|||
// 初始化数据 |
|||
init () { |
|||
// 初始化未选择的地区 |
|||
this.initUnSelectedData() |
|||
// 初始化已选择的地区 |
|||
this.initSelectedData() |
|||
}, |
|||
|
|||
// 初始化未选择的地区 |
|||
initUnSelectedData () { |
|||
const { unSelected, regions, selectedCityIds, excludedCityIds } = this |
|||
const data = _.cloneDeep(regions) |
|||
const newUnSelected = {} |
|||
// 遍历省份 |
|||
for (const pidx in data) { |
|||
const province = data[pidx] |
|||
// 遍历城市 |
|||
const cityList = [] |
|||
for (const cidx in province.city) { |
|||
const cityItem = province.city[cidx] |
|||
if (!inArray(cityItem.id, selectedCityIds) && !inArray(cityItem.id, excludedCityIds)) { |
|||
cityList.push(cityItem) |
|||
} |
|||
} |
|||
if (cityList.length) { |
|||
province.city = cityList |
|||
const isHideChildren = unSelected[pidx] ? unSelected[pidx].isHideChildren : true |
|||
newUnSelected[pidx] = { ...province, isHideChildren } |
|||
} |
|||
} |
|||
this.unSelected = newUnSelected |
|||
}, |
|||
|
|||
// 初始化已选择的地区 |
|||
initSelectedData () { |
|||
const { selected, regions, selectedCityIds } = this |
|||
const data = _.cloneDeep(regions) |
|||
const newSelected = {} |
|||
// 遍历省份 |
|||
for (const pidx in data) { |
|||
const province = data[pidx] |
|||
// 遍历城市 |
|||
const cityList = [] |
|||
for (const cidx in province.city) { |
|||
const cityItem = province.city[cidx] |
|||
if (inArray(cityItem.id, selectedCityIds)) { |
|||
cityList.push(cityItem) |
|||
} |
|||
} |
|||
if (cityList.length) { |
|||
province.city = cityList |
|||
const isHideChildren = selected[pidx] ? selected[pidx].isHideChildren : true |
|||
newSelected[pidx] = { ...province, isHideChildren } |
|||
} |
|||
} |
|||
this.selected = newSelected |
|||
}, |
|||
|
|||
// 展开/收缩子集 |
|||
handleActive (item) { |
|||
item.isHideChildren = !item.isHideChildren |
|||
}, |
|||
|
|||
// 选中地区 |
|||
handleSelected (e, type, item) { |
|||
e.stopPropagation() |
|||
const newCityIds = [] |
|||
if (type === 'province') { |
|||
for (const cidx in item.city) { |
|||
newCityIds.push(item.city[cidx].id) |
|||
} |
|||
} else if (type === 'city') { |
|||
newCityIds.push(item.id) |
|||
} |
|||
this.selectedCityIds = this.selectedCityIds.concat(newCityIds) |
|||
this.init() |
|||
}, |
|||
|
|||
// 取消选中地区 |
|||
handleUnSelected (e, type, item) { |
|||
e.stopPropagation() |
|||
const newCityIds = [] |
|||
if (type === 'province') { |
|||
for (const cidx in item.city) { |
|||
newCityIds.push(item.city[cidx].id) |
|||
} |
|||
} else if (type === 'city') { |
|||
newCityIds.push(item.id) |
|||
} |
|||
this.selectedCityIds = _.difference(this.selectedCityIds, newCityIds) |
|||
this.excludedCityIds = _.difference(this.excludedCityIds, newCityIds) |
|||
this.init() |
|||
}, |
|||
|
|||
// 全选 |
|||
handleSelectAll (e) { |
|||
e.stopPropagation() |
|||
const { selectedCityIds, unSelected } = this |
|||
const newCityIds = [] |
|||
// 遍历省份 |
|||
for (const pidx in unSelected) { |
|||
const province = unSelected[pidx] |
|||
// 遍历城市 |
|||
for (const cidx in province.city) { |
|||
const cityItem = province.city[cidx] |
|||
newCityIds.push(cityItem.id) |
|||
} |
|||
} |
|||
this.selectedCityIds = selectedCityIds.concat(newCityIds) |
|||
this.init() |
|||
}, |
|||
|
|||
/** |
|||
* 确认按钮 |
|||
*/ |
|||
handleSubmit (e) { |
|||
e.preventDefault() |
|||
if (this.selectedCityIds.length < 1) { |
|||
this.$message.error('请至少选择一个区域', 0.8) |
|||
return false |
|||
} |
|||
// 通知父端组件提交完成了 |
|||
this.$emit('handleSubmit', { |
|||
custom: this.custom, |
|||
selectedCityIds: this.selectedCityIds, |
|||
selectedText: this.getSelectedText() |
|||
}) |
|||
// 关闭对话框事件 |
|||
this.handleCancel() |
|||
}, |
|||
|
|||
// 获取已选择地区文本 |
|||
getSelectedText () { |
|||
const { regions, citysCount, selected, selectedCityIds } = this |
|||
const textData = [] |
|||
if (selectedCityIds.length === citysCount) { |
|||
return [{ name: '全国', citys: [] }] |
|||
} |
|||
for (const pidx in selected) { |
|||
const province = selected[pidx] |
|||
const citys = [] |
|||
if (province.city.length !== Object.keys(regions[pidx].city).length) { |
|||
for (const cidx in province.city) { |
|||
const city = province.city[cidx] |
|||
citys.push({ name: city.name }) |
|||
} |
|||
} |
|||
textData.push({ name: province.name, citys }) |
|||
} |
|||
return textData |
|||
}, |
|||
|
|||
/** |
|||
* 关闭对话框事件 |
|||
*/ |
|||
handleCancel () { |
|||
this.visible = false |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
<style lang="less" scoped> |
|||
|
|||
.areas-content { |
|||
.areas-left, |
|||
.areas-right { |
|||
width: 50%; |
|||
padding: 0 10px; |
|||
} |
|||
.areas-title { |
|||
font-size: @font-size-base; |
|||
width: 95%; |
|||
.areas-flip { |
|||
margin-right: 17px; |
|||
user-select: none; |
|||
font-size: 12px; |
|||
} |
|||
} |
|||
.areas-list { |
|||
border: 1px solid #ededed; |
|||
border-radius: 2px; |
|||
height: 450px; |
|||
width: 95%; |
|||
overflow-y: auto; |
|||
padding: 15px 10px; |
|||
padding-bottom: 0; |
|||
.areas-list-body, |
|||
.areas-sublist { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
.areas-item { |
|||
margin-bottom: 8px; |
|||
cursor: pointer; |
|||
.text { |
|||
padding-left: 20px; |
|||
position: relative; |
|||
} |
|||
&.show-children .icon { |
|||
transform: rotate(90deg); |
|||
} |
|||
.areas-item { |
|||
padding-left: 10px; |
|||
color: #999; |
|||
} |
|||
.icon { |
|||
position: absolute; |
|||
font-size: 11px; |
|||
transition: all 0.3s ease-in-out; |
|||
left: 5px; |
|||
top: 5px; |
|||
} |
|||
.item-flip { |
|||
user-select: none; |
|||
font-size: 12px; |
|||
} |
|||
.areas-sublist { |
|||
width: 100%; |
|||
padding-top: 8px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import AreasModal from './AreasModal' |
|||
export default AreasModal |
|||
@ -1,162 +0,0 @@ |
|||
<template> |
|||
<a-modal |
|||
title="新增文件分组" |
|||
:width="720" |
|||
:visible="visible" |
|||
:confirmLoading="confirmLoading" |
|||
:maskClosable="false" |
|||
@ok="handleSubmit" |
|||
@cancel="handleCancel" |
|||
> |
|||
<a-spin :spinning="confirmLoading"> |
|||
<a-form :form="form"> |
|||
<a-form-item label="分组名称" :labelCol="labelCol" :wrapperCol="wrapperCol"> |
|||
<a-input |
|||
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]" |
|||
/> |
|||
</a-form-item> |
|||
<a-form-item label="上级分组" :labelCol="labelCol" :wrapperCol="wrapperCol"> |
|||
<a-tree-select |
|||
:treeData="groupListTree" |
|||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }" |
|||
allowClear |
|||
v-decorator="['parent_id', {initialValue: 0}]" |
|||
></a-tree-select> |
|||
</a-form-item> |
|||
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前"> |
|||
<a-input-number |
|||
:min="0" |
|||
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]" |
|||
/> |
|||
</a-form-item> |
|||
</a-form> |
|||
</a-spin> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import * as Api from '@/api/files/group' |
|||
|
|||
export default { |
|||
props: { |
|||
// 分组列表 |
|||
groupList: { |
|||
type: Array, |
|||
required: true |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
// 对话框标题 |
|||
title: '', |
|||
// 标签布局属性 |
|||
labelCol: { |
|||
span: 7 |
|||
}, |
|||
// 输入框布局属性 |
|||
wrapperCol: { |
|||
span: 13 |
|||
}, |
|||
// modal(对话框)是否可见 |
|||
visible: false, |
|||
// modal(对话框)确定按钮 loading |
|||
confirmLoading: false, |
|||
// 当前表单元素 |
|||
form: this.$form.createForm(this), |
|||
|
|||
// 上级分组列表 |
|||
groupListTree: [] |
|||
} |
|||
}, |
|||
methods: { |
|||
|
|||
/** |
|||
* 显示对话框 |
|||
*/ |
|||
add () { |
|||
// 显示窗口 |
|||
this.visible = true |
|||
console.log(this.groupList) |
|||
// 获取分组列表 |
|||
this.getGroupList() |
|||
}, |
|||
|
|||
/** |
|||
* 获取分组列表 |
|||
*/ |
|||
getGroupList () { |
|||
const { groupList } = this |
|||
// 格式化分组列表 |
|||
this.groupListTree = [{ |
|||
title: '顶级分组', |
|||
key: 0, |
|||
value: 0 |
|||
}].concat(this.formatTreeData(groupList)) |
|||
}, |
|||
|
|||
/** |
|||
* 格式化分组列表 |
|||
*/ |
|||
formatTreeData (list, disabled = false) { |
|||
const data = [] |
|||
list.forEach(item => { |
|||
// 新的元素 |
|||
const netItem = { |
|||
title: item.name, |
|||
key: item.group_id, |
|||
value: item.group_id |
|||
} |
|||
// 递归整理子集 |
|||
if (item.children && item.children.length) { |
|||
netItem['children'] = this.formatTreeData(item['children'], netItem.disabled) |
|||
} |
|||
data.push(netItem) |
|||
}) |
|||
return data |
|||
}, |
|||
|
|||
/** |
|||
* 确认按钮 |
|||
*/ |
|||
handleSubmit (e) { |
|||
e.preventDefault() |
|||
// 表单验证 |
|||
const { form: { validateFields } } = this |
|||
validateFields((errors, values) => { |
|||
// 提交到后端api |
|||
if (!errors) { |
|||
this.onFormSubmit(values) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 关闭对话框事件 |
|||
*/ |
|||
handleCancel () { |
|||
this.visible = false |
|||
this.form.resetFields() |
|||
}, |
|||
|
|||
/** |
|||
* 提交到后端api |
|||
*/ |
|||
onFormSubmit (values) { |
|||
this.confirmLoading = true |
|||
Api.add({ form: values }) |
|||
.then((result) => { |
|||
// 显示成功 |
|||
this.$message.success(result.message, 1.5) |
|||
// 关闭对话框事件 |
|||
this.handleCancel() |
|||
// 通知父端组件提交完成了 |
|||
this.$emit('handleSubmit', values) |
|||
}) |
|||
.finally((result) => { |
|||
this.confirmLoading = false |
|||
}) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
@ -1,600 +0,0 @@ |
|||
<template> |
|||
<a-modal |
|||
:title="title" |
|||
:width="840" |
|||
:visible="visible" |
|||
:isLoading="isLoading" |
|||
:maskClosable="false" |
|||
:destroyOnClose="false" |
|||
@ok="handleSubmit" |
|||
@cancel="handleCancel" |
|||
> |
|||
<a-spin :spinning="isLoading"> |
|||
<div class="library-box clearfix"> |
|||
<!-- 分组列表 --> |
|||
<div class="file-group"> |
|||
<div class="group-tree"> |
|||
<a-directory-tree |
|||
v-if="groupListTreeSelect.length" |
|||
:treeData="groupListTreeSelect" |
|||
:blockNode="true" |
|||
:showIcon="false" |
|||
@select="onSelectGroup" |
|||
></a-directory-tree> |
|||
</div> |
|||
<a class="group-add" href="javascript:void(0);" @click="handleAddGroup">新增分组</a> |
|||
</div> |
|||
<!-- 文件列表 --> |
|||
<div class="file-list"> |
|||
<!-- 头部操作栏 --> |
|||
<div class="top-operate clearfix"> |
|||
<!-- 搜索框 --> |
|||
<a-input-search |
|||
class="fl-l" |
|||
style="width: 200px" |
|||
placeholder="搜索文件名称" |
|||
v-model="queryParam.fileName" |
|||
@search="onSearch" |
|||
/> |
|||
<!-- 上传按钮 --> |
|||
<div class="file-upload fl-r"> |
|||
<span |
|||
class="upload-desc" |
|||
>{{ fileType === FileTypeEnum.VIDEO.value ? '视频' : '图片' }}大小不能超过{{ uploadSizeLimit }}M</span> |
|||
<a-upload |
|||
name="iFile" |
|||
:accept="accept" |
|||
:beforeUpload="beforeUpload" |
|||
:customRequest="onUpload" |
|||
:multiple="true" |
|||
:showUploadList="false" |
|||
> |
|||
<a-button icon="cloud-upload">上传</a-button> |
|||
</a-upload> |
|||
</div> |
|||
</div> |
|||
<div class="file-list-body"> |
|||
<!-- 文件列表 --> |
|||
<ul v-if="fileList.data && fileList.data.length" class="file-list-ul clearfix"> |
|||
<li |
|||
class="file-item" |
|||
:class="{ active: selectedIndexs.indexOf(index) > -1 }" |
|||
v-for="(item, index) in fileList.data" |
|||
:key="index" |
|||
@click="onSelectItem(index)" |
|||
> |
|||
<div |
|||
class="img-cover" |
|||
:style="{ backgroundImage: `url('${item.preview_url}')`, width: fileType === FileTypeEnum.VIDEO.value ? '55px' : '95px' }" |
|||
></div> |
|||
<p class="file-name oneline-hide">{{ item.file_name }}</p> |
|||
<div class="select-mask"> |
|||
<a-icon class="selected-icon" type="check" /> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
<!-- 无数据时显示 --> |
|||
<a-empty v-else-if="!isLoading" /> |
|||
<!-- 底部操作栏 --> |
|||
<div class="footer-operate clearfix"> |
|||
<div class="fl-l" v-if="selectedIndexs.length"> |
|||
<span class="footer-desc">已选择{{ selectedIndexs.length }}项</span> |
|||
<a-config-provider :auto-insert-space-in-button="false"> |
|||
<a-button-group> |
|||
<a-button |
|||
v-if="inArray('delete', actions)" |
|||
class="btn-mini" |
|||
size="small" |
|||
@click="handleDelete()" |
|||
>删除</a-button> |
|||
<a-button |
|||
v-if="inArray('move', actions)" |
|||
class="btn-mini" |
|||
size="small" |
|||
@click="handleBatchMove()" |
|||
>移动</a-button> |
|||
<a-button |
|||
v-if="inArray('copyIds', actions)" |
|||
class="btn-mini" |
|||
size="small" |
|||
@click="handleCopyIds()" |
|||
>复制ID</a-button> |
|||
</a-button-group> |
|||
</a-config-provider> |
|||
</div> |
|||
<!-- 分页组件 --> |
|||
<a-pagination |
|||
class="fl-r" |
|||
size="small" |
|||
v-model="fileList.current_page" |
|||
:total="fileList.total" |
|||
:defaultPageSize="15" |
|||
hideOnSinglePage |
|||
@change="handleNextPage" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</a-spin> |
|||
<!-- 新增分组 --> |
|||
<AddGroupForm ref="AddGroupForm" :groupList="groupList" @handleSubmit="getGroupList" /> |
|||
<!-- 移动分组 --> |
|||
<MoveGroupForm ref="MoveGroupForm" :groupList="groupListTree" @handleSubmit="handleRefresh" /> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import store from '@/store' |
|||
import { debounce, inArray, assignment } from '@/utils/util' |
|||
import * as FileApi from '@/api/files' |
|||
import * as GroupApi from '@/api/files/group' |
|||
import * as UploadApi from '@/api/upload' |
|||
import FileTypeEnum from '@/common/enum/file/FileType' |
|||
import ChannelEnum from '@/common/enum/file/Channel' |
|||
import AddGroupForm from './AddGroupForm' |
|||
import MoveGroupForm from './MoveGroupForm' |
|||
|
|||
export default { |
|||
name: 'FilesModal', |
|||
components: { |
|||
AddGroupForm, |
|||
MoveGroupForm |
|||
}, |
|||
props: { |
|||
// 多选模式, 如果false为单选 |
|||
multiple: PropTypes.bool.def(false), |
|||
// 最大选择的数量限制, multiple模式下有效 |
|||
maxNum: PropTypes.integer.def(100), |
|||
// 已选择的数量 |
|||
selectedNum: PropTypes.integer.def(0), |
|||
// 文件类型 (10图片 30视频) |
|||
fileType: PropTypes.integer.def(FileTypeEnum.IMAGE.value), |
|||
// 文件操作 |
|||
actions: PropTypes.array.def(['delete', 'move', 'copyIds']), |
|||
}, |
|||
data () { |
|||
return { |
|||
// 对话框标题 |
|||
title: '图片库', |
|||
// modal(对话框)是否可见 |
|||
visible: false, |
|||
// 枚举类 |
|||
FileTypeEnum, |
|||
// 后端上传api |
|||
uploadUrl: UploadApi.image, |
|||
// 上传文件大小限制 |
|||
uploadSizeLimit: 2, |
|||
// 文件上传的格式限制 |
|||
accept: '', |
|||
// 查询参数 |
|||
queryParam: { |
|||
// 文件类型: 图片 |
|||
fileType: FileTypeEnum.IMAGE.value, |
|||
// 上传来源: 商户后台 |
|||
channel: ChannelEnum.STORE.value, |
|||
// 当前页码 |
|||
page: 1, |
|||
// 文件名称 |
|||
fileName: '', |
|||
// 文件分组 |
|||
groupId: 0, |
|||
}, |
|||
// modal(对话框)确定按钮 loading |
|||
isLoading: true, |
|||
// 分组列表 |
|||
groupList: [], |
|||
// 文件列表 |
|||
fileList: [], |
|||
// 文件分组列表(树状结构) |
|||
groupListTree: [], |
|||
// 文件分组列表(用于筛选组件) |
|||
groupListTreeSelect: [], |
|||
// 选中的文件 |
|||
selectedIndexs: [], |
|||
// 上传中的文件 |
|||
uploading: [] |
|||
} |
|||
}, |
|||
created () { |
|||
}, |
|||
beforeCreate () { |
|||
assignment(this, { inArray }) |
|||
}, |
|||
methods: { |
|||
|
|||
// 显示对话框 |
|||
show () { |
|||
// 显示窗口 |
|||
this.visible = true |
|||
// 初始化文件类型 |
|||
this.initFileType() |
|||
// 获取分组列表 |
|||
this.getGroupList() |
|||
// 获取文件列表 |
|||
this.getFileList() |
|||
}, |
|||
|
|||
// 初始化文件类型 |
|||
initFileType () { |
|||
const publicConfig = store.getters.publicConfig |
|||
if (this.fileType === FileTypeEnum.IMAGE.value) { |
|||
this.title = '图片库' |
|||
this.accept = 'image/jpeg,image/png,image/gif,image/webp' |
|||
this.uploadUrl = UploadApi.image |
|||
this.uploadSizeLimit = publicConfig.uploadImageSize || 2 |
|||
} |
|||
if (this.fileType === FileTypeEnum.VIDEO.value) { |
|||
this.title = '视频库' |
|||
this.accept = '.mp4' |
|||
this.uploadUrl = UploadApi.video |
|||
this.uploadSizeLimit = publicConfig.uploadVideoSize || 10 |
|||
} |
|||
this.queryParam.fileType = this.fileType |
|||
}, |
|||
|
|||
// 获取文件分组列表 |
|||
getGroupList () { |
|||
// this.isLoading = true |
|||
GroupApi.list({}).then(result => { |
|||
// 记录列表数据 |
|||
const groupList = result.data.list |
|||
this.groupList = groupList |
|||
// 格式化分组列表 |
|||
const groupListTree = this.formatTreeData(groupList) |
|||
// 记录 groupListTree |
|||
this.groupListTree = groupListTree |
|||
// 记录 groupListTreeSelect |
|||
this.groupListTreeSelect = [{ |
|||
title: '全部', |
|||
key: -1, |
|||
value: -1 |
|||
}, { |
|||
title: '未分组', |
|||
key: 0, |
|||
value: 0 |
|||
}].concat(groupListTree) |
|||
// this.isLoading = false |
|||
}) |
|||
}, |
|||
|
|||
// 获取文件列表 |
|||
getFileList () { |
|||
this.isLoading = true |
|||
FileApi.list(this.queryParam) |
|||
.then(result => { |
|||
this.fileList = result.data.list |
|||
this.isLoading = false |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 格式化分组列表 |
|||
*/ |
|||
formatTreeData (list, disabled = false) { |
|||
const data = [] |
|||
list.forEach(item => { |
|||
// 新的元素 |
|||
const netItem = { |
|||
title: item.name, |
|||
key: item.group_id, |
|||
value: item.group_id |
|||
} |
|||
// 递归整理子集 |
|||
if (item.children && item.children.length) { |
|||
netItem['children'] = this.formatTreeData(item['children'], netItem.disabled) |
|||
} else { |
|||
netItem['isLeaf'] = true |
|||
netItem['scopedSlots'] = { icon: 'meh' } |
|||
} |
|||
data.push(netItem) |
|||
}) |
|||
return data |
|||
}, |
|||
|
|||
// 记录选中的分组 |
|||
onSelectGroup (selectedKeys) { |
|||
this.queryParam.groupId = selectedKeys[0] |
|||
this.handleRefresh(true) |
|||
}, |
|||
|
|||
// 记录选中的文件 |
|||
onSelectItem (index) { |
|||
const { multiple, maxNum, selectedIndexs } = this |
|||
// 记录选中状态 |
|||
if (!multiple) { |
|||
this.selectedIndexs = [index] |
|||
return |
|||
} |
|||
const key = selectedIndexs.indexOf(index) |
|||
const selected = key > -1 |
|||
// 验证数量限制 |
|||
if (!selected && (selectedIndexs.length + this.selectedNum) >= maxNum) { |
|||
this.$message.warning(`最多可选${maxNum}个文件`, 1) |
|||
return |
|||
} |
|||
!selected ? this.selectedIndexs.push(index) : this.selectedIndexs.splice(key, 1) |
|||
}, |
|||
|
|||
// 新增分组 |
|||
handleAddGroup () { |
|||
this.$refs.AddGroupForm.add() |
|||
}, |
|||
|
|||
// 搜索文件名称 |
|||
onSearch () { |
|||
this.handleRefresh(true) |
|||
}, |
|||
|
|||
// 事件: 上传文件之前 |
|||
beforeUpload (file, fileList) { |
|||
// 显示错误提示(防抖处理) |
|||
const showErrorMsg = debounce(this.$message.error, 20) |
|||
// 验证文件大小 |
|||
const fileSizeMb = file.size / 1024 / 1024 |
|||
if (fileSizeMb > this.uploadSizeLimit) { |
|||
showErrorMsg(`上传的文件大小不能超出${this.uploadSizeLimit}MB`) |
|||
return false |
|||
} |
|||
// 验证文件上传数量 |
|||
if (fileList.length > 10) { |
|||
showErrorMsg('一次上传的文件数量不能超出10个') |
|||
return false |
|||
} |
|||
return true |
|||
}, |
|||
|
|||
// 事件: 自定义上传 |
|||
onUpload (info) { |
|||
this.isLoading = true |
|||
// 记录上传状态 |
|||
this.uploading.push(true) |
|||
// 构建上传参数 |
|||
const formData = new FormData() |
|||
formData.append('iFile', info.file) |
|||
formData.append('groupId', this.queryParam.groupId) |
|||
// 开始上传 |
|||
this.uploadUrl(formData) |
|||
.finally(() => { |
|||
this.uploading.pop() |
|||
if (this.uploading.length === 0) { |
|||
this.isLoading = false |
|||
this.handleRefresh(true) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 列表分页事件 |
|||
handleNextPage (page, pageSize) { |
|||
this.queryParam.page = page |
|||
this.handleRefresh() |
|||
}, |
|||
|
|||
// 关闭对话框事件 |
|||
handleCancel () { |
|||
this.visible = false |
|||
this.selectedIndexs = [] |
|||
}, |
|||
|
|||
/** |
|||
* 刷新文件列表 |
|||
* @param Boolean bool 强制刷新到第一页 |
|||
*/ |
|||
handleRefresh (bool = false) { |
|||
bool && (this.queryParam.page = 1) |
|||
// 清空选中 |
|||
this.selectedIndexs = [] |
|||
// 获取文件列表 |
|||
this.getFileList() |
|||
}, |
|||
|
|||
// 删除文件 |
|||
handleDelete (item) { |
|||
const that = this |
|||
const fileIds = this.getSelectedItemIds() |
|||
const modal = this.$confirm({ |
|||
title: '您确定要删除该文件吗?', |
|||
content: '删除后不可恢复,请谨慎操作', |
|||
onOk () { |
|||
return FileApi.deleted({ fileIds }) |
|||
.then(result => { |
|||
that.$message.success(result.message, 1.5) |
|||
that.handleRefresh() |
|||
}) |
|||
.catch(() => true) |
|||
.finally(result => modal.destroy()) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 批量移动文件 |
|||
*/ |
|||
handleBatchMove () { |
|||
const fileIds = this.getSelectedItemIds() |
|||
this.$refs.MoveGroupForm.show(fileIds) |
|||
}, |
|||
|
|||
// 复制选中的文件ID集 |
|||
handleCopyIds () { |
|||
const itemIds = this.getSelectedItemIds() |
|||
this.$copyText(itemIds.join(',')).then(res => { |
|||
this.$message.success('复制成功', 1.5) |
|||
}) |
|||
}, |
|||
|
|||
// 获取选中的文件id集 |
|||
getSelectedItemIds () { |
|||
const selectedItems = this.getSelectedItems() |
|||
return selectedItems.map(item => item.file_id) |
|||
}, |
|||
|
|||
// 获取选中的文件 |
|||
getSelectedItems () { |
|||
const selectedItems = [] |
|||
for (const key in this.selectedIndexs) { |
|||
const index = this.selectedIndexs[key] |
|||
selectedItems.push(this.fileList.data[index]) |
|||
} |
|||
return selectedItems |
|||
}, |
|||
|
|||
// 确认按钮 |
|||
handleSubmit (e) { |
|||
e.preventDefault() |
|||
// 获取选中的文件 |
|||
const selectedItems = this.getSelectedItems() |
|||
// 通知父端组件提交完成了 |
|||
this.$emit('handleSubmit', selectedItems) |
|||
// 关闭对话框 |
|||
this.handleCancel() |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
/deep/.ant-modal-header, |
|||
/deep/.ant-modal-footer { |
|||
border: none; |
|||
} |
|||
/deep/.ant-modal-body { |
|||
padding: 6px; |
|||
} |
|||
|
|||
/deep/.ant-empty { |
|||
padding: 120px 0; |
|||
} |
|||
|
|||
/* 文件库 */ |
|||
.library-box { |
|||
user-select: none; |
|||
|
|||
// 文件分组 |
|||
.file-group { |
|||
float: left; |
|||
border-right: 1px solid #e6e6e6; |
|||
|
|||
// 分组列表 |
|||
.group-tree { |
|||
width: 150px; |
|||
height: 440px; |
|||
overflow-y: auto; |
|||
overflow-x: auto; |
|||
|
|||
/deep/.ant-tree { |
|||
display: inline-block; |
|||
min-width: 100%; |
|||
max-height: 380px; |
|||
width: auto; |
|||
} |
|||
} |
|||
|
|||
// 新增分组 |
|||
.group-add { |
|||
display: block; |
|||
margin-top: 20px; |
|||
font-size: @font-size-base; |
|||
padding: 0 30px; |
|||
} |
|||
} |
|||
|
|||
// 文件列表 |
|||
.file-list { |
|||
float: left; |
|||
width: 630px; |
|||
margin-left: 20px; |
|||
|
|||
// 头部操作区 |
|||
.top-operate { |
|||
margin-bottom: 10px; |
|||
.file-upload { |
|||
.upload-desc { |
|||
font-size: 12px; |
|||
padding-right: 10px; |
|||
color: #999; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 文件列表 |
|||
.file-list-body { |
|||
height: 455px; |
|||
.file-list-ul { |
|||
margin: 0; |
|||
padding: 0; |
|||
height: 417px; |
|||
} |
|||
.file-item { |
|||
width: 110px; |
|||
position: relative; |
|||
cursor: pointer; |
|||
border-radius: 2px; |
|||
padding: 4px; |
|||
border: 1px solid rgba(0, 0, 0, 0.05); |
|||
float: left; |
|||
margin: 8px; |
|||
-webkit-transition: All 0.2s ease-in-out; |
|||
-moz-transition: All 0.2s ease-in-out; |
|||
-o-transition: All 0.2s ease-in-out; |
|||
transition: All 0.2s ease-in-out; |
|||
&:hover { |
|||
border: 1px solid #16bce2; |
|||
} |
|||
} |
|||
.file-item { |
|||
// 文件名称 |
|||
.file-name { |
|||
font-size: 12px; |
|||
text-align: center; |
|||
} |
|||
// 预览图 |
|||
.img-cover { |
|||
margin: 0 auto; |
|||
width: 95px; |
|||
height: 95px; |
|||
background: no-repeat center center / 100%; |
|||
} |
|||
// 遮罩层(选中时) |
|||
&.active .select-mask { |
|||
display: block; |
|||
} |
|||
.select-mask { |
|||
display: none; |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background: rgba(0, 0, 0, 0.41); |
|||
text-align: center; |
|||
border-radius: 2px; |
|||
.selected-icon { |
|||
font-size: 26px; |
|||
color: #fff; |
|||
line-height: 122px; |
|||
text-align: center; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 底部操作栏 |
|||
.footer-operate { |
|||
height: 28px; |
|||
margin-top: 10px; |
|||
.footer-desc { |
|||
color: #999; |
|||
margin-right: 10px; |
|||
} |
|||
.btn-mini { |
|||
font-size: @font-size-base; |
|||
padding: 0 15px; |
|||
height: 28px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,136 +0,0 @@ |
|||
<template> |
|||
<a-modal |
|||
:title="title" |
|||
:width="420" |
|||
:visible="visible" |
|||
:confirmLoading="confirmLoading" |
|||
:maskClosable="false" |
|||
@ok="handleSubmit" |
|||
@cancel="handleCancel" |
|||
> |
|||
<a-spin :spinning="confirmLoading"> |
|||
<a-tree |
|||
v-if="groupTreeData.length" |
|||
:selectable="true" |
|||
:blockNode="true" |
|||
:treeData="groupTreeData" |
|||
:autoExpandParent="true" |
|||
@select="onSelect" |
|||
/> |
|||
</a-spin> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import * as Api from '@/api/files' |
|||
|
|||
export default { |
|||
props: { |
|||
// 分组列表 |
|||
groupList: { |
|||
type: Array, |
|||
required: true |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
// 对话框标题 |
|||
title: '移动到分组', |
|||
// 标签布局属性 |
|||
labelCol: { |
|||
xs: { span: 24 }, |
|||
sm: { span: 7 } |
|||
}, |
|||
// 输入框布局属性 |
|||
wrapperCol: { |
|||
xs: { span: 24 }, |
|||
sm: { span: 13 } |
|||
}, |
|||
// modal(对话框)是否可见 |
|||
visible: false, |
|||
// modal(对话框)确定按钮 loading |
|||
confirmLoading: false, |
|||
// 当前表单 |
|||
form: this.$form.createForm(this), |
|||
|
|||
// 文件ID集 |
|||
filesIds: {}, |
|||
// 分组列表 树状结构 |
|||
groupTreeData: [], |
|||
// 选中的分组 |
|||
selectedKeys: [] |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
|
|||
/** |
|||
* 新增操作权限 |
|||
*/ |
|||
show (filesIds) { |
|||
// 显示窗口 |
|||
this.visible = true |
|||
this.filesIds = filesIds |
|||
// 获取分组列表 |
|||
this.getList() |
|||
}, |
|||
|
|||
// 获取分组列表 |
|||
getList () { |
|||
if (this.groupTreeData.length <= 0) { |
|||
this.groupTreeData = [{ |
|||
title: '未分组', |
|||
key: 0, |
|||
value: 0 |
|||
}].concat(this.groupList) |
|||
} |
|||
}, |
|||
|
|||
// 记录选中项 |
|||
onSelect (selectedKeys) { |
|||
this.selectedKeys = selectedKeys |
|||
}, |
|||
|
|||
// 确认按钮 |
|||
handleSubmit (e) { |
|||
e.preventDefault() |
|||
if (this.selectedKeys.length) { |
|||
// 提交到后端api |
|||
this.onFormSubmit() |
|||
} else { |
|||
// 关闭对话框 |
|||
this.handleCancel() |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 取消按钮 |
|||
*/ |
|||
handleCancel () { |
|||
this.visible = false |
|||
this.form.resetFields() |
|||
}, |
|||
|
|||
/** |
|||
* 提交到后端api |
|||
*/ |
|||
onFormSubmit () { |
|||
this.confirmLoading = true |
|||
// 数据提交 |
|||
Api.moveGroup({ groupId: this.selectedKeys[0], fileIds: this.filesIds }) |
|||
.then(result => { |
|||
// 显示成功 |
|||
this.$message.success(result.message) |
|||
// 关闭对话框 |
|||
this.handleCancel() |
|||
// 通知父端组件提交完成了 |
|||
this.$emit('handleSubmit') |
|||
}) |
|||
.finally(() => { |
|||
this.confirmLoading = false |
|||
}) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
@ -1,2 +0,0 @@ |
|||
import FilesModal from './FilesModal' |
|||
export default FilesModal |
|||
@ -1,279 +0,0 @@ |
|||
<template> |
|||
<a-modal |
|||
class="noborder" |
|||
:title="title" |
|||
:width="820" |
|||
:visible="visible" |
|||
:isLoading="isLoading" |
|||
:maskClosable="false" |
|||
@ok="handleSubmit" |
|||
@cancel="handleCancel" |
|||
> |
|||
<!-- 搜索板块 --> |
|||
<div class="table-operator"> |
|||
<a-row class="row-item-search"> |
|||
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch"> |
|||
<a-form-item label="商品名称"> |
|||
<a-input v-decorator="['goodsName']" placeholder="请输入商品名称" /> |
|||
</a-form-item> |
|||
<a-form-item label="商品分类"> |
|||
<a-tree-select |
|||
:treeData="categoryListTree" |
|||
:dropdownStyle="{ maxHeight: '500px', overflow: 'auto' }" |
|||
allowClear |
|||
v-decorator="['categoryId', {initialValue: 0}]" |
|||
></a-tree-select> |
|||
</a-form-item> |
|||
<a-form-item class="search-btn"> |
|||
<a-button type="primary" icon="search" html-type="submit">搜索</a-button> |
|||
</a-form-item> |
|||
</a-form> |
|||
</a-row> |
|||
</div> |
|||
<s-table |
|||
ref="table" |
|||
:scroll="{ y: '420px', scrollToFirstRowOnChange: true }" |
|||
:rowKey="fieldName" |
|||
:loading="isLoading" |
|||
:columns="columns" |
|||
:data="loadData" |
|||
:rowSelection="rowSelection" |
|||
:pageSize="15" |
|||
> |
|||
<!-- 商品信息 --> |
|||
<template slot="item" slot-scope="item"> |
|||
<GoodsItem |
|||
:data="{ |
|||
image: item.goods_image, |
|||
imageAlt: '商品图片', |
|||
title: item.goods_name, |
|||
subtitle: `¥${item.goods_price_min}` |
|||
}" |
|||
:subTitleColor="true" |
|||
/> |
|||
</template> |
|||
<!-- 商品状态 --> |
|||
<span slot="status" slot-scope="text"> |
|||
<a-tag :color="text == 10 ? 'green' : 'red'">{{ text == 10 ? '上架' : '下架' }}</a-tag> |
|||
</span> |
|||
</s-table> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import cloneDeep from 'lodash.clonedeep' |
|||
import * as GoodsApi from '@/api/goods' |
|||
import CategoryModel from '@/common/model/Category' |
|||
import { STable, GoodsItem } from '@/components/Table' |
|||
|
|||
// table表头 |
|||
const columns = [ |
|||
{ |
|||
title: '商品ID', |
|||
dataIndex: 'goods_id' |
|||
}, |
|||
{ |
|||
title: '商品信息', |
|||
width: '335px', |
|||
scopedSlots: { customRender: 'item' } |
|||
}, |
|||
{ |
|||
title: '商品价格', |
|||
dataIndex: 'goods_price_min', |
|||
scopedSlots: { customRender: 'goods_price_min' } |
|||
}, |
|||
{ |
|||
title: '库存总量', |
|||
dataIndex: 'stock_total' |
|||
}, |
|||
{ |
|||
title: '状态', |
|||
dataIndex: 'status', |
|||
scopedSlots: { customRender: 'status' } |
|||
} |
|||
] |
|||
|
|||
export default { |
|||
name: 'GoodsModal', |
|||
props: { |
|||
// 多选模式, 如果false为单选 |
|||
multiple: PropTypes.bool.def(true), |
|||
// 最大选择的数量限制, multiple模式下有效 |
|||
maxNum: PropTypes.integer.def(100), |
|||
// 默认选中的列表记录 |
|||
defaultList: PropTypes.array.def([]) |
|||
}, |
|||
components: { |
|||
STable, |
|||
GoodsItem |
|||
}, |
|||
data () { |
|||
return { |
|||
// 对话框标题 |
|||
title: '商品库', |
|||
// modal(对话框)是否可见 |
|||
visible: false, |
|||
// loading状态 |
|||
isLoading: false, |
|||
// 当前表单元素 |
|||
searchForm: this.$form.createForm(this), |
|||
// 查询参数 |
|||
queryParam: {}, |
|||
// table表头 |
|||
columns, |
|||
// 加载数据方法 必须为 Promise 对象 |
|||
loadData: param => { |
|||
return GoodsApi.list({ ...param, ...this.queryParam }) |
|||
.then(response => { |
|||
return response.data.list |
|||
}) |
|||
}, |
|||
// 主键ID字段名 |
|||
fieldName: 'goods_id', |
|||
// 已选择的元素 |
|||
selectedRowKeys: [], |
|||
// 已选择的列表记录 |
|||
selectedItems: [], |
|||
// 商品分类列表 |
|||
categoryListTree: [] |
|||
} |
|||
}, |
|||
computed: { |
|||
rowSelection () { |
|||
return { |
|||
selectedRowKeys: this.selectedRowKeys, |
|||
onChange: this.onSelectChange |
|||
} |
|||
} |
|||
}, |
|||
created () { |
|||
// 获取商品分类列表 |
|||
this.getCategoryList() |
|||
}, |
|||
methods: { |
|||
|
|||
// 显示对话框 |
|||
handle () { |
|||
// 显示窗口 |
|||
this.visible = true |
|||
this.$nextTick(() => { |
|||
// 刷新列表数据 |
|||
// this.handleRefresh(true) |
|||
// 设置默认数据 |
|||
this.setDefaultValue() |
|||
}) |
|||
}, |
|||
|
|||
// 设置默认数据 |
|||
setDefaultValue () { |
|||
const { fieldName, defaultList } = this |
|||
if (defaultList.length) { |
|||
this.selectedItems = cloneDeep(defaultList) |
|||
this.selectedRowKeys = defaultList.map(item => item[fieldName]) |
|||
} |
|||
}, |
|||
|
|||
// 获取分类列表 |
|||
getCategoryList () { |
|||
this.isLoading = true |
|||
CategoryModel.getListFromScreen() |
|||
.then(selectList => { |
|||
this.categoryListTree = selectList |
|||
}) |
|||
.finally(result => { |
|||
this.isLoading = false |
|||
}) |
|||
}, |
|||
|
|||
// 选中项发生变化时的回调 |
|||
onSelectChange (selectedRowKeys, newSelectedItems) { |
|||
const { selectedItems } = this |
|||
this.selectedRowKeys = selectedRowKeys |
|||
this.selectedItems = this.createSelectedItems(selectedRowKeys, selectedItems, newSelectedItems) |
|||
}, |
|||
|
|||
/** |
|||
* 生成已选中的元素列表 |
|||
* @param array selectedRowKeys 当前已选中的ID集 |
|||
* @param array oldSelectedItems 已选择的列表记录 (change前) |
|||
* @param array newSelectedItems 已选择的列表记录 (change后) |
|||
*/ |
|||
createSelectedItems (selectedRowKeys, oldSelectedItems, newSelectedItems) { |
|||
const { fieldName } = this |
|||
const selectedItems = [] |
|||
oldSelectedItems.forEach(item => { |
|||
if (selectedRowKeys.includes(item[fieldName])) { |
|||
selectedItems.push(item) |
|||
} |
|||
}) |
|||
const oldSelectedKeys = oldSelectedItems.map(item => item[fieldName]) |
|||
newSelectedItems.forEach(item => { |
|||
if (!oldSelectedKeys.includes(item[fieldName]) && selectedRowKeys.includes(item[fieldName])) { |
|||
selectedItems.push(item) |
|||
} |
|||
}) |
|||
return selectedItems |
|||
}, |
|||
|
|||
/** |
|||
* 刷新列表 |
|||
* @param Boolean bool 强制刷新到第一页 |
|||
*/ |
|||
handleRefresh (bool = false) { |
|||
this.$refs.table.refresh(true) |
|||
}, |
|||
|
|||
// 确认搜索 |
|||
handleSearch (e) { |
|||
e.preventDefault() |
|||
this.searchForm.validateFields((error, values) => { |
|||
if (!error) { |
|||
this.queryParam = { ...this.queryParam, ...values } |
|||
this.handleRefresh(true) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 关闭对话框事件 |
|||
handleCancel () { |
|||
this.visible = false |
|||
this.queryParam = {} |
|||
this.searchForm.resetFields() |
|||
this.selectedRowKeys = [] |
|||
this.selectedItems = [] |
|||
}, |
|||
|
|||
// 确认按钮 |
|||
handleSubmit (e) { |
|||
e.preventDefault() |
|||
// 通知父端组件提交完成了 |
|||
this.$emit('handleSubmit', { |
|||
selectedItems: this.selectedItems, |
|||
selectedRowKeys: this.selectedRowKeys |
|||
}) |
|||
// 关闭对话框 |
|||
this.handleCancel() |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.ant-modal-root { |
|||
background: #ccc; |
|||
/deep/.ant-modal-body { |
|||
padding-bottom: 8px; |
|||
} |
|||
/deep/.ant-modal-footer { |
|||
padding-top: 0; |
|||
} |
|||
} |
|||
// 搜索表单 |
|||
.search-form { |
|||
/deep/.ant-form-item-control-wrapper { |
|||
min-width: 180px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import GoodsModal from './GoodsModal' |
|||
export default GoodsModal |
|||
@ -1,262 +0,0 @@ |
|||
<template> |
|||
<a-modal |
|||
class="noborder" |
|||
:title="title" |
|||
:width="820" |
|||
:visible="visible" |
|||
:isLoading="isLoading" |
|||
:maskClosable="false" |
|||
:destroyOnClose="true" |
|||
@ok="handleSubmit" |
|||
@cancel="handleCancel" |
|||
> |
|||
<div class="links-body"> |
|||
<a-collapse :defaultActiveKey="keys" :bordered="false" expandIconPosition="left"> |
|||
<a-collapse-panel v-for="group in linkList" :key="group.key" :header="group.title"> |
|||
<div class="link-list"> |
|||
<div |
|||
v-for="(item, index) in group.data" |
|||
:key="index" |
|||
class="link-item" |
|||
:class="{ active: activeKey == item.id}" |
|||
@click="handleClickItem(item)" |
|||
> |
|||
<span class="link-title">{{ item.title }}</span> |
|||
</div> |
|||
</div> |
|||
</a-collapse-panel> |
|||
</a-collapse> |
|||
<a-drawer |
|||
:title="drawer.title" |
|||
placement="right" |
|||
:width="350" |
|||
:closable="false" |
|||
:visible="drawer.visible" |
|||
:getContainer="false" |
|||
:wrapStyle="{ position: 'absolute' }" |
|||
@close="onCloseDrawer" |
|||
> |
|||
<a-form v-if="drawer.visible" :form="form"> |
|||
<a-form-item |
|||
v-for="(item, index) in curItem.form" |
|||
:key="index" |
|||
:label="item.lable" |
|||
:labelCol="{ span: 6 }" |
|||
:wrapperCol="{ span: 16 }" |
|||
> |
|||
<a-input |
|||
v-decorator="[`values.${index}`, { |
|||
initialValue: item.value, |
|||
rules: [{ required: item.required, message: `${item.lable}必须填写` }] |
|||
}]" |
|||
/> |
|||
<div class="form-item-help"> |
|||
<small v-html="item.tips"></small> |
|||
</div> |
|||
</a-form-item> |
|||
</a-form> |
|||
</a-drawer> |
|||
</div> |
|||
</a-modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import _ from 'lodash' |
|||
import { linkList } from '@/common/model/Links' |
|||
import { buildUrL } from '@/utils/util' |
|||
|
|||
export default { |
|||
name: 'LinkModal', |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
data () { |
|||
return { |
|||
// 对话框标题 |
|||
title: '选择链接', |
|||
// modal(对话框)是否可见 |
|||
visible: false, |
|||
// loading状态 |
|||
isLoading: false, |
|||
// 当前表单元素 |
|||
form: this.$form.createForm(this), |
|||
// 所有链接数据 |
|||
linkList, |
|||
// 当前选中的键值 |
|||
activeKey: '', |
|||
// 当前选中的链接 |
|||
curItem: null, |
|||
// 抽屉组件属性 |
|||
drawer: { |
|||
visible: false, |
|||
title: '' |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
keys () { |
|||
const { linkList } = this |
|||
return linkList.map(item => item.key) |
|||
} |
|||
}, |
|||
created () { }, |
|||
methods: { |
|||
|
|||
// 显示对话框 |
|||
handle (record = null) { |
|||
this.visible = true |
|||
if (record != null) { |
|||
this.handleClickItem(record) |
|||
} |
|||
}, |
|||
|
|||
// 选中事件 |
|||
handleClickItem (item) { |
|||
// 取消选中值 |
|||
if (this.activeKey === item.id) { |
|||
this.activeKey = '' |
|||
this.curItem = null |
|||
return |
|||
} |
|||
// 记录选中值 |
|||
this.activeKey = item.id |
|||
this.curItem = _.cloneDeep(item) |
|||
// 显示表单 |
|||
this.onShowFrom() |
|||
}, |
|||
|
|||
// 显示表单 |
|||
onShowFrom () { |
|||
const { curItem } = this |
|||
if (curItem.form && curItem.form.length) { |
|||
// 显示抽屉 |
|||
this.onShowDrawer() |
|||
} |
|||
}, |
|||
|
|||
// 显示抽屉 |
|||
onShowDrawer () { |
|||
const { drawer, curItem } = this |
|||
drawer.visible = true |
|||
drawer.title = curItem.title |
|||
}, |
|||
|
|||
// 关闭抽屉 |
|||
onCloseDrawer () { |
|||
this.activeKey = '' |
|||
this.curItem = null |
|||
this.drawer.visible = false |
|||
}, |
|||
|
|||
// 关闭对话框事件 |
|||
handleCancel () { |
|||
this.visible = false |
|||
this.onCloseDrawer() |
|||
}, |
|||
|
|||
// 确认按钮 |
|||
handleSubmit (e) { |
|||
e.preventDefault() |
|||
const { curItem, form: { validateFields } } = this |
|||
validateFields((errors, formData) => { |
|||
if (!errors) { |
|||
// 创建返回结果 |
|||
const result = curItem ? this.buildResult(curItem, formData) : null |
|||
// 通知父端组件提交完成了 |
|||
this.$emit('handleSubmit', result) |
|||
// 关闭对话框 |
|||
this.handleCancel() |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 构建选中的link对象 |
|||
buildResult (link, formData) { |
|||
for (const index in link.form) { |
|||
const item = link.form[index] |
|||
if (!item) { |
|||
continue |
|||
} |
|||
// 记录输入的数据 |
|||
if (!formData.values[index]) { |
|||
formData.values[index] = '' |
|||
} |
|||
// 将value赋值到key指向的param对象元素 |
|||
item.value = formData.values[index] |
|||
_.set(link.param, item.key, item.value) |
|||
} |
|||
// 将link对象拼接为url |
|||
if (link.type === 'PAGE') { |
|||
link.param.url = buildUrL(link.param.path, link.param.query) |
|||
} |
|||
// 克隆一个新对象返回 |
|||
return _.cloneDeep(link) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
@import '~ant-design-vue/es/style/themes/default.less'; |
|||
.ant-modal-root { |
|||
background: #ccc; |
|||
/deep/.ant-modal-body { |
|||
padding: 0 24px 15px 24px; |
|||
} |
|||
/deep/.ant-modal-footer { |
|||
padding-top: 0; |
|||
} |
|||
} |
|||
|
|||
/deep/.ant-collapse-header { |
|||
padding: 14px 16px; |
|||
font-size: @font-size-base; |
|||
font-weight: 700; |
|||
color: #595961; |
|||
} |
|||
|
|||
/deep/.ant-collapse-content-box { |
|||
padding-top: 0 !important; |
|||
} |
|||
|
|||
/deep/.ant-collapse > .ant-collapse-item { |
|||
border-bottom: none; |
|||
margin-bottom: 15px; |
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
|
|||
.links-body { |
|||
position: relative; |
|||
min-height: 360px; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.link-list { |
|||
.link-item { |
|||
float: left; |
|||
padding: 5px 15px; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
margin-right: 20px; |
|||
margin-bottom: 15px; |
|||
font-size: 12.5px; |
|||
cursor: pointer; |
|||
&:hover { |
|||
border: 1px solid @primary-color; |
|||
color: @primary-color; |
|||
} |
|||
&.active { |
|||
background: @primary-color; |
|||
border-color: @primary-color; |
|||
color: #fff; |
|||
} |
|||
&:last-child { |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import LinkModal from './LinkModal' |
|||
export default LinkModal |
|||
@ -1,6 +0,0 @@ |
|||
import GoodsModal from './GoodsModal' |
|||
import AreasModal from './AreasModal' |
|||
import FilesModal from './FilesModal' |
|||
import LinkModal from './LinkModal' |
|||
|
|||
export { GoodsModal, AreasModal, FilesModal, LinkModal } |
|||
@ -1,162 +0,0 @@ |
|||
<script> |
|||
import events from './events' |
|||
|
|||
export default { |
|||
name: 'MultiTab', |
|||
data () { |
|||
return { |
|||
fullPathList: [], |
|||
pages: [], |
|||
activeKey: '', |
|||
newTabIndex: 0 |
|||
} |
|||
}, |
|||
created () { |
|||
// bind event |
|||
events.$on('open', val => { |
|||
if (!val) { |
|||
throw new Error(`multi-tab: open tab ${val} err`) |
|||
} |
|||
this.activeKey = val |
|||
}).$on('close', val => { |
|||
if (!val) { |
|||
this.closeThat(this.activeKey) |
|||
return |
|||
} |
|||
this.closeThat(val) |
|||
}).$on('rename', ({ key, name }) => { |
|||
console.log('rename', key, name) |
|||
try { |
|||
const item = this.pages.find(item => item.path === key) |
|||
item.meta.customTitle = name |
|||
this.$forceUpdate() |
|||
} catch (e) { |
|||
} |
|||
}) |
|||
|
|||
this.pages.push(this.$route) |
|||
this.fullPathList.push(this.$route.fullPath) |
|||
this.selectedLastPath() |
|||
}, |
|||
methods: { |
|||
onEdit (targetKey, action) { |
|||
this[action](targetKey) |
|||
}, |
|||
remove (targetKey) { |
|||
this.pages = this.pages.filter(page => page.fullPath !== targetKey) |
|||
this.fullPathList = this.fullPathList.filter(path => path !== targetKey) |
|||
// 判断当前标签是否关闭,若关闭则跳转到最后一个还存在的标签页 |
|||
if (!this.fullPathList.includes(this.activeKey)) { |
|||
this.selectedLastPath() |
|||
} |
|||
}, |
|||
selectedLastPath () { |
|||
this.activeKey = this.fullPathList[this.fullPathList.length - 1] |
|||
}, |
|||
|
|||
// content menu |
|||
closeThat (e) { |
|||
// 判断是否为最后一个标签页,如果是最后一个,则无法被关闭 |
|||
if (this.fullPathList.length > 1) { |
|||
this.remove(e) |
|||
} else { |
|||
this.$message.info('这是最后一个标签了, 无法被关闭') |
|||
} |
|||
}, |
|||
closeLeft (e) { |
|||
const currentIndex = this.fullPathList.indexOf(e) |
|||
if (currentIndex > 0) { |
|||
this.fullPathList.forEach((item, index) => { |
|||
if (index < currentIndex) { |
|||
this.remove(item) |
|||
} |
|||
}) |
|||
} else { |
|||
this.$message.info('左侧没有标签') |
|||
} |
|||
}, |
|||
closeRight (e) { |
|||
const currentIndex = this.fullPathList.indexOf(e) |
|||
if (currentIndex < (this.fullPathList.length - 1)) { |
|||
this.fullPathList.forEach((item, index) => { |
|||
if (index > currentIndex) { |
|||
this.remove(item) |
|||
} |
|||
}) |
|||
} else { |
|||
this.$message.info('右侧没有标签') |
|||
} |
|||
}, |
|||
closeAll (e) { |
|||
const currentIndex = this.fullPathList.indexOf(e) |
|||
this.fullPathList.forEach((item, index) => { |
|||
if (index !== currentIndex) { |
|||
this.remove(item) |
|||
} |
|||
}) |
|||
}, |
|||
closeMenuClick (key, route) { |
|||
this[key](route) |
|||
}, |
|||
renderTabPaneMenu (e) { |
|||
return ( |
|||
<a-menu {...{ on: { click: ({ key, item, domEvent }) => { this.closeMenuClick(key, e) } } }}> |
|||
<a-menu-item key="closeThat">关闭当前标签</a-menu-item> |
|||
<a-menu-item key="closeRight">关闭右侧</a-menu-item> |
|||
<a-menu-item key="closeLeft">关闭左侧</a-menu-item> |
|||
<a-menu-item key="closeAll">关闭全部</a-menu-item> |
|||
</a-menu> |
|||
) |
|||
}, |
|||
// render |
|||
renderTabPane (title, keyPath) { |
|||
const menu = this.renderTabPaneMenu(keyPath) |
|||
|
|||
return ( |
|||
<a-dropdown overlay={menu} trigger={['contextmenu']}> |
|||
<span style={{ userSelect: 'none' }}>{ title }</span> |
|||
</a-dropdown> |
|||
) |
|||
} |
|||
}, |
|||
watch: { |
|||
'$route': function (newVal) { |
|||
this.activeKey = newVal.fullPath |
|||
if (this.fullPathList.indexOf(newVal.fullPath) < 0) { |
|||
this.fullPathList.push(newVal.fullPath) |
|||
this.pages.push(newVal) |
|||
} |
|||
}, |
|||
activeKey: function (newPathKey) { |
|||
this.$router.push({ path: newPathKey }) |
|||
} |
|||
}, |
|||
render () { |
|||
const { onEdit, $data: { pages } } = this |
|||
const panes = pages.map(page => { |
|||
return ( |
|||
<a-tab-pane |
|||
style={{ height: 0 }} |
|||
tab={this.renderTabPane(page.meta.customTitle || page.meta.title, page.fullPath)} |
|||
key={page.fullPath} closable={pages.length > 1} |
|||
> |
|||
</a-tab-pane>) |
|||
}) |
|||
|
|||
return ( |
|||
<div class="ant-pro-multi-tab"> |
|||
<div class="ant-pro-multi-tab-wrapper"> |
|||
<a-tabs |
|||
hideAdd |
|||
type={'editable-card'} |
|||
v-model={this.activeKey} |
|||
tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }} |
|||
{...{ on: { edit: onEdit } }}> |
|||
{panes} |
|||
</a-tabs> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
</script> |
|||
@ -1,2 +0,0 @@ |
|||
import Vue from 'vue' |
|||
export default new Vue() |
|||
@ -1,40 +0,0 @@ |
|||
import events from './events' |
|||
import MultiTab from './MultiTab' |
|||
import './index.less' |
|||
|
|||
const api = { |
|||
/** |
|||
* open new tab on route fullPath |
|||
* @param config |
|||
*/ |
|||
open: function (config) { |
|||
events.$emit('open', config) |
|||
}, |
|||
rename: function (key, name) { |
|||
events.$emit('rename', { key: key, name: name }) |
|||
}, |
|||
/** |
|||
* close current page |
|||
*/ |
|||
closeCurrentPage: function () { |
|||
this.close() |
|||
}, |
|||
/** |
|||
* close route fullPath tab |
|||
* @param config |
|||
*/ |
|||
close: function (config) { |
|||
events.$emit('close', config) |
|||
} |
|||
} |
|||
|
|||
MultiTab.install = function (Vue) { |
|||
if (Vue.prototype.$multiTab) { |
|||
return |
|||
} |
|||
api.instance = events |
|||
Vue.prototype.$multiTab = api |
|||
Vue.component('multi-tab', MultiTab) |
|||
} |
|||
|
|||
export default MultiTab |
|||
@ -1,25 +0,0 @@ |
|||
@import '../index'; |
|||
|
|||
@multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab"; |
|||
@multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper"; |
|||
|
|||
/* |
|||
.topmenu .@{multi-tab-prefix-cls} { |
|||
max-width: 1200px; |
|||
margin: -23px auto 24px auto; |
|||
} |
|||
*/ |
|||
.@{multi-tab-prefix-cls} { |
|||
margin: -23px -24px 24px -24px; |
|||
background: #fff; |
|||
} |
|||
|
|||
.topmenu .@{multi-tab-wrapper-prefix-cls} { |
|||
max-width: 1200px; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
.topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} { |
|||
max-width: 100%; |
|||
margin: 0 auto; |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
@import url('../index.less'); |
|||
|
|||
/* Make clicks pass-through */ |
|||
#nprogress { |
|||
pointer-events: none; |
|||
} |
|||
|
|||
#nprogress .bar { |
|||
background: @primary-color; |
|||
|
|||
position: fixed; |
|||
z-index: 1031; |
|||
top: 0; |
|||
left: 0; |
|||
|
|||
width: 100%; |
|||
height: 2px; |
|||
} |
|||
|
|||
/* Fancy blur effect */ |
|||
#nprogress .peg { |
|||
display: block; |
|||
position: absolute; |
|||
right: 0px; |
|||
width: 100px; |
|||
height: 100%; |
|||
box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color; |
|||
opacity: 1.0; |
|||
|
|||
-webkit-transform: rotate(3deg) translate(0px, -4px); |
|||
-ms-transform: rotate(3deg) translate(0px, -4px); |
|||
transform: rotate(3deg) translate(0px, -4px); |
|||
} |
|||
|
|||
/* Remove these to get rid of the spinner */ |
|||
#nprogress .spinner { |
|||
display: block; |
|||
position: fixed; |
|||
z-index: 1031; |
|||
top: 15px; |
|||
right: 15px; |
|||
} |
|||
|
|||
#nprogress .spinner-icon { |
|||
width: 18px; |
|||
height: 18px; |
|||
box-sizing: border-box; |
|||
|
|||
border: solid 2px transparent; |
|||
border-top-color: @primary-color; |
|||
border-left-color: @primary-color; |
|||
border-radius: 50%; |
|||
|
|||
-webkit-animation: nprogress-spinner 400ms linear infinite; |
|||
animation: nprogress-spinner 400ms linear infinite; |
|||
} |
|||
|
|||
.nprogress-custom-parent { |
|||
overflow: hidden; |
|||
position: relative; |
|||
} |
|||
|
|||
.nprogress-custom-parent #nprogress .spinner, |
|||
.nprogress-custom-parent #nprogress .bar { |
|||
position: absolute; |
|||
} |
|||
|
|||
@-webkit-keyframes nprogress-spinner { |
|||
0% { -webkit-transform: rotate(0deg); } |
|||
100% { -webkit-transform: rotate(360deg); } |
|||
} |
|||
@keyframes nprogress-spinner { |
|||
0% { transform: rotate(0deg); } |
|||
100% { transform: rotate(360deg); } |
|||
} |
|||
|
|||
@ -1,106 +0,0 @@ |
|||
import { Spin } from 'ant-design-vue' |
|||
|
|||
export const PageLoading = { |
|||
name: 'PageLoading', |
|||
props: { |
|||
tip: { |
|||
type: String, |
|||
default: 'Loading..' |
|||
}, |
|||
size: { |
|||
type: String, |
|||
default: 'large' |
|||
} |
|||
}, |
|||
render () { |
|||
const style = { |
|||
textAlign: 'center', |
|||
background: 'rgba(0,0,0,0.6)', |
|||
position: 'fixed', |
|||
top: 0, |
|||
bottom: 0, |
|||
left: 0, |
|||
right: 0, |
|||
zIndex: 1100 |
|||
} |
|||
const spinStyle = { |
|||
position: 'absolute', |
|||
left: '50%', |
|||
top: '40%', |
|||
transform: 'translate(-50%, -50%)' |
|||
} |
|||
return (<div style={style}> |
|||
<Spin size={this.size} style={spinStyle} tip={this.tip} /> |
|||
</div>) |
|||
} |
|||
} |
|||
|
|||
const version = '0.0.1' |
|||
const loading = {} |
|||
|
|||
loading.newInstance = (Vue, options) => { |
|||
let loadingElement = document.querySelector('body>div[type=loading]') |
|||
if (!loadingElement) { |
|||
loadingElement = document.createElement('div') |
|||
loadingElement.setAttribute('type', 'loading') |
|||
loadingElement.setAttribute('class', 'ant-loading-wrapper') |
|||
document.body.appendChild(loadingElement) |
|||
} |
|||
|
|||
const cdProps = Object.assign({ visible: false, size: 'large', tip: 'Loading...' }, options) |
|||
|
|||
const instance = new Vue({ |
|||
data () { |
|||
return { |
|||
...cdProps |
|||
} |
|||
}, |
|||
render () { |
|||
const { tip } = this |
|||
const props = {} |
|||
this.tip && (props.tip = tip) |
|||
if (this.visible) { |
|||
return <PageLoading { ...{ props } } /> |
|||
} |
|||
return null |
|||
} |
|||
}).$mount(loadingElement) |
|||
|
|||
function update (config) { |
|||
const { visible, size, tip } = { ...cdProps, ...config } |
|||
instance.$set(instance, 'visible', visible) |
|||
if (tip) { |
|||
instance.$set(instance, 'tip', tip) |
|||
} |
|||
if (size) { |
|||
instance.$set(instance, 'size', size) |
|||
} |
|||
} |
|||
|
|||
return { |
|||
instance, |
|||
update |
|||
} |
|||
} |
|||
|
|||
const api = { |
|||
show: function (options) { |
|||
this.instance.update({ ...options, visible: true }) |
|||
}, |
|||
hide: function () { |
|||
this.instance.update({ visible: false }) |
|||
} |
|||
} |
|||
|
|||
const install = function (Vue, options) { |
|||
if (Vue.prototype.$loading) { |
|||
return |
|||
} |
|||
api.instance = loading.newInstance(Vue, options) |
|||
Vue.prototype.$loading = api |
|||
} |
|||
|
|||
export default { |
|||
version, |
|||
install |
|||
} |
|||
@ -1,96 +0,0 @@ |
|||
<template> |
|||
<span v-if="PlatformIcons[name]" class="platform-icon"> |
|||
<a-tooltip placement="bottom"> |
|||
<template v-if="showTips" slot="title"> |
|||
<span class="f-12">{{ tipsPrefix }}{{ PlatformName[name] }}</span> |
|||
</template> |
|||
<a-icon |
|||
class="icon" |
|||
:class="[name]" |
|||
:component="PlatformIcons[name]" |
|||
:style="{ fontSize: `${iconSize}px` }" |
|||
/> |
|||
</a-tooltip> |
|||
</span> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import { mpWeixin, h5, app, h5Weixin } from '@/core/icons' |
|||
|
|||
// 注册来源名称 |
|||
const PlatformName = { |
|||
'MP-WEIXIN': '微信小程序', |
|||
'H5-WEIXIN': '微信公众号', |
|||
'H5': 'H5', |
|||
'APP': 'APP' |
|||
} |
|||
|
|||
// 注册来源图标 |
|||
const PlatformIcons = { |
|||
'MP-WEIXIN': mpWeixin, |
|||
'H5-WEIXIN': h5Weixin, |
|||
'H5': h5, |
|||
'APP': app, |
|||
} |
|||
|
|||
export default { |
|||
name: 'PlatformIcon', |
|||
props: { |
|||
// 指定的客户端 (APP、H5、小程序等) |
|||
name: PropTypes.string.def(''), |
|||
// 是否显示文字提示 |
|||
showTips: PropTypes.bool.def(false), |
|||
// 文字提示前缀 |
|||
tipsPrefix: PropTypes.string.def(''), |
|||
// 图标大小 |
|||
iconSize: PropTypes.integer.def(16) |
|||
}, |
|||
data () { |
|||
return { |
|||
PlatformIcons, |
|||
PlatformName |
|||
} |
|||
}, |
|||
methods: { |
|||
fetchNotice () { |
|||
if (!this.visible) { |
|||
this.loading = true |
|||
setTimeout(() => { |
|||
this.loading = false |
|||
}, 2000) |
|||
} else { |
|||
this.loading = false |
|||
} |
|||
this.visible = !this.visible |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less"> |
|||
// 客户端图标 |
|||
.platform-icon { |
|||
font-size: 16px; |
|||
|
|||
.icon { |
|||
margin-right: 5px; |
|||
|
|||
&:last-child { |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
|
|||
.MP-WEIXIN { |
|||
color: #04be02; |
|||
} |
|||
|
|||
.H5-WEIXIN { |
|||
color: #04be02; |
|||
} |
|||
|
|||
.H5 { |
|||
color: #e44c27; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import PlatformIcon from './PlatformIcon' |
|||
export default PlatformIcon |
|||
@ -1,161 +0,0 @@ |
|||
<template> |
|||
<div> |
|||
<a-button @click="handleSelectGoods">选择商品</a-button> |
|||
<a-table |
|||
v-show="selectedItems.length" |
|||
class="table-goodsList" |
|||
rowKey="goods_id" |
|||
:columns="columns" |
|||
:dataSource="selectedItems" |
|||
:pagination="false" |
|||
> |
|||
<!-- 商品信息 --> |
|||
<template slot="item" slot-scope="item"> |
|||
<GoodsItem |
|||
:data="{ |
|||
image: item.goods_image, |
|||
imageAlt: '商品图片', |
|||
title: item.goods_name, |
|||
subtitle: `¥${item.goods_price_min}` |
|||
}" |
|||
:subTitleColor="true" |
|||
/> |
|||
</template> |
|||
<!-- 操作项 --> |
|||
<span slot="action" slot-scope="text, item, index"> |
|||
<a v-action:delete @click="handleDeleteItem(index)">删除</a> |
|||
</span> |
|||
</a-table> |
|||
<GoodsModal |
|||
ref="GoodsModal" |
|||
:multiple="multiple" |
|||
:maxNum="maxNum" |
|||
:defaultList="selectedItems" |
|||
@handleSubmit="handleSelectGoodsSubmit" |
|||
/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import cloneDeep from 'lodash.clonedeep' |
|||
import { GoodsModal } from '@/components/Modal' |
|||
import { GoodsItem } from '@/components/Table' |
|||
|
|||
const columns = [ |
|||
{ |
|||
title: '商品ID', |
|||
dataIndex: 'goods_id' |
|||
}, |
|||
{ |
|||
title: '商品信息', |
|||
scopedSlots: { customRender: 'item' } |
|||
}, |
|||
{ |
|||
title: '库存总量', |
|||
dataIndex: 'stock_total' |
|||
}, |
|||
{ |
|||
title: '操作', |
|||
width: '180px', |
|||
dataIndex: 'action', |
|||
scopedSlots: { customRender: 'action' } |
|||
} |
|||
] |
|||
|
|||
// 商品选择器组件 |
|||
export default { |
|||
name: 'SelectGoods', |
|||
components: { |
|||
GoodsModal, |
|||
GoodsItem |
|||
}, |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
props: { |
|||
// 多选模式, 如果false为单选 |
|||
multiple: PropTypes.bool.def(true), |
|||
// 最大选择的数量限制, multiple模式下有效 |
|||
maxNum: PropTypes.integer.def(100), |
|||
// 默认选中的商品 |
|||
defaultList: PropTypes.array.def([]) |
|||
}, |
|||
data () { |
|||
return { |
|||
columns, |
|||
// 已选择的商品列表 |
|||
selectedItems: [], |
|||
// 已选择的商品ID集 |
|||
selectedGoodsIds: [] |
|||
} |
|||
}, |
|||
watch: { |
|||
// 设置默认数据 |
|||
defaultList: { |
|||
// 首次加载的时候执行函数 |
|||
immediate: true, |
|||
handler (val) { |
|||
const { selectedItems } = this |
|||
if (val.length && !selectedItems.length) { |
|||
this.onUpdate(cloneDeep(val)) |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
created () { |
|||
}, |
|||
methods: { |
|||
|
|||
// 更新数据 |
|||
onUpdate (selectedItems) { |
|||
if (this.multiple || !selectedItems.length) { |
|||
// 多选模式 |
|||
this.selectedItems = selectedItems |
|||
this.selectedGoodsIds = selectedItems.map(item => item.goods_id) |
|||
} else { |
|||
// 单选模式 |
|||
const single = selectedItems[selectedItems.length - 1] |
|||
this.selectedItems = [single] |
|||
this.selectedGoodsIds = [single.goods_id] |
|||
} |
|||
this.onChange() |
|||
}, |
|||
|
|||
// 打开商品选择器 |
|||
handleSelectGoods () { |
|||
this.$refs.GoodsModal.handle() |
|||
}, |
|||
|
|||
// 商品库modal确认回调 |
|||
handleSelectGoodsSubmit (result) { |
|||
const { selectedItems } = result |
|||
this.onUpdate(cloneDeep(selectedItems)) |
|||
}, |
|||
|
|||
// 删除商品 |
|||
handleDeleteItem (index) { |
|||
const { selectedItems } = this |
|||
selectedItems.splice(index, 1) |
|||
this.onUpdate(selectedItems) |
|||
}, |
|||
|
|||
// 触发change事件 |
|||
onChange () { |
|||
const { multiple, selectedGoodsIds } = this |
|||
const sGoodsIds = multiple ? selectedGoodsIds : (selectedGoodsIds.length ? selectedGoodsIds[0] : undefined) |
|||
return this.$emit('change', sGoodsIds) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
// 商品信息 |
|||
.table-goodsList { |
|||
margin-top: 10px; |
|||
min-width: 620px; |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import SelectGoods from './SelectGoods' |
|||
export default SelectGoods |
|||
@ -1,231 +0,0 @@ |
|||
<template> |
|||
<div class="image-list clearfix" :class="{ multiple }"> |
|||
<!-- 文件列表 --> |
|||
<!-- draggable是拖拽组件 --> |
|||
<draggable |
|||
v-if="selectedItems.length" |
|||
v-model="selectedItems" |
|||
@start="drag=true" |
|||
@end="drag=false" |
|||
@update="onUpdate" |
|||
> |
|||
<transition-group class="draggable-item" type="transition" :name="'flip-list'"> |
|||
<div |
|||
v-for="(item, index) in selectedItems" |
|||
:key="item.file_id" |
|||
class="file-item" |
|||
:style="{ width: `${width}px`, height: `${width}px` }" |
|||
> |
|||
<!-- 预览图 --> |
|||
<a :href="item.preview_url" target="_blank"> |
|||
<div class="img-cover" :style="{ backgroundImage: `url('${item.preview_url}')` }"></div> |
|||
</a> |
|||
<!-- 删除文件 --> |
|||
<a-icon |
|||
class="icon-close" |
|||
theme="filled" |
|||
type="close-circle" |
|||
@click="handleDeleteFileItem(index)" |
|||
/> |
|||
</div> |
|||
</transition-group> |
|||
</draggable> |
|||
<!-- 图片选择器 --> |
|||
<!-- 如果单选, selectedItems无内容时 显示 --> |
|||
<!-- 如果多选, selectedItems数量小于 maxNum 显示 --> |
|||
<div |
|||
v-show="(!multiple && selectedItems.length <= 0) || (multiple && selectedItems.length < maxNum)" |
|||
class="selector" |
|||
:style="{width: `${width}px`, height: `${width}px`}" |
|||
title="点击选择图片" |
|||
@click="handleSelectImage" |
|||
> |
|||
<a-icon class="icon-plus" :style="{ fontSize: `${width * 0.4}px` }" type="plus" /> |
|||
</div> |
|||
<!-- 文件选择器 --> |
|||
<FilesModal |
|||
ref="FilesModal" |
|||
:multiple="multiple" |
|||
:maxNum="maxNum" |
|||
:selectedNum="selectedItems.length" |
|||
@handleSubmit="handleSelectImageSubmit" |
|||
/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import draggable from 'vuedraggable' |
|||
import cloneDeep from 'lodash.clonedeep' |
|||
import { FilesModal } from '@/components/Modal' |
|||
|
|||
// 图片选择器组件 |
|||
export default { |
|||
name: 'SelectImage', |
|||
components: { |
|||
FilesModal, |
|||
draggable |
|||
}, |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
props: { |
|||
// 多选模式, 如果false为单选 |
|||
multiple: PropTypes.bool.def(false), |
|||
// 最大选择的数量限制, multiple模式下有效 |
|||
maxNum: PropTypes.integer.def(100), |
|||
// 默认选中的文件 |
|||
defaultList: PropTypes.array.def([]), |
|||
// 元素的尺寸(宽) |
|||
width: PropTypes.integer.def(80) |
|||
}, |
|||
data () { |
|||
return { |
|||
// 选择的图片列表 |
|||
selectedItems: [], |
|||
// 禁止传参 (防止selectedItems为空时defaultList重新赋值) |
|||
allowProps: true |
|||
} |
|||
}, |
|||
watch: { |
|||
// 设置默认数据 |
|||
defaultList: { |
|||
// 首次加载的时候执行函数 |
|||
immediate: true, |
|||
handler (val) { |
|||
const { selectedItems, allowProps } = this |
|||
if (val.length && !selectedItems.length && allowProps) { |
|||
this.selectedItems = cloneDeep(val) |
|||
this.onChange() |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
created () { |
|||
}, |
|||
methods: { |
|||
|
|||
// 拖动排序后的回调事件 |
|||
onUpdate () { |
|||
this.onChange() |
|||
}, |
|||
|
|||
// 打开文件选择器 |
|||
handleSelectImage () { |
|||
this.$refs.FilesModal.show() |
|||
}, |
|||
|
|||
// 文件库modal确认回调 |
|||
handleSelectImageSubmit (result) { |
|||
if (result.length > 0) { |
|||
// 记录选中的图片列表 |
|||
const { multiple, selectedItems } = this |
|||
this.selectedItems = multiple ? selectedItems.concat(result) : result |
|||
this.onChange() |
|||
} |
|||
}, |
|||
|
|||
// 删除文件 |
|||
handleDeleteFileItem (index) { |
|||
this.selectedItems.splice(index, 1) |
|||
if (this.selectedItems.length === 0) { |
|||
this.allowProps = false |
|||
} |
|||
this.onChange() |
|||
}, |
|||
|
|||
// 触发change事件 |
|||
onChange () { |
|||
const { multiple, selectedItems } = this |
|||
if (selectedItems.length <= 0) { |
|||
return this.$emit('change', multiple ? [] : 0) |
|||
} |
|||
// 生成fileId |
|||
const fileId = multiple ? selectedItems.map(item => item.file_id) : selectedItems[0].file_id |
|||
// 触发change事件 |
|||
return this.$emit('change', fileId, selectedItems) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
/deep/.flip-list-move { |
|||
transition: transform 0.3s !important; |
|||
} |
|||
/deep/.no-move { |
|||
transition: transform 0s; |
|||
} |
|||
|
|||
.image-list { |
|||
// 多选模式下margin |
|||
&.multiple { |
|||
.file-item, |
|||
.selector { |
|||
margin-right: 10px; |
|||
margin-bottom: 10px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 文件元素 |
|||
.file-item { |
|||
position: relative; |
|||
float: left; |
|||
width: 80px; |
|||
height: 80px; |
|||
position: relative; |
|||
padding: 2px; |
|||
border: 1px solid #ddd; |
|||
background: #fff; |
|||
.img-cover { |
|||
display: block; |
|||
width: 100%; |
|||
height: 100%; |
|||
background: no-repeat center center / 100%; |
|||
} |
|||
&:hover { |
|||
.icon-close { |
|||
display: block; |
|||
} |
|||
} |
|||
.icon-close { |
|||
display: none; |
|||
position: absolute; |
|||
top: -8px; |
|||
right: -8px; |
|||
cursor: pointer; |
|||
font-size: 16px; |
|||
color: #c5c5c5; |
|||
&:hover { |
|||
color: #7d7d7d; |
|||
} |
|||
} |
|||
&:hover { |
|||
border: 1px solid #a7c3de; |
|||
} |
|||
} |
|||
|
|||
// 选择器 |
|||
.selector { |
|||
width: 80px; |
|||
height: 80px; |
|||
float: left; |
|||
border: 1px dashed #e2e2e2; |
|||
text-align: center; |
|||
color: #dad9d9; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
&:hover { |
|||
border: 1px dashed #40a9ff; |
|||
color: #40a9ff; |
|||
} |
|||
.icon-plus { |
|||
font-size: 32px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import SelectImage from './SelectImage' |
|||
export default SelectImage |
|||
@ -1,58 +0,0 @@ |
|||
import './index.less' |
|||
|
|||
import { Icon, Menu, Dropdown } from 'ant-design-vue' |
|||
import { i18nRender } from '@/locales' |
|||
import i18nMixin from '@/store/i18n-mixin' |
|||
|
|||
const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR'] |
|||
const languageLabels = { |
|||
'zh-CN': '简体中文', |
|||
'zh-TW': '繁体中文', |
|||
'en-US': 'English', |
|||
'pt-BR': 'Português' |
|||
} |
|||
// eslint-disable-next-line |
|||
const languageIcons = { |
|||
'zh-CN': '🇨🇳', |
|||
'zh-TW': '🇭🇰', |
|||
'en-US': '🇺🇸', |
|||
'pt-BR': '🇧🇷' |
|||
} |
|||
|
|||
const SelectLang = { |
|||
props: { |
|||
prefixCls: { |
|||
type: String, |
|||
default: 'ant-pro-drop-down' |
|||
} |
|||
}, |
|||
name: 'SelectLang', |
|||
mixins: [i18nMixin], |
|||
render () { |
|||
const { prefixCls } = this |
|||
const changeLang = ({ key }) => { |
|||
this.setLang(key) |
|||
} |
|||
const langMenu = ( |
|||
<Menu class={['menu', 'ant-pro-header-menu']} selectedKeys={[this.currentLang]} onClick={changeLang}> |
|||
{locales.map(locale => ( |
|||
<Menu.Item key={locale}> |
|||
<span role="img" aria-label={languageLabels[locale]}> |
|||
{languageIcons[locale]} |
|||
</span>{' '} |
|||
{languageLabels[locale]} |
|||
</Menu.Item> |
|||
))} |
|||
</Menu> |
|||
) |
|||
return ( |
|||
<Dropdown overlay={langMenu} placement="bottomRight"> |
|||
<span class={prefixCls}> |
|||
<Icon type="global" title={i18nRender('navBar.lang')} /> |
|||
</span> |
|||
</Dropdown> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default SelectLang |
|||
@ -1,31 +0,0 @@ |
|||
@import "~ant-design-vue/es/style/themes/default"; |
|||
|
|||
@header-menu-prefix-cls: ~'@{ant-prefix}-pro-header-menu'; |
|||
@header-drop-down-prefix-cls: ~'@{ant-prefix}-pro-drop-down'; |
|||
|
|||
.@{header-menu-prefix-cls} { |
|||
|
|||
.anticon { |
|||
margin-right: 8px; |
|||
} |
|||
.ant-dropdown-menu-item { |
|||
min-width: 160px; |
|||
} |
|||
} |
|||
|
|||
.@{header-drop-down-prefix-cls} { |
|||
|
|||
line-height: @layout-header-height; |
|||
vertical-align: top; |
|||
cursor: pointer; |
|||
|
|||
> i { |
|||
font-size: 16px !important; |
|||
transform: none !important; |
|||
|
|||
svg { |
|||
position: relative; |
|||
top: -1px; |
|||
} |
|||
} |
|||
} |
|||
@ -1,86 +0,0 @@ |
|||
<template> |
|||
<a-cascader v-model="sValue" :options="options" :placeholder="placeholder" @change="onChange" /> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import RegionModel from '@/common/model/Region' |
|||
|
|||
// 省市区级联选择器组件 |
|||
export default { |
|||
name: 'SelectRegion', |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
props: { |
|||
// v-model 指定选中项 |
|||
value: PropTypes.array.def(), |
|||
// 元素的尺寸(宽) |
|||
placeholder: PropTypes.string.def('请选择省市区') |
|||
}, |
|||
data () { |
|||
return { |
|||
// 选中项 |
|||
sValue: [], |
|||
// 级联选择器数据 |
|||
options: [] |
|||
} |
|||
}, |
|||
watch: { |
|||
value (val) { |
|||
this.sValue = val |
|||
} |
|||
}, |
|||
created () { |
|||
// 获取地区数据 |
|||
RegionModel.getTreeData().then(regions => { |
|||
this.options = this.getOptions(regions) |
|||
}) |
|||
}, |
|||
methods: { |
|||
|
|||
// 触发change事件 |
|||
onChange (value) { |
|||
this.$emit('change', value) |
|||
}, |
|||
|
|||
/** |
|||
* 格式化级联选择器数据 |
|||
* @param {*} regions 地区数据 |
|||
*/ |
|||
getOptions (regions) { |
|||
const { getOptions, getChildren } = this |
|||
const options = [] |
|||
for (const index in regions) { |
|||
const item = regions[index] |
|||
const children = getChildren(item) |
|||
const optionItem = { |
|||
value: item.id, |
|||
label: item.name |
|||
} |
|||
if (children !== false) { |
|||
optionItem.children = getOptions(children) |
|||
} |
|||
options.push(optionItem) |
|||
} |
|||
return options |
|||
}, |
|||
|
|||
// 获取子集地区 |
|||
getChildren (item) { |
|||
if (item.city) { |
|||
return item.city |
|||
} |
|||
if (item.region) { |
|||
return item.region |
|||
} |
|||
return false |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import SelectRegion from './SelectRegion' |
|||
export default SelectRegion |
|||
@ -1,237 +0,0 @@ |
|||
<template> |
|||
<div class="video-list clearfix" :class="{ multiple }"> |
|||
<!-- 文件列表 --> |
|||
<!-- draggable是拖拽组件 --> |
|||
<draggable |
|||
v-if="selectedItems.length" |
|||
v-model="selectedItems" |
|||
@start="drag=true" |
|||
@end="drag=false" |
|||
@update="onUpdate" |
|||
> |
|||
<transition-group type="transition" :name="'flip-list'"> |
|||
<div |
|||
v-for="(item, index) in selectedItems" |
|||
:key="item.file_id" |
|||
class="file-item" |
|||
:style="{ width: `${width}px`, height: `${width}px` }" |
|||
> |
|||
<!-- 预览图 --> |
|||
<a :href="item.external_url" target="_blank"> |
|||
<div class="img-cover" :style="{ backgroundImage: `url('${item.preview_url}')` }"></div> |
|||
</a> |
|||
<!-- 删除文件 --> |
|||
<a-icon |
|||
class="icon-close" |
|||
theme="filled" |
|||
type="close-circle" |
|||
@click="handleDeleteFileItem(index)" |
|||
/> |
|||
</div> |
|||
</transition-group> |
|||
</draggable> |
|||
<!-- 视频选择器 --> |
|||
<!-- 如果单选, selectedItems无内容时 显示 --> |
|||
<!-- 如果多选, selectedItems数量小于 maxNum 显示 --> |
|||
<div |
|||
v-show="(!multiple && selectedItems.length <= 0) || (multiple && selectedItems.length < maxNum)" |
|||
class="selector" |
|||
:style="{width: `${width}px`, height: `${width}px`}" |
|||
title="点击选择视频" |
|||
@click="handleSelectVideo" |
|||
> |
|||
<a-icon class="icon-plus" :style="{ fontSize: `${width * 0.4}px` }" type="plus" /> |
|||
</div> |
|||
<!-- 文件选择器 --> |
|||
<FilesModal |
|||
ref="FilesModal" |
|||
:multiple="multiple" |
|||
:maxNum="maxNum" |
|||
:selectedNum="selectedItems.length" |
|||
:fileType="FileTypeEnum.VIDEO.value" |
|||
@handleSubmit="handleSelectVideoSubmit" |
|||
/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import draggable from 'vuedraggable' |
|||
import cloneDeep from 'lodash.clonedeep' |
|||
import { FilesModal } from '@/components/Modal' |
|||
import FileTypeEnum from '@/common/enum/file/FileType' |
|||
|
|||
// 视频选择器组件 |
|||
export default { |
|||
name: 'SelectVideo', |
|||
components: { |
|||
FilesModal, |
|||
draggable |
|||
}, |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
props: { |
|||
// 多选模式, 如果false为单选 |
|||
multiple: PropTypes.bool.def(false), |
|||
// 最大选择的数量限制, multiple模式下有效 |
|||
maxNum: PropTypes.integer.def(100), |
|||
// 默认选中的文件 |
|||
defaultList: PropTypes.array.def([]), |
|||
// 元素的尺寸(宽) |
|||
width: PropTypes.integer.def(80) |
|||
}, |
|||
data () { |
|||
return { |
|||
// 枚举类 |
|||
FileTypeEnum, |
|||
// 选择的视频列表 |
|||
selectedItems: [], |
|||
// 禁止传参 (防止selectedItems为空时defaultList重新赋值) |
|||
allowProps: true |
|||
} |
|||
}, |
|||
watch: { |
|||
// 设置默认数据 |
|||
defaultList: { |
|||
// 首次加载的时候执行函数 |
|||
immediate: true, |
|||
handler (val) { |
|||
const { selectedItems, allowProps } = this |
|||
if (val.length && !selectedItems.length && allowProps) { |
|||
this.selectedItems = cloneDeep(val) |
|||
this.onChange() |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
created () { |
|||
}, |
|||
methods: { |
|||
|
|||
// 拖动排序后的回调事件 |
|||
onUpdate () { |
|||
this.onChange() |
|||
}, |
|||
|
|||
// 打开文件选择器 |
|||
handleSelectVideo () { |
|||
this.$refs.FilesModal.show() |
|||
}, |
|||
|
|||
// 文件库modal确认回调 |
|||
handleSelectVideoSubmit (result) { |
|||
if (result.length > 0) { |
|||
// 记录选中的视频列表 |
|||
const { multiple, selectedItems } = this |
|||
this.selectedItems = multiple ? selectedItems.concat(result) : result |
|||
this.onChange() |
|||
} |
|||
}, |
|||
|
|||
// 删除文件 |
|||
handleDeleteFileItem (index) { |
|||
this.selectedItems.splice(index, 1) |
|||
if (this.selectedItems.length === 0) { |
|||
this.allowProps = false |
|||
} |
|||
this.onChange() |
|||
}, |
|||
|
|||
// 触发change事件 |
|||
onChange () { |
|||
const { multiple, selectedItems } = this |
|||
if (selectedItems.length <= 0) { |
|||
return this.$emit('change', multiple ? [] : 0) |
|||
} |
|||
// 生成fileId |
|||
const fileId = multiple ? selectedItems.map(item => item.file_id) : selectedItems[0].file_id |
|||
// 触发change事件 |
|||
return this.$emit('change', fileId, selectedItems) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
/deep/.flip-list-move { |
|||
transition: transform 0.3s !important; |
|||
} |
|||
/deep/.no-move { |
|||
transition: transform 0s; |
|||
} |
|||
|
|||
.video-list { |
|||
margin-bottom: 6px; |
|||
// 多选模式下margin |
|||
&.multiple { |
|||
.file-item, |
|||
.selector { |
|||
margin-right: 10px; |
|||
margin-bottom: 10px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 文件元素 |
|||
.file-item { |
|||
position: relative; |
|||
float: left; |
|||
width: 80px; |
|||
height: 80px; |
|||
position: relative; |
|||
padding: 2px; |
|||
border: 1px solid #ddd; |
|||
background: #fff; |
|||
.img-cover { |
|||
display: block; |
|||
margin: 0 auto; |
|||
width: 45px; |
|||
height: 100%; |
|||
background: no-repeat center center / 100%; |
|||
} |
|||
&:hover { |
|||
.icon-close { |
|||
display: block; |
|||
} |
|||
} |
|||
.icon-close { |
|||
display: none; |
|||
position: absolute; |
|||
top: -8px; |
|||
right: -8px; |
|||
cursor: pointer; |
|||
font-size: 16px; |
|||
color: #c5c5c5; |
|||
&:hover { |
|||
color: #7d7d7d; |
|||
} |
|||
} |
|||
&:hover { |
|||
border: 1px solid #a7c3de; |
|||
} |
|||
} |
|||
|
|||
// 选择器 |
|||
.selector { |
|||
width: 80px; |
|||
height: 80px; |
|||
float: left; |
|||
border: 1px dashed #e2e2e2; |
|||
text-align: center; |
|||
color: #dad9d9; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
&:hover { |
|||
border: 1px dashed #40a9ff; |
|||
color: #40a9ff; |
|||
} |
|||
.icon-plus { |
|||
font-size: 32px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import SelectVideo from './SelectVideo' |
|||
export default SelectVideo |
|||
@ -1,377 +0,0 @@ |
|||
<template> |
|||
<div class="setting-drawer"> |
|||
<a-drawer |
|||
width="300" |
|||
placement="right" |
|||
@close="onClose" |
|||
:closable="false" |
|||
:visible="visible" |
|||
:drawer-style="{ position: 'absolute' }" |
|||
style="position: absolute" |
|||
> |
|||
<div class="setting-drawer-index-content"> |
|||
<div :style="{ marginBottom: '24px' }"> |
|||
<h3 class="setting-drawer-index-title">整体风格设置</h3> |
|||
|
|||
<div class="setting-drawer-index-blockChecbox"> |
|||
<a-tooltip> |
|||
<template slot="title">暗色菜单风格</template> |
|||
<div class="setting-drawer-index-item" @click="handleMenuTheme('dark')"> |
|||
<img |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" |
|||
alt="dark" |
|||
/> |
|||
<div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'"> |
|||
<a-icon type="check" /> |
|||
</div> |
|||
</div> |
|||
</a-tooltip> |
|||
|
|||
<a-tooltip> |
|||
<template slot="title">亮色菜单风格</template> |
|||
<div class="setting-drawer-index-item" @click="handleMenuTheme('light')"> |
|||
<img |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" |
|||
alt="light" |
|||
/> |
|||
<div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'"> |
|||
<a-icon type="check" /> |
|||
</div> |
|||
</div> |
|||
</a-tooltip> |
|||
</div> |
|||
</div> |
|||
|
|||
<div :style="{ marginBottom: '24px' }"> |
|||
<h3 class="setting-drawer-index-title">主题色</h3> |
|||
|
|||
<div style="height: 20px"> |
|||
<a-tooltip |
|||
class="setting-drawer-theme-color-colorBlock" |
|||
v-for="(item, index) in colorList" |
|||
:key="index" |
|||
> |
|||
<template slot="title">{{ item.key }}</template> |
|||
<a-tag :color="item.color" @click="changeColor(item.color)"> |
|||
<a-icon type="check" v-if="item.color === primaryColor"></a-icon> |
|||
</a-tag> |
|||
</a-tooltip> |
|||
</div> |
|||
</div> |
|||
<a-divider /> |
|||
|
|||
<div :style="{ marginBottom: '24px' }"> |
|||
<h3 class="setting-drawer-index-title">导航模式</h3> |
|||
|
|||
<div class="setting-drawer-index-blockChecbox"> |
|||
<a-tooltip> |
|||
<template slot="title">侧边栏导航</template> |
|||
<div class="setting-drawer-index-item" @click="handleLayout('sidemenu')"> |
|||
<img |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" |
|||
alt="sidemenu" |
|||
/> |
|||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'"> |
|||
<a-icon type="check" /> |
|||
</div> |
|||
</div> |
|||
</a-tooltip> |
|||
|
|||
<a-tooltip> |
|||
<template slot="title">顶部栏导航</template> |
|||
<div class="setting-drawer-index-item" @click="handleLayout('topmenu')"> |
|||
<img |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" |
|||
alt="topmenu" |
|||
/> |
|||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'"> |
|||
<a-icon type="check" /> |
|||
</div> |
|||
</div> |
|||
</a-tooltip> |
|||
</div> |
|||
<div :style="{ marginTop: '24px' }"> |
|||
<a-list :split="false"> |
|||
<a-list-item> |
|||
<a-tooltip slot="actions"> |
|||
<template slot="title">该设定仅 [顶部栏导航] 时有效</template> |
|||
<a-select |
|||
size="small" |
|||
style="width: 80px;" |
|||
:defaultValue="contentWidth" |
|||
@change="handleContentWidthChange" |
|||
> |
|||
<a-select-option value="Fixed">固定</a-select-option> |
|||
<a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option> |
|||
</a-select> |
|||
</a-tooltip> |
|||
<a-list-item-meta> |
|||
<div slot="title">内容区域宽度</div> |
|||
</a-list-item-meta> |
|||
</a-list-item> |
|||
<a-list-item> |
|||
<a-switch |
|||
slot="actions" |
|||
size="small" |
|||
:defaultChecked="fixedHeader" |
|||
@change="handleFixedHeader" |
|||
/> |
|||
<a-list-item-meta> |
|||
<div slot="title">固定 Header</div> |
|||
</a-list-item-meta> |
|||
</a-list-item> |
|||
<a-list-item> |
|||
<a-switch |
|||
slot="actions" |
|||
size="small" |
|||
:disabled="!fixedHeader" |
|||
:defaultChecked="autoHideHeader" |
|||
@change="handleFixedHeaderHidden" |
|||
/> |
|||
<a-list-item-meta> |
|||
<a-tooltip slot="title" placement="left"> |
|||
<template slot="title">固定 Header 时可配置</template> |
|||
<div :style="{ opacity: !fixedHeader ? '0.5' : '1' }">下滑时隐藏 Header</div> |
|||
</a-tooltip> |
|||
</a-list-item-meta> |
|||
</a-list-item> |
|||
<a-list-item> |
|||
<a-switch |
|||
slot="actions" |
|||
size="small" |
|||
:disabled="(layoutMode === 'topmenu')" |
|||
:defaultChecked="fixSiderbar" |
|||
@change="handleFixSiderbar" |
|||
/> |
|||
<a-list-item-meta> |
|||
<div |
|||
slot="title" |
|||
:style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }" |
|||
>固定侧边菜单</div> |
|||
</a-list-item-meta> |
|||
</a-list-item> |
|||
</a-list> |
|||
</div> |
|||
</div> |
|||
<a-divider /> |
|||
|
|||
<div :style="{ marginBottom: '24px' }"> |
|||
<h3 class="setting-drawer-index-title">其他设置</h3> |
|||
<div> |
|||
<a-list :split="false"> |
|||
<a-list-item> |
|||
<a-switch |
|||
slot="actions" |
|||
size="small" |
|||
:defaultChecked="colorWeak" |
|||
@change="onColorWeak" |
|||
/> |
|||
<a-list-item-meta> |
|||
<div slot="title">色弱模式</div> |
|||
</a-list-item-meta> |
|||
</a-list-item> |
|||
<a-list-item> |
|||
<a-switch |
|||
slot="actions" |
|||
size="small" |
|||
:defaultChecked="multiTab" |
|||
@change="onMultiTab" |
|||
/> |
|||
<a-list-item-meta> |
|||
<div slot="title">多页签模式</div> |
|||
</a-list-item-meta> |
|||
</a-list-item> |
|||
</a-list> |
|||
</div> |
|||
</div> |
|||
<a-divider /> |
|||
<div :style="{ marginBottom: '24px' }"> |
|||
<a-button @click="doCopy" icon="copy" block>拷贝设置</a-button> |
|||
<a-alert type="warning" :style="{ marginTop: '24px' }"> |
|||
<span slot="message"> |
|||
配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件。修改配置文件后,需要清空本地缓存和LocalStorage |
|||
<a |
|||
href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/config/defaultSettings.js" |
|||
target="_blank" |
|||
>src/config/defaultSettings.js</a> |
|||
</span> |
|||
</a-alert> |
|||
</div> |
|||
</div> |
|||
<div class="setting-drawer-index-handle" @click="toggle" slot="handle"> |
|||
<a-icon type="setting" v-if="!visible" /> |
|||
<a-icon type="close" v-else /> |
|||
</div> |
|||
</a-drawer> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import SettingItem from './SettingItem' |
|||
import config from '@/config/defaultSettings' |
|||
import { updateTheme, updateColorWeak, colorList } from './settingConfig' |
|||
|
|||
export default { |
|||
components: { |
|||
SettingItem |
|||
}, |
|||
mixins: [], |
|||
data () { |
|||
return { |
|||
visible: false, |
|||
colorList |
|||
} |
|||
}, |
|||
watch: { |
|||
|
|||
}, |
|||
mounted () { |
|||
updateTheme(this.primaryColor) |
|||
if (this.colorWeak !== config.colorWeak) { |
|||
updateColorWeak(this.colorWeak) |
|||
} |
|||
}, |
|||
methods: { |
|||
showDrawer () { |
|||
this.visible = true |
|||
}, |
|||
onClose () { |
|||
this.visible = false |
|||
}, |
|||
toggle () { |
|||
this.visible = !this.visible |
|||
}, |
|||
onColorWeak (checked) { |
|||
this.$store.dispatch('ToggleWeak', checked) |
|||
updateColorWeak(checked) |
|||
}, |
|||
onMultiTab (checked) { |
|||
this.$store.dispatch('ToggleMultiTab', checked) |
|||
}, |
|||
handleMenuTheme (theme) { |
|||
this.$store.dispatch('ToggleTheme', theme) |
|||
}, |
|||
doCopy () { |
|||
// get current settings from mixin or this.$store.state.app, pay attention to the property name |
|||
const text = `export default { |
|||
primaryColor: '${this.primaryColor}', // primary color of ant design |
|||
navTheme: '${this.navTheme}', // theme for nav menu |
|||
layout: '${this.layoutMode}', // nav menu position: sidemenu or topmenu |
|||
contentWidth: '${this.contentWidth}', // layout of content: Fluid or Fixed, only works when layout is topmenu |
|||
fixedHeader: ${this.fixedHeader}, // sticky header |
|||
fixSiderbar: ${this.fixSiderbar}, // sticky siderbar |
|||
autoHideHeader: ${this.autoHideHeader}, // auto hide header |
|||
colorWeak: ${this.colorWeak}, |
|||
multiTab: ${this.multiTab}, |
|||
production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true' |
|||
}` |
|||
this.$copyText(text).then(message => { |
|||
console.log('copy', message) |
|||
this.$message.success('复制完毕') |
|||
}).catch(err => { |
|||
console.log('copy.err', err) |
|||
this.$message.error('复制失败') |
|||
}) |
|||
}, |
|||
handleLayout (mode) { |
|||
this.$store.dispatch('ToggleLayoutMode', mode) |
|||
// 因为顶部菜单不能固定左侧菜单栏,所以强制关闭 |
|||
this.handleFixSiderbar(false) |
|||
}, |
|||
handleContentWidthChange (type) { |
|||
this.$store.dispatch('ToggleContentWidth', type) |
|||
}, |
|||
changeColor (color) { |
|||
if (this.primaryColor !== color) { |
|||
this.$store.dispatch('ToggleColor', color) |
|||
updateTheme(color) |
|||
} |
|||
}, |
|||
handleFixedHeader (fixed) { |
|||
this.$store.dispatch('ToggleFixedHeader', fixed) |
|||
}, |
|||
handleFixedHeaderHidden (autoHidden) { |
|||
this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden) |
|||
}, |
|||
handleFixSiderbar (fixed) { |
|||
if (this.layoutMode === 'topmenu') { |
|||
this.$store.dispatch('ToggleFixSiderbar', false) |
|||
return |
|||
} |
|||
this.$store.dispatch('ToggleFixSiderbar', fixed) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.setting-drawer-index-content { |
|||
.setting-drawer-index-blockChecbox { |
|||
display: flex; |
|||
|
|||
.setting-drawer-index-item { |
|||
margin-right: 16px; |
|||
position: relative; |
|||
border-radius: 4px; |
|||
cursor: pointer; |
|||
|
|||
img { |
|||
width: 48px; |
|||
} |
|||
|
|||
.setting-drawer-index-selectIcon { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
width: 100%; |
|||
padding-top: 15px; |
|||
padding-left: 24px; |
|||
height: 100%; |
|||
color: #1890ff; |
|||
font-size: @font-size-base; |
|||
font-weight: 700; |
|||
} |
|||
} |
|||
} |
|||
.setting-drawer-theme-color-colorBlock { |
|||
width: 20px; |
|||
height: 20px; |
|||
border-radius: 2px; |
|||
float: left; |
|||
cursor: pointer; |
|||
margin-right: 8px; |
|||
padding-left: 0px; |
|||
padding-right: 0px; |
|||
text-align: center; |
|||
color: #fff; |
|||
font-weight: 700; |
|||
|
|||
i { |
|||
font-size: @font-size-base; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.setting-drawer-index-handle { |
|||
position: absolute; |
|||
top: 240px; |
|||
background: #1890ff; |
|||
width: 48px; |
|||
height: 48px; |
|||
right: 300px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
pointer-events: auto; |
|||
z-index: 1001; |
|||
text-align: center; |
|||
font-size: 16px; |
|||
border-radius: 4px 0 0 4px; |
|||
|
|||
i { |
|||
color: rgb(255, 255, 255); |
|||
font-size: 20px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,36 +0,0 @@ |
|||
<template> |
|||
<div class="setting-drawer-index-item"> |
|||
<h3 class="setting-drawer-index-title">{{ title }}</h3> |
|||
<slot></slot> |
|||
<a-divider v-if="divider" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'SettingItem', |
|||
props: { |
|||
title: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
divider: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.setting-drawer-index-item { |
|||
margin-bottom: 24px; |
|||
|
|||
.setting-drawer-index-title { |
|||
font-size: @font-size-base; |
|||
color: rgba(0, 0, 0, 0.85); |
|||
line-height: 22px; |
|||
margin-bottom: 12px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import SettingDrawer from './SettingDrawer' |
|||
export default SettingDrawer |
|||
@ -1,48 +0,0 @@ |
|||
// import message from 'ant-design-vue/es/message'
|
|||
// import defaultSettings from '../defaultSettings';
|
|||
// import themeColor from './themeColor.js'
|
|||
|
|||
// let lessNodesAppended
|
|||
const colorList = [ |
|||
{ |
|||
key: '薄暮', color: '#F5222D' |
|||
}, |
|||
{ |
|||
key: '火山', color: '#FA541C' |
|||
}, |
|||
{ |
|||
key: '日暮', color: '#FAAD14' |
|||
}, |
|||
{ |
|||
key: '明青', color: '#13C2C2' |
|||
}, |
|||
{ |
|||
key: '极光绿', color: '#52C41A' |
|||
}, |
|||
{ |
|||
key: '拂晓蓝(默认)', color: '#1890FF' |
|||
}, |
|||
{ |
|||
key: '极客蓝', color: '#2F54EB' |
|||
}, |
|||
{ |
|||
key: '酱紫', color: '#722ED1' |
|||
} |
|||
] |
|||
|
|||
const updateTheme = newPrimaryColor => { |
|||
// const hideMessage = message.loading('正在切换主题!', 0)
|
|||
// themeColor.changeColor(newPrimaryColor).finally(() => {
|
|||
// setTimeout(() => {
|
|||
// hideMessage()
|
|||
// }, 10)
|
|||
// })
|
|||
} |
|||
|
|||
const updateColorWeak = colorWeak => { |
|||
// document.body.className = colorWeak ? 'colorWeak' : '';
|
|||
const app = document.body.querySelector('#app') |
|||
colorWeak ? app.classList.add('colorWeak') : app.classList.remove('colorWeak') |
|||
} |
|||
|
|||
export { updateTheme, colorList, updateColorWeak } |
|||
@ -1,24 +0,0 @@ |
|||
import client from 'webpack-theme-color-replacer/client' |
|||
import generate from '@ant-design/colors/lib/generate' |
|||
|
|||
export default { |
|||
getAntdSerials (color) { |
|||
// 淡化(即less的tint)
|
|||
const lightens = new Array(9).fill().map((t, i) => { |
|||
return client.varyColor.lighten(color, i / 10) |
|||
}) |
|||
// colorPalette变换得到颜色值
|
|||
const colorPalettes = generate(color) |
|||
const rgb = client.varyColor.toNum3(color.replace('#', '')).join(',') |
|||
return lightens.concat(colorPalettes).concat(rgb) |
|||
}, |
|||
changeColor (newColor) { |
|||
var options = { |
|||
newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
|
|||
changeUrl (cssUrl) { |
|||
return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
|
|||
} |
|||
} |
|||
return client.changer.changeColor(options, Promise) |
|||
} |
|||
} |
|||
@ -1,105 +0,0 @@ |
|||
<template> |
|||
<div class="goods-info clearfix"> |
|||
<!-- 商品图片 --> |
|||
<div class="in-left"> |
|||
<img :src="dataObj.image" :alt="dataObj.imageAlt" /> |
|||
</div> |
|||
<div class="in-right"> |
|||
<!-- 商品名称 --> |
|||
<p class="title twoline-hide" :style="{ width: `${dataObj.titleWidth}px` }">{{ dataObj.title }}</p> |
|||
<!-- 副标题 --> |
|||
<p |
|||
v-if="isEmpty(dataObj.goodsProps)" |
|||
class="subtitle" |
|||
:class="{ 'c-p': subTitleColor }" |
|||
>{{ dataObj.subtitle }}</p> |
|||
<!-- 商品规格 --> |
|||
<div v-else class="goods-props clearfix"> |
|||
<div class="goods-props-item" v-for="(props, idx) in dataObj.goodsProps" :key="idx"> |
|||
<span>{{ props.value.name }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import { isEmpty } from '@/utils/util' |
|||
|
|||
// Table内容元素: 商品信息 |
|||
export default { |
|||
name: 'GoodsItem', |
|||
props: { |
|||
// 商品信息 |
|||
data: PropTypes.object.def({}), |
|||
// 副标题颜色 |
|||
subTitleColor: PropTypes.bool.def(false) |
|||
}, |
|||
computed: { |
|||
dataObj () { |
|||
return Object.assign({ |
|||
image: '', |
|||
imageAlt: '', |
|||
title: '', |
|||
subtitle: '', |
|||
goodsProps: [], |
|||
titleWidth: 200 |
|||
}, this.$props.data) |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
isEmpty |
|||
} |
|||
}, |
|||
methods: { |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
@size: 60px; |
|||
.goods-info { |
|||
width: 270px; |
|||
line-height: 1.3; |
|||
white-space: normal; |
|||
.in-left { |
|||
float: left; |
|||
margin-right: 8px; |
|||
|
|||
img { |
|||
width: @size; |
|||
height: @size; |
|||
} |
|||
} |
|||
.in-right { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
float: left; |
|||
// width: 200px; |
|||
height: @size; |
|||
.title { |
|||
font-size: @font-size-base; |
|||
margin-bottom: 5px; |
|||
} |
|||
.subtitle { |
|||
font-size: 12px; |
|||
color: #7b7b7b; |
|||
} |
|||
} |
|||
// 商品规格 |
|||
.goods-props { |
|||
color: #8a8a8a; |
|||
font-size: 12px; |
|||
overflow: hidden; |
|||
|
|||
&-item { |
|||
display: inline-block; |
|||
margin-right: 4px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import GoodsItem from './GoodsItem' |
|||
export default GoodsItem |
|||
@ -1,341 +0,0 @@ |
|||
Table 重封装组件说明 |
|||
==== |
|||
|
|||
|
|||
封装说明 |
|||
---- |
|||
|
|||
> 基础的使用方式与 API 与 [官方版(Table)](https://vuecomponent.github.io/ant-design-vue/components/table-cn/) 本一致,在其基础上,封装了加载数据的方法。 |
|||
> |
|||
> 你无需在你是用表格的页面进行分页逻辑处理,仅需向 Table 组件传递绑定 `:data="Promise"` 对象即可 |
|||
|
|||
该 `table` 由 [@Saraka](https://github.com/saraka-tsukai) 完成封装 |
|||
|
|||
|
|||
例子1 |
|||
---- |
|||
(基础使用) |
|||
|
|||
```vue |
|||
|
|||
<template> |
|||
<s-table |
|||
ref="table" |
|||
size="default" |
|||
:rowKey="(record) => record.data.id" |
|||
:columns="columns" |
|||
:data="loadData" |
|||
:rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" |
|||
> |
|||
</s-table> |
|||
</template> |
|||
|
|||
<script> |
|||
import STable from '@/components' |
|||
|
|||
export default { |
|||
components: { |
|||
STable |
|||
}, |
|||
data() { |
|||
return { |
|||
columns: [ |
|||
{ |
|||
title: '规则编号', |
|||
dataIndex: 'no' |
|||
}, |
|||
{ |
|||
title: '描述', |
|||
dataIndex: 'description' |
|||
}, |
|||
{ |
|||
title: '服务调用次数', |
|||
dataIndex: 'callNo', |
|||
sorter: true, |
|||
needTotal: true, |
|||
customRender: (text) => text + ' 次' |
|||
}, |
|||
{ |
|||
title: '状态', |
|||
dataIndex: 'status', |
|||
needTotal: true |
|||
}, |
|||
{ |
|||
title: '更新时间', |
|||
dataIndex: 'updatedAt', |
|||
sorter: true |
|||
} |
|||
], |
|||
// 查询条件参数 |
|||
queryParam: {}, |
|||
// 加载数据方法 必须为 Promise 对象 |
|||
loadData: params => { |
|||
return this.$http.get('/service', { |
|||
params: Object.assign(params, this.queryParam) |
|||
}).then(res => { |
|||
return res.result |
|||
}) |
|||
}, |
|||
selectedRowKeys: [], |
|||
selectedRows: [] |
|||
} |
|||
}, |
|||
methods: { |
|||
onSelectChange (selectedRowKeys, selectedRows) { |
|||
this.selectedRowKeys = selectedRowKeys |
|||
this.selectedRows = selectedRows |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
``` |
|||
|
|||
|
|||
|
|||
例子2 |
|||
---- |
|||
|
|||
(简单的表格,最后一列是各种操作) |
|||
|
|||
```vue |
|||
<template> |
|||
<s-table |
|||
ref="table" |
|||
size="default" |
|||
:columns="columns" |
|||
:data="loadData" |
|||
> |
|||
<span slot="action" slot-scope="text, record"> |
|||
<a>编辑</a> |
|||
<a-divider type="vertical"/> |
|||
<a-dropdown> |
|||
<a class="ant-dropdown-link"> |
|||
更多 <a-icon type="down"/> |
|||
</a> |
|||
<a-menu slot="overlay"> |
|||
<a-menu-item> |
|||
<a href="javascript:;">1st menu item</a> |
|||
</a-menu-item> |
|||
<a-menu-item> |
|||
<a href="javascript:;">2nd menu item</a> |
|||
</a-menu-item> |
|||
<a-menu-item> |
|||
<a href="javascript:;">3rd menu item</a> |
|||
</a-menu-item> |
|||
</a-menu> |
|||
</a-dropdown> |
|||
</span> |
|||
</s-table> |
|||
</template> |
|||
|
|||
<script> |
|||
import STable from '@/components/table/' |
|||
|
|||
export default { |
|||
components: { |
|||
STable |
|||
}, |
|||
data() { |
|||
return { |
|||
columns: [ |
|||
{ |
|||
title: '规则编号', |
|||
dataIndex: 'no' |
|||
}, |
|||
{ |
|||
title: '描述', |
|||
dataIndex: 'description' |
|||
}, |
|||
{ |
|||
title: '服务调用次数', |
|||
dataIndex: 'callNo', |
|||
}, |
|||
{ |
|||
title: '状态', |
|||
dataIndex: 'status', |
|||
}, |
|||
{ |
|||
title: '更新时间', |
|||
dataIndex: 'updatedAt', |
|||
}, |
|||
{ |
|||
table: '操作', |
|||
dataIndex: 'action', |
|||
scopedSlots: {customRender: 'action'}, |
|||
} |
|||
], |
|||
// 查询条件参数 |
|||
queryParam: {}, |
|||
// 加载数据方法 必须为 Promise 对象 |
|||
loadData: params => { |
|||
return this.$http.get('/service', { |
|||
params: Object.assign(params, this.queryParam) |
|||
}).then(res => { |
|||
return res.result |
|||
}) |
|||
}, |
|||
} |
|||
}, |
|||
methods: { |
|||
edit(row) { |
|||
// axios 发送数据到后端 修改数据成功后 |
|||
// 调用 refresh() 重新加载列表数据 |
|||
// 这里 setTimeout 模拟发起请求的网络延迟.. |
|||
setTimeout(() => { |
|||
this.$refs.table.refresh() // refresh() 不传参默认值 false 不刷新到分页第一页 |
|||
}, 1500) |
|||
|
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
``` |
|||
|
|||
|
|||
|
|||
内置方法 |
|||
---- |
|||
|
|||
通过 `this.$refs.table` 调用 |
|||
|
|||
`this.$refs.table.refresh(true)` 刷新列表 (用户新增/修改数据后,重载列表数据) |
|||
|
|||
> 注意:要调用 `refresh(bool)` 需要给表格组件设定 `ref` 值 |
|||
> |
|||
> `refresh()` 方法可以传一个 `bool` 值,当有传值 或值为 `true` 时,则刷新时会强制刷新到第一页(常用户页面 搜索 按钮进行搜索时,结果从第一页开始分页) |
|||
|
|||
|
|||
内置属性 |
|||
---- |
|||
> 除去 `a-table` 自带属性外,还而外提供了一些额外属性属性 |
|||
|
|||
|
|||
| 属性 | 说明 | 类型 | 默认值 | |
|||
| -------------- | ----------------------------------------------- | ----------------- | ------ | |
|||
| alert | 设置是否显示表格信息栏 | [object, boolean] | null | |
|||
| showPagination | 显示分页选择器,可传 'auto' \| boolean | [string, boolean] | 'auto' | |
|||
| data | 加载数据方法 必须为 `Promise` 对象 **必须绑定** | Promise | - | |
|||
|
|||
|
|||
`alert` 属性对象: |
|||
|
|||
```javascript |
|||
alert: { |
|||
show: Boolean, |
|||
clear: [Function, Boolean] |
|||
} |
|||
``` |
|||
|
|||
注意事项 |
|||
---- |
|||
|
|||
> 你可能需要为了与后端提供的接口返回结果一致而去修改以下代码: |
|||
> (需要注意的是,这里的修改是全局性的,意味着整个项目所有使用该 table 组件都需要遵守这个返回结果定义的字段。) |
|||
> |
|||
> 文档中的结构有可能由于组件 bug 进行修正而改动。实际修改请以当时最新版本为准 |
|||
|
|||
修改 `@/components/table/index.js` 第 156 行起 |
|||
|
|||
|
|||
|
|||
```javascript |
|||
result.then(r => { |
|||
this.localPagination = this.showPagination && Object.assign({}, this.localPagination, { |
|||
current: r.pageNo, // 返回结果中的当前分页数 |
|||
total: r.totalCount, // 返回结果中的总记录数 |
|||
showSizeChanger: this.showSizeChanger, |
|||
pageSize: (pagination && pagination.pageSize) || |
|||
this.localPagination.pageSize |
|||
}) || false |
|||
// 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页 |
|||
if (r.data.length === 0 && this.showPagination && this.localPagination.current > 1) { |
|||
this.localPagination.current-- |
|||
this.loadData() |
|||
return |
|||
} |
|||
|
|||
// 这里用于判断接口是否有返回 r.totalCount 且 this.showPagination = true 且 pageNo 和 pageSize 存在 且 totalCount 小于等于 pageNo * pageSize 的大小 |
|||
// 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能 |
|||
try { |
|||
if ((['auto', true].includes(this.showPagination) && r.totalCount <= (r.pageNo * this.localPagination.pageSize))) { |
|||
this.localPagination.hideOnSinglePage = true |
|||
} |
|||
} catch (e) { |
|||
this.localPagination = false |
|||
} |
|||
console.log('loadData -> this.localPagination', this.localPagination) |
|||
this.localDataSource = r.data // 返回结果中的数组数据 |
|||
this.localLoading = false |
|||
}) |
|||
``` |
|||
返回 JSON 例子: |
|||
```json |
|||
{ |
|||
"message": "", |
|||
"result": { |
|||
"data": [{ |
|||
id: 1, |
|||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', |
|||
title: 'Alipay', |
|||
description: '那是一种内在的东西, 他们到达不了,也无法触及的', |
|||
status: 1, |
|||
updatedAt: '2018-07-26 00:00:00' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', |
|||
title: 'Angular', |
|||
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的', |
|||
status: 1, |
|||
updatedAt: '2018-07-26 00:00:00' |
|||
}, |
|||
{ |
|||
id: 3, |
|||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', |
|||
title: 'Ant Design', |
|||
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', |
|||
status: 1, |
|||
updatedAt: '2018-07-26 00:00:00' |
|||
}, |
|||
{ |
|||
id: 4, |
|||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', |
|||
title: 'Ant Design Pro', |
|||
description: '那时候我只会想自己想要什么,从不想自己拥有什么', |
|||
status: 1, |
|||
updatedAt: '2018-07-26 00:00:00' |
|||
}, |
|||
{ |
|||
id: 5, |
|||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', |
|||
title: 'Bootstrap', |
|||
description: '凛冬将至', |
|||
status: 1, |
|||
updatedAt: '2018-07-26 00:00:00' |
|||
}, |
|||
{ |
|||
id: 6, |
|||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', |
|||
title: 'Vue', |
|||
description: '生命就像一盒巧克力,结果往往出人意料', |
|||
status: 1, |
|||
updatedAt: '2018-07-26 00:00:00' |
|||
} |
|||
], |
|||
"pageSize": 10, |
|||
"pageNo": 0, |
|||
"totalPage": 6, |
|||
"totalCount": 57 |
|||
}, |
|||
"status": 200, |
|||
"timestamp": 1534955098193 |
|||
} |
|||
``` |
|||
|
|||
|
|||
|
|||
更新时间 |
|||
---- |
|||
|
|||
该文档最后更新于: 2019-06-23 PM 17:19 |
|||
@ -1,323 +0,0 @@ |
|||
import T from 'ant-design-vue/es/table/Table' |
|||
import get from 'lodash.get' |
|||
|
|||
export default { |
|||
data () { |
|||
return { |
|||
needTotalList: [], |
|||
|
|||
selectedRows: [], |
|||
selectedRowKeys: [], |
|||
|
|||
localLoading: false, |
|||
localDataSource: [], |
|||
localPagination: Object.assign({}, this.pagination) |
|||
} |
|||
}, |
|||
props: Object.assign({}, T.props, { |
|||
rowKey: { |
|||
type: [String, Function], |
|||
default: 'key' |
|||
}, |
|||
data: { |
|||
type: Function, |
|||
required: true |
|||
}, |
|||
pageNum: { |
|||
type: Number, |
|||
default: 1 |
|||
}, |
|||
pageSize: { |
|||
type: Number, |
|||
default: 15 |
|||
}, |
|||
showSizeChanger: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
size: { |
|||
type: String, |
|||
default: 'default' |
|||
}, |
|||
// 展开的图标显示在哪一列
|
|||
expandIconColumnIndex: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
/** |
|||
* alert: { |
|||
* show: true, |
|||
* clear: Function |
|||
* } |
|||
*/ |
|||
alert: { |
|||
type: [Object, Boolean], |
|||
default: null |
|||
}, |
|||
rowSelection: { |
|||
type: Object, |
|||
default: null |
|||
}, |
|||
/** @Deprecated */ |
|||
showAlertInfo: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
showPagination: { |
|||
type: String | Boolean, |
|||
default: 'auto' |
|||
}, |
|||
/** |
|||
* enable page URI mode |
|||
* |
|||
* e.g: |
|||
* /users/1 |
|||
* /users/2 |
|||
* /users/3?queryParam=test |
|||
* ... |
|||
*/ |
|||
pageURI: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}), |
|||
watch: { |
|||
'localPagination.current' (val) { |
|||
this.pageURI && this.$router.push({ |
|||
...this.$route, |
|||
name: this.$route.name, |
|||
params: Object.assign({}, this.$route.params, { |
|||
page: val |
|||
}) |
|||
}) |
|||
}, |
|||
pageNum (val) { |
|||
Object.assign(this.localPagination, { |
|||
current: val |
|||
}) |
|||
}, |
|||
pageSize (val) { |
|||
Object.assign(this.localPagination, { |
|||
pageSize: val |
|||
}) |
|||
}, |
|||
showSizeChanger (val) { |
|||
Object.assign(this.localPagination, { |
|||
showSizeChanger: val |
|||
}) |
|||
}, |
|||
// 同步正在加载中的状态
|
|||
loading (val) { |
|||
this.localLoading = val |
|||
} |
|||
}, |
|||
created () { |
|||
const { page } = this.$route.params |
|||
const localPageNum = this.pageURI && (page && parseInt(page)) || this.pageNum |
|||
this.localPagination = ['auto', true].includes(this.showPagination) && Object.assign({}, this.localPagination, { |
|||
current: localPageNum, |
|||
pageSize: this.pageSize, |
|||
showSizeChanger: this.showSizeChanger |
|||
}) || false |
|||
// console.log('this.localPagination', this.localPagination)
|
|||
this.needTotalList = this.initTotalList(this.columns) |
|||
this.loadData() |
|||
}, |
|||
methods: { |
|||
/** |
|||
* 表格重新加载方法 |
|||
* 如果参数为 true, 则强制刷新到第一页 |
|||
* @param Boolean bool |
|||
*/ |
|||
refresh (bool = false) { |
|||
bool && (this.localPagination = Object.assign({}, { |
|||
current: 1, pageSize: this.pageSize |
|||
})) |
|||
this.loadData() |
|||
}, |
|||
/** |
|||
* 加载数据方法 |
|||
* @param {Object} pagination 分页选项器 |
|||
* @param {Object} filters 过滤条件 |
|||
* @param {Object} sorter 排序条件 |
|||
*/ |
|||
loadData (pagination, filters, sorter) { |
|||
this.localLoading = true |
|||
const params = Object.assign({ |
|||
page: (pagination && pagination.current) || |
|||
this.showPagination && this.localPagination.current || this.pageNum |
|||
// pageSize: (pagination && pagination.pageSize) ||
|
|||
// this.showPagination && this.localPagination.pageSize || this.pageSize
|
|||
}, |
|||
(sorter && sorter.field && { |
|||
sortField: sorter.field |
|||
}) || {}, |
|||
(sorter && sorter.order && { |
|||
sortOrder: sorter.order |
|||
}) || {}, { |
|||
...filters |
|||
} |
|||
) |
|||
// console.log('params', params)
|
|||
const result = this.data(params) |
|||
// 对接自己的通用数据接口需要修改下方代码中的 r.page, r.totalCount, r.data
|
|||
// eslint-disable-next-line
|
|||
if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') { |
|||
result.then(r => { |
|||
this.localPagination = this.showPagination && Object.assign({}, this.localPagination, { |
|||
current: r.current_page, // 返回结果中的当前分页数
|
|||
total: r.total, // 返回结果中的总记录数
|
|||
showSizeChanger: this.showSizeChanger, |
|||
pageSize: (pagination && pagination.pageSize) || |
|||
this.localPagination.pageSize |
|||
}) || false |
|||
// 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页
|
|||
if (r.data.length === 0 && this.showPagination && this.localPagination.current > 1) { |
|||
this.localPagination.current-- |
|||
this.loadData() |
|||
return |
|||
} |
|||
|
|||
// 这里用于判断接口是否有返回 r.totalCount 且 this.showPagination = true 且 page 和 pageSize 存在 且 totalCount 小于等于 page * pageSize 的大小
|
|||
// 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能
|
|||
try { |
|||
if ((['auto', true].includes(this.showPagination) && r.total <= (r.current_page * this.localPagination.pageSize))) { |
|||
this.localPagination.hideOnSinglePage = true |
|||
} |
|||
} catch (e) { |
|||
this.localPagination = false |
|||
} |
|||
// console.log('loadData -> this.localPagination', this.localPagination)
|
|||
this.localDataSource = r.data // 返回结果中的数组数据
|
|||
this.localLoading = false |
|||
}) |
|||
} |
|||
}, |
|||
initTotalList (columns) { |
|||
const totalList = [] |
|||
columns && columns instanceof Array && columns.forEach(column => { |
|||
if (column.needTotal) { |
|||
totalList.push({ |
|||
...column, |
|||
total: 0 |
|||
}) |
|||
} |
|||
}) |
|||
return totalList |
|||
}, |
|||
/** |
|||
* 用于更新已选中的列表数据 total 统计 |
|||
* @param selectedRowKeys |
|||
* @param selectedRows |
|||
*/ |
|||
updateSelect (selectedRowKeys, selectedRows) { |
|||
this.selectedRows = selectedRows |
|||
this.selectedRowKeys = selectedRowKeys |
|||
const list = this.needTotalList |
|||
this.needTotalList = list.map(item => { |
|||
return { |
|||
...item, |
|||
total: selectedRows.reduce((sum, val) => { |
|||
const total = sum + parseInt(get(val, item.dataIndex)) |
|||
return isNaN(total) ? 0 : total |
|||
}, 0) |
|||
} |
|||
}) |
|||
}, |
|||
/** |
|||
* 清空 table 已选中项 |
|||
*/ |
|||
clearSelected () { |
|||
if (this.rowSelection) { |
|||
this.rowSelection.onChange([], []) |
|||
this.updateSelect([], []) |
|||
} |
|||
}, |
|||
/** |
|||
* 处理交给 table 使用者去处理 clear 事件时,内部选中统计同时调用 |
|||
* @param callback |
|||
* @returns {*} |
|||
*/ |
|||
renderClear (callback) { |
|||
if (this.selectedRowKeys.length <= 0) return null |
|||
return ( |
|||
<a style="margin-left: 24px" onClick={() => { |
|||
callback() |
|||
this.clearSelected() |
|||
}}>清空</a> |
|||
) |
|||
}, |
|||
renderAlert () { |
|||
// 绘制统计列数据
|
|||
const needTotalItems = this.needTotalList.map((item) => { |
|||
return (<span style="margin-right: 12px"> |
|||
{item.title}总计 <a style="font-weight: 600">{!item.customRender ? item.total : item.customRender(item.total)}</a> |
|||
</span>) |
|||
}) |
|||
|
|||
// 绘制 清空 按钮
|
|||
const clearItem = (typeof this.alert.clear === 'boolean' && this.alert.clear) ? ( |
|||
this.renderClear(this.clearSelected) |
|||
) : (this.alert !== null && typeof this.alert.clear === 'function') ? ( |
|||
this.renderClear(this.alert.clear) |
|||
) : null |
|||
|
|||
// 绘制 alert 组件
|
|||
return ( |
|||
<a-alert showIcon={true} style="margin-bottom: 16px"> |
|||
<template slot="message"> |
|||
<span style="margin-right: 12px">已选择: <a style="font-weight: 600">{this.selectedRows.length}</a></span> |
|||
{needTotalItems} |
|||
{clearItem} |
|||
</template> |
|||
</a-alert> |
|||
) |
|||
} |
|||
}, |
|||
|
|||
render () { |
|||
const props = {} |
|||
const localKeys = Object.keys(this.$data) |
|||
const showAlert = (typeof this.alert === 'object' && this.alert !== null && this.alert.show) && typeof this.rowSelection.selectedRowKeys !== 'undefined' || this.alert |
|||
Object.keys(T.props).forEach(k => { |
|||
const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}` |
|||
if (localKeys.includes(localKey)) { |
|||
props[k] = this[localKey] |
|||
return props[k] |
|||
} |
|||
if (k === 'rowSelection') { |
|||
if (showAlert && this.rowSelection) { |
|||
// 如果需要使用alert,则重新绑定 rowSelection 事件
|
|||
// console.log('this.rowSelection', this.rowSelection)
|
|||
props[k] = { |
|||
...this.rowSelection, |
|||
selectedRows: this.selectedRows, |
|||
selectedRowKeys: this.selectedRowKeys, |
|||
onChange: (selectedRowKeys, selectedRows) => { |
|||
this.updateSelect(selectedRowKeys, selectedRows) |
|||
typeof this[k].onChange !== 'undefined' && this[k].onChange(selectedRowKeys, selectedRows) |
|||
} |
|||
} |
|||
return props[k] |
|||
} else if (!this.rowSelection) { |
|||
// 如果没打算开启 rowSelection 则清空默认的选择项
|
|||
props[k] = null |
|||
return props[k] |
|||
} |
|||
} |
|||
this[k] && (props[k] = this[k]) |
|||
return props[k] |
|||
}) |
|||
const table = ( |
|||
<a-table {...{ props, scopedSlots: { ...this.$scopedSlots } }} onChange={this.loadData} onExpand={(expanded, record) => { this.$emit('expand', expanded, record) }}> |
|||
{Object.keys(this.$slots).map(name => (<template slot={name}>{this.$slots[name]}</template>))} |
|||
</a-table> |
|||
) |
|||
return ( |
|||
<div class="table-wrapper"> |
|||
{showAlert ? this.renderAlert() : null} |
|||
{table} |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
<template> |
|||
<div v-if="user" class="user-info clearfix"> |
|||
<div class="in-left"> |
|||
<a-tooltip> |
|||
<template slot="title">会员ID: {{ user.user_id }}</template> |
|||
<img v-if="user.avatar_url" :src="user.avatar_url" alt="会员头像" /> |
|||
<img v-else src="~@/assets/img/default-avatar.png" alt="会员头像" /> |
|||
</a-tooltip> |
|||
</div> |
|||
<div class="in-right flex flex-dir-column flex-x-center"> |
|||
<p class="user-name oneline-hide">{{ user.nick_name }}</p> |
|||
<p class="user-platform"> |
|||
<platform-icon :name="user.platform" :showTips="true" /> |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import PlatformIcon from '@/components/PlatformIcon' |
|||
|
|||
// Table内容元素: 会员信息 |
|||
export default { |
|||
name: 'UserItem', |
|||
components: { |
|||
PlatformIcon |
|||
}, |
|||
props: { |
|||
// 会员信息 |
|||
user: PropTypes.object.def() |
|||
}, |
|||
data () { |
|||
return { |
|||
|
|||
} |
|||
}, |
|||
methods: { |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
// 会员信息 |
|||
.user-info { |
|||
width: 160px; |
|||
height: 40px; |
|||
line-height: 1.3; |
|||
|
|||
.in-left { |
|||
float: left; |
|||
margin-right: 10px; |
|||
|
|||
img { |
|||
width: 40px; |
|||
height: 40px; |
|||
border-radius: 50%; |
|||
} |
|||
} |
|||
|
|||
.in-right { |
|||
float: left; |
|||
width: 100px; |
|||
height: 100%; |
|||
|
|||
.user-name { |
|||
margin-bottom: 2px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,2 +0,0 @@ |
|||
import UserItem from './UserItem' |
|||
export default UserItem |
|||
@ -1,5 +0,0 @@ |
|||
import STable from './STable' |
|||
import UserItem from './UserItem' |
|||
import GoodsItem from './GoodsItem' |
|||
|
|||
export { STable, UserItem, GoodsItem } |
|||
@ -1,198 +0,0 @@ |
|||
<template> |
|||
<div> |
|||
<!-- 富文本编辑器 --> |
|||
<VueUeditorWrap |
|||
ref="Ueditor" |
|||
v-model="content" |
|||
:config="myConfig" |
|||
@before-init="beforeUeditorInit" |
|||
/> |
|||
<!-- 文件选择器 --> |
|||
<FilesModal |
|||
ref="FilesModal" |
|||
:fileType="filesModalType" |
|||
:multiple="true" |
|||
@handleSubmit="handleFilesSelect" |
|||
/> |
|||
<!-- 链接选择器 --> |
|||
<LinkModal ref="LinkModal" @handleSubmit="handleLinkSelect" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import _ from 'lodash' |
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types' |
|||
import VueUeditorWrap from 'vue-ueditor-wrap' |
|||
import { defaultConfig } from './config' |
|||
import { FilesModal, LinkModal } from '@/components/Modal' |
|||
import FileTypeEnum from '@/common/enum/file/FileType' |
|||
|
|||
export default { |
|||
name: 'Ueditor', |
|||
components: { |
|||
FilesModal, |
|||
LinkModal, |
|||
VueUeditorWrap |
|||
}, |
|||
model: { |
|||
prop: 'value', |
|||
event: 'change' |
|||
}, |
|||
props: { |
|||
// 内容 |
|||
// eslint-disable-next-line vue/require-default-prop |
|||
value: PropTypes.string, |
|||
// 编辑器配置 |
|||
config: PropTypes.object.def({}) |
|||
}, |
|||
data () { |
|||
const myConfig = _.merge(defaultConfig, this.config) |
|||
return { |
|||
myConfig, |
|||
content: '', |
|||
filesModalType: FileTypeEnum.IMAGE.value |
|||
} |
|||
}, |
|||
watch: { |
|||
value: { |
|||
// 首次加载的时候执行函数 |
|||
immediate: true, |
|||
handler (val) { |
|||
this.content = val |
|||
} |
|||
}, |
|||
content (newVal) { |
|||
// 使用setTimeout防止热更新时报错 |
|||
setTimeout(() => { |
|||
this.$emit('change', newVal) |
|||
}, 10) |
|||
} |
|||
}, |
|||
created () { }, |
|||
methods: { |
|||
|
|||
// Ueditor组件初始化 |
|||
beforeUeditorInit (editorId) { |
|||
this.registerLink(editorId) |
|||
this.registerSimpleupload(editorId) |
|||
this.registerInsertvideo(editorId) |
|||
}, |
|||
|
|||
// 注册图片选择按钮 |
|||
registerSimpleupload (editorId) { |
|||
const app = this |
|||
window.UE.registerUI('simpleupload', function (editor, uiName) { |
|||
// 创建一个 button |
|||
var btn = new window.UE.ui.Button({ |
|||
// 按钮的名字 |
|||
name: uiName, |
|||
// 提示 |
|||
title: '插入图片', |
|||
// 需要添加的额外样式,可指定 icon 图标,图标路径参考常见问题 2 |
|||
cssRules: '', |
|||
// 点击时执行的命令 |
|||
onclick: () => { |
|||
app.filesModalType = FileTypeEnum.IMAGE.value |
|||
app.$nextTick(() => { |
|||
app.$refs.FilesModal.show() |
|||
}) |
|||
} |
|||
}) |
|||
// 因为你是添加 button,所以需要返回这个 button |
|||
return btn |
|||
}, undefined /* 指定添加到工具栏上的哪个位置,默认时追加到最后 */, editorId /* 指定这个 UI 是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */) |
|||
}, |
|||
|
|||
// 注册视频选择按钮 |
|||
registerInsertvideo (editorId) { |
|||
const app = this |
|||
window.UE.registerUI('insertvideo', function (editor, uiName) { |
|||
// 创建一个 button |
|||
var btn = new window.UE.ui.Button({ |
|||
// 按钮的名字 |
|||
name: uiName, |
|||
// 提示 |
|||
title: '插入视频', |
|||
// 需要添加的额外样式,可指定 icon 图标,图标路径参考常见问题 2 |
|||
cssRules: '', |
|||
// 点击时执行的命令 |
|||
onclick: () => { |
|||
app.filesModalType = FileTypeEnum.VIDEO.value |
|||
app.$nextTick(() => { |
|||
app.$refs.FilesModal.show() |
|||
}) |
|||
} |
|||
}) |
|||
// 因为你是添加 button,所以需要返回这个 button |
|||
return btn |
|||
}, undefined /* 指定添加到工具栏上的哪个位置,默认时追加到最后 */, editorId /* 指定这个 UI 是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */) |
|||
}, |
|||
|
|||
// 注册链接选择按钮 |
|||
registerLink (editorId) { |
|||
const app = this |
|||
window.UE.registerUI('link', function (editor, uiName) { |
|||
// 创建一个 button |
|||
var btn = new window.UE.ui.Button({ |
|||
// 按钮的名字 |
|||
name: uiName, |
|||
// 提示 |
|||
title: '超链接', |
|||
// 需要添加的额外样式,可指定 icon 图标,图标路径参考常见问题 2 |
|||
cssRules: '', |
|||
// 点击时执行的命令 |
|||
onclick: () => { |
|||
app.$refs.LinkModal.handle() |
|||
} |
|||
}) |
|||
// 因为你是添加 button,所以需要返回这个 button |
|||
return btn |
|||
}, undefined /* 指定添加到工具栏上的哪个位置,默认时追加到最后 */, editorId /* 指定这个 UI 是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */) |
|||
}, |
|||
|
|||
// 文件库modal确认回调 |
|||
handleFilesSelect (selectedItems) { |
|||
const app = this |
|||
if (selectedItems.length > 0) { |
|||
let content = '' |
|||
if (app.filesModalType == FileTypeEnum.IMAGE.value) { |
|||
content = selectedItems.map(item => `<p><img src="${item.preview_url}" /></p>`) |
|||
} |
|||
if (app.filesModalType == FileTypeEnum.VIDEO.value) { |
|||
content = selectedItems.map(item => `<p><video style="width: 100%; height: 240px;" src="${item.external_url}" controls>${item.file_id}</video></p>`) |
|||
} |
|||
app.inserthtml(content.join('')) |
|||
} |
|||
}, |
|||
|
|||
// 链接选择器modal 确认回调 |
|||
handleLinkSelect (result) { |
|||
const app = this |
|||
if (result.type === 'PAGE') { |
|||
const attr = { href: result.param.url } |
|||
if (!app.getSelectionText()) { |
|||
attr.textValue = result.title |
|||
} |
|||
app.getEditor().execCommand('link', attr) |
|||
} |
|||
}, |
|||
|
|||
// 插入内容到编辑器 |
|||
inserthtml (content) { |
|||
this.getEditor().execCommand('inserthtml', content) |
|||
}, |
|||
|
|||
// 获取选中的文本 |
|||
getSelectionText () { |
|||
//当点击按钮时编辑区域已经失去了焦点,如果直接用getText将不会得到内容,所以要在选回来,然后取得内容 |
|||
this.getEditor().selection.getRange().select() |
|||
return this.getEditor().selection.getText() |
|||
}, |
|||
|
|||
getEditor () { |
|||
return this.$refs.Ueditor.editor |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
@ -1,44 +0,0 @@ |
|||
// 获取url二级目录
|
|||
const pathname = window.location.pathname |
|||
|
|||
// 默认配置
|
|||
export const defaultConfig = { |
|||
// 编辑器层级的基数,默认是900
|
|||
zIndex: 1000, |
|||
// 编辑器自动被内容撑高
|
|||
autoHeightEnabled: false, |
|||
// 初始容器高度
|
|||
initialFrameHeight: 540, |
|||
// 初始容器宽度
|
|||
initialFrameWidth: 375, |
|||
// 上传文件接口(这个地址是我为了方便各位体验文件上传功能搭建的临时接口,请勿在生产环境使用!!!部署在国外的服务器,如果无法访问,请自备梯子)
|
|||
// serverUrl: 'http://35.201.165.105:8000/controller.php',
|
|||
// UEditor 资源文件的存放路径,如果你使用的是 vue-cli 生成的项目,通常不需要设置该选项,vue-ueditor-wrap 会自动处理常见的情况,如果需要特殊配置,参考下方的常见问题2
|
|||
UEDITOR_HOME_URL: `${pathname}static/UEditor/`, |
|||
// 给编辑区域的iframe引入一个css文件
|
|||
iframeCssUrl: `${pathname}static/UEditor/themes/iframe.css`, |
|||
// 图片操作的浮层开关
|
|||
imagePopup: false, |
|||
// 打开右键菜单功能
|
|||
enableContextMenu: false, |
|||
// 是否保持toolbar的位置不动,默认true
|
|||
autoFloatEnabled: false, |
|||
// 工具栏上的所有的功能按钮和下拉框
|
|||
toolbars: [[ |
|||
'source', '|', 'undo', 'redo', '|', |
|||
'bold', 'italic', 'underline', 'strikethrough', 'removeformat', |
|||
// 纯文本粘贴
|
|||
'pasteplain', '|', 'forecolor', 'backcolor', 'selectall', 'cleardoc', '|', |
|||
'rowspacingtop', 'rowspacingbottom', 'lineheight', '|', |
|||
'fontsize', '|', |
|||
'indent', 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|', |
|||
'unlink', |
|||
// 'link',
|
|||
// 'simpleupload',
|
|||
// 'insertvideo'
|
|||
]] |
|||
// 当鼠标放在工具栏上时显示的tooltip提示
|
|||
// labelMap: {
|
|||
// simpleupload: '插入图片'
|
|||
// }
|
|||
} |
|||
@ -1,2 +0,0 @@ |
|||
import Ueditor from './Ueditor' |
|||
export default Ueditor |
|||
@ -1,46 +0,0 @@ |
|||
/** |
|||
* components util |
|||
*/ |
|||
|
|||
/** |
|||
* 清理空值,对象 |
|||
* @param children |
|||
* @returns {*[]} |
|||
*/ |
|||
export function filterEmpty (children = []) { |
|||
return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) |
|||
} |
|||
|
|||
/** |
|||
* 获取字符串长度,英文字符 长度1,中文字符长度2 |
|||
* @param {*} str |
|||
*/ |
|||
export const getStrFullLength = (str = '') => |
|||
str.split('').reduce((pre, cur) => { |
|||
const charCode = cur.charCodeAt(0) |
|||
if (charCode >= 0 && charCode <= 128) { |
|||
return pre + 1 |
|||
} |
|||
return pre + 2 |
|||
}, 0) |
|||
|
|||
/** |
|||
* 截取字符串,根据 maxLength 截取后返回 |
|||
* @param {*} str |
|||
* @param {*} maxLength |
|||
*/ |
|||
export const cutStrByFullLength = (str = '', maxLength) => { |
|||
let showLength = 0 |
|||
return str.split('').reduce((pre, cur) => { |
|||
const charCode = cur.charCodeAt(0) |
|||
if (charCode >= 0 && charCode <= 128) { |
|||
showLength += 1 |
|||
} else { |
|||
showLength += 2 |
|||
} |
|||
if (showLength <= maxLength) { |
|||
return pre + cur |
|||
} |
|||
return pre |
|||
}, '') |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
import MultiTab from '@/components/MultiTab' |
|||
import Dialog from '@/components/Dialog' |
|||
import ContentHeader from '@/components/ContentHeader' |
|||
import { STable } from '@/components/Table' |
|||
|
|||
import SelectImage from '@/components/SelectImage' |
|||
import SelectVideo from '@/components/SelectVideo' |
|||
import SelectGoods from '@/components/SelectGoods' |
|||
import SelectRegion from '@/components/SelectRegion' |
|||
import Getpoint from '@/components/Getpoint' |
|||
import Ueditor from '@/components/Ueditor' |
|||
import InputNumberGroup from '@/components/InputNumberGroup' |
|||
|
|||
export { |
|||
STable, |
|||
MultiTab, |
|||
Dialog, |
|||
|
|||
SelectImage, |
|||
SelectVideo, |
|||
SelectGoods, |
|||
SelectRegion, |
|||
Getpoint, |
|||
Ueditor, |
|||
InputNumberGroup, |
|||
ContentHeader |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
@import "~ant-design-vue/lib/style/index"; |
|||
|
|||
// The prefix to use on all css classes from ant-pro. |
|||
@ant-pro-prefix : ant-pro; |
|||
@ant-global-sider-zindex : 106; |
|||
@ant-global-header-zindex : 105; |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue