refactor(seller): 商家端系统性优化与 Vue3 迁移遗留问题修复
- 清理:删除 4 个未使用依赖、约 2700 行死代码(shop/ossManage 等)、tree-table 死组件、37 处未使用 import - ESLint 9 flat config 升级(.eslintrc.js -> eslint.config.js),修复迁移遗留的 34 处真实 bug - axios 重构:拦截器不再吞错、refreshToken 单例共享 Promise、超时收敛、form 序列化抽公共函数、vue-router 4 失效 API 修正 - 安全:4 处 v-html 增加 xss 过滤;logout 改为精确删除 localStorage 键 - API 重复定义收敛(delSpec/insertOrUpdateSpec、getDictData、ws) - 首屏瘦身:tinymce 懒加载(约省 7.4MB)、core-js 按需 polyfill、vendor chunk 合并、dplayer/@antv/g2 动态 import、3 张大图压缩 - 页面修复:发布商品表单布局(iview 遗留 margin 移除)、beforeVideoUpload 补定义、el-tag type 空值、lili-dialog emits 声明、消息中心布局塌陷修复与样式重构、department-choose 空值、affix-time 不再发多余店铺请求、goods/distributionGoods 去掉品牌筛选
@@ -1,4 +0,0 @@
|
||||
build/*.js
|
||||
src/assets
|
||||
public
|
||||
dist
|
||||
@@ -1,198 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
},
|
||||
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||
|
||||
// add your custom rules here
|
||||
//it is base on https://github.com/vuejs/eslint-config-vue
|
||||
rules: {
|
||||
"vue/max-attributes-per-line": [2, {
|
||||
"singleline": 10,
|
||||
"multiline": {
|
||||
"max": 1,
|
||||
"allowFirstLine": false
|
||||
}
|
||||
}],
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/multiline-html-element-content-newline":"off",
|
||||
"vue/name-property-casing": ["error", "PascalCase"],
|
||||
"vue/no-v-html": "off",
|
||||
'accessor-pairs': 2,
|
||||
'arrow-spacing': [2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'block-spacing': [2, 'always'],
|
||||
'brace-style': [2, '1tbs', {
|
||||
'allowSingleLine': true
|
||||
}],
|
||||
'camelcase': [0, {
|
||||
'properties': 'always'
|
||||
}],
|
||||
'comma-dangle': [2, 'never'],
|
||||
'comma-spacing': [2, {
|
||||
'before': false,
|
||||
'after': true
|
||||
}],
|
||||
'comma-style': [2, 'last'],
|
||||
'constructor-super': 2,
|
||||
'curly': [2, 'multi-line'],
|
||||
'dot-location': [2, 'property'],
|
||||
'eol-last': 2,
|
||||
'eqeqeq': ["error", "always", {"null": "ignore"}],
|
||||
'generator-star-spacing': [2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'handle-callback-err': [2, '^(err|error)$'],
|
||||
'indent': [2, 2, {
|
||||
'SwitchCase': 1
|
||||
}],
|
||||
'jsx-quotes': [2, 'prefer-single'],
|
||||
'key-spacing': [2, {
|
||||
'beforeColon': false,
|
||||
'afterColon': true
|
||||
}],
|
||||
'keyword-spacing': [2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'new-cap': [2, {
|
||||
'newIsCap': true,
|
||||
'capIsNew': false
|
||||
}],
|
||||
'new-parens': 2,
|
||||
'no-array-constructor': 2,
|
||||
'no-caller': 2,
|
||||
'no-console': 'off',
|
||||
'no-class-assign': 2,
|
||||
'no-cond-assign': 2,
|
||||
'no-const-assign': 2,
|
||||
'no-control-regex': 0,
|
||||
'no-delete-var': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-class-members': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-empty-character-class': 2,
|
||||
'no-empty-pattern': 2,
|
||||
'no-eval': 2,
|
||||
'no-ex-assign': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-parens': [2, 'functions'],
|
||||
'no-fallthrough': 2,
|
||||
'no-floating-decimal': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-implied-eval': 2,
|
||||
'no-inner-declarations': [2, 'functions'],
|
||||
'no-invalid-regexp': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-iterator': 2,
|
||||
'no-label-var': 2,
|
||||
'no-labels': [2, {
|
||||
'allowLoop': false,
|
||||
'allowSwitch': false
|
||||
}],
|
||||
'no-lone-blocks': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multi-spaces': 2,
|
||||
'no-multi-str': 2,
|
||||
'no-multiple-empty-lines': [2, {
|
||||
'max': 1
|
||||
}],
|
||||
'no-native-reassign': 2,
|
||||
'no-negated-in-lhs': 2,
|
||||
'no-new-object': 2,
|
||||
'no-new-require': 2,
|
||||
'no-new-symbol': 2,
|
||||
'no-new-wrappers': 2,
|
||||
'no-obj-calls': 2,
|
||||
'no-octal': 2,
|
||||
'no-octal-escape': 2,
|
||||
'no-path-concat': 2,
|
||||
'no-proto': 2,
|
||||
'no-redeclare': 2,
|
||||
'no-regex-spaces': 2,
|
||||
'no-return-assign': [2, 'except-parens'],
|
||||
'no-self-assign': 2,
|
||||
'no-self-compare': 2,
|
||||
'no-sequences': 2,
|
||||
'no-shadow-restricted-names': 2,
|
||||
'no-spaced-func': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-this-before-super': 2,
|
||||
'no-throw-literal': 2,
|
||||
'no-trailing-spaces': 2,
|
||||
'no-undef': 2,
|
||||
'no-undef-init': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unmodified-loop-condition': 2,
|
||||
'no-unneeded-ternary': [2, {
|
||||
'defaultAssignment': false
|
||||
}],
|
||||
'no-unreachable': 2,
|
||||
'no-unsafe-finally': 2,
|
||||
'no-unused-vars': [2, {
|
||||
'vars': 'all',
|
||||
'args': 'none'
|
||||
}],
|
||||
'no-useless-call': 2,
|
||||
'no-useless-computed-key': 2,
|
||||
'no-useless-constructor': 2,
|
||||
'no-useless-escape': 0,
|
||||
'no-whitespace-before-property': 2,
|
||||
'no-with': 2,
|
||||
'one-var': [2, {
|
||||
'initialized': 'never'
|
||||
}],
|
||||
'operator-linebreak': [2, 'after', {
|
||||
'overrides': {
|
||||
'?': 'before',
|
||||
':': 'before'
|
||||
}
|
||||
}],
|
||||
'padded-blocks': [2, 'never'],
|
||||
'quotes': [2, 'single', {
|
||||
'avoidEscape': true,
|
||||
'allowTemplateLiterals': true
|
||||
}],
|
||||
'semi': [2, 'never'],
|
||||
'semi-spacing': [2, {
|
||||
'before': false,
|
||||
'after': true
|
||||
}],
|
||||
'space-before-blocks': [2, 'always'],
|
||||
'space-before-function-paren': [2, 'never'],
|
||||
'space-in-parens': [2, 'never'],
|
||||
'space-infix-ops': 2,
|
||||
'space-unary-ops': [2, {
|
||||
'words': true,
|
||||
'nonwords': false
|
||||
}],
|
||||
'spaced-comment': [2, 'always', {
|
||||
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||
}],
|
||||
'template-curly-spacing': [2, 'never'],
|
||||
'use-isnan': 2,
|
||||
'valid-typeof': 2,
|
||||
'wrap-iife': [2, 'any'],
|
||||
'yield-star-spacing': [2, 'both'],
|
||||
'yoda': [2, 'never'],
|
||||
'prefer-const': 2,
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||
'object-curly-spacing': [2, 'always', {
|
||||
objectsInObjects: false
|
||||
}],
|
||||
'array-bracket-spacing': [2, 'never']
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
presets: [
|
||||
// 按实际用到的语法特性按需注入 polyfill,替代 main.js 的全量 core-js/stable
|
||||
["@vue/cli-plugin-babel/preset", { useBuiltIns: "usage", corejs: 3 }],
|
||||
],
|
||||
};
|
||||
|
||||
65
seller/eslint.config.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const globals = require('globals')
|
||||
const js = require('@eslint/js')
|
||||
const pluginVue = require('eslint-plugin-vue')
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'node_modules/**',
|
||||
'public/**',
|
||||
// tree-table 是 JSX 写的旧组件,全项目无引用,待删除,暂不纳入检查
|
||||
'src/views/my-components/tree-table/**',
|
||||
],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
{
|
||||
files: ['src/**/*.{js,vue}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
// webpack 构建环境注入
|
||||
require: 'readonly',
|
||||
module: 'writable',
|
||||
process: 'readonly',
|
||||
// public/config.js 通过 <script> 注入,见 public/index.html
|
||||
BASE: 'readonly',
|
||||
// 高德地图 loader 动态注入
|
||||
AMapUI: 'readonly',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['warn', { args: 'none', caughtErrors: 'none', ignoreRestSiblings: true }],
|
||||
'no-empty': ['warn', { allowEmptyCatch: true }],
|
||||
'no-constant-condition': ['error', { checkLoops: false }],
|
||||
'no-console': 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
|
||||
'no-undef': 'error',
|
||||
// 存量代码大量使用 ==,先告警,逐步治理
|
||||
eqeqeq: ['warn', 'always', { null: 'ignore' }],
|
||||
// 项目依赖 v-html 渲染服务端内容,由 xss 过滤兜底,不在此处拦截
|
||||
'vue/no-v-html': 'off',
|
||||
// 页面组件多为单词命名且被路由引用,改名成本高
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
// 存量 49 处直接改 prop,先告警,逐步治理
|
||||
'vue/no-mutating-props': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
// 根目录构建配置 + scripts 工具:CommonJS + Node
|
||||
files: ['*.config.js', 'scripts/**/*.js'],
|
||||
languageOptions: {
|
||||
sourceType: 'commonjs',
|
||||
globals: { ...globals.node },
|
||||
},
|
||||
rules: {
|
||||
// scripts 下是一次性迁移脚本,已执行完毕,不做变量层面的约束
|
||||
'no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"dev": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "0.0.7",
|
||||
@@ -22,15 +24,11 @@
|
||||
"element-plus": "^2.14.1",
|
||||
"js-cookie": "^2.2.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"price-color": "^1.0.2",
|
||||
"sass": "^1.63.6",
|
||||
"sass-loader": "^10.4.1",
|
||||
"sockjs-client": "^1.4.0",
|
||||
"swiper": "^6.3.5",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^3.5.35",
|
||||
"vue-awesome-swiper": "^4.1.1",
|
||||
"vue-json-excel": "^0.3.0",
|
||||
"vue-lazyload": "^3.0.0",
|
||||
"vue-qr": "^5.0.3",
|
||||
"vue-router": "^4.6.4",
|
||||
@@ -39,6 +37,7 @@
|
||||
"xss": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@vue/cli-plugin-babel": "^5.0.9",
|
||||
"@vue/cli-plugin-router": "^5.0.9",
|
||||
"@vue/cli-plugin-vuex": "^5.0.9",
|
||||
@@ -46,11 +45,15 @@
|
||||
"@vue/compiler-sfc": "^3.5.35",
|
||||
"compression-webpack-plugin": "^10.0.0",
|
||||
"css-loader": "^6.10.0",
|
||||
"eslint": "^9",
|
||||
"eslint-plugin-vue": "^10",
|
||||
"globals": "^17.13.0",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.4",
|
||||
"style-loader": "^3.3.4",
|
||||
"style-resources-loader": "^1.5.0",
|
||||
"vue-cli-plugin-style-resources-loader": "^0.1.5",
|
||||
"vue-eslint-parser": "^10",
|
||||
"webpack": "^5.95.0"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
<script src="<%=js%>"></script>
|
||||
<% } %>
|
||||
<script src="<%= BASE_URL %>config.js"></script>
|
||||
<script src="<%= BASE_URL %>tinymce/tinymce.min.js"></script>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry doesn't work properly without JavaScript
|
||||
|
||||
@@ -20,11 +20,13 @@ export default {
|
||||
this.$store.commit("setOpenedList");
|
||||
this.$store.commit("initCachepage");
|
||||
if (!localStorage.getItem("category")) {
|
||||
getCategoryTree().then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
}
|
||||
});
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res.success && Array.isArray(res.result)) {
|
||||
localStorage.setItem("category", JSON.stringify(res.result));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import {getRequest, postRequest, postRequestWithNoForm, putRequest, deleteRequest, importRequest, getRequestWithNoToken} from '@/libs/axios';
|
||||
import {getRequest, postRequest, postRequestWithNoForm, putRequest, deleteRequest} from '@/libs/axios';
|
||||
|
||||
|
||||
//获取分销商品列表
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
postRequest,
|
||||
putRequest,
|
||||
deleteRequest,
|
||||
importRequest,
|
||||
getRequestWithNoToken,
|
||||
postRequestWithNoForm
|
||||
} from "@/libs/axios";
|
||||
// 获取数据字典
|
||||
|
||||
@@ -281,11 +281,6 @@ export const delGoodsUnit = (ids) => {
|
||||
return deleteRequest(`/goods/goodsUnit/delete/${ids}`);
|
||||
};
|
||||
|
||||
//
|
||||
export const handleRefreshToken = (token) => {
|
||||
return getRequestWithNoToken(`/passport/login/refresh/${token}`);
|
||||
};
|
||||
|
||||
// 获取一级部门
|
||||
export const initDepartment = (params) => {
|
||||
return getRequest("/department", params);
|
||||
|
||||
@@ -4,9 +4,7 @@ import {
|
||||
postRequest,
|
||||
putRequest,
|
||||
deleteRequest,
|
||||
getRequestHasHeader,
|
||||
importRequest,
|
||||
getRequestWithNoToken
|
||||
getRequestHasHeader
|
||||
} from "@/libs/axios";
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import { getRequest, putRequest, postRequest } from "@/libs/axios";
|
||||
import { getRequest, putRequest, postRequest, deleteRequest } from "@/libs/axios";
|
||||
|
||||
// 分页获取会员评价
|
||||
export const getMemberReview = (params) => {
|
||||
@@ -26,14 +26,10 @@ export const getMember = (params) => {
|
||||
return getRequest("/member/getByPage", params);
|
||||
};
|
||||
|
||||
// 添加或修改
|
||||
export const insertOrUpdateSpec = (params) => {
|
||||
// 添加或修改会员通知发送设置
|
||||
export const insertOrUpdateMemberNotice = (params) => {
|
||||
return postRequest("/memberNoticeSenter/insertOrUpdate", params);
|
||||
};
|
||||
//删除gUI个
|
||||
export const delSpec = (id, params) => {
|
||||
return deleteRequest(`/goods/spec/del/${id}`, params);
|
||||
};
|
||||
|
||||
// 获取会员列表
|
||||
export const getMemberListData = (params) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import { getRequest, postRequest, putRequest, deleteRequest, importRequest, uploadFileRequest } from '@/libs/axios';
|
||||
import { getRequest, postRequest, putRequest, deleteRequest } from '@/libs/axios';
|
||||
|
||||
|
||||
// 认证
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import { getRequest, postRequest, putRequest, deleteRequest, postRequestWithNoForm } from "@/libs/axios";
|
||||
|
||||
import { baseUrl } from "@/libs/axios.js";
|
||||
|
||||
// 下载待发货的订单列表
|
||||
export const verificationCode = verificationCode => {
|
||||
return getRequest(`/order/order/getOrderByVerificationCode/${verificationCode}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import {getRequest, postRequest, putRequest, deleteRequest, importRequest, getRequestWithNoToken} from '@/libs/axios';
|
||||
import {getRequest, postRequest, putRequest, deleteRequest} from '@/libs/axios';
|
||||
|
||||
//保存导航栏
|
||||
export const save = (params) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import { getRequest, postRequest, putRequest, deleteRequest, importRequest, getRequestWithNoToken } from '@/libs/axios';
|
||||
import { getRequest, postRequest } from '@/libs/axios';
|
||||
|
||||
// 获取kuaidi配置
|
||||
export const getParams = (params) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import { getRequest, postRequest, postRequestWithNoForm, putRequest, deleteRequest, importRequest, uploadFileRequest } from '@/libs/axios';
|
||||
import { getRequest, postRequest, postRequestWithNoForm, putRequest, deleteRequest } from '@/libs/axios';
|
||||
|
||||
|
||||
// 获取店铺直播间列表
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import {getRequest, postRequest, putRequest, deleteRequest, importRequest, getRequestWithNoToken} from '@/libs/axios';
|
||||
|
||||
|
||||
// 获取数据字典
|
||||
export const getDictData = "/dictData/getByType/"
|
||||
// Websocket
|
||||
export const ws = "/ws"
|
||||
|
||||
import {getRequest, postRequest, putRequest, deleteRequest} from '@/libs/axios';
|
||||
|
||||
//查询店铺列表
|
||||
export const getShopListData = (params) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import {getRequest, postRequest, putRequest, deleteRequest, importRequest, getRequestWithNoToken} from '@/libs/axios';
|
||||
import {getRequest} from '@/libs/axios';
|
||||
|
||||
//商品统计
|
||||
export const getGoodsStatisticsData = (params) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 63 KiB |
BIN
seller/src/assets/login-bg.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -3,7 +3,6 @@ import { getStore, setStore } from "./storage";
|
||||
import { router } from "../router/index";
|
||||
import { Message } from "@/utils/message";
|
||||
import Cookies from "js-cookie";
|
||||
import { handleRefreshToken } from "@/api/index";
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
|
||||
// 统一请求路径前缀
|
||||
@@ -14,20 +13,23 @@ export const sellerUrl =
|
||||
export const baseUrl = sellerUrl + BASE.PREFIX;
|
||||
// 文件上传接口
|
||||
export const uploadFile = sellerUrl + "/store/common/upload/file";
|
||||
var isRefreshToken = 0;
|
||||
const refreshToken = getTokenDebounce();
|
||||
const service = axios.create({
|
||||
|
||||
// 实例级超时:商家端有 Excel 导入导出、文件上传等慢操作,给 60s
|
||||
timeout: 60000,
|
||||
baseURL: baseUrl
|
||||
});
|
||||
axios.defaults.timeout = 100000
|
||||
const recordCurrentPath = () => {
|
||||
return router.currentRoute.value.fullPath;
|
||||
};
|
||||
// 跳转登录页
|
||||
const redirectLogin = () => {
|
||||
router.push({path:'/login',query:{redirect: recordCurrentPath()}});
|
||||
}
|
||||
const redirectLogin = (message) => {
|
||||
if (router.currentRoute.value.name !== "login") {
|
||||
if (message) {
|
||||
Message.error(message);
|
||||
}
|
||||
router.push({ path: "/login", query: { redirect: recordCurrentPath() } });
|
||||
}
|
||||
};
|
||||
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
@@ -48,7 +50,7 @@ service.interceptors.request.use(
|
||||
},
|
||||
err => {
|
||||
Message.error("请求超时");
|
||||
return Promise.resolve(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -57,124 +59,107 @@ service.interceptors.response.use(
|
||||
response => {
|
||||
const data = response.data;
|
||||
// 根据返回的code值来做不同的处理(和后端约定)
|
||||
if (!data.success && data.message) {
|
||||
Message.error(data.message);
|
||||
}
|
||||
switch (data.code) {
|
||||
case 400:
|
||||
if (data.message !== null) {
|
||||
Message.error(data.message);
|
||||
} else {
|
||||
Message.error("系统异常");
|
||||
}
|
||||
break;
|
||||
case 401:
|
||||
// 未登录 清除已登录状态
|
||||
Cookies.set("userInfoSeller", "");
|
||||
setStore("accessToken", "");
|
||||
if (router.history.current.name != "login") {
|
||||
if (data.message !== null) {
|
||||
Message.error(data.message);
|
||||
} else {
|
||||
Message.error("未知错误,请重新登录");
|
||||
}
|
||||
redirectLogin();
|
||||
}
|
||||
break;
|
||||
redirectLogin(data.message || "未知错误,请重新登录");
|
||||
return data;
|
||||
case 400:
|
||||
case 500:
|
||||
// 系统异常
|
||||
if (data.message !== null) {
|
||||
Message.error(data.message);
|
||||
} else {
|
||||
Message.error("系统异常");
|
||||
}
|
||||
break;
|
||||
Message.error(data.message || "系统异常");
|
||||
return data;
|
||||
default:
|
||||
if (!data.success && data.message) {
|
||||
Message.error(data.message);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
async error => {
|
||||
// 返回状态码不为200时候的错误处理
|
||||
if (error.response) {
|
||||
if (error.response.status === 401) {
|
||||
const response = error.response;
|
||||
const config = error.config;
|
||||
if (response) {
|
||||
if (response.status === 401) {
|
||||
// 这种情况一般调到登录页
|
||||
} else if (error.response.status === 404) {
|
||||
// 避免刷新token报错
|
||||
} else if (error.response.status === 403) {
|
||||
isRefreshToken++;
|
||||
if (isRefreshToken === 1) {
|
||||
const getTokenRes = await refreshToken();
|
||||
if (getTokenRes === "success") {
|
||||
// 刷新token
|
||||
if (isRefreshToken === 1) {
|
||||
error.response.config.headers.accessToken = getStore(
|
||||
"accessToken"
|
||||
);
|
||||
return service(error.response.config);
|
||||
} else {
|
||||
router.go(0);
|
||||
}
|
||||
} else {
|
||||
Cookies.set("userInfoSeller", "");
|
||||
redirectLogin();
|
||||
}
|
||||
isRefreshToken = 0;
|
||||
redirectLogin("登录状态已过期,请重新登录");
|
||||
} else if (
|
||||
response.status === 403 &&
|
||||
config &&
|
||||
!config._isRefreshRequest &&
|
||||
!config._retryAfterRefresh
|
||||
) {
|
||||
// accessToken 过期:刷新 token 后重试原请求
|
||||
config._retryAfterRefresh = true;
|
||||
const result = await refreshToken();
|
||||
if (result === "success") {
|
||||
config.headers.accessToken = getStore("accessToken");
|
||||
return service(config);
|
||||
}
|
||||
Cookies.set("userInfoSeller", "");
|
||||
redirectLogin();
|
||||
} else if (response.status === 404) {
|
||||
// 刷新请求已用 _isRefreshRequest 标记隔离,这里的 404 都是真实业务请求
|
||||
Message.error(
|
||||
(response.data && response.data.message) || "请求的资源不存在"
|
||||
);
|
||||
} else {
|
||||
// 其他错误处理
|
||||
Message.error(error.response.data.message);
|
||||
Message.error(
|
||||
(response.data && response.data.message) || "系统异常,请稍后重试"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 断网/超时等无响应的错误
|
||||
Message.error(
|
||||
error.code === "ECONNABORTED"
|
||||
? "请求超时,请稍后重试"
|
||||
: "网络异常,请检查网络连接"
|
||||
);
|
||||
}
|
||||
|
||||
/* router.push("/login") */
|
||||
return Promise.resolve(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 防抖闭包来一波
|
||||
function getTokenDebounce() {
|
||||
let lock = false;
|
||||
let success = false;
|
||||
return function() {
|
||||
if (!lock) {
|
||||
lock = true;
|
||||
let oldRefreshToken = getStore("refreshToken");
|
||||
handleRefreshToken(oldRefreshToken)
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
let { accessToken, refreshToken } = res.result;
|
||||
setStore("accessToken", accessToken);
|
||||
setStore("refreshToken", refreshToken);
|
||||
|
||||
success = true;
|
||||
lock = false;
|
||||
} else {
|
||||
success = false;
|
||||
lock = false;
|
||||
// router.push('/login')
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
success = false;
|
||||
lock = false;
|
||||
});
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
// 一直看lock,直到请求失败或者成功
|
||||
const timer = setInterval(() => {
|
||||
if (!lock) {
|
||||
clearInterval(timer);
|
||||
if (success) {
|
||||
resolve("success");
|
||||
} else {
|
||||
resolve("fail");
|
||||
}
|
||||
// 刷新 token 单例 Promise:并发 403 时共享同一次刷新,避免重复请求与轮询
|
||||
let refreshTokenPromise = null;
|
||||
function refreshToken() {
|
||||
if (!refreshTokenPromise) {
|
||||
const oldRefreshToken = getStore("refreshToken");
|
||||
refreshTokenPromise = service({
|
||||
method: "get",
|
||||
url: `/passport/login/refresh/${oldRefreshToken}`,
|
||||
_isRefreshRequest: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
const { accessToken, refreshToken: newRefreshToken } = res.result;
|
||||
setStore("accessToken", accessToken);
|
||||
setStore("refreshToken", newRefreshToken);
|
||||
return "success";
|
||||
}
|
||||
}, 500); // 轮询时间间隔
|
||||
});
|
||||
};
|
||||
return "fail";
|
||||
})
|
||||
.catch(() => "fail")
|
||||
.finally(() => {
|
||||
refreshTokenPromise = null;
|
||||
});
|
||||
}
|
||||
return refreshTokenPromise;
|
||||
}
|
||||
|
||||
// 将对象序列化为 application/x-www-form-urlencoded 格式
|
||||
const formSerialize = (data) => {
|
||||
let ret = "";
|
||||
for (let it in data) {
|
||||
ret += encodeURIComponent(it) + "=" + encodeURIComponent(data[it]) + "&";
|
||||
}
|
||||
return ret.substring(0, ret.length - 1);
|
||||
};
|
||||
|
||||
export const getRequest = (url, params, resBlob) => {
|
||||
let accessToken = getStore("accessToken");
|
||||
let data = {
|
||||
@@ -200,22 +185,7 @@ export const getRequestHasHeader = (url, params, headers) => {
|
||||
method: "get",
|
||||
url: `${url}`,
|
||||
params: params,
|
||||
transformRequest: headers
|
||||
? undefined
|
||||
: [
|
||||
function(data) {
|
||||
let ret = "";
|
||||
for (let it in data) {
|
||||
ret +=
|
||||
encodeURIComponent(it) +
|
||||
"=" +
|
||||
encodeURIComponent(data[it]) +
|
||||
"&";
|
||||
}
|
||||
ret = ret.substring(0, ret.length - 1);
|
||||
return ret;
|
||||
}
|
||||
],
|
||||
transformRequest: headers ? undefined : [formSerialize],
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
accessToken: accessToken,
|
||||
@@ -232,22 +202,7 @@ export const postRequest = (url, params, headers) => {
|
||||
method: "post",
|
||||
url: `${url}`,
|
||||
data: params,
|
||||
transformRequest: headers
|
||||
? undefined
|
||||
: [
|
||||
function(data) {
|
||||
let ret = "";
|
||||
for (let it in data) {
|
||||
ret +=
|
||||
encodeURIComponent(it) +
|
||||
"=" +
|
||||
encodeURIComponent(data[it]) +
|
||||
"&";
|
||||
}
|
||||
ret = ret.substring(0, ret.length - 1);
|
||||
return ret;
|
||||
}
|
||||
],
|
||||
transformRequest: headers ? undefined : [formSerialize],
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
accessToken: accessToken,
|
||||
@@ -270,42 +225,13 @@ export const postRequestWithNoForm = (url, params) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const postRequestWithHeaders = (url, params) => {
|
||||
let accessToken = getStore("accessToken");
|
||||
return axios({
|
||||
method: "post",
|
||||
url: `${url}`,
|
||||
data: params,
|
||||
|
||||
headers: {
|
||||
accessToken: accessToken,
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const putRequest = (url, params, headers) => {
|
||||
let accessToken = getStore("accessToken");
|
||||
return service({
|
||||
method: "put",
|
||||
url: `${url}`,
|
||||
data: params,
|
||||
transformRequest: headers
|
||||
? undefined
|
||||
: [
|
||||
function(data) {
|
||||
let ret = "";
|
||||
for (let it in data) {
|
||||
ret +=
|
||||
encodeURIComponent(it) +
|
||||
"=" +
|
||||
encodeURIComponent(data[it]) +
|
||||
"&";
|
||||
}
|
||||
ret = ret.substring(0, ret.length - 1);
|
||||
return ret;
|
||||
}
|
||||
],
|
||||
transformRequest: headers ? undefined : [formSerialize],
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
accessToken: accessToken,
|
||||
|
||||
@@ -176,7 +176,7 @@ util.getRouterObjByName = function (routers, name) {
|
||||
if (!name || !routers || !routers.length) {
|
||||
return null;
|
||||
}
|
||||
let routerObj = null;
|
||||
let routerObj;
|
||||
for (let item of routers) {
|
||||
if (item.name == name) {
|
||||
return item;
|
||||
@@ -221,7 +221,7 @@ util.setCurrentPath = function (vm, name) {
|
||||
});
|
||||
}
|
||||
});
|
||||
let currentPathArr = [];
|
||||
let currentPathArr;
|
||||
if (name == "home_index") {
|
||||
currentPathArr = [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createApp } from "vue";
|
||||
import "core-js/stable";
|
||||
import "./styles/theme.less";
|
||||
import App from "./App.vue";
|
||||
import { router } from "./router/index";
|
||||
@@ -84,4 +83,13 @@ router.isReady().then(() => {
|
||||
app.mount("#app");
|
||||
});
|
||||
|
||||
// axios 拦截器已对所有请求失败统一弹出提示(Message.error),
|
||||
// 调用方未写 .catch 的 axios rejection 不再触发开发环境错误弹层;
|
||||
// 非 axios 的未处理 rejection(真代码 bug)仍会正常暴露
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
if (event.reason && event.reason.isAxiosError) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
export { app, util };
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { removeStore } from '@/libs/storage';
|
||||
|
||||
// 退出登录时需要清除的会话/店铺数据
|
||||
// 保留 uuid(设备标识)、setting(主题)、currNav、category(缓存)、saveLogin(记住登录状态)等无关键
|
||||
const LOGOUT_KEYS = [
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
'menuData',
|
||||
'seller_expiration_time',
|
||||
'sellersiteName',
|
||||
'sellerlogoImg',
|
||||
'sellerIconImg'
|
||||
];
|
||||
|
||||
const user = {
|
||||
state: {},
|
||||
mutations: {
|
||||
logout () {
|
||||
Cookies.remove('userInfoSeller');
|
||||
// 清空打开的页面等数据
|
||||
localStorage.clear();
|
||||
// 精确清除会话相关键,不再 localStorage.clear() 误删无关数据
|
||||
LOGOUT_KEYS.forEach(key => removeStore(key));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
import xss from "xss";
|
||||
|
||||
// 富文本 XSS 过滤:默认白名单基础上放行 style 属性(内容由 cssfilter 过滤)
|
||||
const richTextWhiteList = { ...xss.whiteList };
|
||||
Object.keys(richTextWhiteList).forEach((tag) => {
|
||||
richTextWhiteList[tag] = [...(richTextWhiteList[tag] || []), "style"];
|
||||
});
|
||||
const richTextFilter = new xss.FilterXSS({
|
||||
whiteList: richTextWhiteList,
|
||||
css: true
|
||||
});
|
||||
|
||||
// 渲染服务端 HTML 前过滤,配合 v-html 使用:v-html="$filters.xssFilter(html)"
|
||||
export function xssFilter(html) {
|
||||
return html ? richTextFilter.process(html) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 金钱单位置换 2999 --> 2,999.00
|
||||
* @param val
|
||||
@@ -154,6 +171,10 @@ export function secrecyMobile(mobile) {
|
||||
return mobile.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$3");
|
||||
}
|
||||
|
||||
function padLeftZero(str) {
|
||||
return ("00" + str).substr(str.length);
|
||||
}
|
||||
|
||||
export function formatDate(date, fmt) {
|
||||
if (/(y+)/.test(fmt)) {
|
||||
fmt = fmt.replace(
|
||||
@@ -181,8 +202,8 @@ export function formatDate(date, fmt) {
|
||||
}
|
||||
|
||||
// 楼层装修,选择链接处理跳转方式
|
||||
// 所有跳转的分类 依次为 商品、分类、店铺、活动、页面、其他
|
||||
export function formatLinkType (item) {
|
||||
const types = ['goods', 'category', 'shops', 'marketing', 'pages', 'other'] // 所有跳转的分类 依次为 商品、分类、店铺、活动、页面、其他
|
||||
let url = '';
|
||||
switch (item.___type) {
|
||||
case 'goods':
|
||||
|
||||
@@ -11,12 +11,11 @@ export {
|
||||
* @returns {*}
|
||||
*/
|
||||
export default function cloneObj(obj) {
|
||||
let str = null
|
||||
let newobj = obj.constructor === Array ? [] : {}
|
||||
if (typeof obj !== 'object') {
|
||||
return
|
||||
} else if (window && window.JSON) {
|
||||
str = JSON.stringify(obj) // 系列化对象
|
||||
const str = JSON.stringify(obj) // 系列化对象
|
||||
newobj = JSON.parse(str) // 还原
|
||||
} else {
|
||||
for (var i in obj) {
|
||||
|
||||
@@ -151,19 +151,6 @@ function binl2hex(binarray) {
|
||||
return str
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of little-endian words to a base64 encoded string.
|
||||
*/
|
||||
function binl2b64(binarray) {
|
||||
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
var str = ""
|
||||
for (var i = 0; i < binarray.length * 32; i += 6) {
|
||||
str += tab.charAt(((binarray[i >> 5] << (i % 32)) & 0x3F) |
|
||||
((binarray[i >> 5 + 1] >> (32 - i % 32)) & 0x3F))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an 8-bit character string to a sequence of 16-word blocks, stored
|
||||
* as an array, and append appropriate padding for MD4/5 calculation.
|
||||
@@ -173,28 +160,13 @@ function str2binl(str) {
|
||||
var nblk = ((str.length + 8) >> 6) + 1 // number of 16-word blocks
|
||||
var blks = new Array(nblk * 16)
|
||||
for (var i = 0; i < nblk * 16; i++) blks[i] = 0
|
||||
for (var i = 0; i < str.length; i++)
|
||||
for (i = 0; i < str.length; i++)
|
||||
blks[i >> 2] |= (str.charCodeAt(i) & 0xFF) << ((i % 4) * 8)
|
||||
blks[i >> 2] |= 0x80 << ((i % 4) * 8)
|
||||
blks[nblk * 16 - 2] = str.length * 8
|
||||
return blks
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a wide-character string to a sequence of 16-word blocks, stored as
|
||||
* an array, and append appropriate padding for MD4/5 calculation.
|
||||
*/
|
||||
function strw2binl(str) {
|
||||
var nblk = ((str.length + 4) >> 5) + 1 // number of 16-word blocks
|
||||
var blks = new Array(nblk * 16)
|
||||
for (var i = 0; i < nblk * 16; i++) blks[i] = 0
|
||||
for (var i = 0; i < str.length; i++)
|
||||
blks[i >> 1] |= str.charCodeAt(i) << ((i % 2) * 16)
|
||||
blks[i >> 1] |= 0x80 << ((i % 2) * 16)
|
||||
blks[nblk * 16 - 2] = str.length * 16
|
||||
return blks
|
||||
}
|
||||
|
||||
/*
|
||||
* External interface
|
||||
*/
|
||||
@@ -202,22 +174,6 @@ function hexMD5(str) {
|
||||
return binl2hex(coreMD5(str2binl(str)))
|
||||
}
|
||||
|
||||
function hexMD5w(str) {
|
||||
return binl2hex(coreMD5(strw2binl(str)))
|
||||
}
|
||||
|
||||
function b64MD5(str) {
|
||||
return binl2b64(coreMD5(str2binl(str)))
|
||||
}
|
||||
|
||||
function b64MD5w(str) {
|
||||
return binl2b64(coreMD5(strw2binl(str)))
|
||||
}
|
||||
/* Backward compatibility */
|
||||
function calcMD5(str) {
|
||||
return binl2hex(coreMD5(str2binl(str)))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
md5: hexMD5
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export const VARCHAR255 = {
|
||||
}
|
||||
|
||||
export const URL200 = {
|
||||
pattern: /[a-zA-z]+\:\/\/[^\s]{1,190}/,
|
||||
pattern: /[a-zA-z]+:\/\/[^\s]{1,190}/,
|
||||
message: '请输入长度不超过200的URL地址'
|
||||
}
|
||||
export const REQUIRED = {
|
||||
|
||||
@@ -227,25 +227,27 @@ export default {
|
||||
mobile: this.formFirst.mobile,
|
||||
verificationEnums: "FIND_USER",
|
||||
};
|
||||
sendSms(params).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("验证码发送成功");
|
||||
const that = this;
|
||||
this.interval = setInterval(() => {
|
||||
that.time--;
|
||||
if (that.time === 0) {
|
||||
that.time = 60;
|
||||
that.codeMsg = "重新发送";
|
||||
that.verifyStatus = false;
|
||||
clearInterval(that.interval);
|
||||
} else {
|
||||
that.codeMsg = that.time;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
this.$Message.warning(res.message);
|
||||
}
|
||||
});
|
||||
sendSms(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("验证码发送成功");
|
||||
const that = this;
|
||||
this.interval = setInterval(() => {
|
||||
that.time--;
|
||||
if (that.time === 0) {
|
||||
that.time = 60;
|
||||
that.codeMsg = "重新发送";
|
||||
that.verifyStatus = false;
|
||||
clearInterval(that.interval);
|
||||
} else {
|
||||
that.codeMsg = that.time;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
this.$Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
verifyChange(con) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="field-preview" :class="{ 'is-inline': isInlineOptions }">
|
||||
<template v-if="field.field_type === 'RICH_TEXT'">
|
||||
<div class="rich-preview" v-html="field.rich_text_content || '富文本说明'" />
|
||||
<div class="rich-preview" v-html="$filters.xssFilter(field.rich_text_content) || '富文本说明'" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="field-label">
|
||||
|
||||
@@ -17,22 +17,6 @@
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品品牌" prop="brandId">
|
||||
<el-select
|
||||
v-model="searchForm.brandId"
|
||||
placeholder="请选择商品品牌"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in brandList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="店铺分类" prop="storeCategoryPath">
|
||||
<el-cascader
|
||||
v-model="searchForm.storeCategoryPath"
|
||||
@@ -245,7 +229,7 @@ import {
|
||||
distributionGoodsBatchCheck,
|
||||
updateDistributionGoodsCommission,
|
||||
} from "@/api/distribution";
|
||||
import { getBrandListData, getShopGoodsLabelListSeller } from "@/api/goods";
|
||||
import { getShopGoodsLabelListSeller } from "@/api/goods";
|
||||
import liliDialog from "@/views/lili-dialog";
|
||||
|
||||
export default {
|
||||
@@ -266,12 +250,10 @@ export default {
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
goodsName: "",
|
||||
brandId: "",
|
||||
storeCategoryPath: "",
|
||||
goodsType: "",
|
||||
authStatus: "",
|
||||
},
|
||||
brandList: [],
|
||||
shopCategoryList: [],
|
||||
shopCategoryProps: {
|
||||
value: "value",
|
||||
@@ -298,14 +280,13 @@ export default {
|
||||
this.getDataList();
|
||||
},
|
||||
loadFilterOptions() {
|
||||
getBrandListData().then((res) => {
|
||||
this.brandList = Array.isArray(res) ? res : res?.result || [];
|
||||
});
|
||||
getShopGoodsLabelListSeller().then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
});
|
||||
getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
formatShopCategory(list) {
|
||||
return (list || []).map((item) => ({
|
||||
|
||||
@@ -247,13 +247,22 @@ div.base-info-item {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
/* 带刷新按钮的表单项:select(260px) + 按钮一行放下 */
|
||||
.el-form-item.goods-field-with-refresh {
|
||||
width: auto;
|
||||
min-width: 430px;
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-name-width {
|
||||
width: 50%;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
margin-left: 120px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -291,7 +300,6 @@ div.base-info-item {
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
margin-left: 120px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,21 +60,6 @@
|
||||
placeholder="请选择平台商品分类"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品品牌" prop="brandId">
|
||||
<el-select
|
||||
v-model="searchForm.brandId"
|
||||
placeholder="请选择商品品牌"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in brandList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品类型" prop="goodsType">
|
||||
<el-select
|
||||
v-model="searchForm.goodsType"
|
||||
@@ -287,7 +272,6 @@
|
||||
|
||||
<script>
|
||||
import {
|
||||
getBrandListData,
|
||||
getCategoryTree,
|
||||
getGoodsListData,
|
||||
getGoodsNumerData,
|
||||
@@ -314,14 +298,12 @@ export default {
|
||||
categoryPath: "",
|
||||
skuName: "",
|
||||
skuSn: "",
|
||||
brandId: "",
|
||||
stockStatus: "",
|
||||
stockMin: "",
|
||||
stockMax: "",
|
||||
priceMin: "",
|
||||
priceMax: "",
|
||||
},
|
||||
brandList: [],
|
||||
platformCategoryList: [],
|
||||
shopCategoryList: [],
|
||||
platformCategoryProps: {
|
||||
@@ -439,19 +421,20 @@ export default {
|
||||
this.getNumberData();
|
||||
},
|
||||
loadFilterOptions() {
|
||||
getBrandListData().then((res) => {
|
||||
this.brandList = Array.isArray(res) ? res : res?.result || [];
|
||||
});
|
||||
getShopGoodsLabelListSeller().then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
});
|
||||
getCategoryTree().then((res) => {
|
||||
if (res?.success) {
|
||||
this.platformCategoryList = res.result || [];
|
||||
}
|
||||
});
|
||||
getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.platformCategoryList = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
onStockStatusChange(value) {
|
||||
if (value !== "IN_STOCK") {
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<el-form-item label="商品卖点" prop="sellingPoint">
|
||||
<el-input v-model="baseInfoForm.sellingPoint" class="goods-field" clearable placeholder="商品卖点" type="text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品品牌" prop="brandId">
|
||||
<el-form-item class="goods-field-with-refresh" label="商品品牌" prop="brandId">
|
||||
<el-select
|
||||
v-model="baseInfoForm.brandId"
|
||||
class="goods-field"
|
||||
@@ -61,7 +61,7 @@
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item class="form-item-view-el" label="计量单位" prop="goodsUnit">
|
||||
<el-form-item class="form-item-view-el goods-field-with-refresh" label="计量单位" prop="goodsUnit">
|
||||
<el-select v-model="baseInfoForm.goodsUnit" class="goods-field">
|
||||
<el-option v-for="(item, index) in goodsUnitList" :key="index" :label="item" :value="item" />
|
||||
</el-select>
|
||||
@@ -587,7 +587,7 @@
|
||||
<el-radio-button :value="0">下架</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="needsLogistics" class="form-item-view-el" label="运费模板" prop="templateId">
|
||||
<el-form-item v-if="needsLogistics" class="form-item-view-el goods-field-with-refresh" label="运费模板" prop="templateId">
|
||||
<el-select v-model="baseInfoForm.templateId" class="goods-field">
|
||||
<el-option v-for="item in logisticsTemplate" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
@@ -672,7 +672,6 @@ import tinymec from "@/views/lili-components/editor/index.vue";
|
||||
|
||||
import { uploadFile } from "@/libs/axios";
|
||||
import { regular } from "@/utils";
|
||||
import DPlayer from 'dplayer';
|
||||
import ossManage from "@/views/sys/oss-manage/ossManage";
|
||||
import AppointmentPublishPanel from "./components/AppointmentPublishPanel.vue";
|
||||
import {
|
||||
@@ -696,7 +695,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
firstData: {
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
@@ -1162,8 +1161,9 @@ export default {
|
||||
this.showSkuPicture = true;
|
||||
},
|
||||
// 初始化视频操作
|
||||
initVideo() {
|
||||
async initVideo() {
|
||||
if (this.baseInfoForm.goodsVideo) {
|
||||
const { default: DPlayer } = await import("dplayer");
|
||||
this.goodsVideo = new DPlayer({
|
||||
container: document.getElementById('dplayer'),
|
||||
video: {
|
||||
@@ -1338,8 +1338,6 @@ export default {
|
||||
file.url = file.response.result;
|
||||
if (images) {
|
||||
images.push(file.url);
|
||||
} else {
|
||||
images = [file.url];
|
||||
}
|
||||
this.previewPicture = file.url;
|
||||
}
|
||||
@@ -1448,6 +1446,20 @@ export default {
|
||||
desc: "文件 " + file.name + " 的格式不正确",
|
||||
});
|
||||
},
|
||||
beforeVideoUpload(file) {
|
||||
const allowTypes = ["avi", "wmv", "mpeg", "mp4", "mov"];
|
||||
const ext = (file.name.split(".").pop() || "").toLowerCase();
|
||||
if (!allowTypes.includes(ext)) {
|
||||
this.$Message.warning("视频格式仅支持 avi、wmv、mpeg、mp4、mov");
|
||||
return false;
|
||||
}
|
||||
const maxSize = 50 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
this.$Message.warning("视频大小不能超过 50MB");
|
||||
return false;
|
||||
}
|
||||
this.loadingVideo = true;
|
||||
},
|
||||
handleSuccessGoodsVideo(res, file) {
|
||||
if (file.response) {
|
||||
file.url = file.response.result;
|
||||
@@ -2097,7 +2109,6 @@ export default {
|
||||
this.$Message.error("规格值不能大于10个!");
|
||||
return;
|
||||
}
|
||||
let beforeLength = item.spec_values.length;
|
||||
let itemValue = {
|
||||
name: item.name,
|
||||
value: this.newSkuValues[$index],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="noticeFlage" :title="noticesDetail.title">
|
||||
<div v-if="noticesDetail" class="noticesDetail" v-html="noticesDetail.content"></div>
|
||||
<div v-if="noticesDetail" class="noticesDetail" v-html="$filters.xssFilter(noticesDetail.content)"></div>
|
||||
</el-dialog>
|
||||
<div class="box flex">
|
||||
<div class="box-left">
|
||||
@@ -32,11 +32,11 @@
|
||||
type="circle"
|
||||
:width="120"
|
||||
:stroke-width="5"
|
||||
:percentage="userData.serviceScore * 20"
|
||||
:percentage="scorePercent(userData.serviceScore)"
|
||||
color="#fecb89"
|
||||
>
|
||||
<template #default>
|
||||
<p class="bold">{{ userData.serviceScore }}分</p>
|
||||
<p class="bold">{{ userData.serviceScore || 0 }}分</p>
|
||||
</template>
|
||||
</el-progress>
|
||||
<h5>服务得分</h5>
|
||||
@@ -46,11 +46,11 @@
|
||||
type="circle"
|
||||
:width="120"
|
||||
:stroke-width="5"
|
||||
:percentage="userData.deliveryScore * 20"
|
||||
:percentage="scorePercent(userData.deliveryScore)"
|
||||
color="#a7c5eb"
|
||||
>
|
||||
<template #default>
|
||||
<p class="bold">{{ userData.deliveryScore }}分</p>
|
||||
<p class="bold">{{ userData.deliveryScore || 0 }}分</p>
|
||||
</template>
|
||||
</el-progress>
|
||||
<h5>交货得分</h5>
|
||||
@@ -60,11 +60,11 @@
|
||||
type="circle"
|
||||
:width="120"
|
||||
:stroke-width="5"
|
||||
:percentage="userData.descriptionScore * 20"
|
||||
:percentage="scorePercent(userData.descriptionScore)"
|
||||
color="#848ccf"
|
||||
>
|
||||
<template #default>
|
||||
<p class="bold">{{ userData.descriptionScore }}分</p>
|
||||
<p class="bold">{{ userData.descriptionScore || 0 }}分</p>
|
||||
</template>
|
||||
</el-progress>
|
||||
<h5>评价得分</h5>
|
||||
@@ -249,7 +249,7 @@ export default {
|
||||
topGoodsData: [],
|
||||
afterSaleNumData: {},
|
||||
procurementStatusCount: {},
|
||||
userData: "",
|
||||
userData: {},
|
||||
notices: "",
|
||||
noticesDetail: {
|
||||
title: "",
|
||||
@@ -301,6 +301,11 @@ export default {
|
||||
if (t < y) return "compare-down";
|
||||
return "compare-flat";
|
||||
},
|
||||
// ElProgress 的 percentage 必须是 0-100 的数字,评分缺失/异常时兜底为 0
|
||||
scorePercent(score) {
|
||||
const n = Number(score) * 20;
|
||||
return Number.isFinite(n) ? Math.min(100, Math.max(0, n)) : 0;
|
||||
},
|
||||
async init() {
|
||||
const userInfo = JSON.parse(Cookies.get("userInfoSeller"));
|
||||
this.userData = userInfo;
|
||||
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.getFiveYears();
|
||||
this.getShopList();
|
||||
if (!this.closeShop) this.getShopList();
|
||||
},
|
||||
methods: {
|
||||
getShopList() {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { initEditor } from "@/views/lili-components/editor/config";
|
||||
import { loadTinymce } from "@/views/lili-components/editor/load-tinymce";
|
||||
|
||||
import uploadImage from "@/views/lili-components/editor/upload-image.vue";
|
||||
export default {
|
||||
@@ -154,11 +155,18 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
// 编辑器脚本改为按需加载,加载完成后再初始化
|
||||
loadTinymce().then(() => {
|
||||
this.init();
|
||||
});
|
||||
},
|
||||
activated() {
|
||||
if (window.tinymce) {
|
||||
this.initTinymce();
|
||||
} else {
|
||||
loadTinymce().then(() => {
|
||||
this.initTinymce();
|
||||
});
|
||||
}
|
||||
},
|
||||
deactivated() {
|
||||
|
||||
21
seller/src/views/lili-components/editor/load-tinymce.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// tinymce 按需加载:只有用到富文本编辑器的页面才注入脚本,避免首屏全量加载
|
||||
let loadingPromise = null;
|
||||
|
||||
export function loadTinymce() {
|
||||
if (window.tinymce) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!loadingPromise) {
|
||||
loadingPromise = new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = window.location.origin + process.env.BASE_URL + "tinymce/tinymce.min.js";
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => {
|
||||
loadingPromise = null;
|
||||
reject(new Error("tinymce 加载失败"));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
return loadingPromise;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const plugins = [
|
||||
'advlist', 'anchor', 'autolink', 'autosave', 'charmap', 'code', 'codesample', 'directionality', 'emoticons', 'fullscreen', 'image', 'importcss', 'insertdatetime', 'link', 'lists', 'media', 'nonbreaking', 'pagebreak', 'preview', 'save', 'searchreplace', 'table', 'template', 'visualblocks', 'visualchars', 'wordcount'
|
||||
'advlist', 'anchor', 'autolink', 'autosave', 'charmap', 'code', 'codesample', 'directionality', 'emoticons', 'fullscreen', 'image', 'importcss', 'insertdatetime', 'link', 'lists', 'media', 'nonbreaking', 'pagebreak', 'preview', 'save', 'searchreplace', 'table', 'visualblocks', 'visualchars', 'wordcount'
|
||||
]
|
||||
export default plugins
|
||||
|
||||
@@ -213,6 +213,7 @@ const defaultFilterForm = () => ({
|
||||
});
|
||||
|
||||
export default {
|
||||
emits: ["selected"],
|
||||
props: {
|
||||
selectedWay: {
|
||||
type: Array,
|
||||
@@ -453,19 +454,25 @@ export default {
|
||||
},
|
||||
init() {
|
||||
this.getQueryGoodsList();
|
||||
API_Goods.getCategoryTree().then((res) => {
|
||||
if (res?.success) {
|
||||
this.platformCategoryList = res.result || [];
|
||||
}
|
||||
});
|
||||
API_Goods.getShopGoodsLabelListSeller().then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
});
|
||||
API_Goods.getBrandListData().then((res) => {
|
||||
this.brandList = Array.isArray(res) ? res : res?.result || [];
|
||||
});
|
||||
API_Goods.getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.platformCategoryList = res.result || [];
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
API_Goods.getShopGoodsLabelListSeller()
|
||||
.then((res) => {
|
||||
if (res?.success) {
|
||||
this.shopCategoryList = this.formatShopCategory(res.result || []);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
API_Goods.getBrandListData()
|
||||
.then((res) => {
|
||||
this.brandList = Array.isArray(res) ? res : res?.result || [];
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -59,6 +59,7 @@ export default {
|
||||
goodsDialog,
|
||||
linkDialog,
|
||||
},
|
||||
emits: ["closeFlag", "selectedGoodsData", "selectedLink"],
|
||||
data() {
|
||||
return {
|
||||
goodsVisible: false,
|
||||
|
||||
@@ -25,6 +25,7 @@ import wap from "./wap.js";
|
||||
import goodsDialog from "./goods-dialog";
|
||||
import templateWay from "./template/index";
|
||||
export default {
|
||||
emits: ["selectedLink"],
|
||||
components: {
|
||||
goodsDialog,
|
||||
},
|
||||
|
||||
@@ -8,16 +8,20 @@
|
||||
</div>
|
||||
<!-- 二级分类 -->
|
||||
<div class="list">
|
||||
<div class="list-item" :class="{active:secondIndex === secondI}" @click="handleClickSecondChild(second,secondI)" v-if="secondLevel.length != 0" v-for="(second,secondI) in secondLevel"
|
||||
:key="secondI">
|
||||
{{second.name}}
|
||||
</div>
|
||||
<template v-if="secondLevel.length != 0">
|
||||
<div class="list-item" :class="{active:secondIndex === secondI}" @click="handleClickSecondChild(second,secondI)" v-for="(second,secondI) in secondLevel"
|
||||
:key="secondI">
|
||||
{{second.name}}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!--三级分类 -->
|
||||
<div class="list">
|
||||
<div class="list-item" :class="{active:thirdIndex === thirdI}" @click="handleClickthirdChild(third,thirdI)" v-if="thirdLevel.length != 0" v-for="(third,thirdI) in thirdLevel" :key="thirdI">
|
||||
{{third.name}}
|
||||
</div>
|
||||
<template v-if="thirdLevel.length != 0">
|
||||
<div class="list-item" :class="{active:thirdIndex === thirdI}" @click="handleClickthirdChild(third,thirdI)" v-for="(third,thirdI) in thirdLevel" :key="thirdI">
|
||||
{{third.name}}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loginBg: require("@/assets/login-bg.png"),
|
||||
loginBg: require("@/assets/login-bg.jpg"),
|
||||
loginLogo: require("@/assets/logo-lilishop.png"),
|
||||
saveLogin: true,
|
||||
sendCodeLoading: false,
|
||||
@@ -203,28 +203,32 @@ export default {
|
||||
this.setStore("accessToken", accessToken);
|
||||
this.setStore("refreshToken", res.result.refreshToken);
|
||||
|
||||
userMsg().then((res) => {
|
||||
if (res.success) {
|
||||
this.setStore("saveLogin", this.saveLogin);
|
||||
if (this.saveLogin) {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result), {
|
||||
expires: 7,
|
||||
userMsg()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.setStore("saveLogin", this.saveLogin);
|
||||
if (this.saveLogin) {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result), {
|
||||
expires: 7,
|
||||
});
|
||||
} else {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result));
|
||||
}
|
||||
|
||||
util.initRouter(this);
|
||||
this.$store.commit("setAvatarPath", res.result.storeLogo);
|
||||
|
||||
const redirectRouter = this.$route.query.redirect;
|
||||
this.$router.push({
|
||||
path: redirectRouter || "/home",
|
||||
});
|
||||
} else {
|
||||
Cookies.set("userInfoSeller", JSON.stringify(res.result));
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
util.initRouter(this);
|
||||
this.$store.commit("setAvatarPath", res.result.storeLogo);
|
||||
|
||||
const redirectRouter = this.$route.query.redirect;
|
||||
this.$router.push({
|
||||
path: redirectRouter || "/home",
|
||||
});
|
||||
} else {
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
sendCode() {
|
||||
if (!this.isMobileValid) {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import defaultLogo from "@/assets/logo.png";
|
||||
import { getBaseSite } from "@/api/common.js";
|
||||
|
||||
export default {
|
||||
@@ -43,19 +42,21 @@ export default {
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
},
|
||||
getSite() {
|
||||
getBaseSite().then((res) => {
|
||||
if (res.success && res.result.settingValue) {
|
||||
const data = JSON.parse(res.result.settingValue);
|
||||
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
||||
localStorage.setItem("seller_expiration_time", expirationTime);
|
||||
localStorage.setItem("sellersiteName", data.siteName);
|
||||
localStorage.setItem("sellerlogoImg", data.storeSideLogo);
|
||||
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
||||
this.storeSideLogo = data.storeSideLogo;
|
||||
window.document.title = data.siteName;
|
||||
this.applyFavicon(data.storeSideIcon);
|
||||
}
|
||||
});
|
||||
getBaseSite()
|
||||
.then((res) => {
|
||||
if (res.success && res.result.settingValue) {
|
||||
const data = JSON.parse(res.result.settingValue);
|
||||
const expirationTime = new Date().setHours(new Date().getHours() + 1);
|
||||
localStorage.setItem("seller_expiration_time", expirationTime);
|
||||
localStorage.setItem("sellersiteName", data.siteName);
|
||||
localStorage.setItem("sellerlogoImg", data.storeSideLogo);
|
||||
localStorage.setItem("sellerIconImg", data.storeSideIcon);
|
||||
this.storeSideLogo = data.storeSideLogo;
|
||||
window.document.title = data.siteName;
|
||||
this.applyFavicon(data.storeSideIcon);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -1,164 +1,92 @@
|
||||
.message-main-con{
|
||||
min-height: calc(100vh - 120px);
|
||||
}
|
||||
.message {
|
||||
&-main-con {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
&-mainlist-con {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 10px;
|
||||
width: 300px;
|
||||
bottom: 0;
|
||||
padding: 10px 0;
|
||||
div {
|
||||
padding: 10px;
|
||||
margin: 0 20px;
|
||||
border-bottom: 1px dashed #d2d3d2;
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
.mes-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.message-count-badge-outer {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.message-count-badge {
|
||||
background: #d2d3d2;
|
||||
}
|
||||
.message-count-badge-red {
|
||||
background: #ed3f14;
|
||||
}
|
||||
.mes-type-btn-text {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-content-con {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
left: 300px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, .1);
|
||||
overflow: auto;
|
||||
.message-title-list-con {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.message-content-top-bar {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
background: white;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-bottom: 1px solid #dededb;
|
||||
.mes-back-btn-con {
|
||||
position: absolute;
|
||||
width: 70px;
|
||||
height: 30px;
|
||||
left: 0;
|
||||
top: 5px;
|
||||
}
|
||||
.mes-title {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 70px;
|
||||
bottom: 0;
|
||||
left: 70px;
|
||||
line-height: 40px;
|
||||
padding: 0 30px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.mes-time-con {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #b7b7b5;
|
||||
}
|
||||
.message-content-body {
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
.message-content {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.message-main-con {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
height: calc(100vh - 140px);
|
||||
}
|
||||
|
||||
.page-fix {
|
||||
position: fixed;
|
||||
right: 2vw;
|
||||
bottom: 3vh;
|
||||
.message-mainlist-con {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
padding: 12px 8px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
|
||||
.mes-type-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
height: 40px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.back-message-list-enter,
|
||||
.back-message-list-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.message-content-con {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 4px 16px 12px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
overflow: auto;
|
||||
|
||||
.back-message-list-enter-active,
|
||||
.back-message-list-leave-active {
|
||||
transition: all .5s;
|
||||
}
|
||||
.page-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.back-message-list-enter-to,
|
||||
.back-message-list-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
.message-content-top-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
margin: 0 -16px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
.view-message-enter,
|
||||
.view-message-leave-to {
|
||||
opacity: 0;
|
||||
.mes-title {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.view-message-enter-active,
|
||||
.view-message-leave-active {
|
||||
transition: all .5s;
|
||||
}
|
||||
.mes-time-con {
|
||||
padding: 16px 0;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.view-message-enter-to,
|
||||
.view-message-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mes-current-type-btn-enter,
|
||||
.mes-current-type-btn-leave-to {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.mes-current-type-btn-enter-active,
|
||||
.mes-current-type-btn-leave-active {
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.mes-current-type-btn-enter-to,
|
||||
.mes-current-type-btn-leave {
|
||||
opacity: 1;
|
||||
width: 12px;
|
||||
}
|
||||
.message-content-body {
|
||||
padding: 0 8px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
|
||||
.mes-time-icon {
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -1,123 +1,104 @@
|
||||
<template>
|
||||
<div class="message-main-con">
|
||||
<div class="message-mainlist-con">
|
||||
<div>
|
||||
<el-button size="large" text style="width: 100%" @click="setCurrentMesType('unread')">
|
||||
<div class="mes-wrap">
|
||||
<transition name="mes-current-type-btn">
|
||||
<el-icon v-show="currentMessageType == 'unread'"><Check /></el-icon>
|
||||
</transition>
|
||||
<span class="mes-type-btn-text">未读消息</span>
|
||||
<el-badge :value="unReadCount" class="message-count-badge-outer" />
|
||||
</div>
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button size="large" text style="width: 100%" @click="setCurrentMesType('read')">
|
||||
<div class="mes-wrap">
|
||||
<transition name="mes-current-type-btn">
|
||||
<el-icon v-show="currentMessageType == 'read'"><Check /></el-icon>
|
||||
</transition>
|
||||
<span class="mes-type-btn-text">已读消息</span>
|
||||
</div>
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button size="large" text style="width: 100%" @click="setCurrentMesType('recycleBin')">
|
||||
<div class="mes-wrap">
|
||||
<transition name="mes-current-type-btn">
|
||||
<el-icon v-show="currentMessageType == 'recycleBin'"><Check /></el-icon>
|
||||
</transition>
|
||||
<span class="mes-type-btn-text">回收站</span>
|
||||
</div>
|
||||
</el-button>
|
||||
<div
|
||||
v-for="item in mesTypeList"
|
||||
:key="item.type"
|
||||
class="mes-type-item"
|
||||
:class="{ active: currentMessageType === item.type }"
|
||||
@click="setCurrentMesType(item.type)"
|
||||
>
|
||||
<span class="mes-type-btn-text">{{ item.title }}</span>
|
||||
<el-badge
|
||||
v-if="item.type === 'unread' && unReadCount > 0"
|
||||
:value="unReadCount"
|
||||
class="message-count-badge-outer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-content-con">
|
||||
<transition name="view-message">
|
||||
<div v-if="showMesTitleList" class="message-title-list-con">
|
||||
<el-table
|
||||
ref="messageList"
|
||||
v-loading="loading"
|
||||
class="mt_10"
|
||||
:data="currentMesList"
|
||||
:empty-text="noDataText"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column label=" " min-width="300">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text mes-title-link" @click="openMessage(row)">{{ row.title }}</a>
|
||||
<div v-if="showMesTitleList" class="message-title-list-con">
|
||||
<el-table
|
||||
ref="messageList"
|
||||
v-loading="loading"
|
||||
:data="currentMesList"
|
||||
:empty-text="noDataText"
|
||||
:show-header="false"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column min-width="300">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text mes-title-link" @click="openMessage(row)">{{ row.title }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-icon class="mes-time-icon"><Clock /></el-icon>
|
||||
<span>{{ row.createTime }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<template v-if="currentMessageType == 'unread'">
|
||||
<a class="link-text" @click="markAsRead(row)">标为已读</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label=" " width="190" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-icon style="margin-right: 5px; vertical-align: middle"><Clock /></el-icon>
|
||||
<span>{{ row.createTime }}</span>
|
||||
<template v-else-if="currentMessageType == 'read'">
|
||||
<a class="link-text" @click="deleteMes(row)">删除</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label=" " width="210" align="center">
|
||||
<template #default="{ row }">
|
||||
<template v-if="currentMessageType == 'unread'">
|
||||
<a class="link-text" @click="markAsRead(row)">标为已读</a>
|
||||
</template>
|
||||
<template v-else-if="currentMessageType == 'read'">
|
||||
<a class="link-text" @click="deleteMes(row)">删除</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a class="link-text" @click="restoreMes(row)">还原</a>
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="deleteReal(row)">彻底删除</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a class="link-text" @click="restoreMes(row)">还原</a>
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="deleteReal(row)">彻底删除</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="page-fix mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="params.pageNumber"
|
||||
v-model:page-size="params.pageSize"
|
||||
:page-sizes="[5, 10]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="changePage"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="page-bar">
|
||||
<el-pagination
|
||||
v-model:current-page="params.pageNumber"
|
||||
v-model:page-size="params.pageSize"
|
||||
:page-sizes="[5, 10]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="changePage"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="back-message-list">
|
||||
<div v-if="!showMesTitleList" class="message-view-content-con">
|
||||
<div class="message-content-top-bar">
|
||||
<span class="mes-back-btn-con">
|
||||
<el-button type="primary" link @click="backMesTitleList">
|
||||
<el-icon><ArrowLeft /></el-icon> 返回
|
||||
</el-button>
|
||||
</span>
|
||||
<h3 class="mes-title">{{ mes.title }}</h3>
|
||||
</div>
|
||||
<p class="mes-time-con">
|
||||
<el-icon><Clock /></el-icon>
|
||||
{{ mes.time }}
|
||||
</p>
|
||||
<div class="message-content-body">
|
||||
<p class="message-content" v-html="mes.content"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="message-view-content-con">
|
||||
<div class="message-content-top-bar">
|
||||
<el-button type="primary" link @click="backMesTitleList">
|
||||
<el-icon><ArrowLeft /></el-icon>返回
|
||||
</el-button>
|
||||
<h3 class="mes-title">{{ mes.title }}</h3>
|
||||
</div>
|
||||
</transition>
|
||||
<p class="mes-time-con">
|
||||
<el-icon class="mes-time-icon"><Clock /></el-icon>{{ mes.time }}
|
||||
</p>
|
||||
<div class="message-content-body">
|
||||
<p class="message-content" v-html="$filters.xssFilter(mes.content)"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Check, Clock, ArrowLeft } from "@element-plus/icons-vue";
|
||||
import { Clock, ArrowLeft } from "@element-plus/icons-vue";
|
||||
import * as API_Index from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "message_index",
|
||||
components: { Check, Clock, ArrowLeft },
|
||||
components: { Clock, ArrowLeft },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
mesTypeList: [
|
||||
{ type: "unread", title: "未读消息" },
|
||||
{ type: "read", title: "已读消息" },
|
||||
{ type: "recycleBin", title: "回收站" },
|
||||
],
|
||||
params: {
|
||||
status: "UN_READY",
|
||||
pageNumber: 1,
|
||||
@@ -146,7 +127,7 @@ export default {
|
||||
this.refreshMessage();
|
||||
},
|
||||
refreshMessage() {
|
||||
let status = "UN_READY";
|
||||
let status;
|
||||
const type = this.currentMessageType;
|
||||
if (type == "unread") status = "UN_READY";
|
||||
else if (type == "read") status = "ALREADY_READY";
|
||||
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
initDepartmentData() {
|
||||
initDepartment().then((res) => {
|
||||
if (res.success) {
|
||||
const arr = res.result;
|
||||
const arr = Array.isArray(res.result) ? res.result : [];
|
||||
this.filterData(arr);
|
||||
this.department = arr;
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
// text
|
||||
@prefixCls: zk-checkbox;
|
||||
// color
|
||||
@border: #dddee1;
|
||||
@hoverBorder: #bcbcbc;
|
||||
@blue: #2d8cf0;
|
||||
|
||||
.@{prefixCls}-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.@{prefixCls} {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @hoverBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}__inner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 1px solid @border;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
transition: border-color .2s ease-in-out,background-color .2s ease-in-out;
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 4px;
|
||||
border: 2px solid #fff;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
transform: rotate(45deg) scale(0);
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--indeterminate {
|
||||
.@{prefixCls}__inner {
|
||||
background-color: @blue;
|
||||
border-color: @blue;
|
||||
&::after {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 1px;
|
||||
transform: scale(1);
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--checked {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @blue;
|
||||
background-color: @blue;
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 4px;
|
||||
border: 2px solid #ffffff;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
transform: rotate(45deg) scale(1);
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--disabled {
|
||||
cursor: not-allowed;
|
||||
.@{prefixCls}__inner {
|
||||
background-color: #f3f3f3;
|
||||
border-color: @border;
|
||||
&::after {
|
||||
animation-name: none;
|
||||
border-color: #ccc;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @border;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<template lang="html">
|
||||
<label :class="`${prefixCls}-wrapper`">
|
||||
<span :class="checkboxClass">
|
||||
<span
|
||||
:class="`${prefixCls}__inner`"
|
||||
@click="toggle"></span>
|
||||
</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'zk-checkbox',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
indeterminate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prefixCls: 'zk-checkbox',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
checkboxClass() {
|
||||
return [
|
||||
`${this.prefixCls}`,
|
||||
{
|
||||
[`${this.prefixCls}--disabled`]: this.disabled,
|
||||
[`${this.prefixCls}--checked`]: this.value,
|
||||
[`${this.prefixCls}--indeterminate`]: this.indeterminate,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
if (this.disabled) {
|
||||
return false;
|
||||
}
|
||||
const value = !this.value;
|
||||
this.$emit('input', value);
|
||||
return this.$emit('on-change', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped src="./Checkbox.less"></style>
|
||||
@@ -1,170 +0,0 @@
|
||||
@import "./font/iconfont";
|
||||
|
||||
// text
|
||||
@prefixCls: zk-table;
|
||||
// color
|
||||
@black: #515a6e;
|
||||
@white: #ffffff;
|
||||
@border: #e9eaec;
|
||||
@hoverRow: #ebf7ff;
|
||||
@backgroundRow: #f8f8f9;
|
||||
|
||||
.@{prefixCls} {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: @white;
|
||||
border: 1px solid @border;
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
color: @black;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-cell {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.@{prefixCls}__cell-inner {
|
||||
padding: 0px 18px;
|
||||
}
|
||||
|
||||
.@{prefixCls}-default {
|
||||
font-size: 14px;
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 48px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}-small {
|
||||
font-size: 12px;
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 40px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}-large {
|
||||
font-size: 16px;
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 43px !important;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 60px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-wrapper,
|
||||
.@{prefixCls}__footer-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-wrapper {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.@{prefixCls}__header,
|
||||
.@{prefixCls}__body,
|
||||
.@{prefixCls}__footer {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
background-color: @backgroundRow;
|
||||
border-bottom: 1px solid @border;
|
||||
}
|
||||
|
||||
.@{prefixCls}__footer-row {
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
background-color: @white;
|
||||
border-top: 1px solid @border;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-top: 1px solid @border;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-cell,
|
||||
.@{prefixCls}__body-cell,
|
||||
.@{prefixCls}__footer-cell {
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.@{prefixCls}--firstProp-header-inner {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.@{prefixCls}--empty-row {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.@{prefixCls}--empty-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.@{prefixCls}--center-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.@{prefixCls}--right-cell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.@{prefixCls}--stripe-row {
|
||||
background-color: @backgroundRow;
|
||||
}
|
||||
|
||||
.@{prefixCls}--row-hover {
|
||||
background-color: @hoverRow;
|
||||
}
|
||||
|
||||
.@{prefixCls}--border-cell {
|
||||
&:not(:last-of-type) {
|
||||
border-right: 1px solid @border;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--tree-icon {
|
||||
margin-right: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.@{prefixCls}--expand-inner {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: transform .2s ease-in-out;
|
||||
}
|
||||
|
||||
.@{prefixCls}--expanded-inner {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.@{prefixCls}--expand-content {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
v-if="columns.length > 0"
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
:class="[prefixCls, `${prefixCls}-${size}`, tableClass]">
|
||||
<div
|
||||
v-show="showHeader"
|
||||
ref="header-wrapper"
|
||||
:class="`${prefixCls}__header-wrapper`"
|
||||
@mousewheel="handleEvent('header', $event)">
|
||||
<table-header
|
||||
ref="table-header">
|
||||
</table-header>
|
||||
</div>
|
||||
<div
|
||||
ref="body-wrapper"
|
||||
:style="bodyWrapperStyle"
|
||||
:class="`${prefixCls}__body-wrapper`"
|
||||
@scroll="handleEvent('body', $event)">
|
||||
<table-body
|
||||
ref="table-body"
|
||||
:class="bodyClass">
|
||||
</table-body>
|
||||
</div>
|
||||
<div
|
||||
v-show="showSummary && data.length > 0"
|
||||
ref="footer-wrapper"
|
||||
:class="`${prefixCls}__footer-wrapper`"
|
||||
@mousewheel="handleEvent('footer', $event)">
|
||||
<table-footer
|
||||
ref="table-footer">
|
||||
</table-footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TableHeader from "./TableHeader";
|
||||
import TableBody from "./TableBody";
|
||||
import TableFooter from "./TableFooter";
|
||||
import { mixins, scrollBarWidth as getSbw } from "./utils";
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
// function getBodyData(data, isTreeType, childrenProp, isFold, level = 1) {
|
||||
function getBodyData(
|
||||
primaryKey,
|
||||
oldBodyData,
|
||||
data,
|
||||
isTreeType,
|
||||
childrenProp,
|
||||
isFold,
|
||||
parentFold,
|
||||
level = 1
|
||||
) {
|
||||
let bodyData = [];
|
||||
data.forEach((row, index) => {
|
||||
const children = row[childrenProp];
|
||||
const childrenLen =
|
||||
Object.prototype.toString.call(children).slice(8, -1) === "Array"
|
||||
? children.length
|
||||
: 0;
|
||||
let curIsFold = isFold;
|
||||
if (
|
||||
isFold &&
|
||||
typeof primaryKey === "string" &&
|
||||
Array.isArray(oldBodyData)
|
||||
) {
|
||||
for (let i = 0; i < oldBodyData.length; i++) {
|
||||
const oldRow = oldBodyData[i];
|
||||
if (oldRow[primaryKey] === row[primaryKey]) {
|
||||
if ("_isFold" in oldRow) {
|
||||
curIsFold = oldRow._isFold;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bodyData.push({
|
||||
_isHover: false,
|
||||
_isExpanded: false,
|
||||
_isChecked: false,
|
||||
_level: level,
|
||||
// _isHide: isFold ? level !== 1 : false,
|
||||
// _isFold: isFold,
|
||||
_isHide: level !== 1 ? isFold && parentFold : false,
|
||||
_isFold: isFold && curIsFold,
|
||||
_childrenLen: childrenLen,
|
||||
_normalIndex: index + 1,
|
||||
...row
|
||||
});
|
||||
if (isTreeType) {
|
||||
if (childrenLen > 0) {
|
||||
// bodyData = bodyData.concat(getBodyData(children, true, childrenProp, isFold, level + 1));
|
||||
bodyData = bodyData.concat(
|
||||
getBodyData(
|
||||
primaryKey,
|
||||
oldBodyData,
|
||||
children,
|
||||
true,
|
||||
childrenProp,
|
||||
isFold,
|
||||
curIsFold,
|
||||
level + 1
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
return bodyData;
|
||||
}
|
||||
|
||||
function initialState(table, expandKey) {
|
||||
return {
|
||||
bodyHeight: "auto",
|
||||
firstProp: expandKey || (table.columns[0] && table.columns[0].key),
|
||||
// bodyData: getBodyData(table.data, table.treeType, table.childrenProp, table.isFold),
|
||||
bodyData: getBodyData(
|
||||
table.primaryKey,
|
||||
table.bodyData,
|
||||
table.data,
|
||||
table.treeType,
|
||||
table.childrenProp,
|
||||
table.isFold,
|
||||
false
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function initialColumns(table, clientWidth) {
|
||||
let columnsWidth = 0;
|
||||
const minWidthColumns = [];
|
||||
const otherColumns = [];
|
||||
const columns = table.columns.concat();
|
||||
if (table.expandType) {
|
||||
columns.unshift({
|
||||
width: "50"
|
||||
});
|
||||
}
|
||||
if (table.selectable) {
|
||||
columns.unshift({
|
||||
width: "50"
|
||||
});
|
||||
}
|
||||
if (table.showIndex) {
|
||||
columns.unshift({
|
||||
width: "50px",
|
||||
key: "_normalIndex",
|
||||
title: table.indexText
|
||||
});
|
||||
}
|
||||
columns.forEach((column, index) => {
|
||||
let width = "";
|
||||
let minWidth = "";
|
||||
if (!column.width) {
|
||||
if (column.minWidth) {
|
||||
minWidth =
|
||||
typeof column.minWidth === "number"
|
||||
? column.minWidth
|
||||
: parseInt(column.minWidth, 10);
|
||||
} else {
|
||||
minWidth = 80;
|
||||
}
|
||||
minWidthColumns.push({
|
||||
...column,
|
||||
minWidth,
|
||||
_index: index
|
||||
});
|
||||
} else {
|
||||
width =
|
||||
typeof column.width === "number"
|
||||
? column.width
|
||||
: parseInt(column.width, 10);
|
||||
otherColumns.push({
|
||||
...column,
|
||||
width,
|
||||
_index: index
|
||||
});
|
||||
}
|
||||
columnsWidth += minWidth || width;
|
||||
});
|
||||
const scrollBarWidth = getSbw();
|
||||
const totalWidth = columnsWidth + scrollBarWidth;
|
||||
const isScrollX = totalWidth > clientWidth;
|
||||
if (!isScrollX) {
|
||||
const extraWidth = clientWidth - totalWidth;
|
||||
const averageExtraWidth = Math.floor(extraWidth / minWidthColumns.length);
|
||||
minWidthColumns.forEach(column => {
|
||||
column.computedWidth = column.minWidth + averageExtraWidth;
|
||||
});
|
||||
}
|
||||
const tableColumns = otherColumns.concat(minWidthColumns);
|
||||
tableColumns.sort((a, b) => a._index - b._index);
|
||||
return tableColumns;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "TreeTable",
|
||||
mixins: [mixins],
|
||||
components: {
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "default",
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxHeight: {
|
||||
type: [String, Number],
|
||||
default: "auto"
|
||||
},
|
||||
stripe: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
treeType: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
childrenProp: {
|
||||
type: String,
|
||||
default: "children"
|
||||
},
|
||||
isFold: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
expandType: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selectable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: "checkbox"
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: "暂无数据"
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
indexText: {
|
||||
type: String,
|
||||
default: "#"
|
||||
},
|
||||
showSummary: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
sumText: {
|
||||
type: String,
|
||||
default: "合计"
|
||||
},
|
||||
primaryKey: String,
|
||||
summaryMethod: Function,
|
||||
showRowHover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rowKey: Function,
|
||||
rowClassName: [String, Function],
|
||||
cellClassName: [String, Function],
|
||||
rowStyle: [Object, Function],
|
||||
cellStyle: [Object, Function],
|
||||
expandKey: String
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
treeTableRoot: this,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
computedWidth: "",
|
||||
computedHeight: "",
|
||||
tableColumns: [],
|
||||
...initialState(this, this.expandKey)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bodyWrapperStyle() {
|
||||
return {
|
||||
height: this.bodyHeight
|
||||
};
|
||||
},
|
||||
tableClass() {
|
||||
return {
|
||||
[`${this.prefixCls}--border`]: this.border
|
||||
};
|
||||
},
|
||||
bodyClass() {
|
||||
return {
|
||||
[`${this.prefixCls}--stripe`]: this.stripe
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEvent(type, $event) {
|
||||
this.validateType(type, ["header", "body", "footer"], "handleEvent");
|
||||
const eventType = $event.type;
|
||||
if (eventType === "scroll") {
|
||||
this.$refs["header-wrapper"].scrollLeft = $event.target.scrollLeft;
|
||||
this.$refs["footer-wrapper"].scrollLeft = $event.target.scrollLeft;
|
||||
}
|
||||
if (eventType === "mousewheel") {
|
||||
const deltaX = $event.deltaX;
|
||||
const $body = this.$refs["body-wrapper"];
|
||||
if (deltaX > 0) {
|
||||
$body.scrollLeft += 10;
|
||||
} else {
|
||||
$body.scrollLeft -= 10;
|
||||
}
|
||||
}
|
||||
return this.$emit(`${type}-${eventType}`, $event);
|
||||
},
|
||||
// computedWidth, computedHeight, tableColumns
|
||||
measure() {
|
||||
this.$nextTick(() => {
|
||||
const { clientWidth, clientHeight } = this.$el;
|
||||
this.computedWidth = clientWidth + 2;
|
||||
this.computedHeight = clientHeight + 2;
|
||||
|
||||
const maxHeight = parseInt(this.maxHeight, 10);
|
||||
if (this.maxHeight !== "auto" && this.computedHeight > maxHeight) {
|
||||
this.bodyHeight = `${maxHeight - 83}px`;
|
||||
}
|
||||
this.tableColumns = initialColumns(this, clientWidth);
|
||||
});
|
||||
},
|
||||
getCheckedProp(key = "index") {
|
||||
if (!this.selectable) {
|
||||
return [];
|
||||
}
|
||||
const checkedIndexs = [];
|
||||
this.bodyData.forEach((item, index) => {
|
||||
if (item._isChecked) {
|
||||
if (key === "index") {
|
||||
checkedIndexs.push(index);
|
||||
} else {
|
||||
checkedIndexs.push(item[key]);
|
||||
}
|
||||
}
|
||||
});
|
||||
return checkedIndexs;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$props: {
|
||||
deep: true,
|
||||
handler() {
|
||||
Object.assign(this.$data, initialState(this, this.expandKey));
|
||||
}
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
this.measure();
|
||||
},
|
||||
mounted() {
|
||||
this.measure();
|
||||
window.addEventListener("resize", this.measure);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.measure);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" src="./Table.less"></style>
|
||||
@@ -1,335 +0,0 @@
|
||||
import Checkbox from '../Checkbox/Checkbox'; // eslint-disable-line
|
||||
// import Radio from '../Radio/Radio'; // eslint-disable-line
|
||||
import { mixins } from './utils';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export default {
|
||||
name: 'TreeTable__body',
|
||||
mixins: [mixins],
|
||||
inject: {
|
||||
treeTableRoot: { default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
radioSelectedIndex: -1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
table() {
|
||||
if (this.treeTableRoot) return this.treeTableRoot;
|
||||
let parent = this.$parent;
|
||||
while (parent) {
|
||||
if (parent.$options && parent.$options.name === 'TreeTable') return parent;
|
||||
parent = parent.$parent;
|
||||
}
|
||||
return this.$parent;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleStatus(type, row, rowIndex, value) {
|
||||
this.validateType(type, ['Expanded', 'Checked', 'Hide', 'Fold'], 'toggleStatus', false);
|
||||
const target = this.table.bodyData[rowIndex];
|
||||
this.table.bodyData.splice(rowIndex, 1, {
|
||||
...target,
|
||||
[`_is${type}`]: typeof value === 'undefined' ? !row[`_is${type}`] : value,
|
||||
});
|
||||
},
|
||||
getChildrenIndex(parentLevel, parentIndex, careFold = true) {
|
||||
const data = this.table.bodyData;
|
||||
let childrenIndex = [];
|
||||
for (let i = parentIndex + 1; i < data.length; i++) {
|
||||
if (data[i]._level <= parentLevel) break;
|
||||
if (data[i]._level - 1 === parentLevel) {
|
||||
childrenIndex.push(i);
|
||||
}
|
||||
}
|
||||
const len = childrenIndex.length; // important!!!
|
||||
if (len > 0) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
const childData = data[childrenIndex[i]];
|
||||
if (
|
||||
childData._childrenLen &&
|
||||
(!careFold || (careFold && !childData._isFold))
|
||||
) {
|
||||
childrenIndex = childrenIndex.concat(
|
||||
this.getChildrenIndex(childData._level, childrenIndex[i], careFold));
|
||||
}
|
||||
}
|
||||
}
|
||||
return childrenIndex;
|
||||
},
|
||||
handleEvent($event, type, data, others) {
|
||||
const certainType = this.validateType(type, ['cell', 'row', 'checkbox', 'icon', 'radio'], 'handleEvent');
|
||||
const eventType = $event ? $event.type : '';
|
||||
const { row, rowIndex, column, columnIndex } = data;
|
||||
const latestData = this.table.bodyData;
|
||||
// Checkbox
|
||||
if (certainType.checkbox) {
|
||||
const { isChecked } = others;
|
||||
this.toggleStatus('Checked', row, rowIndex, isChecked);
|
||||
if (row._childrenLen > 0) {
|
||||
const childrenIndex = this.getChildrenIndex(row._level, rowIndex, false);
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
this.toggleStatus('Checked', latestData[childrenIndex[i]], childrenIndex[i], isChecked);
|
||||
}
|
||||
}
|
||||
return this.table.$emit('checkbox-click', latestData[rowIndex], column, columnIndex, $event);
|
||||
}
|
||||
// Radio
|
||||
if (certainType.radio) {
|
||||
this.radioSelectedIndex = rowIndex;
|
||||
return this.table.$emit('radio-click', { row, rowIndex, column, columnIndex, $event });
|
||||
}
|
||||
// Tree's icon
|
||||
if (certainType.icon) {
|
||||
$event.stopPropagation();
|
||||
this.toggleStatus('Fold', row, rowIndex);
|
||||
const childrenIndex = this.getChildrenIndex(row._level, rowIndex);
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
this.toggleStatus('Hide', latestData[childrenIndex[i]], childrenIndex[i]);
|
||||
}
|
||||
return this.table.$emit('tree-icon-click', latestData[rowIndex], column, columnIndex, $event);
|
||||
}
|
||||
if (certainType.cell && eventType === 'click') {
|
||||
// 点击扩展单元格
|
||||
if (this.isExpandCell(this.table, columnIndex)) {
|
||||
this.toggleStatus('Expanded', row, rowIndex);
|
||||
return this.table.$emit('expand-cell-click', latestData[rowIndex], column, columnIndex, $event);
|
||||
}
|
||||
}
|
||||
// 行:Hover
|
||||
if (certainType.row && (eventType === 'mouseenter' || eventType === 'mouseleave')) {
|
||||
const { hover } = others;
|
||||
const target = latestData[rowIndex];
|
||||
latestData.splice(rowIndex, 1, {
|
||||
...target,
|
||||
_isHover: hover,
|
||||
});
|
||||
}
|
||||
if (certainType.row && others && others.clickRow && certainType.radio) {
|
||||
this.radioSelectedIndex = rowIndex;
|
||||
return this.table.$emit('radio-click', { row, rowIndex, column, columnIndex, $event });
|
||||
}
|
||||
if (certainType.cell) {
|
||||
return this.table.$emit(`${type}-${eventType}`, latestData[rowIndex], rowIndex, column, columnIndex, $event);
|
||||
}
|
||||
return this.table.$emit(`${type}-${eventType}`, latestData[rowIndex], rowIndex, $event);
|
||||
},
|
||||
},
|
||||
render() {
|
||||
// style
|
||||
function getStyle(type, row, rowIndex, column, columnIndex) {
|
||||
const certainType = this.validateType(type, ['cell', 'row'], 'getStyle');
|
||||
const style = this.table[`${type}Style`];
|
||||
if (typeof style === 'function') {
|
||||
if (certainType.row) {
|
||||
return style.call(null, row, rowIndex);
|
||||
}
|
||||
if (certainType.cell) {
|
||||
return style.call(null, row, rowIndex, column, columnIndex);
|
||||
}
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
// className
|
||||
function getClassName(type, row, rowIndex, column, columnIndex) {
|
||||
const certainType = this.validateType(type, ['cell', 'row', 'inner'], 'getClassName');
|
||||
const classList = [];
|
||||
if (column && column.key == "_normalIndex") {
|
||||
classList.push(`${this.prefixCls}--center-cell`);
|
||||
}
|
||||
if (certainType.row || certainType.cell) {
|
||||
const className = this.table[`${type}ClassName`];
|
||||
if (typeof className === 'string') {
|
||||
classList.push(className);
|
||||
} else if (typeof className === 'function') {
|
||||
if (certainType.row) {
|
||||
classList.push(className.call(null, row, rowIndex) || '');
|
||||
}
|
||||
if (certainType.cell) {
|
||||
classList.push(className.call(null, row, rowIndex, column, columnIndex) || '');
|
||||
}
|
||||
}
|
||||
if (certainType.row) {
|
||||
classList.push(`${this.prefixCls}__body-row`);
|
||||
if (this.table.stripe && rowIndex % 2 !== 0) {
|
||||
classList.push(`${this.prefixCls}--stripe-row`);
|
||||
}
|
||||
if (this.table.showRowHover && row._isHover) {
|
||||
classList.push(`${this.prefixCls}--row-hover`);
|
||||
}
|
||||
}
|
||||
if (certainType.cell) {
|
||||
classList.push(`${this.prefixCls}__body-cell`);
|
||||
if (this.table.border) {
|
||||
classList.push(`${this.prefixCls}--border-cell`);
|
||||
}
|
||||
const align = column.align;
|
||||
if (['center', 'right'].indexOf(align) > -1) {
|
||||
classList.push(`${this.prefixCls}--${align}-cell`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (certainType.inner) {
|
||||
classList.push(`${this.prefixCls}__cell-inner`);
|
||||
if (this.isExpandCell(this.table, columnIndex)) {
|
||||
classList.push(`${this.prefixCls}--expand-inner`);
|
||||
if (row._isExpanded) {
|
||||
classList.push(`${this.prefixCls}--expanded-inner`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
// Vue 3:scoped slot 合并到 $slots
|
||||
function renderTemplateSlot(table, slotName, scope) {
|
||||
if (!table || !slotName) return '';
|
||||
const slots = table.$slots || {};
|
||||
let slot = slots[slotName];
|
||||
if (!slot && table.$scopedSlots && table.$scopedSlots[slotName]) {
|
||||
slot = table.$scopedSlots[slotName];
|
||||
}
|
||||
if (!slot) return '';
|
||||
return slot(scope);
|
||||
}
|
||||
|
||||
// 根据type渲染单元格Cell
|
||||
function renderCell(row, rowIndex, column, columnIndex) {
|
||||
if (!row || !column) return '';
|
||||
// ExpandType
|
||||
if (this.isExpandCell(this.table, columnIndex)) {
|
||||
return <i class='zk-icon zk-icon-angle-right'></i>;
|
||||
}
|
||||
// SelectionType's Checkbox
|
||||
if (this.isSelectionCell(this.table, columnIndex)) {
|
||||
let res = null;
|
||||
if (this.table.selectType === 'checkbox') {
|
||||
let allCheck;
|
||||
let childrenIndex;
|
||||
const hasChildren = row._childrenLen > 0;
|
||||
if (hasChildren) {
|
||||
childrenIndex = this.getChildrenIndex(row._level, rowIndex, false);
|
||||
allCheck = true;
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
if (!this.table.bodyData[childrenIndex[i]]._isChecked) {
|
||||
allCheck = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allCheck = row._isChecked;
|
||||
}
|
||||
let indeterminate = false;
|
||||
if (hasChildren && !allCheck) {
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
if (this.table.bodyData[childrenIndex[i]]._isChecked) {
|
||||
indeterminate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = (
|
||||
<input
|
||||
type="radio"
|
||||
checked={this.radioSelectedIndex === rowIndex}
|
||||
onChange={() =>
|
||||
this.handleEvent(null, 'radio', { row, rowIndex, column, columnIndex })
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Tree's firstProp
|
||||
if (this.table.treeType && this.table.firstProp === column.key) {
|
||||
return <span
|
||||
class={`${this.prefixCls}--level-${row._level}-cell`}
|
||||
style={{
|
||||
marginLeft: `${(row._level - 1) * 24}px`,
|
||||
paddingLeft: row._childrenLen === 0 ? '20px' : '',
|
||||
}}>
|
||||
{row._childrenLen > 0 &&
|
||||
<i
|
||||
class={`${this.prefixCls}--tree-icon zk-icon zk-icon-${row._isFold ? 'plus' : 'minus'}-square-o`}
|
||||
on-click={$event => this.handleEvent($event, 'icon', { row, rowIndex, column, columnIndex }, { isFold: row._isFold })}></i>
|
||||
}
|
||||
{row[column.key] ? row[column.key] : ''}
|
||||
</span>;
|
||||
}
|
||||
// TreeType children's index
|
||||
if (this.table.showIndex && this.table.treeType && column.key === '_normalIndex' && row._level > 1) {
|
||||
return '';
|
||||
}
|
||||
if (column.type === undefined || column.type === 'custom') {
|
||||
return row[column.key];
|
||||
} else if (column.type === 'template') {
|
||||
return renderTemplateSlot.call(this, this.table, column.template, {
|
||||
row,
|
||||
rowIndex,
|
||||
column,
|
||||
columnIndex,
|
||||
});
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Template
|
||||
return (
|
||||
<table cellspacing="0" cellpadding="0" border="0" class={`${this.prefixCls}__body`}>
|
||||
<tbody>
|
||||
{ this.table.bodyData.length > 0
|
||||
? this.table.bodyData.map((row, rowIndex) =>
|
||||
[
|
||||
<tr
|
||||
v-show={!row._isHide}
|
||||
key={`table_row_${rowIndex}`}
|
||||
style={getStyle.call(this, 'row', row, rowIndex)}
|
||||
class={getClassName.call(this, 'row', row, rowIndex)}
|
||||
on-click={$event => this.handleEvent($event, 'row', { row, rowIndex }, { clickRow: true })}
|
||||
on-dblclick={$event => this.handleEvent($event, 'row', { row, rowIndex })}
|
||||
on-contextmenu={$event => this.handleEvent($event, 'row', { row, rowIndex })}
|
||||
on-mouseenter={$event => this.handleEvent($event, 'row', { row, rowIndex }, { hover: true })}
|
||||
on-mouseleave={$event => this.handleEvent($event, 'row', { row, rowIndex }, { hover: false })}>
|
||||
{ this.table.tableColumns.map((column, columnIndex) =>
|
||||
column.key ? <td
|
||||
style={getStyle.call(this, 'cell', row, rowIndex, column, columnIndex)}
|
||||
class={getClassName.call(this, 'cell', row, rowIndex, column, columnIndex)}
|
||||
on-click={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-dblclick={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-contextmenu={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-mouseenter={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-mouseleave={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}>
|
||||
<div class={getClassName.call(this, 'inner', row, rowIndex, column, columnIndex)}>
|
||||
{renderCell.call(this, row, rowIndex, column, columnIndex)}
|
||||
</div>
|
||||
</td>:"")
|
||||
}
|
||||
</tr>,
|
||||
this.table.expandType && row._isExpanded &&
|
||||
<tr
|
||||
key={rowIndex}
|
||||
class={`${this.prefixCls}__body-row ${this.prefixCls}--expand-row`}>
|
||||
<td
|
||||
class={`${this.prefixCls}--expand-content`}
|
||||
colspan={this.table.tableColumns.length}>
|
||||
{renderTemplateSlot.call(this, this.table, 'expand', { row, rowIndex })}
|
||||
</td>
|
||||
</tr>,
|
||||
])
|
||||
: <tr
|
||||
class={`${this.prefixCls}--empty-row`}>
|
||||
<td
|
||||
class={`${this.prefixCls}__body-cell ${this.prefixCls}--empty-content`}
|
||||
colspan={this.table.tableColumns.length}>
|
||||
{this.table.emptyText}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,84 +0,0 @@
|
||||
import { mixins } from './utils';
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export default {
|
||||
name: 'TreeTable__footer',
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
table() {
|
||||
return this.$parent;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
render() {
|
||||
// 计算各列总和
|
||||
function renderCell({ key }, columnIndex) {
|
||||
if (columnIndex === 0) {
|
||||
return this.table.sumText;
|
||||
}
|
||||
const rows = this.table.bodyData;
|
||||
const values = rows.map(row => Number(row[key]));
|
||||
const precisions = [];
|
||||
let notNumber = true;
|
||||
values.forEach((value) => {
|
||||
if (!isNaN(value)) {
|
||||
notNumber = false;
|
||||
const decimal = value.toString().split('.')[1];
|
||||
precisions.push(decimal ? decimal.length : 0);
|
||||
}
|
||||
});
|
||||
const precision = Math.max.apply(null, precisions);
|
||||
if (!notNumber) {
|
||||
return values.reduce((prev, curr) => {
|
||||
const value = Number(curr);
|
||||
if (!isNaN(value)) {
|
||||
return parseFloat((prev + curr).toFixed(precision));
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// className
|
||||
function getClassName() {
|
||||
const classList = [];
|
||||
classList.push(`${this.prefixCls}__footer-cell`);
|
||||
if (this.table.border) {
|
||||
classList.push(`${this.prefixCls}--border-cell`);
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
// Template
|
||||
return (
|
||||
<table cellspacing="0" cellpadding="0" border="0" class={ `${this.prefixCls}__footer` }>
|
||||
<colgroup>
|
||||
{ this.table.tableColumns.map(column =>
|
||||
<col width={ column.computedWidth || column.minWidth || column.width }></col>)
|
||||
}
|
||||
</colgroup>
|
||||
<tfoot>
|
||||
<tr class={ `${this.prefixCls}__footer-row` }>
|
||||
{ this.table.tableColumns.map((column, columnIndex) =>
|
||||
<td class={ getClassName.call(this) }>
|
||||
<div class={ `${this.prefixCls}__cell-inner` }>
|
||||
{ this.table.summaryMethod
|
||||
? this.table.summaryMethod(this.table.bodyData, column, columnIndex)
|
||||
: renderCell.call(this, column, columnIndex) }
|
||||
</div>
|
||||
</td>)
|
||||
}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
import Checkbox from "../Checkbox/Checkbox"; // eslint-disable-line
|
||||
import { mixins } from "./utils";
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export default {
|
||||
name: "TreeTable__header",
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
table() {
|
||||
return this.$parent;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleAllChecked(checked) {
|
||||
this.table.bodyData = this.table.bodyData.map(row => ({
|
||||
...row,
|
||||
_isChecked: checked
|
||||
}));
|
||||
}
|
||||
},
|
||||
render() {
|
||||
// className
|
||||
function getClassName(type, { headerAlign, key }) {
|
||||
const certainType = this.validateType(
|
||||
type,
|
||||
["cell", "inner"],
|
||||
"getClassName"
|
||||
);
|
||||
const classList = [];
|
||||
if (key == "_normalIndex") {
|
||||
classList.push(`${this.prefixCls}--center-cell`);
|
||||
}
|
||||
if (certainType.cell) {
|
||||
classList.push(`${this.prefixCls}__header-cell`);
|
||||
if (this.table.border) {
|
||||
classList.push(`${this.prefixCls}--border-cell`);
|
||||
}
|
||||
if (["center", "right"].indexOf(headerAlign) > -1) {
|
||||
classList.push(`${this.prefixCls}--${headerAlign}-cell`);
|
||||
}
|
||||
}
|
||||
if (certainType.inner) {
|
||||
classList.push(`${this.prefixCls}__cell-inner`);
|
||||
if (this.table.treeType && this.table.firstProp === key) {
|
||||
classList.push(`${this.prefixCls}--firstProp-header-inner`);
|
||||
}
|
||||
}
|
||||
return classList.join(" ");
|
||||
}
|
||||
|
||||
// 根据type渲染单元格Label
|
||||
function renderLabel(column, columnIndex) {
|
||||
if (
|
||||
this.isSelectionCell(this.table, columnIndex) &&
|
||||
this.selectType === "checkbox"
|
||||
) {
|
||||
const allCheck = this.table.bodyData.every(row => row._isChecked);
|
||||
const indeterminate =
|
||||
!allCheck && this.table.bodyData.some(row => row._isChecked);
|
||||
return (
|
||||
<Checkbox
|
||||
indeterminate={indeterminate}
|
||||
value={allCheck}
|
||||
onOn-change={checked => this.toggleAllChecked(checked)}
|
||||
></Checkbox>
|
||||
);
|
||||
}
|
||||
return column.title ? column.title : "";
|
||||
}
|
||||
|
||||
// Template
|
||||
return (
|
||||
<table
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
class={`${this.prefixCls}__header`}
|
||||
>
|
||||
{/* <colgroup>
|
||||
{this.table.tableColumns.map(column => (
|
||||
<col
|
||||
width={column.computedWidth || column.minWidth || column.width}
|
||||
></col>
|
||||
))}
|
||||
</colgroup> */}
|
||||
<thead>
|
||||
<tr class={`${this.prefixCls}__header-row`}>
|
||||
{
|
||||
this.table.tableColumns.map((column, columnIndex) =>
|
||||
column.key ? (
|
||||
<th class={getClassName.call(this, "cell", column)}>
|
||||
<div class={getClassName.call(this, "inner", column)}>
|
||||
{renderLabel.call(this, column, columnIndex)}
|
||||
</div>
|
||||
</th>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
// icon
|
||||
@font-face {font-family: "iconfont";
|
||||
src: url('iconfont.eot?t=1505310522875'); /* IE9*/
|
||||
src: url('iconfont.eot?t=1505310522875#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAW0AAsAAAAACOQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kggY21hcAAAAYAAAABuAAABojLtBtFnbHlmAAAB8AAAAa8AAAKA73SzT2hlYWQAAAOgAAAALwAAADYO3fRqaGhlYQAAA9AAAAAcAAAAJAfeA4ZobXR4AAAD7AAAABMAAAAUE+kAAGxvY2EAAAQAAAAADAAAAAwBbAHYbWF4cAAABAwAAAAeAAAAIAEUAF1uYW1lAAAELAAAAUUAAAJtPlT+fXBvc3QAAAV0AAAAQAAAAFryy5h0eJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/s04gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDwzZm7438AQw9zA0AAUZgTJAQAoHgyieJzFkcENwyAUQ98HyqHKKDmEZoEOklOnYOK/RmI+uXSCWDLG/pZAALyALK5iAfthDBxKLfLMO/LCJl+lRqL7fp7y3VuoKprV0KxO0qbyGOy5o/+xxPq9nV6YflNX9DY5fsA/k6Pj+yTpAn3jEO8AAHiclVDNitNQFD7n3slNE9vE5N7kpOn0J0mbKB3DGDMZRGw3bhQXA2LB5TyAbmfjohvBhQvfYEAEoc8wr+EDiK4KPkITU0EcXDmHw3fOgfN9fHygATTf+BUPQMIduA9P4AwAxRxjiw0xysqczdGLNI+UxbMki/QkzvljpFgov6jKlIQubLRwhA+iospyluFJuWCPsPCHiP1B+MKdHbr8I5pBNnpXP2Of0Bsnh/biXv30aKmKiexcdF2377ofOkLTOowd2Ba+Jt/QDFPUnzU79K7Gd9kYu/0sfP6qNxm45+/LN8MZGYjrNcrBxPqydEKn7behL92+frvXCcJeMlV48eNWILvD9Du0hXtgl+wSHIBZnJZLzNKyKgh9paPAdVca260hAxPBMBowz9t1uzUDuT8CswEDDtq8Gr7mADaM4QgetrKRhbozQooWeOrkKJVIojg9ccqqjcT3uBJ6lGNZnUYjnBU+ORbGaeYskM93m+kx4vGUrX5PQXK3kUSSrSS9JFWvFJHCjaIGJCFSugcOLeM6c7f6wyFZf/37+PO6AgD/x/vNnN/A7H8bhF86tmcbAHicY2BkYGAAYl/p/jnx/DZfGbhZGEDg6v0IKwT9/yELA7MEkMvBwAQSBQAZYgnZAHicY2BkYGBu+N/AEMPCAAJAkpEBFbACAEcLAm54nGNhYGBgfsnAwMKAwAAOmwD9AAAAAAAAdgCYAPYBQHicY2BkYGBgZQgEYhBgAmIuIGRg+A/mMwAAES0BcgAAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAlZGJkZmRhZGVkY2BsYI7MS89J1W3KDM9o4S3IKe0WLe4sDSxKFU3ny83Mw+Jy8AAAHSWD8A=') format('woff'),
|
||||
url('iconfont.ttf?t=1505310522875') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||
url('iconfont.svg?t=1505310522875#iconfont') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.zk-icon {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.zk-icon-plus-square-o:before { content: "\e631"; }
|
||||
|
||||
.zk-icon-minus-square-o:before { content: "\e632"; }
|
||||
|
||||
.zk-icon-angle-right:before { content: "\e633"; }
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<!--
|
||||
2013-9-30: Created.
|
||||
-->
|
||||
<svg>
|
||||
<metadata>
|
||||
Created by iconfont
|
||||
</metadata>
|
||||
<defs>
|
||||
|
||||
<font id="iconfont" horiz-adv-x="1024" >
|
||||
<font-face
|
||||
font-family="iconfont"
|
||||
font-weight="500"
|
||||
font-stretch="normal"
|
||||
units-per-em="1024"
|
||||
ascent="896"
|
||||
descent="-128"
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
|
||||
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
|
||||
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
|
||||
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
|
||||
|
||||
|
||||
|
||||
<glyph glyph-name="angle-right" unicode="" d="M384.087 97.474c-7.857 0-15.713 2.997-21.707 8.992-11.989 11.989-11.989 31.426 0 43.415l234.118 234.12-234.118 234.118c-11.988 11.989-11.988 31.427 0 43.416 11.989 11.988 31.426 11.988 43.416 0l255.826-255.827c11.989-11.989 11.989-31.427 0-43.416l-255.826-255.827c-5.995-5.995-13.851-8.992-21.708-8.992z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="plus-square-o" unicode="" d="M810.666667 768H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333v-597.333334c0-46.933333 38.4-85.333333 85.333333-85.333333h597.333334c46.933333 0 85.333333 38.4 85.333333 85.333333V682.666667c0 46.933333-38.4 85.333333-85.333333 85.333333z m42.666666-682.666667c0-25.6-17.066667-42.666667-42.666666-42.666666H213.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666V682.666667c0 25.6 17.066667 42.666667 42.666666 42.666666h597.333334c25.6 0 42.666667-17.066667 42.666666-42.666666v-597.333334zM768 384c0-25.6-17.066667-42.666667-42.666667-42.666667H298.666667c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666667h426.666666c25.6 0 42.666667-17.066667 42.666667-42.666667zM512 640c25.6 0 42.666667-17.066667 42.666667-42.666667v-426.666666c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666667 42.666667V597.333333c0 25.6 17.066667 42.666667 42.666667 42.666667z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="minus-square-o" unicode="" d="M810.666667 768H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333v-597.333334c0-46.933333 38.4-85.333333 85.333333-85.333333h597.333334c46.933333 0 85.333333 38.4 85.333333 85.333333V682.666667c0 46.933333-38.4 85.333333-85.333333 85.333333z m42.666666-682.666667c0-25.6-17.066667-42.666667-42.666666-42.666666H213.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666V682.666667c0 25.6 17.066667 42.666667 42.666666 42.666666h597.333334c25.6 0 42.666667-17.066667 42.666666-42.666666v-597.333334zM768 384c0-25.6-17.066667-42.666667-42.666667-42.666667H298.666667c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666667h426.666666c25.6 0 42.666667-17.066667 42.666667-42.666667z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
|
||||
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,6 +0,0 @@
|
||||
import mixins from './mixins';
|
||||
import scrollBarWidth from './scrollBarWidth';
|
||||
export {
|
||||
mixins,
|
||||
scrollBarWidth
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
prefixCls: 'zk-table',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
validateType(type, validTypes, funcName, isReturn = true) {
|
||||
if (validTypes.indexOf(type) < 0) throw new Error(`${funcName}'s type must is ${validTypes.join(' or ')}.`);
|
||||
if (isReturn) {
|
||||
const certainType = {};
|
||||
validTypes.forEach((item) => {
|
||||
certainType[item] = item === type;
|
||||
});
|
||||
return certainType;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isExpandCell(table, columnIndex) {
|
||||
return table.expandType && (
|
||||
(table.showIndex && columnIndex === 1) ||
|
||||
(!table.showIndex && columnIndex === 0)
|
||||
);
|
||||
},
|
||||
isSelectionCell(table, columnIndex) {
|
||||
return table.selectable && (
|
||||
(table.showIndex && table.expandType && columnIndex === 2) ||
|
||||
(!table.showIndex && table.expandType && columnIndex === 1) ||
|
||||
(table.showIndex && !table.expandType && columnIndex === 1) ||
|
||||
(!table.showIndex && !table.expandType && columnIndex === 0)
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
let scrollBarWidth;
|
||||
|
||||
export default function getScrollBarWidth() {
|
||||
if (typeof document === "undefined") return 0;
|
||||
if (scrollBarWidth !== undefined) return scrollBarWidth;
|
||||
|
||||
const outer = document.createElement("div");
|
||||
outer.style.visibility = "hidden";
|
||||
outer.style.width = "100px";
|
||||
outer.style.position = "absolute";
|
||||
outer.style.top = "-9999px";
|
||||
document.body.appendChild(outer);
|
||||
|
||||
const widthNoScroll = outer.offsetWidth;
|
||||
outer.style.overflow = "scroll";
|
||||
|
||||
const inner = document.createElement("div");
|
||||
inner.style.width = "100%";
|
||||
outer.appendChild(inner);
|
||||
|
||||
const widthWithScroll = inner.offsetWidth;
|
||||
outer.parentNode.removeChild(outer);
|
||||
scrollBarWidth = widthNoScroll - widthWithScroll;
|
||||
|
||||
return scrollBarWidth;
|
||||
}
|
||||
@@ -119,14 +119,18 @@ export default {
|
||||
this.distance = 0;
|
||||
this.bgColor = "#04ad11";
|
||||
this.verifyText = "拖动滑块解锁";
|
||||
getVerifyImg(this.type).then((res) => {
|
||||
if (res.result) {
|
||||
this.data = res.result;
|
||||
this.show = true;
|
||||
} else {
|
||||
this.$Message.warning("请求失败请重试!");
|
||||
}
|
||||
});
|
||||
getVerifyImg(this.type)
|
||||
.then((res) => {
|
||||
if (res.result) {
|
||||
this.data = res.result;
|
||||
this.show = true;
|
||||
} else {
|
||||
this.$Message.warning("请求失败请重试!");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$Message.warning("验证码加载失败,请点击重试");
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -227,13 +227,13 @@ export default {
|
||||
const map = {
|
||||
UNPAID: "danger",
|
||||
PAID: "primary",
|
||||
UNDELIVERED: "",
|
||||
UNDELIVERED: "info",
|
||||
DELIVERED: "info",
|
||||
COMPLETED: "success",
|
||||
TAKE: "warning",
|
||||
CANCELLED: "danger",
|
||||
};
|
||||
return map[status] || "";
|
||||
return map[status] || "info";
|
||||
},
|
||||
canInvoicing(row) {
|
||||
if (!row) return false;
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="活动时间" width="200">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row" class="activity-time" v-html="formatActivityTime(row)"></div>
|
||||
<div v-if="row" class="activity-time" v-html="$filters.xssFilter(formatActivityTime(row))"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -298,7 +298,7 @@ export default {
|
||||
INSIDE: "success",
|
||||
IGAME: "success",
|
||||
};
|
||||
return map[type] || "";
|
||||
return map[type] || "info";
|
||||
},
|
||||
couponTypeText(type) {
|
||||
const map = {
|
||||
@@ -312,7 +312,7 @@ export default {
|
||||
DISCOUNT: "primary",
|
||||
PRICE: "info",
|
||||
};
|
||||
return map[type] || "";
|
||||
return map[type] || "info";
|
||||
},
|
||||
isSameSelection(next, current) {
|
||||
const a = next || [];
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFlashDiscountList, updateFlashDiscountStatus, getFlashDiscountDetail, saveFlashDiscount, editFlashDiscount } from "@/api/promotion.js";
|
||||
import { getFlashDiscountList, updateFlashDiscountStatus } from "@/api/promotion.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addLiveStoreGoods, getLiveGoods } from "@/api/promotion.js";
|
||||
import { getLiveInfo } from "@/api/promotion.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
@mouseup.stop
|
||||
@mousemove.stop
|
||||
>
|
||||
<img width="17" height="17" src="../assets/styles/icons8-edit-64.png"></img>
|
||||
<img width="17" height="17" src="../assets/styles/icons8-edit-64.png" />
|
||||
</li>
|
||||
<li class="hz-flex-img">
|
||||
<img class="hz-u-img" :src="zoneForm.img" />
|
||||
|
||||
@@ -219,7 +219,7 @@ export default {
|
||||
handleAddNav() {
|
||||
this.navList.list.push({ name: "", url: "", title: "" });
|
||||
this.$nextTick(() => {
|
||||
this.selectedNav.title = val.title;
|
||||
this.selectedNav = this.navList.list[this.navList.list.length - 1];
|
||||
});
|
||||
},
|
||||
handleMoveEnd({ newIndex, oldIndex }) {
|
||||
|
||||
@@ -196,7 +196,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -181,7 +181,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {}
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {}
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {}
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
||||
@@ -83,7 +83,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -238,7 +238,7 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
||||
@@ -145,7 +145,6 @@ export default {
|
||||
new Date().getTime()) /
|
||||
1000
|
||||
);
|
||||
const that = this;
|
||||
this.interval = setInterval(() => {
|
||||
this.diffSeconds--;
|
||||
}, 1000);
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
.search {
|
||||
.oss-operation {
|
||||
margin-bottom: 2vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oss-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oss-card {
|
||||
margin: 10px 20px 10px 0;
|
||||
width: 290px;
|
||||
|
||||
:hover {
|
||||
.content .other .name {
|
||||
color: #1890ff;
|
||||
transition: color .3s;
|
||||
}
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.el-card__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:hover {
|
||||
.play {
|
||||
transition: opacity .3s;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
height: 135px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.video {
|
||||
height: 135px;
|
||||
position: relative;
|
||||
|
||||
.cover {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.play {
|
||||
position: absolute;
|
||||
top: 43px;
|
||||
left: 117px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.other {
|
||||
padding: 16px;
|
||||
height: 135px;
|
||||
|
||||
.name {
|
||||
font-size: 16px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.key {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 45px;
|
||||
word-break: break-all;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 36px;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
background: #f7f9fa;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
|
||||
i:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 33.33%;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.btn-no {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 33.33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.oss-manage-box {
|
||||
.file-list {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-right: 1px solid #e5e6eb;
|
||||
padding: 16px 0 24px;
|
||||
}
|
||||
.pic-list {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0 0 24px 0;
|
||||
}
|
||||
.img-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
margin-top: 20px;
|
||||
.img-item {
|
||||
width: 120px;
|
||||
height: 158px;
|
||||
box-sizing: border-box;
|
||||
margin: 0 13px 8px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.card {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid transparent;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.card-checkbox {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1000;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
|
||||
.el-checkbox__label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.oss-card-selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
background-color: #ffffff;
|
||||
color: #666666;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.card:hover,
|
||||
.custom-checkbox-card-checked {
|
||||
border: 2px solid #ff5c58;
|
||||
}
|
||||
.text {
|
||||
width: 120px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
color: #252931;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-box {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.pagination-box {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.demo-tree-render .el-tree-node__content{
|
||||
width: 94%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,6 @@ import {validateMobile} from "@/libs/validate";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import departmentChoose from "@/views/my-components/lili/department-choose";
|
||||
import departmentTreeChoose from "@/views/my-components/lili/department-tree-choose";
|
||||
import uploadPicInput from "@/views/my-components/lili/upload-pic-input";
|
||||
|
||||
export default {
|
||||
name: "clerk-manage",
|
||||
@@ -202,7 +201,6 @@ export default {
|
||||
Search,
|
||||
departmentChoose,
|
||||
departmentTreeChoose,
|
||||
uploadPicInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -448,7 +448,7 @@ export default {
|
||||
selectedNodes.forEach((e) => {
|
||||
let perm = {
|
||||
title: e.title,
|
||||
isSuper: e.isSuper ? (e.isSuper = 1) : (e.isSuper = 0) || 0,
|
||||
isSuper: e.isSuper ? 1 : 0,
|
||||
menuId: e.id,
|
||||
roleId: this.editRolePermId,
|
||||
};
|
||||
|
||||
@@ -566,7 +566,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
res: {
|
||||
handler: (val) => {},
|
||||
handler(val) {},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -196,12 +196,16 @@ export default {
|
||||
}
|
||||
},
|
||||
loadOptions() {
|
||||
getCategoryTree().then((res) => {
|
||||
if (res.success) this.categoryList = res.result || [];
|
||||
});
|
||||
getBrandListData().then((res) => {
|
||||
if (res.success) this.brandList = res.result || [];
|
||||
});
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res.success) this.categoryList = res.result || [];
|
||||
})
|
||||
.catch(() => {});
|
||||
getBrandListData()
|
||||
.then((res) => {
|
||||
if (res.success) this.brandList = res.result || [];
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
formatDate(date, startOfDay) {
|
||||
const y = date.getFullYear();
|
||||
|
||||
@@ -249,7 +249,6 @@
|
||||
</template>
|
||||
<script>
|
||||
import * as API_Goods from "@/api/goods";
|
||||
import { Chart } from "@antv/g2";
|
||||
import orderRow from "./order/orderDetail";
|
||||
import refundRow from "./order/refundOrder";
|
||||
import affixTime from "@/views/lili-components/affix-time";
|
||||
@@ -467,6 +466,7 @@ export default {
|
||||
this.chartList = res.result;
|
||||
|
||||
if (!this.orderChart) {
|
||||
const { Chart } = await import("@antv/g2");
|
||||
this.orderChart = new Chart({
|
||||
container: "orderChart",
|
||||
autoFit: true,
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
<script>
|
||||
import affixTime from "@/views/lili-components/affix-time";
|
||||
import * as API_Member from "@/api/member";
|
||||
import { Chart } from "@antv/g2";
|
||||
|
||||
export default {
|
||||
components: { affixTime },
|
||||
@@ -131,7 +130,7 @@ export default {
|
||||
},
|
||||
init() {
|
||||
this.orderChart ? this.orderChart.clear() : "";
|
||||
API_Member.getStatisticsList(this.params).then((res) => {
|
||||
API_Member.getStatisticsList(this.params).then(async (res) => {
|
||||
if (res.result) {
|
||||
this.data = res.result;
|
||||
res.result.forEach((item) => {
|
||||
@@ -140,6 +139,7 @@ export default {
|
||||
});
|
||||
|
||||
if (!this.orderChart) {
|
||||
const { Chart } = await import("@antv/g2");
|
||||
this.orderChart = new Chart({
|
||||
container: "orderChart",
|
||||
autoFit: true,
|
||||
|
||||
@@ -382,7 +382,6 @@ import {
|
||||
renameFile,
|
||||
updateFileDirectory,
|
||||
} from "@/api/index";
|
||||
import DPlayer from "dplayer";
|
||||
import {sellerUrl} from "@/libs/axios";
|
||||
import config from "@/config/index";
|
||||
|
||||
@@ -587,7 +586,7 @@ export default {
|
||||
key: "createBy",
|
||||
width: 200,
|
||||
render: (h, params) => {
|
||||
let m = "";
|
||||
let m;
|
||||
if (params.row.userEnums == "MANAGER") {
|
||||
m = "[管理员]";
|
||||
} else if (params.row.userEnums == "STORE") {
|
||||
@@ -771,7 +770,7 @@ export default {
|
||||
width: 120,
|
||||
sortable: true,
|
||||
render: (h, params) => {
|
||||
let m = "";
|
||||
let m;
|
||||
if (params.row.userEnums == "MANAGER") {
|
||||
m = "[管理员]";
|
||||
} else if (params.row.userEnums == "STORE") {
|
||||
@@ -1293,13 +1292,14 @@ export default {
|
||||
this.pendingVideoUrl = v.url;
|
||||
this.videoVisible = true;
|
||||
},
|
||||
initDPlayer() {
|
||||
async initDPlayer() {
|
||||
const container = this.$refs.dplayerRef;
|
||||
if (!container || !this.pendingVideoUrl) return;
|
||||
if (dp) {
|
||||
dp.destroy();
|
||||
dp = null;
|
||||
}
|
||||
const { default: DPlayer } = await import("dplayer");
|
||||
dp = new DPlayer({
|
||||
container,
|
||||
screenshot: true,
|
||||
|
||||
@@ -39,6 +39,10 @@ module.exports = {
|
||||
if (message.includes("ResizeObserver loop")) {
|
||||
return false;
|
||||
}
|
||||
// 接口请求失败已由 axios 拦截器统一弹 toast,不再触发错误弹层
|
||||
if (error?.name === "AxiosError" || message.startsWith("Request failed with status code")) {
|
||||
return false;
|
||||
}
|
||||
// 腾讯地图 GL 在跨域脚本里抛错时,浏览器只上报 Script error.
|
||||
if (message === "Script error." || message.includes("far <= 0") || message.includes("near <= 0")) {
|
||||
return false;
|
||||
@@ -62,18 +66,21 @@ module.exports = {
|
||||
runtimeChunk: "single",
|
||||
splitChunks: {
|
||||
chunks: "all",
|
||||
maxInitialRequests: Infinity,
|
||||
minSize: 20000,
|
||||
minSize: 30000,
|
||||
cacheGroups: {
|
||||
// 框架核心单独成 chunk:长期缓存,不随业务代码变化而失效
|
||||
framework: {
|
||||
test: /[\\/]node_modules[\\/](vue|vue-router|vuex|axios|element-plus|@element-plus|@vue|@vueuse|core-js)[\\/]/,
|
||||
name: "chunk-framework",
|
||||
priority: 30,
|
||||
enforce: true,
|
||||
},
|
||||
// 首屏直接依赖的其余第三方包合并为一个 chunk,避免上百个小请求
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module) {
|
||||
const match =
|
||||
module.context &&
|
||||
module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/);
|
||||
const packageName = match ? match[1] : "vendor";
|
||||
return `npm.${packageName.replace("@", "")}`;
|
||||
},
|
||||
name: "chunk-vendor",
|
||||
chunks: "initial",
|
||||
priority: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||