Browse Source

移除不使用的组件

lite
xyiege 2 years ago
parent
commit
1e4e262662
  1. 4
      package.json
  2. 26
      src/App.vue
  3. 10
      src/common/enum/coupon/ApplyRange.js
  4. 10
      src/common/enum/coupon/CouponType.js
  5. 10
      src/common/enum/coupon/ExpireType.js
  6. 5
      src/common/enum/coupon/index.js
  7. 84
      src/common/enum/enum.js
  8. 10
      src/common/enum/file/Channel.js
  9. 11
      src/common/enum/file/FileType.js
  10. 12
      src/common/enum/file/Storage.js
  11. 10
      src/common/enum/order/DeliveryStatus.js
  12. 9
      src/common/enum/order/DeliveryType.js
  13. 9
      src/common/enum/order/OrderSource.js
  14. 12
      src/common/enum/order/OrderStatus.js
  15. 10
      src/common/enum/order/PayStatus.js
  16. 10
      src/common/enum/order/PayType.js
  17. 10
      src/common/enum/order/ReceiptStatus.js
  18. 11
      src/common/enum/order/export/ExportStatus.js
  19. 3
      src/common/enum/order/export/index.js
  20. 17
      src/common/enum/order/index.js
  21. 11
      src/common/enum/order/refund/AuditStatus.js
  22. 12
      src/common/enum/order/refund/RefundStatus.js
  23. 10
      src/common/enum/order/refund/RefundType.js
  24. 9
      src/common/enum/order/refund/index.js
  25. 10
      src/common/enum/page/PageType.js
  26. 3
      src/common/enum/page/index.js
  27. 10
      src/common/enum/recharge/order/PayStatus.js
  28. 10
      src/common/enum/recharge/order/RechargeType.js
  29. 10
      src/common/enum/setting/sms/Scene.js
  30. 15
      src/common/enum/store/Setting.js
  31. 10
      src/common/enum/store/address/Type.js
  32. 3
      src/common/enum/store/address/index.js
  33. 3
      src/common/enum/store/index.js
  34. 12
      src/common/enum/store/page/category/Style.js
  35. 3
      src/common/enum/store/page/category/index.js
  36. 12
      src/common/enum/user/balance/log/Scene.js
  37. 69
      src/common/model/Category.js
  38. 266
      src/common/model/Links.js
  39. 57
      src/common/model/Region.js
  40. 48
      src/common/model/article/Category.js
  41. 176
      src/common/model/goods/Index.js
  42. 446
      src/common/model/goods/MultiSpec.js
  43. 17
      src/common/model/setting/Delivery.js
  44. 52
      src/components/ContentHeader/ContentHeader.vue
  45. 2
      src/components/ContentHeader/index.js
  46. 113
      src/components/Dialog.js
  47. 44
      src/components/Getpoint/Getpoint.vue
  48. 2
      src/components/Getpoint/index.js
  49. 46
      src/components/GlobalFooter/index.vue
  50. 74
      src/components/GlobalHeader/AvatarDropdown.vue
  51. 55
      src/components/GlobalHeader/RightContent.vue
  52. 63
      src/components/InputNumberGroup/InputNumberGroup.vue
  53. 2
      src/components/InputNumberGroup/index.js
  54. 369
      src/components/Modal/AreasModal/AreasModal.vue
  55. 2
      src/components/Modal/AreasModal/index.js
  56. 162
      src/components/Modal/FilesModal/AddGroupForm.vue
  57. 600
      src/components/Modal/FilesModal/FilesModal.vue
  58. 136
      src/components/Modal/FilesModal/MoveGroupForm.vue
  59. 2
      src/components/Modal/FilesModal/index.js
  60. 279
      src/components/Modal/GoodsModal/GoodsModal.vue
  61. 2
      src/components/Modal/GoodsModal/index.js
  62. 262
      src/components/Modal/LinkModal/LinkModal.vue
  63. 2
      src/components/Modal/LinkModal/index.js
  64. 6
      src/components/Modal/index.js
  65. 162
      src/components/MultiTab/MultiTab.vue
  66. 2
      src/components/MultiTab/events.js
  67. 40
      src/components/MultiTab/index.js
  68. 25
      src/components/MultiTab/index.less
  69. 76
      src/components/NProgress/nprogress.less
  70. 106
      src/components/PageLoading/index.jsx
  71. 96
      src/components/PlatformIcon/PlatformIcon.vue
  72. 2
      src/components/PlatformIcon/index.js
  73. 161
      src/components/SelectGoods/SelectGoods.vue
  74. 2
      src/components/SelectGoods/index.js
  75. 231
      src/components/SelectImage/SelectImage.vue
  76. 2
      src/components/SelectImage/index.js
  77. 58
      src/components/SelectLang/index.jsx
  78. 31
      src/components/SelectLang/index.less
  79. 86
      src/components/SelectRegion/SelectRegion.vue
  80. 2
      src/components/SelectRegion/index.js
  81. 237
      src/components/SelectVideo/SelectVideo.vue
  82. 2
      src/components/SelectVideo/index.js
  83. 377
      src/components/SettingDrawer/SettingDrawer.vue
  84. 36
      src/components/SettingDrawer/SettingItem.vue
  85. 2
      src/components/SettingDrawer/index.js
  86. 48
      src/components/SettingDrawer/settingConfig.js
  87. 24
      src/components/SettingDrawer/themeColor.js
  88. 105
      src/components/Table/GoodsItem/GoodsItem.vue
  89. 2
      src/components/Table/GoodsItem/index.js
  90. 341
      src/components/Table/README.md
  91. 323
      src/components/Table/STable.js
  92. 72
      src/components/Table/UserItem/UserItem.vue
  93. 2
      src/components/Table/UserItem/index.js
  94. 5
      src/components/Table/index.js
  95. 198
      src/components/Ueditor/Ueditor.vue
  96. 44
      src/components/Ueditor/config.js
  97. 2
      src/components/Ueditor/index.js
  98. 46
      src/components/_util/util.js
  99. 27
      src/components/index.js
  100. 6
      src/components/index.less

4
package.json

@ -3,8 +3,8 @@
"version": "3.0.0",
"private": true,
"scripts": {
"serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"build:preview": "vue-cli-service build --mode preview",

26
src/App.vue

@ -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>

10
src/common/enum/coupon/ApplyRange.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类优惠券适用范围
* ApplyRangeEnum
*/
export default new Enum([
{ key: 'ALL', name: '全部商品', value: 10 },
{ key: 'SOME_GOODS', name: '指定商品', value: 20 }
])

10
src/common/enum/coupon/CouponType.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类优惠券类型
* CouponTypeEnum
*/
export default new Enum([
{ key: 'FULL_DISCOUNT', name: '满减券', value: 10 },
{ key: 'DISCOUNT', name: '折扣券', value: 20 }
])

10
src/common/enum/coupon/ExpireType.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类优惠券到期类型
* ExpireTypeEnum
*/
export default new Enum([
{ key: 'RECEIVE', name: '领取后', value: 10 },
{ key: 'FIXED_TIME', name: '固定时间', value: 20 }
])

5
src/common/enum/coupon/index.js

@ -1,5 +0,0 @@
import ApplyRangeEnum from './ApplyRange'
import ExpireTypeEnum from './ExpireType'
import CouponTypeEnum from './CouponType'
export { ApplyRangeEnum, CouponTypeEnum, ExpireTypeEnum }

84
src/common/enum/enum.js

@ -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

10
src/common/enum/file/Channel.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类文件上传来源
* ChannelEnum
*/
export default new Enum([
{ key: 'STORE', name: '商户后台', value: 10 },
{ key: 'CLIENT', name: '用户端', value: 20 }
])

11
src/common/enum/file/FileType.js

@ -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 }
])

12
src/common/enum/file/Storage.js

@ -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' }
])

10
src/common/enum/order/DeliveryStatus.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类订单发货状态
* DeliveryStatusEnum
*/
export default new Enum([
{ key: 'NOT_DELIVERED', name: '未发货', value: 10 },
{ key: 'DELIVERED', name: '已发货', value: 20 }
])

9
src/common/enum/order/DeliveryType.js

@ -1,9 +0,0 @@
import Enum from '../enum'
/**
* 枚举类配送方式
* DeliveryTypeEnum
*/
export default new Enum([
{ key: 'EXPRESS', name: '快递配送', value: 10 }
])

9
src/common/enum/order/OrderSource.js

@ -1,9 +0,0 @@
import Enum from '../enum'
/**
* 枚举类订单来源
* OrderSourceEnum
*/
export default new Enum([
{ key: 'MASTER', name: '普通订单', value: 10 }
])

12
src/common/enum/order/OrderStatus.js

@ -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 }
])

10
src/common/enum/order/PayStatus.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类订单支付状态
* PayStatusEnum
*/
export default new Enum([
{ key: 'PENDING', name: '待支付', value: 10 },
{ key: 'SUCCESS', name: '已支付', value: 20 }
])

10
src/common/enum/order/PayType.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类订单支付方式
* PayTypeEnum
*/
export default new Enum([
{ key: 'BALANCE', name: '余额支付', value: 10 },
{ key: 'WECHAT', name: '微信支付', value: 20 }
])

10
src/common/enum/order/ReceiptStatus.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类订单收货状态
* ReceiptStatusEnum
*/
export default new Enum([
{ key: 'NOT_RECEIVED', name: '未收货', value: 10 },
{ key: 'RECEIVED', name: '已收货', value: 20 }
])

11
src/common/enum/order/export/ExportStatus.js

@ -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 }
])

3
src/common/enum/order/export/index.js

@ -1,3 +0,0 @@
import ExportStatusEnum from './ExportStatus'
export { ExportStatusEnum }

17
src/common/enum/order/index.js

@ -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
}

11
src/common/enum/order/refund/AuditStatus.js

@ -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 }
])

12
src/common/enum/order/refund/RefundStatus.js

@ -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 }
])

10
src/common/enum/order/refund/RefundType.js

@ -1,10 +0,0 @@
import Enum from '../../enum'
/**
* 枚举类售后类型
* RefundTypeEnum
*/
export default new Enum([
{ key: 'RETURN', name: '退货退款', value: 10 },
{ key: 'EXCHANGE', name: '换货', value: 20 }
])

9
src/common/enum/order/refund/index.js

@ -1,9 +0,0 @@
import AuditStatusEnum from './AuditStatus'
import RefundStatusEnum from './RefundStatus'
import RefundTypeEnum from './RefundType'
export {
AuditStatusEnum,
RefundStatusEnum,
RefundTypeEnum
}

10
src/common/enum/page/PageType.js

@ -1,10 +0,0 @@
import Enum from '../enum'
/**
* 枚举类订单支付方式
* PageTypeEnum
*/
export default new Enum([
{ key: 'HOME', name: '首页', value: 10 },
{ key: 'CUSTOM', name: '自定义页', value: 20 }
])

3
src/common/enum/page/index.js

@ -1,3 +0,0 @@
import PageTypeEnum from './PageType'
export { PageTypeEnum }

10
src/common/enum/recharge/order/PayStatus.js

@ -1,10 +0,0 @@
import Enum from '../../enum'
/**
* 枚举类用户充值订单--支付状态
* PayStatusEnum
*/
export default new Enum([
{ key: 'PENDING', name: '待支付', value: 10 },
{ key: 'SUCCESS', name: '支付成功', value: 20 }
])

10
src/common/enum/recharge/order/RechargeType.js

@ -1,10 +0,0 @@
import Enum from '../../enum'
/**
* 枚举类用户充值订单--充值方式
* RechargeTypeEnum
*/
export default new Enum([
{ key: 'CUSTOM', name: '自定义金额', value: 10 },
{ key: 'PLAN', name: '套餐充值', value: 20 }
])

10
src/common/enum/setting/sms/Scene.js

@ -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' }
])

15
src/common/enum/store/Setting.js

@ -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' }
])

10
src/common/enum/store/address/Type.js

@ -1,10 +0,0 @@
import Enum from '../../enum'
/**
* 枚举类地址类型
* AddressTypeEnum
*/
export default new Enum([
{ key: 'DELIVERY', name: '发货地址', value: 10 },
{ key: 'RETURN', name: '退货地址', value: 20 }
])

3
src/common/enum/store/address/index.js

@ -1,3 +0,0 @@
import AddressTypeEnum from './Type'
export { AddressTypeEnum }

3
src/common/enum/store/index.js

@ -1,3 +0,0 @@
import SettingEnum from './Setting'
export { SettingEnum }

12
src/common/enum/store/page/category/Style.js

@ -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 }
])

3
src/common/enum/store/page/category/index.js

@ -1,3 +0,0 @@
import PageCategoryStyleEnum from './Style'
export { PageCategoryStyleEnum }

12
src/common/enum/user/balance/log/Scene.js

@ -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 },
])

69
src/common/model/Category.js

@ -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
}
}

266
src/common/model/Links.js

@ -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]

57
src/common/model/Region.js

@ -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)
})
})
}
}

48
src/common/model/article/Category.js

@ -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
}
}

176
src/common/model/goods/Index.js

@ -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 }
}
}

446
src/common/model/goods/MultiSpec.js

@ -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
}, [[]])
}

17
src/common/model/setting/Delivery.js

@ -1,17 +0,0 @@
import * as Api from '@/api/setting/delivery'
/**
* 格式化分类列表
*/
export default {
/**
* 向服务端获取分类列表并格式化
*/
getListSelect () {
return new Promise((resolve, reject) => {
})
}
}

52
src/components/ContentHeader/ContentHeader.vue

@ -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>

2
src/components/ContentHeader/index.js

@ -1,2 +0,0 @@
import ContentHeader from './ContentHeader'
export default ContentHeader

113
src/components/Dialog.js

@ -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)
}
}
})
}

44
src/components/Getpoint/Getpoint.vue

@ -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>

2
src/components/Getpoint/index.js

@ -1,2 +0,0 @@
import Getpoint from './Getpoint'
export default Getpoint

46
src/components/GlobalFooter/index.vue

@ -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>

74
src/components/GlobalHeader/AvatarDropdown.vue

@ -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>

55
src/components/GlobalHeader/RightContent.vue

@ -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>

63
src/components/InputNumberGroup/InputNumberGroup.vue

@ -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>

2
src/components/InputNumberGroup/index.js

@ -1,2 +0,0 @@
import InputNumberGroup from './InputNumberGroup'
export default InputNumberGroup

369
src/components/Modal/AreasModal/AreasModal.vue

@ -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>

2
src/components/Modal/AreasModal/index.js

@ -1,2 +0,0 @@
import AreasModal from './AreasModal'
export default AreasModal

162
src/components/Modal/FilesModal/AddGroupForm.vue

@ -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>

600
src/components/Modal/FilesModal/FilesModal.vue

@ -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>

136
src/components/Modal/FilesModal/MoveGroupForm.vue

@ -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>

2
src/components/Modal/FilesModal/index.js

@ -1,2 +0,0 @@
import FilesModal from './FilesModal'
export default FilesModal

279
src/components/Modal/GoodsModal/GoodsModal.vue

@ -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>

2
src/components/Modal/GoodsModal/index.js

@ -1,2 +0,0 @@
import GoodsModal from './GoodsModal'
export default GoodsModal

262
src/components/Modal/LinkModal/LinkModal.vue

@ -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] = ''
}
// valuekeyparam
item.value = formData.values[index]
_.set(link.param, item.key, item.value)
}
// linkurl
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>

2
src/components/Modal/LinkModal/index.js

@ -1,2 +0,0 @@
import LinkModal from './LinkModal'
export default LinkModal

6
src/components/Modal/index.js

@ -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 }

162
src/components/MultiTab/MultiTab.vue

@ -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>

2
src/components/MultiTab/events.js

@ -1,2 +0,0 @@
import Vue from 'vue'
export default new Vue()

40
src/components/MultiTab/index.js

@ -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

25
src/components/MultiTab/index.less

@ -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;
}

76
src/components/NProgress/nprogress.less

@ -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); }
}

106
src/components/PageLoading/index.jsx

@ -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
}

96
src/components/PlatformIcon/PlatformIcon.vue

@ -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: {
// (APPH5)
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>

2
src/components/PlatformIcon/index.js

@ -1,2 +0,0 @@
import PlatformIcon from './PlatformIcon'
export default PlatformIcon

161
src/components/SelectGoods/SelectGoods.vue

@ -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>

2
src/components/SelectGoods/index.js

@ -1,2 +0,0 @@
import SelectGoods from './SelectGoods'
export default SelectGoods

231
src/components/SelectImage/SelectImage.vue

@ -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: [],
// (selectedItemsdefaultList)
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>

2
src/components/SelectImage/index.js

@ -1,2 +0,0 @@
import SelectImage from './SelectImage'
export default SelectImage

58
src/components/SelectLang/index.jsx

@ -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

31
src/components/SelectLang/index.less

@ -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;
}
}
}

86
src/components/SelectRegion/SelectRegion.vue

@ -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>

2
src/components/SelectRegion/index.js

@ -1,2 +0,0 @@
import SelectRegion from './SelectRegion'
export default SelectRegion

237
src/components/SelectVideo/SelectVideo.vue

@ -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: [],
// (selectedItemsdefaultList)
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>

2
src/components/SelectVideo/index.js

@ -1,2 +0,0 @@
import SelectVideo from './SelectVideo'
export default SelectVideo

377
src/components/SettingDrawer/SettingDrawer.vue

@ -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>

36
src/components/SettingDrawer/SettingItem.vue

@ -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>

2
src/components/SettingDrawer/index.js

@ -1,2 +0,0 @@
import SettingDrawer from './SettingDrawer'
export default SettingDrawer

48
src/components/SettingDrawer/settingConfig.js

@ -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 }

24
src/components/SettingDrawer/themeColor.js

@ -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)
}
}

105
src/components/Table/GoodsItem/GoodsItem.vue

@ -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>

2
src/components/Table/GoodsItem/index.js

@ -1,2 +0,0 @@
import GoodsItem from './GoodsItem'
export default GoodsItem

341
src/components/Table/README.md

@ -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

323
src/components/Table/STable.js

@ -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>
)
}
}

72
src/components/Table/UserItem/UserItem.vue

@ -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>

2
src/components/Table/UserItem/index.js

@ -1,2 +0,0 @@
import UserItem from './UserItem'
export default UserItem

5
src/components/Table/index.js

@ -1,5 +0,0 @@
import STable from './STable'
import UserItem from './UserItem'
import GoodsItem from './GoodsItem'
export { STable, UserItem, GoodsItem }

198
src/components/Ueditor/Ueditor.vue

@ -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>

44
src/components/Ueditor/config.js

@ -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: '插入图片'
// }
}

2
src/components/Ueditor/index.js

@ -1,2 +0,0 @@
import Ueditor from './Ueditor'
export default Ueditor

46
src/components/_util/util.js

@ -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
}, '')
}

27
src/components/index.js

@ -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
}

6
src/components/index.less

@ -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…
Cancel
Save