Commit 9dae96ab by li

manager

parents
> 1%
last 2 versions
not ie <= 10
NODE_ENV=production
VUE_APP_PREVIEW=false
VUE_APP_BASE_URL=/
VUE_APP_API_BASE_URL=
\ No newline at end of file
NODE_ENV=development
VUE_APP_PREVIEW=true
VUE_APP_BASE_URL=/
VUE_APP_API_BASE_URL=http://38.47.232.137:9217
\ No newline at end of file
/**
默认eslint规则:
代码末尾不能加分号 ;(强迫症的我受不了 哭)
代码中不能存在多行空行;(这个我更也忍不了大哭)
tab键不能使用,必须换成两个空格;(超级不习惯)
代码中不能存在声明了但未使用的变量;(这个我觉得可以有)
* **/
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/strongly-recommended',
'@vue/standard'
],
rules: {
'no-console': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'generator-star-spacing': 'off',
'no-mixed-operators': 0,
'vue/max-attributes-per-line': [
2,
{
'singleline': 5, //单行的情况下可以接受的属性数量
'multiline': {
'max': 1, //多行的情况下每行最多属性数量
'allowFirstLine': false //不允许在多行的情况下第一行有属性
}
}
],
'vue/attribute-hyphenation': 0,
'vue/html-self-closing': 0,
'vue/component-name-in-template-casing': 0,
'vue/html-closing-bracket-spacing': 0,
'vue/singleline-html-element-content-newline': 0,
'vue/no-unused-components': 0,
'vue/multiline-html-element-content-newline': 0,
'vue/no-use-v-if-with-v-for': 0,
'vue/html-closing-bracket-newline': 0,
'vue/no-parsing-error': 0,
'no-tabs': 0,
'quotes': [
2,
'single',
{
'avoidEscape': true,
'allowTemplateLiterals': true
}
],
/**
* 分号配置项:
* 第一个参数:
" off"或0 - 关闭规则
" warn"或1 - 将该规则作为警告打开(不影响退出代码)
" error"或2 - 将规则作为错误打开(退出代码将为1)
*
*第二个参数
always(默认):在语句末尾需要分号
never:不允许加分号
*
* 第三个参数:
"beforeStatementContinuationChars": "any"(默认)如果下一行语句以 [,(,/,+,或 - 开头,忽略语句末尾的分号(或缺失分号),
"beforeStatementContinuationChars": "always" 如果下一行语句以 [,(,/,+,或 - 开头,在语句末尾需要添加分号。
"beforeStatementContinuationChars": "never" 如果该语句不会因为ASI而带来风险,那么即使它的下一行语句以 [,(,/,+,或 - 开头,也不允许在语句末尾添加分号。
* **/
'semi': [
2,
'never',
{
'beforeStatementContinuationChars': 'never'
}
],
'no-delete-var': 2,
'prefer-const': [
2,
{
'ignoreReadBeforeAssign': false
}
],
'template-curly-spacing': 'off',
'indent': 'off'
},
parserOptions: {
parser: 'babel-eslint'
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)'
],
env: {
jest: true
}
}
]
}
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
const plugins = []
if (IS_PROD) {
plugins.push('transform-remove-console')
}
// lazy load ant-design-vue
// if your use import on Demand, Use this code
plugins.push(['import', {
'libraryName': 'ant-design-vue',
'libraryDirectory': 'es',
'style': true // `style: true` 会加载 less 文件
}])
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
[
'@babel/preset-env',
{
'useBuiltIns': 'entry',
'corejs': 3
}
]
],
'plugins': [
[
'import',
{
'libraryName': 'ant-design-vue',
'libraryDirectory': 'es',
'style': 'css'
}
]
]
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "jeepay-ui-manager",
"version": "1.10.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"build:preview": "vue-cli-service build --mode preview",
"lint:nofix": "vue-cli-service lint --no-fix"
},
"dependencies": {
"@ant-design-vue/pro-layout": "^1.0.7",
"@antv/data-set": "^0.10.2",
"@antv/g2plot": "^2.3.21",
"@antv/util": "^2.0.13",
"ant-design-vue": "^1.7.2",
"axios": "^0.19.0",
"core-js": "^3.1.2",
"enquire.js": "^2.1.6",
"js-base64": "^2.5.2",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.pick": "^4.4.0",
"md5": "^2.2.1",
"mockjs2": "1.0.8",
"moment": "^2.24.0",
"nprogress": "^0.2.0",
"store": "^2.0.12",
"viser-vue": "^2.4.6",
"vue": "^2.6.10",
"vue-clipboard2": "^0.2.1",
"vue-cropper": "0.4.9",
"vue-i18n": "^8.17.4",
"vue-quill-editor": "^3.0.6",
"vue-router": "^3.1.2",
"vue-svg-component-runtime": "^1.0.1",
"vuex": "^3.1.1",
"wangeditor": "^3.1.1"
},
"devDependencies": {
"@ant-design/colors": "^3.2.1",
"@vue/cli-plugin-babel": "^4.0.4",
"@vue/cli-plugin-eslint": "^4.0.4",
"@vue/cli-plugin-router": "^4.0.4",
"@vue/cli-plugin-unit-jest": "^4.0.4",
"@vue/cli-plugin-vuex": "^4.0.4",
"@vue/cli-service": "^4.0.4",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "^1.0.0-beta.29",
"babel-eslint": "^10.0.1",
"babel-plugin-import": "^1.12.2",
"babel-plugin-transform-remove-console": "^6.9.4",
"eslint": "^5.16.0",
"eslint-plugin-html": "^5.0.0",
"eslint-plugin-vue": "^5.2.3",
"git-revision-webpack-plugin": "^3.0.6",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"opencollective": "^1.0.3",
"opencollective-postinstall": "^2.0.2",
"style-resources-loader": "^1.4.1",
"vue-cli-plugin-style-resources-loader": "^0.1.5",
"vue-svg-icon-loader": "^2.1.1",
"vue-template-compiler": "^2.6.10",
"webpack-theme-color-replacer": "^1.3.12"
}
}
module.exports = {
plugins: {
autoprefixer: {}
}
}
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="90.000000pt" height="32.000000pt" viewBox="0 0 90.000000 32.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,32.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
</g>
</svg>
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>imgs/favicon.ico">
<title>运营平台</title>
<style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
<!-- require cdn assets css -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
</head>
<body>
<noscript>
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
<div class="first-loading-wrp">
<% { %>
<img type="text/javascript" src="<%= BASE_URL %>imgs/logo.svg">
<% } %>
<div class="loading-wrp">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
<div style="display: flex; justify-content: center; align-items: center;">聚合支付</div>
</div>
</div>
<!-- require cdn assets js -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<a-config-provider :locale="locale">
<div id="app">
<router-view/>
<loading v-show="globalLoading"></loading>
</div>
</a-config-provider>
</template>
<script>
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
import Loading from './components/GlobalLoad/GlobalLoad' // 全局Loading组件
import { mapState } from 'vuex' // 引入vuex状态管理,mapState管理中存在全局loading
export default {
data () {
return {
locale: zhCN
}
},
components: {
Loading // 注册全局loading 组件
},
computed: {
// 全局 loading
...mapState([
'globalLoading'
])
}
}
</script>
import request from '@/http/request'
import { Base64 } from 'js-base64'
// 登录认证接口
export function login ({ username, password, vercode, vercodeToken }) {
const data = {
ia: Base64.encode(username), // 账号
ip: Base64.encode(password), // 密码
vc: Base64.encode(vercode), // 验证码值
vt: Base64.encode(vercodeToken) // 验证码token
}
return request.request({
url: '/api/anon/auth/validate',
method: 'post',
data: data
}, true, false, false)
}
// 获取图形验证码信息接口
export function vercode () {
return request.request({ url: '/api/anon/auth/vercode', method: 'get' }, true, true, true)
}
// 获取当前用户信息
export function getInfo () {
return request.request({
url: '/api/current/user',
method: 'get'
})
}
// 退出接口
export function logout () {
return new Promise(resolve => { resolve() })
}
import request from '@/http/request'
/*
* 全系列 restful api格式, 定义通用req对象
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/8 07:18
*/
export const req = {
// 通用列表查询接口
list: (url, params) => {
return request.request({ url: url, method: 'GET', params: params }, true, true, false)
},
// 通用新增接口
add: (url, data) => {
return request.request({ url: url, method: 'POST', data: data }, true, true, false)
},
// 通用查询单条数据接口
getById: (url, bizId) => {
return request.request({ url: url + '/' + bizId, method: 'GET' }, true, true, false)
},
// 通用修改接口
updateById: (url, bizId, data) => {
return request.request({ url: url + '/' + bizId, method: 'PUT', data: data }, true, true, false)
},
// 通用删除接口
delById: (url, bizId) => {
return request.request({ url: url + '/' + bizId, method: 'DELETE' }, true, true, false)
}
}
// 全系列 restful api格式 (全局loading方式)
export const reqLoad = {
// 通用列表查询接口
list: (url, params) => {
return request.request({ url: url, method: 'GET', params: params }, true, true, true)
},
// 通用新增接口
add: (url, data) => {
return request.request({ url: url, method: 'POST', data: data }, true, true, true)
},
// 通用查询单条数据接口
getById: (url, bizId) => {
return request.request({ url: url + '/' + bizId, method: 'GET' }, true, true, true)
},
// 通用修改接口
updateById: (url, bizId, data) => {
return request.request({ url: url + '/' + bizId, method: 'PUT', data: data }, true, true, true)
},
// 通用删除接口
delById: (url, bizId) => {
return request.request({ url: url + '/' + bizId, method: 'DELETE' }, true, true, true)
}
}
/** 角色管理页面 **/
export const API_URL_ENT_LIST = '/api/sysEnts'
export const API_URL_ROLE_LIST = '/api/sysRoles'
export const API_URL_ROLE_ENT_RELA_LIST = '/api/sysRoleEntRelas'
export const API_URL_SYS_USER_LIST = '/api/sysUsers'
export const API_URL_USER_ROLE_RELA_LIST = '/api/sysUserRoleRelas'
/** 服务商、商户管理 **/
export const API_URL_ISV_LIST = '/api/isvInfo'
export const API_URL_MCH_LIST = '/api/mchInfo'
/** 商户App管理 **/
export const API_URL_MCH_APP = '/api/mchApps'
/** 支付订单管理 **/
export const API_URL_PAY_ORDER_LIST = '/api/payOrder'
/** 退款订单管理 **/
export const API_URL_REFUND_ORDER_LIST = '/api/refundOrder'
/** 商户通知管理 **/
export const API_URL_MCH_NOTIFY_LIST = '/api/mchNotify'
/** 系统日志 **/
export const API_URL_SYS_LOG = 'api/sysLog'
/** 系统配置 **/
export const API_URL_SYS_CONFIG = 'api/sysConfigs'
/** 首页统计 **/
export const API_URL_MAIN_STATISTIC = 'api/mainChart'
/** 支付接口定义页面 **/
export const API_URL_IFDEFINES_LIST = '/api/payIfDefines'
export const API_URL_PAYWAYS_LIST = '/api/payWays'
/** 服务商、商户支付参数配置 **/
export const API_URL_ISV_PAYCONFIGS_LIST = '/api/isv/payConfigs'
export const API_URL_MCH_PAYCONFIGS_LIST = '/api/mch/payConfigs'
/** 商户支付通道配置 **/
export const API_URL_MCH_PAYPASSAGE_LIST = '/api/mch/payPassages'
/** 转账订单管理 **/
export const API_URL_TRANSFER_ORDER_LIST = '/api/transferOrders'
/** 上传图片/文件地址 **/
export const upload = {
avatar: request.baseUrl + '/api/ossFiles/avatar',
ifBG: request.baseUrl + '/api/ossFiles/ifBG',
cert: request.baseUrl + '/api/ossFiles/cert'
}
const api = {
user: '/user',
role_list: '/role',
service: '/service',
permission: '/permission',
permissionNoPager: '/permission/no-pager',
orgTree: '/org/tree'
}
export default api
/** 获取权限树状结构图 **/
export function getEntTree (sysType) {
return request.request({ url: '/api/sysEnts/showTree?sysType=' + sysType, method: 'GET' })
}
/** 退款接口 */
export function payOrderRefund (payOrderId, refundAmount, refundReason) {
return request.request({
url: '/api/payOrder/refunds/' + payOrderId,
method: 'POST',
data: { refundAmount, refundReason }
})
}
/** 更新用户角色信息 */
export function uSysUserRoleRela (sysUserId, roleIdList) {
return request.request({
url: 'api/sysUserRoleRelas/relas/' + sysUserId,
method: 'POST',
data: { roleIdListStr: JSON.stringify(roleIdList) }
})
}
export function getRoleList (parameter) {
return request({
url: '/api/sysRoles',
method: 'get',
params: parameter
})
}
export function getServiceList (parameter) {
return request({
url: api.service,
method: 'get',
params: parameter
})
}
export function getPermissions (parameter) {
return request({
url: api.permissionNoPager,
method: 'get',
params: parameter
})
}
export function getOrgTree (parameter) {
return request({
url: api.orgTree,
method: 'get',
params: parameter
})
}
// id == 0 add post
// id != 0 update put
export function saveService (parameter) {
return request({
url: api.service,
method: parameter.id === 0 ? 'post' : 'put',
data: parameter
})
}
export function saveSub (sub) {
return request({
url: '/sub',
method: sub.id === 0 ? 'post' : 'put',
data: sub
})
}
export function getIsvPayConfigUnique (infoId, ifCode) {
return request.request({
url: '/api/isv/payConfigs/' + infoId + '/' + ifCode,
method: 'get'
})
}
export function getMchPayConfigUnique (infoId, ifCode) {
return request.request({
url: '/api/mch/payConfigs/' + infoId + '/' + ifCode,
method: 'get'
})
}
export function getAvailablePayInterfaceList (mchNo, wayCode) {
return request.request({
url: '/api/mch/payPassages/availablePayInterface/' + mchNo + '/' + wayCode,
method: 'GET'
})
}
export function getPayAmountWeek () {
return request.request({
url: API_URL_MAIN_STATISTIC + '/payAmountWeek',
method: 'GET'
})
}
export function getNumCount () {
return request.request({
url: API_URL_MAIN_STATISTIC + '/numCount',
method: 'GET'
})
}
export function getPayCount (parameter) {
return request.request({
url: API_URL_MAIN_STATISTIC + '/payCount',
method: 'GET',
params: parameter
})
}
export function getPayType (parameter) {
return request.request({
url: API_URL_MAIN_STATISTIC + '/payTypeCount',
method: 'GET',
params: parameter
})
}
export function getMainUserInfo (parameter) {
return request.request({
url: API_URL_MAIN_STATISTIC + '/' + parameter,
method: 'GET'
})
}
export function updateUserPass (parameter) {
return request.request({
url: '/api/current/modifyPwd',
method: 'put',
data: parameter
})
}
export function updateUserInfo (parameter) {
return request.request({
url: '/api/current/user',
method: 'put',
data: parameter
})
}
export function getUserInfo () {
return request.request({
url: '/api/current/user',
method: 'get'
})
}
export function getConfigs (parameter) {
return request.request({
url: API_URL_SYS_CONFIG + '/' + parameter,
method: 'GET'
})
}
export function getEntBySysType (entId, sysType) {
return request.request({
url: '/api/sysEnts/bySysType',
method: 'GET',
params: { entId: entId, sysType: sysType }
})
}
export function mchNotifyResend (notifyId) {
return request.request({
url: '/api/mchNotify/resend/' + notifyId,
method: 'POST'
})
}
/** 查询支付宝授权地址URL **/
export function queryAlipayIsvsubMchAuthUrl (mchAppId) {
return request.request({
url: '/api/mch/payConfigs/alipayIsvsubMchAuthUrls/' + mchAppId,
method: 'GET'
})
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<defs>
<clipPath id="clip-path">
<rect id="矩形_2548" data-name="矩形 2548" width="32" height="32" transform="translate(6848 11598)" fill="#fff" stroke="#707070" stroke-width="1"/>
</clipPath>
<clipPath id="clip-path-2">
<rect id="矩形_2547" data-name="矩形 2547" width="32" height="29.211" transform="translate(0 0)" fill="#1953ff"/>
</clipPath>
</defs>
<g id="logo-jeepay" transform="translate(-6848 -11598)" clip-path="url(#clip-path)">
<g id="组_1000" data-name="组 1000" transform="translate(3462.959 9987.395)">
<g id="组_999" data-name="组 999" transform="translate(3385.041 1612)" clip-path="url(#clip-path-2)">
<path id="路径_4157" data-name="路径 4157" d="M3398.508,1679.5h0l-4.173,3.659a2.08,2.08,0,0,0,1.371,3.644h7.49a.729.729,0,0,0,.553-1.2Z" transform="translate(-3390.837 -1657.592)" fill="#1953ff" opacity="0.5"/>
<path id="路径_4158" data-name="路径 4158" d="M3387.958,1615.772l-2.874,20.448a4.382,4.382,0,0,0,4.339,4.991h1.628a.73.73,0,0,0,.723-.628l.762-5.418a1.459,1.459,0,0,1,1.445-1.256h4.765a1.46,1.46,0,0,0,1.446-1.256l.673-4.789a1.461,1.461,0,0,1,1.446-1.257h4.353a1.461,1.461,0,0,1,1.446,1.664l-.792,5.639-.91,6.473a.729.729,0,0,0,.722.83h2.655a4.382,4.382,0,0,0,4.339-3.772l2.874-20.448a4.381,4.381,0,0,0-4.338-4.991H3392.3A4.382,4.382,0,0,0,3387.958,1615.772Zm19.628,7.182h-4.351a1.462,1.462,0,0,1-1.447-1.665l.615-4.379a1.462,1.462,0,0,1,1.448-1.258h4.351a1.462,1.462,0,0,1,1.448,1.665l-.615,4.379A1.462,1.462,0,0,1,3407.586,1622.954Z" transform="translate(-3385.041 -1612)" fill="#1953ff"/>
</g>
</g>
</g>
</svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="90.000000pt" height="32.000000pt" viewBox="0 0 90.000000 32.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,32.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
</g>
</svg>
@jee-theme: #1A53FF; //主题色
@jee-back: #F0F2F5; //主要背景色
@jee-card-back: #FFF; //卡片底色
@jee-theme-mask: rgba(26,83,255,0.1); //主体遮罩色(10% 透明度)
@jee-strengthen: #596380; //强化色
@jee-theme-hover: #0033CC; //主题Hover
@jee-warning: #FF4B33; //危险,强化警告色
@jee-inside-link: #1A79FF; //内部链接
@jee-external-link: #AE1B6E; //外部链接
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="308" height="230" viewBox="0 0 308 230"><defs><style>.a{fill:none;}.b{clip-path:url(#a);}.c{opacity:0.05;}.d{opacity:0.1;}.e{fill:#999691;}.f{fill:#ffd200;}.g{fill:#aeb8c2;}.h{fill:#ff4633;}.i{fill:#fff;}</style><clipPath id="a"><rect class="a" width="308" height="230" transform="translate(179 574)"/></clipPath></defs><g transform="translate(-179 -574)"><g class="b"><ellipse class="c" cx="154" cy="35" rx="154" ry="35" transform="translate(179 734)"/><ellipse class="d" cx="34" cy="7.727" rx="34" ry="7.727" transform="translate(354 774.773)"/><path class="e" d="M235.514,736.729H179V716.794l40.835-86.53H244.19l-40.835,86.53h32.159V683.575h21.607v33.219h11.958v19.935H257.121V757.8H235.514Z"/><path class="e" d="M290.391,714.573V673.478c0-27.189,18.694-45.883,45.883-45.883s45.884,18.694,45.884,45.883v41.095c0,27.2-18.694,45.883-45.884,45.883S290.391,741.777,290.391,714.573Zm69.979,1.515v-44.2a24.092,24.092,0,1,0-48.184,0v44.2a24.092,24.092,0,0,0,48.184,0Z"/><path class="e" d="M487,717.948c0,24.175-19.574,42.249-43.489,42.249-19.134,0-36.313-11.871-41.809-30.378l20.642-5.582A21.347,21.347,0,0,0,443.424,740,21.571,21.571,0,0,0,465.3,717.948c0-12.578-9.03-22.242-21.875-22.242a23.294,23.294,0,0,0-11.07,3.015l-9.744-16.833,36.847-31.532H406.664V630.264H484.88v20.193L456.089,678.08C475.663,682.075,487,698.548,487,717.948Z"/><path class="f" d="M487,649.66,179,736.34v-19L487,630.66Z"/><path d="M194.8,732l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M218.8,725.241l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M242.8,718.487l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M266.8,711.733l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M290.8,704.978l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M314.8,698.224l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M338.8,691.47l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M362.8,684.715l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M386.8,677.961l-12,3.126,2.394-19.628,12-3.126Z"/><path d="M410.8,671.207l-12,3.126L401.2,654.7l12-3.126Z"/><path d="M434.8,664.452l-12,3.126L425.2,647.95l12-3.126Z"/><path d="M458.8,657.7l-12,3.126L449.2,641.2l12-3.126Z"/><path d="M482.8,650.944l-12,3.126,2.394-19.628,12-3.126Z"/><path class="g" d="M386,650V782c0,.552.895,1,2,1s2-.448,2-1V650Z"/><rect class="h" width="80" height="80" rx="9.891" transform="translate(348 574)"/><path class="i" d="M388,584a30,30,0,1,0,30,30A30,30,0,0,0,388,584Zm0,55a25,25,0,1,1,25-25A25,25,0,0,1,388,639Z"/><rect class="i" width="25" height="5" transform="translate(375.5 611.5)"/></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="308" height="230" viewBox="0 0 308 230"><defs><style>.a{fill:none;}.b{clip-path:url(#a);}.c{opacity:0.05;}.d{fill:#f5f5f7;}.e{fill:#dce0e6;}.f{fill:#24285b;}.g{fill:#596380;}.h{fill:#ffd200;}.i{fill:#fff;}.j{fill:#97a3ad;}</style><clipPath id="a"><rect class="a" width="308" height="230" transform="translate(1010 582)"/></clipPath></defs><g transform="translate(-1010 -582)"><g class="b"><ellipse class="c" cx="154" cy="35" rx="154" ry="35" transform="translate(1010 742)"/><ellipse class="c" cx="121" cy="2" rx="121" ry="2" transform="translate(1043 752)"/><rect class="d" width="228.672" height="158.82" transform="translate(1049.664 582)"/><rect class="e" width="228.672" height="125.754" transform="translate(1049.664 615.066)"/><ellipse class="f" cx="4.398" cy="4.429" rx="4.398" ry="4.429" transform="translate(1063.711 592.142)"/><ellipse class="g" cx="4.398" cy="4.429" rx="4.398" ry="4.429" transform="translate(1076.794 592.142)"/><ellipse class="h" cx="4.398" cy="4.429" rx="4.398" ry="4.429" transform="translate(1089.123 592.142)"/><path class="i" d="M1111.9,664.221a2.54,2.54,0,0,1,0-3.575l0,0,14.97-15.067a2.5,2.5,0,0,1,3.545.2,2.542,2.542,0,0,1,0,3.369l-14.971,15.077A2.5,2.5,0,0,1,1111.9,664.221Z"/><path class="i" d="M1127.106,664.464l-14.97-15.077a2.541,2.541,0,0,1-.057-3.575,2.5,2.5,0,0,1,3.55-.057c.021.02.041.041.061.062l14.961,15.067a2.542,2.542,0,0,1,.066,3.575,2.5,2.5,0,0,1-3.549.067l-.062-.062Z"/><path class="i" d="M1194.91,664.221a2.54,2.54,0,0,1-.005-3.575l.005,0,14.97-15.067a2.5,2.5,0,0,1,3.544-.2,2.539,2.539,0,0,1,.2,3.569,2.483,2.483,0,0,1-.2.2l-14.96,15.077A2.517,2.517,0,0,1,1194.91,664.221Z"/><path class="i" d="M1210.151,664.464l-14.97-15.077a2.541,2.541,0,0,1-.057-3.575,2.5,2.5,0,0,1,3.549-.057l.062.062,14.96,15.067a2.541,2.541,0,0,1,.067,3.575,2.5,2.5,0,0,1-3.549.067l-.062-.062Z"/><path class="i" d="M1142.82,707.39a2.508,2.508,0,0,1-2.49-2.326c-.7-8.605,2.54-25.543,19.8-28.01,5.392-.759,9.9.485,13.394,3.721,8.2,7.594,7.39,23.561,7.36,24.269a2.514,2.514,0,1,1-5.02-.283h0c0-.142.713-14.258-5.743-20.225-2.37-2.184-5.4-2.973-9.288-2.426-16.948,2.335-15.573,21.72-15.5,22.56a2.523,2.523,0,0,1-2.3,2.72Z"/><ellipse cx="52.8" cy="12" rx="52.8" ry="12" transform="translate(1111.2 765)"/><path class="j" d="M1065.337,756.844H1010V737.324l39.985-84.729h23.855l-39.985,84.729h31.482V704.8H1086.5v32.527H1098.2v19.519H1086.5v20.635h-21.158Z"/><path class="j" d="M1285.134,756.844H1229.8V737.324l39.985-84.729h23.855l-39.985,84.729h31.482V704.8h21.158v32.527H1318v19.519h-11.709v20.635h-21.158Z"/><path class="j" d="M1142.457,787.955a23.67,23.67,0,0,1,42.566.054,115.012,115.012,0,0,0,21.241-3.822c-5.762-17.839-21.556-29.205-42.531-29.205-20.925,0-36.693,11.3-42.5,29.049A111.5,111.5,0,0,0,1142.457,787.955Z"/></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="308" height="230" viewBox="0 0 308 230"><defs><style>.a{fill:none;}.b{clip-path:url(#a);}.c{fill:#e6e6e6;}.d{fill:#f5f5f7;}.e{fill:#9294ad;}.f{fill:#ffd200;}.g{fill:#aeb8c2;}.h{fill:#dce0e6;}.i{fill:#24285b;}.j{fill:#ff4d4d;}.k{fill:#fff;}</style><clipPath id="a"><rect class="a" width="308" height="230" transform="translate(638 574)"/></clipPath></defs><g transform="translate(-638 -574)"><g class="b"><path class="c" d="M788,799.455H748.216v-3.017h36.743V742.822H788Z"/><path class="d" d="M879.86,659.975c-.944,0-1.868.06-2.781.15v-1.3a39.4,39.4,0,0,0-39.585-39.217h-1.675c-.724-25.9-22.5-46.31-48.642-45.593a47.1,47.1,0,0,0-46.021,48.19,46.481,46.481,0,0,0,1.192,9.239,39.2,39.2,0,0,0,1.492,78.363H879.85a24.909,24.909,0,1,0,0-49.815h0Z"/><path class="e" d="M724.522,702.141c0,23.811-20.829,42.683-44.952,42.683A42.973,42.973,0,0,1,638,713.341l20.651-5.468c2.582,10.23,12.107,16.579,21.72,16.579a22.462,22.462,0,0,0,22.61-22.311c0-12.346-10.326-22.223-22.61-22.223-9.88,0-16.2,4.321-19.672,8.289l-18.338-5.82,3.917-67.2h69.61v19.93h-50.56l-1.959,31.219a39.764,39.764,0,0,1,19.94-5.644C707.342,660.693,724.522,678.33,724.522,702.141Z"/><path class="e" d="M744.521,699.142V658.224c0-27.074,18.782-45.682,46.11-45.682s46.109,18.608,46.109,45.682v40.918c0,27.074-18.782,45.682-46.109,45.682S744.521,726.216,744.521,699.142Zm70.322,1.5V656.636a24.212,24.212,0,0,0-48.424,0v44.006a24.213,24.213,0,0,0,48.424,0Z"/><path class="e" d="M853.89,699.142V658.224c0-27.074,18.782-45.682,46.11-45.682s46.11,18.608,46.11,45.682v40.918c0,27.074-18.782,45.682-46.11,45.682S853.89,726.216,853.89,699.142Zm70.322,1.5V656.636a24.212,24.212,0,0,0-48.424,0v44.006a24.213,24.213,0,0,0,48.424,0Z"/><rect class="f" width="94.385" height="58.936" transform="translate(743.832 700.859)"/><rect class="g" width="94.385" height="58.936" transform="translate(743.832 700.859)"/><rect class="h" width="111.021" height="28.91" transform="translate(735.691 676.123)"/><rect class="i" width="66.239" height="6.727" transform="translate(748.216 686.741)"/><ellipse class="j" cx="5.542" cy="5.49" rx="5.542" ry="5.49" transform="translate(823.063 685.615)"/><rect class="h" width="111.021" height="28.91" transform="translate(735.691 710.975)"/><rect class="i" width="66.239" height="6.727" transform="translate(748.216 721.584)"/><ellipse class="k" cx="5.542" cy="5.49" rx="5.542" ry="5.49" transform="translate(823.063 720.468)"/><rect class="h" width="111.021" height="28.91" transform="translate(735.691 745.828)"/><rect class="i" width="66.239" height="6.727" transform="translate(748.216 756.437)"/><ellipse class="k" cx="5.542" cy="5.49" rx="5.542" ry="5.49" transform="translate(823.063 755.321)"/><rect class="c" width="36.581" height="3.017" transform="translate(786.482 796.438)"/><ellipse class="i" cx="5.988" cy="5.933" rx="5.988" ry="5.933" transform="translate(743.842 792.134)"/><ellipse class="i" cx="5.988" cy="5.933" rx="5.988" ry="5.933" transform="translate(817.074 792.134)"/></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="76" height="76" viewBox="0 0 76 76"><defs><style>.a{fill:#1953ff;}</style></defs><path class="a" d="M-1198-1084a6.007,6.007,0,0,1-6-6v-64a6.007,6.007,0,0,1,6-6h64a6.007,6.007,0,0,1,6,6v64a6.006,6.006,0,0,1-6,6Zm-4-70v64a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4v-64a4,4,0,0,0-4-4h-64A4.005,4.005,0,0,0-1202-1154Zm35,47v-14h-14a1,1,0,0,1-1-1,1,1,0,0,1,1-1h14v-14a1,1,0,0,1,1-1,1,1,0,0,1,1,1v14h14a1,1,0,0,1,1,1,1,1,0,0,1-1,1h-14v14a1,1,0,0,1-1,1A1,1,0,0,1-1167-1107Z" transform="translate(1204 1160)"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="76" height="76" viewBox="0 0 76 76"><defs><style>.a{fill:#ccced1;}</style></defs><path class="a" d="M-1198-1084a6.007,6.007,0,0,1-6-6v-64a6.007,6.007,0,0,1,6-6h64a6.007,6.007,0,0,1,6,6v64a6.006,6.006,0,0,1-6,6Zm-4-70v64a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4v-64a4,4,0,0,0-4-4h-64A4.005,4.005,0,0,0-1202-1154Zm35,47v-14h-14a1,1,0,0,1-1-1,1,1,0,0,1,1-1h14v-14a1,1,0,0,1,1-1,1,1,0,0,1,1,1v14h14a1,1,0,0,1,1,1,1,1,0,0,1-1,1h-14v14a1,1,0,0,1-1,1A1,1,0,0,1-1167-1107Z" transform="translate(1204 1160)"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920" height="970" viewBox="0 0 1920 970"><defs><style>.a{fill:url(#a);}.b{clip-path:url(#b);}.c{filter:url(#n);}.d{filter:url(#k);}.e{filter:url(#h);}.f{filter:url(#e);}</style><linearGradient id="a" y1="0.383" x2="1" y2="0.589" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#f1f8ff"/><stop offset="1" stop-color="#f7fbfe"/></linearGradient><clipPath id="b"><rect class="a" width="1920" height="970"/></clipPath><filter id="e" x="-169" y="375" width="756" height="756" filterUnits="userSpaceOnUse"><feOffset dx="20" dy="-20" input="SourceAlpha"/><feGaussianBlur stdDeviation="30" result="f"/><feFlood flood-color="#211f47" flood-opacity="0.071"/><feComposite operator="in" in2="f"/><feComposite in="SourceGraphic"/></filter><filter id="h" x="1230" y="568" width="433" height="433" filterUnits="userSpaceOnUse"><feOffset dx="2" dy="-10" input="SourceAlpha"/><feGaussianBlur stdDeviation="30" result="i"/><feFlood flood-color="#211f47" flood-opacity="0.071"/><feComposite operator="in" in2="i"/><feComposite in="SourceGraphic"/></filter><filter id="k" x="1440" y="88.44" width="655.121" height="655.121" filterUnits="userSpaceOnUse"><feOffset dx="2" dy="30" input="SourceAlpha"/><feGaussianBlur stdDeviation="30" result="l"/><feFlood flood-color="#211f47" flood-opacity="0.122"/><feComposite operator="in" in2="l"/><feComposite in="SourceGraphic"/></filter><filter id="n" x="422.061" y="2.561" width="350.879" height="350.879" filterUnits="userSpaceOnUse"><feOffset dx="2" dy="10" input="SourceAlpha"/><feGaussianBlur stdDeviation="30" result="o"/><feFlood flood-color="#211f47" flood-opacity="0.122"/><feComposite operator="in" in2="o"/><feComposite in="SourceGraphic"/></filter></defs><g class="b"><rect class="a" width="1920" height="970"/><g class="f" transform="matrix(1, 0, 0, 1, 0, 0)"><circle class="a" cx="288" cy="288" r="288" transform="translate(-99 485)"/></g><g class="e" transform="matrix(1, 0, 0, 1, 0, 0)"><circle class="a" cx="126.5" cy="126.5" r="126.5" transform="translate(1318 668)"/></g><g class="d" transform="matrix(1, 0, 0, 1, 0, 0)"><rect class="a" width="335.961" height="335.961" rx="30" transform="translate(1528 386) rotate(-45)"/></g><g class="c" transform="matrix(1, 0, 0, 1, 0, 0)"><rect class="a" width="170.879" height="170.879" transform="translate(510.06 82.56)"/></g></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14"><defs><style>.a{fill:#fff;stroke:#707070;}.b{fill:#596380;}.c{clip-path:url(#a);}.d{clip-path:url(#b);}</style><clipPath id="a"><rect class="a" width="14" height="14" transform="translate(796 520)"/></clipPath><clipPath id="b"><rect class="b" width="12" height="12" transform="translate(0)"/></clipPath></defs><g class="c" transform="translate(-796 -520)"><g transform="translate(-100 289.628)"><g class="d" transform="translate(897 231.372)"><path class="b" d="M1182.9,231.267h-3.343v3.745a2.254,2.254,0,0,1,0,4.508v3.75h3.343a2.291,2.291,0,0,0,2.258-2.308V233.58a2.289,2.289,0,0,0-2.258-2.313Zm0,0" transform="translate(-1173.16 -231.269)"/><path class="b" d="M903.133,235.581a1.657,1.657,0,0,0-.531.086v-4.3h-3.343A2.291,2.291,0,0,0,897,233.68v7.381a2.291,2.291,0,0,0,2.258,2.308H902.6v-4.293a1.713,1.713,0,0,0,.531.086,1.791,1.791,0,0,0,0-3.582Zm0,0" transform="translate(-897 -231.372)"/></g></g></g></svg>
\ No newline at end of file
<?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"><svg t="1621586617012" class="icon" viewBox="0 0 1146 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6384" xmlns:xlink="http://www.w3.org/1999/xlink" width="17.90625" height="16"><defs><style type="text/css"></style></defs><path d="M0 788.389381a507.469027 108.743363 0 1 0 1014.938053 0 507.469027 108.743363 0 1 0-1014.938053 0Z" fill="#F3F3F8" p-id="6385"></path><path d="M321.880354 246.303717h367.915044l174.714337 220.749026s-174.895575 180.151504-352.328496 179.607788-357.584425-181.238938-357.584425-181.238938z" fill="#FBFBFD" p-id="6386"></path><path d="M797.451327 815.575221H217.486726a63.433628 63.433628 0 0 1-63.433629-63.433628V471.221239h166.558584l14.680354 29.360708A81.738761 81.738761 0 0 0 416.849558 579.964602h199.362831a63.614867 63.614867 0 0 0 63.433629-60.533806L711.72531 471.221239h149.159646v280.920354A63.433628 63.433628 0 0 1 797.451327 815.575221z" fill="#FBFBFD" p-id="6387"></path><path d="M797.451327 806.513274a54.371681 54.371681 0 0 0 54.371682-54.371681v-271.858407h-135.385487L688.707965 522.330619a72.495575 72.495575 0 0 1-72.495576 66.69593H416.849558a90.619469 90.619469 0 0 1-90.61947-86.088496l-11.236814-22.654867H163.115044v271.858407a54.371681 54.371681 0 0 0 54.371682 54.371681h579.964601m0 18.123894H217.486726a72.495575 72.495575 0 0 1-72.495576-72.495575v-289.982301h181.238938l18.123894 36.247788a72.495575 72.495575 0 0 0 72.495576 72.495575h199.362831a54.371681 54.371681 0 0 0 54.371682-54.371682l36.247787-54.371681h163.115045v289.982301a72.495575 72.495575 0 0 1-72.495576 72.495575z" fill="#E2E2EE" p-id="6388"></path><path d="M863.059823 467.958938L684.539469 253.734513H330.398584L151.87823 467.958938l-13.774159-11.599292L322.061593 235.610619h370.814867l183.957522 220.749027z" fill="#E2E2EE" p-id="6389"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="90.000000pt" height="32.000000pt" viewBox="0 0 90.000000 32.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,32.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14"><defs><style>.a{fill:#fff;stroke:#707070;}.b{fill:#596380;}.c{clip-path:url(#a);}.d{clip-path:url(#b);}</style><clipPath id="a"><rect class="a" width="14" height="14" transform="translate(786 458)"/></clipPath><clipPath id="b"><rect class="b" width="11" height="14"/></clipPath></defs><g class="c" transform="translate(-786 -458)"><g transform="translate(155.5 328)"><g transform="translate(632 130)"><g class="d" transform="translate(0)"><path class="b" d="M636.689,140.115V141.7a.687.687,0,1,0,1.374,0v-1.584a1.4,1.4,0,0,0,.687-1.21,1.374,1.374,0,1,0-2.748,0,1.4,1.4,0,0,0,.687,1.21Zm-2.061-4.7v-2.1a2.748,2.748,0,1,1,5.5,0v2.1H641.5a1.386,1.386,0,0,1,1.374,1.4V143.1a1.386,1.386,0,0,1-1.374,1.4h-8.243a1.386,1.386,0,0,1-1.374-1.4v-6.288a1.386,1.386,0,0,1,1.374-1.4Zm2.748-3.493a1.386,1.386,0,0,0-1.374,1.4v2.1h2.748v-2.1a1.386,1.386,0,0,0-1.374-1.4Zm0,0" transform="translate(-631.882 -130.511)"/></g></g></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90.667" height="22" viewBox="0 0 90.667 22"><defs><clipPath id="a"><path d="M2404.472,992h-22.8l3.2-22h22.8Z" transform="translate(-2381.676 -970)" fill="#1a53ff"/></clipPath><clipPath id="b"><rect width="62.667" height="16" fill="#262626"/></clipPath></defs><g transform="translate(-30 -21)"><g transform="translate(30 21)"><g clip-path="url(#a)"><path d="M2388.177,1037.5h0l-6.5,5.5h11.4Z" transform="translate(-2381.676 -1021)" fill="#1a53ff" opacity="0.5"/><path d="M2384.881,970l-3.2,22h5.7l.8-5.5h5.7l.8-5.5h5.7l-.8,5.5-.8,5.5h5.7l3.2-22Zm15.9,8.25h-5.7l.8-5.5h5.7Z" transform="translate(-2381.677 -970)" fill="#1a53ff"/></g></g><g transform="translate(-628.729 -2051)"><g transform="translate(686.73 2076)" clip-path="url(#b)"><path d="M930.451,2092.935a2.5,2.5,0,0,0-2.543-1.935h-5.479l-.225,1.6h5.367a1.366,1.366,0,0,1,1.082.476,1.014,1.014,0,0,1-.781,1.657h-3.554a3.383,3.383,0,0,0-3.243,2.844l-.175,1.245a2.414,2.414,0,0,0,2.443,2.845h2.488a3.952,3.952,0,0,0,2.037-.588l-.082.588h1.6l.5-3.556.474-3.378.125-.889h0A2.585,2.585,0,0,0,930.451,2092.935Zm-1.915,3.4-.1.711-.15,1.067a2.329,2.329,0,0,1-2.229,1.956h-2.488a1.057,1.057,0,0,1-1.069-1.244l.175-1.245a1.482,1.482,0,0,1,1.419-1.244h3.554a2.665,2.665,0,0,0,.911-.163Z" transform="translate(-879.269 -2088.333)" fill="#262626"/><path d="M802.041,2091h-3.2a3.383,3.383,0,0,0-3.242,2.844l-.237,1.689-.225,1.6-.237,1.689a2.414,2.414,0,0,0,2.443,2.845h3.2a3.383,3.383,0,0,0,3.242-2.845l.013-.089h-1.6l-.013.089a1.482,1.482,0,0,1-1.418,1.244h-3.2a1.057,1.057,0,0,1-1.069-1.244l.237-1.689h7.285l.225-1.6.237-1.689A2.414,2.414,0,0,0,802.041,2091Zm-5.079,4.533.237-1.689a1.482,1.482,0,0,1,1.419-1.244h3.2a1.057,1.057,0,0,1,1.069,1.244l-.237,1.689Z" transform="translate(-775.657 -2088.333)" fill="#262626"/><path d="M740.041,2091h-3.2a3.383,3.383,0,0,0-3.242,2.844l-.237,1.689-.225,1.6-.237,1.689a2.415,2.415,0,0,0,2.444,2.845h3.2a3.383,3.383,0,0,0,3.242-2.845l.013-.089h-1.6l-.013.089a1.482,1.482,0,0,1-1.418,1.244h-3.2a1.057,1.057,0,0,1-1.069-1.244l.237-1.689h7.285l.225-1.6.237-1.689A2.414,2.414,0,0,0,740.041,2091Zm-5.079,4.533.237-1.689a1.482,1.482,0,0,1,1.418-1.244h3.2a1.057,1.057,0,0,1,1.069,1.244l-.237,1.689Z" transform="translate(-724.674 -2088.333)" fill="#262626"/><path d="M693.933,2076H690.38l-.25,1.778h1.777l-.849,6.044-.325,2.311a1.694,1.694,0,0,1-1.621,1.423H686.98l-.25,1.777h2.132a3.806,3.806,0,0,0,3.648-3.2l1.174-8.355h0Z" transform="translate(-686.73 -2076)" fill="#262626"/><path d="M990.5,2091l-.225,1.6-.325,2.311-.474,3.377-.075.533a1.482,1.482,0,0,1-1.418,1.244h-3.2a1.057,1.057,0,0,1-1.069-1.244l.549-3.911.325-2.311.225-1.6h-1.6l-1.1,7.822a2.414,2.414,0,0,0,2.443,2.845h3.2a3.094,3.094,0,0,0,1.284-.29l-.041.29a1.268,1.268,0,0,1-1.216,1.066H982.1l-.225,1.6h5.686a3.171,3.171,0,0,0,3.04-2.666l.4-2.845.075-.533L992.1,2091Z" transform="translate(-929.43 -2088.333)" fill="#262626"/><path d="M862.644,2091h-3.2a3.094,3.094,0,0,0-1.285.29l.041-.29h-1.6l-.4,2.844-.7,4.978-.774,5.511h1.6l.415-2.956a2.574,2.574,0,0,0,1.2.29h3.2a3.383,3.383,0,0,0,3.242-2.845l.7-4.978A2.414,2.414,0,0,0,862.644,2091Zm.145,7.822a1.482,1.482,0,0,1-1.419,1.244h-3.2a1.057,1.057,0,0,1-1.069-1.244l.7-4.978a1.482,1.482,0,0,1,1.418-1.244h3.2a1.057,1.057,0,0,1,1.069,1.244Z" transform="translate(-824.879 -2088.333)" fill="#262626"/></g></g></g></svg>
\ No newline at end of file
<?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"><svg t="1621851766037" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2360" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><defs><style type="text/css"></style></defs><path d="M243.2 512c0-49.664-39.936-89.6-89.6-89.6S64 462.336 64 512s39.936 89.6 89.6 89.6 89.6-39.936 89.6-89.6z m179.2 0c0 49.664 39.936 89.6 89.6 89.6s89.6-39.936 89.6-89.6-39.936-89.6-89.6-89.6-89.6 39.936-89.6 89.6z m358.4 0c0 49.664 39.936 89.6 89.6 89.6S960 561.664 960 512s-39.936-89.6-89.6-89.6c-49.152 0-89.6 39.936-89.6 89.6z" p-id="2361" fill="#2c2c2c"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="20.001" height="20" viewBox="0 0 20.001 20"><defs><style>.a{fill:#252626;}</style></defs><path class="a" d="M-3862-1142a3,3,0,0,1-3-3v-14a3,3,0,0,1,3-3h14a3,3,0,0,1,3,3v14a3,3,0,0,1-3,3Zm-1-17v14a1,1,0,0,0,1,1h14a1,1,0,0,0,1-1v-14a1,1,0,0,0-1-1h-14A1,1,0,0,0-3863-1159Zm11,12v-2a1,1,0,0,1,1-1,1,1,0,0,1,1,1v2a1,1,0,0,1-1,1A1,1,0,0,1-3852-1147Zm-4,0v-10a1,1,0,0,1,1-1,1,1,0,0,1,1,1v10a1,1,0,0,1-1,1A1,1,0,0,1-3856-1147Zm-4,0v-7a1,1,0,0,1,1-1,1,1,0,0,1,1,1v7a1,1,0,0,1-1,1A1,1,0,0,1-3860-1147Z" transform="translate(3865 1162)"/></svg>
\ No newline at end of file
<?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"><svg t="1619595883977" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3229" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M495.055 647.042a26.196 26.196 0 0 1-18.583-7.697L250.689 413.561c-10.263-10.263-10.263-26.903 0-37.166 10.263-10.263 26.903-10.263 37.166 0l207.2 207.2 207.2-207.2c10.263-10.263 26.904-10.263 37.167 0 10.262 10.263 10.262 26.903 0 37.166L513.638 639.345a26.196 26.196 0 0 1-18.583 7.697z" fill="#2c2c2c" p-id="3230"></path></svg>
\ No newline at end of file
<?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"><svg t="1619597015272" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5736" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M617.6 233.6c12.8-12.8 32-12.8 44.8 0 12.8 12.8 12.8 28.8 3.2 41.6l-3.2 3.2-233.6 233.6 233.6 233.6c12.8 12.8 12.8 28.8 3.2 41.6l-3.2 3.2c-12.8 12.8-28.8 12.8-41.6 3.2l-3.2-3.2-256-256c-12.8-12.8-12.8-28.8-3.2-41.6l3.2-3.2 256-256z" fill="#2c2c2c" p-id="5737"></path></svg>
\ No newline at end of file
<?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"><svg t="1619597331494" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1762" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M406.4 233.6c-12.8-12.8-32-12.8-44.8 0-12.8 12.8-12.8 28.8-3.2 41.6l3.2 3.2 233.6 233.6-233.6 233.6c-12.8 12.8-12.8 28.8-3.2 41.6l3.2 3.2c12.8 12.8 28.8 12.8 41.6 3.2l3.2-3.2 256-256c12.8-12.8 12.8-28.8 3.2-41.6l-3.2-3.2-256-256z" fill="#2c2c2c" p-id="1763"></path></svg>
\ No newline at end of file
<?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"><svg t="1619595944852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4607" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M495.055 308.698a26.196 26.196 0 0 0-18.583 7.697L250.689 542.179c-10.263 10.263-10.263 26.903 0 37.166s26.903 10.263 37.166 0l207.2-207.2 207.2 207.2c10.263 10.263 26.904 10.263 37.167 0 10.262-10.263 10.262-26.903 0-37.166L513.638 316.395a26.196 26.196 0 0 0-18.583-7.697z" fill="#2c2c2c" p-id="4608"></path></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14"><defs><style>.a,.b{fill:#1a53ff;}.a{stroke:#707070;}.c{clip-path:url(#a);}.d{clip-path:url(#b);}</style><clipPath id="a"><rect class="a" width="14" height="14" transform="translate(796 520)"/></clipPath><clipPath id="b"><rect class="b" width="12" height="12" transform="translate(0)"/></clipPath></defs><g class="c" transform="translate(-796 -520)"><g transform="translate(-100 289.628)"><g class="d" transform="translate(897 231.372)"><path class="b" d="M1182.9,231.267h-3.343v3.745a2.254,2.254,0,0,1,0,4.508v3.75h3.343a2.291,2.291,0,0,0,2.258-2.308V233.58a2.289,2.289,0,0,0-2.258-2.313Zm0,0" transform="translate(-1173.16 -231.269)"/><path class="b" d="M903.133,235.581a1.657,1.657,0,0,0-.531.086v-4.3h-3.343A2.291,2.291,0,0,0,897,233.68v7.381a2.291,2.291,0,0,0,2.258,2.308H902.6v-4.293a1.713,1.713,0,0,0,.531.086,1.791,1.791,0,0,0,0-3.582Zm0,0" transform="translate(-897 -231.372)"/></g></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14"><defs><style>.a,.b{fill:#1a53ff;}.a{stroke:#707070;}.c{clip-path:url(#a);}.d{clip-path:url(#b);}</style><clipPath id="a"><rect class="a" width="14" height="14" transform="translate(786 458)"/></clipPath><clipPath id="b"><rect class="b" width="11" height="14"/></clipPath></defs><g class="c" transform="translate(-786 -458)"><g transform="translate(155.5 328)"><g transform="translate(632 130)"><g class="d" transform="translate(0)"><path class="b" d="M636.689,140.115V141.7a.687.687,0,1,0,1.374,0v-1.584a1.4,1.4,0,0,0,.687-1.21,1.374,1.374,0,1,0-2.748,0,1.4,1.4,0,0,0,.687,1.21Zm-2.061-4.7v-2.1a2.748,2.748,0,1,1,5.5,0v2.1H641.5a1.386,1.386,0,0,1,1.374,1.4V143.1a1.386,1.386,0,0,1-1.374,1.4h-8.243a1.386,1.386,0,0,1-1.374-1.4v-6.288a1.386,1.386,0,0,1,1.374-1.4Zm2.748-3.493a1.386,1.386,0,0,0-1.374,1.4v2.1h2.748v-2.1a1.386,1.386,0,0,0-1.374-1.4Zm0,0" transform="translate(-631.882 -130.511)"/></g></g></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14"><defs><style>.a,.b{fill:#1a53ff;}.a{stroke:#707070;}.c{clip-path:url(#a);}.d{clip-path:url(#b);}</style><clipPath id="a"><rect class="a" width="14" height="14" transform="translate(793 387)"/></clipPath><clipPath id="b"><rect class="b" width="13" height="14" transform="translate(0 0)"/></clipPath></defs><g class="c" transform="translate(-793 -387)"><g transform="translate(216.5 256)"><g class="d" transform="translate(577 131)"><path class="b" d="M740.84,134.733A3.714,3.714,0,1,0,744.554,131a3.724,3.724,0,0,0-3.714,3.733Zm0,0" transform="translate(-738.054 -131)"/><path class="b" d="M581.643,622.52h3.714a4.631,4.631,0,0,1,3.283,1.367,4.679,4.679,0,0,1,1.36,3.3v.933H577v-.933a4.679,4.679,0,0,1,1.36-3.3,4.631,4.631,0,0,1,3.283-1.367Zm0,0" transform="translate(-577 -614.12)"/></g></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14"><defs><style>.a,.b{fill:#596380;}.a{stroke:#707070;}.c{clip-path:url(#a);}.d{clip-path:url(#b);}</style><clipPath id="a"><rect class="a" width="14" height="14" transform="translate(793 387)"/></clipPath><clipPath id="b"><rect class="b" width="13" height="14" transform="translate(0 0)"/></clipPath></defs><g class="c" transform="translate(-793 -387)"><g transform="translate(216.5 256)"><g class="d" transform="translate(577 131)"><path class="b" d="M740.84,134.733A3.714,3.714,0,1,0,744.554,131a3.724,3.724,0,0,0-3.714,3.733Zm0,0" transform="translate(-738.054 -131)"/><path class="b" d="M581.643,622.52h3.714a4.631,4.631,0,0,1,3.283,1.367,4.679,4.679,0,0,1,1.36,3.3v.933H577v-.933a4.679,4.679,0,0,1,1.36-3.3,4.631,4.631,0,0,1,3.283-1.367Zm0,0" transform="translate(-577 -614.12)"/></g></g></g></svg>
\ No newline at end of file
<template>
<global-footer class="footer custom-render">
<template v-slot:links>
</template>
<template v-slot:copyright>
Copyright © 2023 <a href="http://www.juhezhifu.com" target="_blank">juhezhifu.com</a>. All rights reserved.
</template>
</global-footer>
</template>
<script>
import { GlobalFooter } from '@ant-design-vue/pro-layout'
export default {
name: 'ProGlobalFooter',
components: {
GlobalFooter
}
}
</script>
<template>
<a-dropdown placement="bottomRight">
<span class="ant-pro-account-avatar">
<a-avatar size="small" :src="greetImg" class="antd-pro-global-header-index-avatar" />
<span>{{ currentUserName }}</span>
</span>
<template v-slot:overlay>
<a-menu class="ant-pro-drop-down menu" :selected-keys="[]">
<a-menu-item v-if="$access('ENT_C_USERINFO')" key="settings" @click="handleToSettings">
<a-icon type="setting" />
账户设置
</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout" @click="handleLogout">
<a-icon type="logout" />
退出登录
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
<script>
export default {
name: 'AvatarDropdown',
props: {
},
data: function () {
return {
// greetImg: '', // 头像图片地址
sex: ''
}
},
computed: {
// 返回用户名
currentUserName () {
return this.$store.state.user.userName
},
// 返回头像
greetImg () {
return this.$store.state.user.avatarImgPath
}
},
methods: {
handleToSettings () {
this.$router.push({ name: 'ENT_C_USERINFO' })
},
handleLogout: function (e) {
this.$infoBox.confirmPrimary('确认退出?', '', () => {
this.$store.dispatch('Logout').then(() => {
this.$router.push({ name: 'login' })
})
})
}
}
}
</script>
<style lang="less" scoped>
.ant-pro-drop-down {
/deep/ .action {
margin-right: 8px;
}
/deep/ .ant-dropdown-menu-item {
min-width: 160px;
}
}
</style>
<template>
<div :class="wrpCls">
<avatar-dropdown :menu="showMenu" :current-user="currentUser" :class="prefixCls" />
</div>
</template>
<script>
import AvatarDropdown from './AvatarDropdown'
// import store from '@/store'
export default {
name: 'RightContent',
components: {
AvatarDropdown
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-global-header-index-action'
},
isMobile: {
type: Boolean,
default: () => false
},
topMenu: {
type: Boolean,
required: true
},
theme: {
type: String,
required: true
}
},
data () {
return {
showMenu: true,
currentUser: {}
}
},
computed: {
wrpCls () {
return {
'ant-pro-global-header-index-right': true,
[`ant-pro-global-header-index-${(this.isMobile || !this.topMenu) ? 'light' : this.theme}`]: true
}
}
},
mounted () {
// console.log(store)
this.currentUser = {
name: 'dd'
}
}
}
</script>
<template>
<div class="loading">
<div>
<a-spin size="large" />
</div>
</div>
</template>
<script>
export default {
name: 'GlobalLoad',
data () {
return {}
}
}
</script>
<style scoped>
.loading{
position: fixed;
top:0;
left:0;
z-index:100;
width: 100%;
height: 100%;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255,255,255,0.25);
}
</style>
<template>
<div>
<a-row :gutter="[24,24]" style="width:100%">
<!-- 卡片默认新增框 -->
<a-col
:xxl="24/span.xxl"
:xl="24/span.xl"
:lg="24/span.lg"
:md="24/span.md"
:sm="24/span.sm"
:xs="24/span.xs"
@click="$emit('addJeepayCard')"
v-if="addAuthority"
>
<div class="jeepay-card-add" :style="{'height': height + 'px'}">
<div class="jeepay-card-add-top">
<img src="~@/assets/svg/add-icon.svg" alt="add-icon" class="jeepay-card-add-icon">
<img src="~@/assets/svg/add-icon-hover.svg" alt="add-icon" class="jeepay-card-add-icon-hover">
</div>
<div class="jeepay-card-add-text">
新建{{ name }}
</div>
</div>
</a-col>
<!-- 数据 -->
<a-col
v-for="(item, key) in cardList"
:key="key"
:xxl="24/span.xxl"
:xl="24/span.xl"
:lg="24/span.lg"
:md="24/span.md"
:sm="24/span.sm"
:xs="24/span.xs"
>
<slot name="cardContentSlot" :record="item"></slot>
<slot name="cardOpSlot" :record="item"></slot>
</a-col>
</a-row>
</div>
</template>
<script>
export default {
name: 'JeepayCard',
props: {
span: { type: Object, default: () => ({ xxl: 6, xl: 4, lg: 4, md: 3, sm: 2, xs: 1 }) },
height: { type: Number, default: 200 },
name: { type: String, default: '' },
addAuthority: { type: Boolean, default: false },
reqCardListFunc: { type: Function, default: () => () => ({}) }
},
data () {
return {
cardList: []
}
},
created () {
this.refCardList()
},
methods: {
refCardList () {
const that = this
this.reqCardListFunc().then(resData => {
that.cardList = resData
})
}
}
}
</script>
<style lang="less" scoped>
.jeepay-card-add {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 2px dashed rgba(0, 0, 0, 0.15);
background: rgba(0, 0, 0, 0.03);
border-radius: 6px;
box-sizing: border-box;
cursor: pointer;
}
.jeepay-card-add-top {
width: 80px;
height: 80px;
position: relative;
}
.jeepay-card-add:hover {
border-color: rgba(25,83,255,0.3);
background: rgba(25,83,255,0.06);
transition: all 0.3s ease-in-out;
}
.jeepay-card-add:hover .jeepay-card-add-icon {
opacity: 0;
transition: all 0.2s ease-in-out;
}
.jeepay-card-add:hover .jeepay-card-add-icon-hover {
opacity: 1;
transition: all 0.5s ease-in-out;
}
.jeepay-card-add:hover .jeepay-card-add-text {
color: rgba(25,83,255,1);
transition: all 0.3s ease-in-out;
}
.jeepay-card-add-icon {
position: absolute;
width: 80px;
height: 80px;
opacity: 1;
}
.jeepay-card-add-icon-hover {
position: absolute;
width: 80px;
height: 80px;
opacity: 0;
}
.jeepay-card-add-text {
padding-top: 5px;
font-size: 13px;
color: rgba(0, 0, 0, 0.35);
}
</style>
<!--
Jeepay 通用表格, 支持基础分页, 检索
@author terrfly
@site https://www.jeepay.vip
@date 2021/5/8 07:18
-->
<template>
<div>
<a-table
:columns="tableColumns"
:data-source="apiResData.records"
:pagination="pagination"
:loading="showLoading"
@change="handleTableChange"
:row-selection="rowSelection"
:rowKey="rowKey"
:scroll="{ x: scrollX }"
:customRow="(record, index) => {
if(!tableRowCrossColor){
return {};
}
return { style: { 'background-color': index % 2 == 0 ? '#FCFCFC' : '#FFFFFF'} }
}"
>
<!-- 自定义列插槽, 参考:https://github.com/feseed/admin-antd-vue/blob/master/src/components/ShTable.vue -->
<!-- eslint-disable-next-line -->
<template v-for="colCustom in columnsCustomSlots" :slot="colCustom.customRender" slot-scope="record">
<slot :name="colCustom.customRender" :record="record"></slot>
</template>
</a-table>
</div>
</template>
<script>
export default {
name: 'JeepayTable', // 定义组件名称
// 传递数据参数 ( 父-->子 参数 )
props: {
initData: { type: Boolean, default: true }, // 初始化列表数据, 默认true
tableColumns: Array, // 表格数组列
reqTableDataFunc: { type: Function }, // 请求列表数据
currentChange: { type: Function, default: (v1, v2) => {} }, // 更新当前选择行事件, 默认空函数
searchData: Object, // 搜索条件参数
pageSize: { type: Number, default: 10 }, // 默认每页条数
rowSelection: Object, // checkbox选择
rowKey: { type: [String, Function] }, // 定义rowKey 如果不定义将会出现(树状结构出问题, checkbox不消失等)
scrollX: { type: Number, default: 500 }, // 表格显示滚动条的宽度
tableRowCrossColor: { type: Boolean, default: false } // 是隔行换色
},
data () {
return {
apiResData: { total: 0, records: [] }, // 接口返回数据
iPage: { pageNumber: 1, pageSize: this.pageSize }, // 默认table 分页/排序请求后端格式
pagination: { total: 0, current: 1, pageSize: this.pageSize, showSizeChanger: true, showTotal: total => `共${total}条` }, // ATable 分页配置项
showLoading: false
}
},
// 计算属性
computed: {
columnsCustomSlots () { // 自定义列插槽 1. 过滤器仅获取到包含slot属性的元素, 2. 返回slot数组
return this.tableColumns.filter(item => item.scopedSlots).map(item => item.scopedSlots)
}
},
mounted () {
if (this.initData) { // 是否自动加载数据
this.refTable(true)
}
},
methods: {
handleTableChange (pagination, filters, sorter) { // 分页、排序、筛选变化时触发
this.pagination = pagination
this.iPage = {
pageSize: pagination.pageSize, // 每页条数
pageNumber: pagination.current, // 当前页码
sortField: sorter.columnKey, // 排序字段
sortOrder: sorter.order, // 排序顺序
...filters // 过滤数据
}
this.refTable()
},
// 查询数据
refTable (isToFirst = false) {
const that = this
if (isToFirst) {
this.iPage.pageNumber = 1
this.pagination.current = 1
}
// 更新检索数据
this.showLoading = true
this.reqTableDataFunc(Object.assign({}, this.iPage, this.searchData)).then(resData => {
this.pagination.total = resData.total // 更新总数量
this.apiResData = resData // 列表数据更新
this.showLoading = false // 关闭loading
// 数据为0 ,并且为当前页面没有在第一页则需要自动跳转到上一页(解决,删除第二页数据全部删除后无数据的情况 )
if (resData.records.length === 0 && this.iPage.pageNumber > 1) {
that.$nextTick(() => {
// 最大页码
const maxPageNumber = (resData.total / this.iPage.pageSize) + ((resData.total % this.iPage.pageSize) === 0 ? 0 : 1)
if (maxPageNumber === 0) { // 已经无数据
return false
}
// 跳转到的页码
const toPageSize = (this.iPage.pageNumber - 1) > maxPageNumber ? maxPageNumber : (this.iPage.pageNumber - 1)
this.iPage.pageNumber = toPageSize
this.pagination.current = toPageSize
that.refTable(false)
})
}
// 请求成功后,关闭查询按钮的loading
that.$emit('btnLoadClose')
}).catch(res => {
this.showLoading = false
that.$emit('btnLoadClose')
}) // 关闭loading
}
}
}
</script>
<style lang="less">
// 调整antdv table默认padding高度
.ant-table-fixed{
tr{
th{
padding: 8px 8px !important;
}
th:first-child{ // 第一个表格 左填充16, 其他为8
padding-left: 16px !important;
}
td{
padding: 8px 8px !important;
}
td:first-child{
padding-left: 16px !important;
}
}
}
</style>
<!--
Jeepay 通用状态切换按钮, 支持switch和badge两个格式, 根据权限进行判断
@author terrfly
@site https://www.jeepay.vip
@date 2021/5/8 07:18
-->
<template>
<div>
<template v-if="!showSwitchType">
<div v-if="state == 0" ><a-badge status="error" text="停用" /></div>
<div v-else-if="state == 1" ><a-badge status="processing" text="启用" /></div>
<div v-else ><a-badge status="warning" text="未知" /></div>
</template>
<template v-if="showSwitchType">
<a-switch class="els" checked-children="启用" un-checked-children="停用" :checked="switchChecked" @change="onChangeInner" />
</template>
</div>
</template>
<script>
export default {
name: 'JeepayTableColState', // 定义组件名称
// 传递数据参数 ( 父-->子 参数 )
props: {
state: { type: Number, default: -1 }, // 初始化列表数据, 默认true
showSwitchType: { type: Boolean, default: false }, // 默认 badge
onChange: { type: Function, default: (checked) => { return new Promise(resolve => { resolve() }) } } // change回调事件
},
data: function () {
return { switchChecked: false }
},
mounted () {
this.switchChecked = this.state === 1
},
watch: {
state: function (o, n) {
this.switchChecked = this.state === 1
}
},
methods: {
onChangeInner: function (checked) {
const that = this
this.switchChecked = checked
// 回调输出 0 和 1; 成功不需要处理, 失败需要经状态变更为原始状态
this.onChange((checked ? 1 : 0)).then().catch(res => {
that.$nextTick(() => { that.switchChecked = !checked })
})
}
}
}
</script>
<!--
Jeepay 通用列,添加更多菜单项
@author terrfly
@site https://www.jeepay.vip
@date 2021/5/8 07:18
-->
<script>
export default {
name: 'JeepayTableColumns', // 定义组件名称
render (createElement, context) {
const slots = []
this.$slots.default.map(item => {
if (item.tag) {
slots.push(item)
}
return false
})
if (slots.length <= 3) { // 小于等于三个直接渲染
return createElement(
'div',
{ style: 'display:flex; justify-content: space-evenly;' },
slots // 子节点数组
)
} else {
const firstEL = [slots[0], slots[1]]
const menuEL = []
for (let i = 2; i < slots.length; i++) {
menuEL.push(<a-menu-item>{slots[i]}</a-menu-item>)
}
return <div style="display:flex; justify-content: space-evenly;"> {firstEL}
<a-dropdown>
<a-button class="ant-dropdown-link" type="link" style="">更多<a-icon type="down" /></a-button>
<a-menu slot="overlay">
{menuEL}
</a-menu>
</a-dropdown>
</div>
}
}
}
</script>
<style lang="less" scoped>
//当前页面的按钮, 减少padding
button { padding: 8px !important;}
</style>
<template>
<div class="jee-text-up table-head-layout">
<a-input required="required" :value="msg" @input="$emit('input', $event.target.value)">
</a-input>
<label>{{ placeholder }}</label>
</div>
</template>
<script>
export default {
name: 'JeepayTextUp',
// props: ['msg', 'placeholder']
props: {
msg: { type: String, default: '' },
placeholder: { type: String, default: '' }
}
}
</script>
<style scoped lang="less">
// 文字上移 效果
.jee-text-up {
position: relative;
input {
outline: 0;
text-indent: 60px;
transition: all .3s ease-in-out;
}
input::-webkit-input-placeholder {
color: #BFBFBF;
text-indent: 0;
}
input + label {
pointer-events: none;
position: absolute;
left: 0;
bottom: 6px;
padding: 2px 11px;
color: #BFBFBF;
font-size: 13px;
text-transform: uppercase;
transition: all .3s ease-in-out;
border-radius: 3px;
background: rgba(122, 184, 147, 0);
height: 20px;
line-height: 20px;
display: flex;
justify-content: center;
align-items: center;
}
// 三角形
input + label:after {
position: absolute;
content: "";
width: 0;
height: 0;
top: 100%;
left: 50%;
margin-left: -3px;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
transition: all .3s ease-in-out;
}
input:focus,
input:active,
input:valid + label {
text-indent: 0;
background: #fff;
}
input:focus + label,
input:active + label,
input:valid + label {
color: #fff;
background: @jee-theme;
transform: translateY(-33px);
}
input:focus + label:after,
input:active + label:after {
border-top: 4px solid @jee-theme;
}
input:valid {
text-indent: 0; //文字不下移
}
input:valid + label{
background: #dadada; // 更换背景色
}
input:valid + label:after{ // 更换背景色
border-top: 4px solid #dadada;
}
}
// 文字上移效果 初版
// .jee-text-up {
// position: relative;
// .jee-text-label {
// position: absolute;
// z-index: 1;
// left: 15px;
// top: 0px;
// transition: all .3s;
// color: #bfbfbf;
// pointer-events: none;
// height: 20px;
// line-height: 20px;
// }
// input:focus + .jee-text-label, input:valid + .jee-text-label {
// top: -35px;
// padding: 1px 10px;
// border-radius: 5px;
// background: @jee-theme;
// color: #fff;
// }
// }
</style>
<template>
<div>
<a-upload
name="file"
:action="action"
:headers="headers"
:accept="accept"
:showUploadList="false"
:multiple="true"
:before-upload="beforeUpload"
@change="handleChange"
>
<slot name="uploadSlot" :loading="loading"></slot>
<!-- <img v-if="fileUrl && type === 'image'" :src="fileUrl" />
<a-input v-else-if="fileUrl && type === 'file'" :value="fileUrl" />
<a-button style="marginLeft:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button> -->
</a-upload>
</div>
</template>
<script>
import appConfig from '@/config/appConfig'
import storage from '@/utils/jeepayStorageWrapper'
function getHeaders () {
const headers = {}
headers[appConfig.ACCESS_TOKEN_NAME] = storage.getToken() // token
return headers
}
export default {
name: 'JeepayUpload',
props: {
action: { type: String, default: '' },
accept: { type: String, default: '' },
size: { type: Number, default: 10 }, // 文件大小限制
num: { type: Number, default: 1 } // 文件数量限制
},
data () {
return {
loading: false, // 上传状态
fileList: [],
headers: getHeaders() // 放入token
}
},
created () {
},
methods: {
// 上传回调
handleChange (info) {
// 限制文件数量
/* let fileList = [...info.fileList]
fileList = fileList.length > this.num ? fileList.splice(0 - this.num) : fileList // 取最新加入的元素
fileList = fileList.map(file => {
if (file.response) {
file.url = file.response.data
}
return file
}) */
const res = info.file.response
if (info.file.status === 'uploading') {
this.loading = true
}
if (info.file.status === 'done') {
if (res.code !== 0) {
this.$message.error(res.msg)
}
this.loading = false
this.$emit('uploadSuccess', res.data)
} else if (info.file.status === 'error') {
console.log(info)
this.$message.error(`上传失败`)
}
},
// 上传图片前的校验
beforeUpload (file) {
const validate = file.size / 1024 / 1024 < this.size
if (!validate) {
this.$message.error('文件应小于' + this.size + 'M!')
}
return validate
}
}
}
</script>
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: @primary-color;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
/* Fancy blur effect */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color;
opacity: 1.0;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: @primary-color;
border-left-color: @primary-color;
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes nprogress-spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/**
* 全局配置信息, 包含网站标题, 动态组件定义
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/8 07:18
*/
/** 应用配置项 **/
export default {
APP_TITLE: 'Jeepay运营平台', // 设置浏览器title
ACCESS_TOKEN_NAME: 'iToken' // 设置请求token的名字, 用于请求header 和 localstorage中存在名称
}
/**
* 与后端开发人员的路由名称及配置项
* 组件名称 :{ 默认跳转路径(如果后端配置则已动态配置为准), 组件渲染 }
* */
export const asyncRouteDefine = {
'CurrentUserInfo': { defaultPath: '/current/userinfo', component: () => import('@/views/current/UserinfoPage') }, // 用户设置
'MainPage': { defaultPath: '/main', component: () => import('@/views/dashboard/Analysis') },
'SysUserPage': { defaultPath: '/users', component: () => import('@/views/sysuser/SysUserPage') },
'RolePage': { defaultPath: '/roles', component: () => import('@/views/role/RolePage') },
'EntPage': { defaultPath: '/ents', component: () => import('@/views/ent/EntPage') },
'PayWayPage': { defaultPath: '/payways', component: () => import('@/views/payconfig/payWay/List') },
'IfDefinePage': { defaultPath: '/ifdefines', component: () => import('@/views/payconfig/payIfDefine/List') },
'IsvListPage': { defaultPath: '/isv', component: () => import('@/views/isv/IsvList') }, // 服务商列表
'MchListPage': { defaultPath: '/mch', component: () => import('@/views/mch/MchList') }, // 商户列表
'MchAppPage': { defaultPath: '/apps', component: () => import ('@/views/mchApp/List') }, // 商户应用列表
'PayOrderListPage': { defaultPath: '/payOrder', component: () => import('@/views/order/pay/PayOrderList') }, // 支付订单列表
'RefundOrderListPage': { defaultPath: '/refundOrder', component: () => import('@/views/order/refund/RefundOrderList') }, // 退款订单列表
'TransferOrderListPage': { defaultPath: '/transferOrder', component: () => import('@/views/order/transfer/TransferOrderList') }, // 转账订单
'MchNotifyListPage': { defaultPath: '/notify', component: () => import('@/views/order/notify/MchNotifyList') }, // 商户通知列表
'SysConfigPage': { defaultPath: '/config', component: () => import('@/views/sys/config/SysConfig') }, // 系统配置
'SysLogPage': { defaultPath: '/log', component: () => import('@/views/sys/log/SysLog') } // 系统日志
}
import { printANSI } from '@/utils/screenLog'
export default function Initializer () {
printANSI() // 请自行移除该行. please remove this line
}
import Vue from 'vue'
// base library
import {
ConfigProvider,
Layout,
Input,
InputNumber,
Button,
Switch,
Radio,
Checkbox,
Select,
Card,
Form,
FormModel,
Row,
Col,
Modal,
Table,
Tabs,
Icon,
Badge,
Popover,
Dropdown,
List,
Avatar,
Breadcrumb,
Steps,
Spin,
Menu,
Drawer,
Tooltip,
Alert,
Tag,
Divider,
DatePicker,
TimePicker,
Upload,
Progress,
Skeleton,
Popconfirm,
PageHeader,
Result,
Statistic,
Descriptions,
Space,
Pagination,
message,
notification,
Tree
} from 'ant-design-vue'
import Viser from 'viser-vue'
// ext library
import VueCropper from 'vue-cropper'
Vue.use(ConfigProvider)
Vue.use(Layout)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Button)
Vue.use(Switch)
Vue.use(Radio)
Vue.use(Checkbox)
Vue.use(Select)
Vue.use(Card)
Vue.use(Form)
Vue.use(FormModel)
Vue.use(Row)
Vue.use(Col)
Vue.use(Modal)
Vue.use(Table)
Vue.use(Tabs)
Vue.use(Icon)
Vue.use(Badge)
Vue.use(Popover)
Vue.use(Dropdown)
Vue.use(List)
Vue.use(Avatar)
Vue.use(Breadcrumb)
Vue.use(Steps)
Vue.use(Spin)
Vue.use(Menu)
Vue.use(Drawer)
Vue.use(Tooltip)
Vue.use(Alert)
Vue.use(Tag)
Vue.use(Divider)
Vue.use(DatePicker)
Vue.use(TimePicker)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Skeleton)
Vue.use(Popconfirm)
Vue.use(PageHeader)
Vue.use(Result)
Vue.use(Statistic)
Vue.use(Descriptions)
Vue.use(Space)
Vue.use(Pagination)
Vue.use(Tree)
Vue.prototype.$confirm = Modal.confirm
Vue.prototype.$message = message
Vue.prototype.$notification = notification
Vue.prototype.$info = Modal.info
Vue.prototype.$success = Modal.success
Vue.prototype.$error = Modal.error
Vue.prototype.$warning = Modal.warning
Vue.use(Viser)
Vue.use(VueCropper)
process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] NOTICE: Antd use lazy-load.')
import Vue from 'vue'
// base library
import Antd from 'ant-design-vue'
import Viser from 'viser-vue'
import VueCropper from 'vue-cropper'
import 'ant-design-vue/dist/antd.less'
// ext library
import VueClipboard from 'vue-clipboard2'
import './directives/action'
VueClipboard.config.autoSetContainer = true
Vue.use(Antd)
Vue.use(Viser)
Vue.use(VueClipboard)
Vue.use(VueCropper)
process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] WARNING: Antd now use fulled imported.')
@import '../node_modules/ant-design-vue/es/style/themes/default.less';
// @import './default.less';
html,
body,
#app, #root {
height: 100%;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Microsoft YaHei,PingFang SC,Hiragino Sans GB,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
letter-spacing: 0.4px;
}
// jee主题颜色class列表, 用于文字颜色
.jee-theme {
color: @jee-theme; //主题色
}
.jee-back {
color: @jee-back; //主要背景色
}
.jee-card-back {
color: @jee-card-back; //卡片底色
}
.jee-theme-mask {
color: @jee-theme-mask; //主体遮罩色(10% 透明度)
}
.jee-strengthen {
color: @jee-strengthen;//强化色
}
.jee-theme-hover {
color: @jee-theme-hover;//主题Hover
}
.jee-warning {
color: @jee-warning; //危险,强化警告色
}
.jee-inside-link {
color: @jee-inside-link; //内部链接
}
.jee-external-link {
color: @jee-external-link; //外部链接
}
// jee主题颜色class列表, 用于文字颜色
.jee-theme {
color: @jee-theme; //主题色
}
.jee-back {
color: @jee-back; //主要背景色
}
.jee-card-back {
color: @jee-card-back; //卡片底色
}
.jee-theme-mask {
color: @jee-theme-mask; //主体遮罩色(10% 透明度)
}
.jee-strengthen {
color: @jee-strengthen;//强化色
}
.jee-theme-hover {
color: @jee-theme-hover;//主题Hover
}
.jee-warning {
color: @jee-warning; //危险,强化警告色
}
.jee-inside-link {
color: @jee-inside-link; //内部链接
}
.jee-external-link {
color: @jee-external-link; //外部链接
}
// jee主题颜色class列表, 用于背景颜色
.jee-theme-back-color {
background-color: @jee-theme; //主题色
}
.jee-back-color {
background-color: @jee-back; //主要背景色
}
.jee-card-back-color {
background-color: @jee-card-back; //卡片底色
}
.jee-theme-mask-back-color {
background-color: @jee-theme-mask; //主体遮罩色(10% 透明度)
}
.jee-strengthen-back-color {
background-color: @jee-strengthen;//强化色
}
.jee-theme-hover-back-color {
background-color: @jee-theme-hover;//主题Hover
}
.jee-warning-back-color {
background-color: @jee-warning; //危险,强化警告色
}
.jee-inside-link-back-color {
background-color: @jee-inside-link; //内部链接
}
.jee-external-link-back-color {
background-color: @jee-external-link; //外部链接
}
.colorWeak {
filter: invert(80%);
}
.ant-layout{
.layout-basic {
height: 100vh;
min-height: 100vh;
}
}
canvas {
display: block;
}
body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #f0f2f5;
}
ul, ol {
list-style: none;
}
// 滚动条高度 和宽度
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
// 滚动条颜色
::-webkit-scrollbar-thumb {
background: #B3B3B3;
}
// 滑块区域底色
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.03)
}
// 滚动条hover色
::-webkit-scrollbar-thumb:hover {
background: #A6A6A6;
}
// 滚动条选中色
::-webkit-scrollbar-thumb:active {
background: #8C8C8C;
}
// 结合 表格组件中的 :scroll ,让表格在收缩时,展示滚动条
.ant-table-body{
overflow-x: auto !important;
}
// /*显示滚动条上方以及下方的渐增按钮*/
// ::-webkit-scrollbar-button:start:decrement,
// ::-webkit-scrollbar-button:end:increment {
// display: block;
// }
// /* 定义垂直滚动条渐增按扭的样式 */
// ::-webkit-scrollbar-button:vertical:end:increment {
// background-image: url(~@/assets/svg/scroll_down.svg);
// background-size: cover;
// background-position: center;
// }
// /* 定义垂直滚动条渐减按扭的样式 */
// ::-webkit-scrollbar-button:vertical:start:decrement {
// background-image: url(~@/assets/svg/scroll_up.svg);
// background-size: cover;
// background-position: center;
// }
// /* 定义水平滚动条渐增按扭的样式 */
// ::-webkit-scrollbar-button:horizontal:end:increment {
// background-image: url(~@/assets/svg/scroll_right.svg);
// background-size: cover;
// background-position: center;
// }
// /* 定义水平滚动条渐减按扭的样式 */
// ::-webkit-scrollbar-button:horizontal:start:decrement {
// background-image: url(~@/assets/svg/scroll_left.svg);
// background-size: cover;
// background-position: center;
// }
// 隐藏面包屑底下的标题
.ant-page-header-heading {
display: none;
}
// 数据列表 样式
.table-alert {
margin-bottom: 16px;
}
// 数据列表 操作
.table-operator {
margin-bottom: 18px;
button {
margin-right: 8px;
}
}
// 数据列表 搜索条件
.table-page-search-wrapper {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding: 30px;
padding-bottom: 0;
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
.ant-form-inline {
.ant-form-item {
display: flex;
// margin-bottom: 24px;
margin-right: 0;
.ant-form-item-control-wrapper {
flex: 1 1;
display: inline-block;
vertical-align: middle;
}
> .ant-form-item-label {
line-height: 32px;
padding-right: 8px;
width: auto;
}
.ant-form-item-control {
height: 32px;
line-height: 32px;
}
}
}
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
}
@media (max-width: @screen-xs) {
.ant-table {
width: 100%;
overflow-x: auto;
&-thead > tr,
&-tbody > tr {
> th,
> td {
white-space: pre;
> span {
display: block;
}
}
}
}
}
// 修改侧边栏宽度为230
.ant-pro-sider-menu-sider {
// min-width: 80px !important;
// max-width: 230px !important;
// 去掉侧边栏阴影
.light {
box-shadow: none;
}
}
// 删除表格的内边距
.ant-card-body {
padding: 0 !important;
}
// 增加内容区域的边框圆角
.ant-card {
border-radius: 10px;
overflow: hidden;
}
// 登录页输入框fcous hover事件,边框为jee主题蓝
.ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled) {
border-color: @jee-theme
}
.ant-input:focus {
border-color: @jee-theme
}
// 抽屉,按钮板块,居中
.drawer-btn-center {
position: absolute;
right: 0px;
bottom: 0px;
width: 100%;
border-top: 1px solid rgb(233, 233, 233);
padding: 10px 16px;
background: rgb(255, 255, 255);
text-align: center;
z-index: 2;
&:first-child {
margin-right: 80px;
}
button {
margin: 0;
padding: 3px 20px;
}
}
.els {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap
}
// 内容区域去掉最外层的magin 24px ,改为 padding 15px
.ant-pro-basicLayout-content {
margin: 0 ;
padding: 15px;
}
// 表格页面的间距
.ant-table-pagination.ant-pagination {
margin: 20px;
}
// 去掉表格边框线
.ant-card-bordered {
border:none !important;
}
.ant-table-align-left {
padding-left: 38px;
}
// 向下的30外边距
.mg-b-30 {
margin-bottom: 30px
}
// 表格,搜索框板块布局
.table-head-ground {
display: flex;
justify-content: start;
flex-wrap: wrap;
.table-layer {
display: flex;
flex-wrap: wrap;
flex-grow: 1;
flex-shrink: 1;
}
}
.table-head-layout {
min-width: 220px;
max-width: 240px;
flex-grow: 1;
flex-shrink: 1;
margin-bottom:30px !important;
margin-right: 16px !important;
}
// 404 500 403
.result-err {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
width: 100%;
overflow: auto;
div {
margin: 20px 0;
text-align:center;
font-size: 16px;
p.big-text {
font-size: 36px;
font-weight: 700;
}
}
}
\ No newline at end of file
/**
* Http请求包装对象
* 参考: iview https://githubtest.com/icarusion/iview-admin/blob/master/src/libs/axios.js
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/8 07:18
*/
import axios from 'axios'
import storage from '@/utils/jeepayStorageWrapper'
import appConfig from '@/config/appConfig'
import Vue from 'vue'
import store from '@/store/index'
class HttpRequest {
constructor (baseUrl = process.env.VUE_APP_API_BASE_URL) {
this.baseUrl = baseUrl
this.queue = {} // 发送队列, 格式为: {请求url: true}, 可以做一些验证之类
}
// 基础配置信息
baseConfig () {
const headers = {}
headers[appConfig.ACCESS_TOKEN_NAME] = storage.getToken()
return {
baseURL: this.baseUrl,
headers: headers
}
}
destroy (url, showLoading) {
delete this.queue[url]
}
interceptors (instance, url, showErrorMsg, showLoading) {
// 请求拦截
instance.interceptors.request.use(config => {
// 添加全局的loading...
if (!Object.keys(this.queue).length && showLoading) {
store.commit('showLoading') // 加载中显示loading组件
}
this.queue[url] = true
return config
}, error => {
store.commit('hideLoading') // 报错关闭loading组件
return Promise.reject(error)
})
// 响应拦截
instance.interceptors.response.use(res => {
this.destroy(url, showLoading)
if (showLoading) {
store.commit('hideLoading') // 报错关闭loading组件
}
const resData = res.data // 接口实际返回数据 格式为:{code: '', msg: '', data: ''}, res.data 是axios封装对象的返回数据;
if (resData.code !== 0) { // 相应结果不为0, 说明异常
if (showErrorMsg) {
Vue.prototype.$message.error(resData.msg) // 显示异常信息
}
return Promise.reject(resData)
} else {
return resData.data
}
}, error => {
this.destroy(url, showLoading)
if (showLoading) {
store.commit('hideLoading') // 报错关闭loading组件
}
let errorInfo = error.response && error.response.data && error.response.data.data
if (!errorInfo) {
errorInfo = error.response.data
}
if (error.response.status === 401) { // 无访问权限,会话超时, 提示用户信息 & 退出系统
const toLoginTimeout = setTimeout(function () {
store.dispatch('Logout').then(() => {
window.location.reload()
})
}, 3000)
Vue.prototype.$infoBox.confirmDanger(
'会话超时,请重新登录', '3s后将自动退出...',
() => {
store.dispatch('Logout').then(() => {
window.location.reload()
})
},
() => {
clearTimeout(toLoginTimeout)
},
{ okText: '重新登录', cancelText: '关闭对话' })
} else {
if (showErrorMsg) {
Vue.prototype.$message.error(JSON.stringify(errorInfo)) // 显示异常信息
}
}
return Promise.reject(errorInfo)
})
}
// interceptorsFlag: 是否进行自定义拦截器处理,默认为: true
// showErrorMsg 发送请求出现异常是否全局提示错误信息
// showLoading 发送请求前后显示全局loading
request (options, interceptorsFlag = true, showErrorMsg = true, showLoading = false) {
const instance = axios.create()
options = Object.assign(this.baseConfig(), options)
if (interceptorsFlag) { // 注入 req, respo 拦截器
this.interceptors(instance, options.url, showErrorMsg, showLoading)
}
return instance(options)
}
}
export default HttpRequest
import HttpRequest from '@/http/HttpRequest'
const request = new HttpRequest()
export default request
@import "~ant-design-vue/es/style/themes/default.less";
// 清除头部栏下方阴影
.ant-layout-header, .ant-pro-basicLayout .ant-layout-header:not(.ant-pro-top-menu) {
background: initial;
}
// 清除头部栏下方阴影 修改背景色
.ant-pro-global-header {
background: initial;
box-shadow: initial;
}
// 面包屑导航部分 修改背景色
.ant-pro-page-header-wrap-page-header-warp {
background: initial;
}
//左上角 logo 图标样式
.ant-pro-sider-menu-logo {
padding-left: 30px;
}
.ant-pro-sider-menu-logo svg {
height: 26px;
width: initial;
}
.ant-pro-global-header-index-right {
margin-right: 8px;
&.ant-pro-global-header-index-dark {
.ant-pro-global-header-index-action {
color: hsla(0, 0%, 100%, .85);
&:hover {
background: #1890ff;
}
}
}
.ant-pro-account-avatar {
.antd-pro-global-header-index-avatar {
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
margin-right: 8px;
color: @primary-color;
vertical-align: top;
background: rgba(255, 255, 255, 0.85);
}
}
.menu {
.anticon {
margin-right: 8px;
}
.ant-dropdown-menu-item {
min-width: 100px;
}
}
}
<template>
<pro-layout
:menus="menus"
:collapsed="collapsed"
:mediaQuery="query"
:isMobile="isMobile"
:handleMediaQuery="handleMediaQuery"
:handleCollapse="handleCollapse"
:i18nRender="false"
v-bind="settings"
:breadcrumbRender="handleBreadcrumbRender"
:siderWidth="210"
>
<!-- 1.0.0+ 版本 pro-layout 提供 API,
我们推荐使用这种方式进行 LOGO 和 title 自定义
-->
<template v-slot:menuHeaderRender>
<div>
<!-- 当侧边栏卷起来的时候,切换仅有J字母的图标 -->
<img src="@/assets/logo-j.svg" alt="jeequan">
<!-- 在这里可以添加title,我们以图片的方式替代文字 -->
<img v-show="!collapsed" src="@/assets/svg/jeepay.svg" alt="jeepay" style="width:90px;margin: 5px 0 0 5px">
</div>
</template>
<!-- 1.0.0+ 版本 pro-layout 提供 API,
增加 Header 左侧内容区自定义
-->
<template v-slot:headerContentRender>
<div class="ant-pro-global-header-trigger" @click="routeReload()">
<a-tooltip title="刷新页面">
<a-icon type="reload" style="font-size: 18px;cursor: pointer;" />
</a-tooltip>
</div>
</template>
<template v-slot:rightContentRender>
<right-content :top-menu="settings.layout === 'topmenu'" :is-mobile="isMobile" :theme="settings.theme" />
</template>
<!-- custom footer / 自定义Footer -->
<template v-slot:footerRender>
<global-footer />
</template>
<router-view v-if="isRouterAlive"/>
</pro-layout>
</template>
<script>
// import { SettingDrawer, updateTheme } from '@ant-design-vue/pro-layout'
import { mapState } from 'vuex'
import RightContent from '@/components/GlobalHeader/RightContent'
import GlobalFooter from '@/components/GlobalFooter'
import appConfig from '@/config/appConfig'
export default {
name: 'BasicLayout',
components: {
// SettingDrawer,
RightContent,
GlobalFooter
},
data () {
return {
isRouterAlive: true,
// preview.pro.antdv.com only use.
isProPreviewSite: process.env.VUE_APP_PREVIEW === 'true' && process.env.NODE_ENV !== 'development',
// end
// base
menus: [],
// 侧栏收起状态
collapsed: false,
title: appConfig.APP_TITLE,
settings: {
// 布局类型
layout: 'sidemenu', // 'sidemenu', 'topmenu'
contentWidth: 'Fluid', // Fixed , Fluid 内容区布局: 固定 | 流式
theme: 'light', // 主题 'dark' | 'light'
// 主色调
primaryColor: '#1a53ff', // 默认主题色, 如果修改颜色不生效,请清理 localStorage
fixedHeader: false, // 固定 Header
fixSiderbar: true, // 固定左侧菜单栏
colorWeak: false, // 色盲模式将
hideHintAlert: false,
hideCopyButton: false
},
// 媒体查询
query: {},
// 是否手机模式
isMobile: false
}
},
computed: {
...mapState({
// 动态主路由
mainMenu: state => state.asyncRouter.addRouters
})
},
created () {
const routes = this.mainMenu.find(item => item.path === '/')
this.menus = (routes && routes.children) || []
// 处理侧栏收起状态
// this.$watch('collapsed', () => {
// this.$store.commit(SIDEBAR_TYPE, this.collapsed)
// })
// this.$watch('isMobile', () => {
// this.$store.commit(TOGGLE_MOBILE_TYPE, this.isMobile)
// })
},
mounted () {
const userAgent = navigator.userAgent
if (userAgent.indexOf('Edge') > -1) {
this.$nextTick(() => {
this.collapsed = !this.collapsed
setTimeout(() => {
this.collapsed = !this.collapsed
}, 16)
})
}
// first update color
// TIPS: THEME COLOR HANDLER!! PLEASE CHECK THAT!!
// if (process.env.NODE_ENV !== 'production' || process.env.VUE_APP_PREVIEW === 'true') {
// updateTheme(this.settings.primaryColor)
// }
},
methods: {
handleMediaQuery (val) {
this.query = val
if (this.isMobile && !val['screen-xs']) {
this.isMobile = false
return
}
if (!this.isMobile && val['screen-xs']) {
this.isMobile = true
this.collapsed = false
}
},
handleCollapse (val) {
this.collapsed = val
},
handleBreadcrumbRender (rrr) {
if (rrr.route.path.startsWith('/ENT')) {
return <span>{rrr.route.breadcrumbName}</span>
}
return <router-link to={rrr.route.path || '/'}><span>{rrr.route.breadcrumbName}</span></router-link>
},
routeReload () { // 刷新页面函数
this.isRouterAlive = false
this.$nextTick(() => { this.isRouterAlive = true })
}
}
}
</script>
<style lang="less">
@import "./BasicLayout.less";
</style>
<template>
<div>
<router-view />
</div>
</template>
<script>
export default {
name: 'BlankLayout'
}
</script>
<style scoped>
</style>
<template>
<page-header-wrapper>
<router-view />
</page-header-wrapper>
</template>
<script>
export default {
name: 'PageView'
}
</script>
<script>
export default {
name: 'RouteView',
props: {
keepAlive: {
type: Boolean,
default: true
}
},
data () {
return {}
},
render () {
const { $route: { meta } } = this
const inKeep = (
<keep-alive>
<router-view />
</keep-alive>
)
const notKeep = (
<router-view />
)
// 这里增加了 multiTab 的判断,当开启了 multiTab 时
// 应当全部组件皆缓存,否则会导致切换页面后页面还原成原始状态
// 若确实不需要,可改为 return meta.keepAlive ? inKeep : notKeep
if (!meta.keepAlive) {
return notKeep
}
return this.keepAlive || meta.keepAlive ? inKeep : notKeep
}
}
</script>
<template>
<div id="userLayout" :class="['user-layout-wrapper']">
<div class="container">
<div class="user-layout-lang">
</div>
<div class="user-layout-content">
<div class="top">
<div class="header">
<a href="/">
<img src="~@/assets/logo.svg" class="logo" alt="logo">
</a>
</div>
<div class="desc">
<img src="~@/assets/svg/operate.svg" class="logo" alt="logo">
<span>运营平台</span>
</div>
</div>
<router-view />
<div class="footer">
<!-- <div class="links">-->
<!-- <a href="_self">帮助</a>-->
<!-- <a href="_self">隐私</a>-->
<!-- <a href="_self">条款</a>-->
<!-- </div>-->
<!-- <div class="copyright">
Copyright &copy; 2021 juhezhifu.com
</div> -->
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'UserLayout',
components: {
},
mounted () {
document.body.classList.add('userLayout')
},
beforeDestroy () {
document.body.classList.remove('userLayout')
}
}
</script>
<style lang="less" scoped>
#userLayout.user-layout-wrapper {
height: 100%;
&.mobile {
.container {
.main {
max-width: 368px;
width: 98%;
}
}
}
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
min-height: 100%;
background: #f0f2f5 url(~@/assets/svg/background.svg) no-repeat 50%;
background-size: cover;
//padding: 50px 0 84px;
position: relative;
.user-layout-lang {
width: 100%;
// height: 40px;
// line-height: 44px;
height: 0;
text-align: right;
}
.user-layout-content {
.top {
text-align: center;
.header {
height: 44px;
line-height: 44px;
margin-bottom:80px;
.badge {
position: absolute;
display: inline-block;
line-height: 1;
vertical-align: middle;
margin-left: -12px;
margin-top: -10px;
opacity: 0.8;
}
.logo {
height: 44px;
vertical-align: top;
border-style: none;
}
.title {
font-size: 33px;
color: rgba(0, 0, 0, .85);
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
font-weight: 600;
position: relative;
top: 2px;
}
}
.desc {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
margin-bottom: 40px;
font-size: 20px;
font-family: PingFang SC, PingFang SC-Medium;
font-weight: 500;
color: #252626;
letter-spacing: 1px;
display: flex;
justify-content: center;
align-items: center;
height: 28px;
line-height: 28px;
span {
margin-left: 10px;
}
}
}
.main {
min-width: 260px;
width: 368px;
margin: 0 auto;
}
.footer {
// position: absolute;
width: 100%;
bottom: 0;
padding: 0 16px;
margin: 48px 0 24px;
text-align: center;
.links {
margin-bottom: 8px;
font-size: 14px;
a {
color: rgba(0, 0, 0, 0.45);
transition: all 0.3s;
&:not(:last-child) {
margin-right: 40px;
}
}
}
.copyright {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
}
}
a {
text-decoration: none;
}
}
}
</style>
import UserLayout from './UserLayout'
import BlankLayout from './BlankLayout'
import BasicLayout from './BasicLayout'
import RouteView from './RouteView'
import PageView from './PageView'
export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView }
// with polyfills
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store/'
import ProLayout, { PageHeaderWrapper } from '@ant-design-vue/pro-layout'
import bootstrap from './core/bootstrap'
import './core/lazy_use' // use lazy load components
import './permission' // permission control 路由守卫
import './utils/filter' // global filter
import './global.less' // global style
import 'ant-design-vue/dist/antd.less'
import infoBox from '@/utils/infoBox'
import VueClipboard from 'vue-clipboard2' // 复制插件
Vue.config.productionTip = false
// mount axios to `Vue.$http` and `this.$http`
// use pro-layout components
Vue.component('pro-layout', ProLayout)
Vue.component('page-container', PageHeaderWrapper)
Vue.component('page-header-wrapper', PageHeaderWrapper)
Vue.use(VueClipboard) // 复制插件
/**
* @description 全局注册权限验证
*/
Vue.prototype.$access = (entId) => {
// eslint-disable-next-line eqeqeq
return store.state.user.accessList.some(item => item == entId)
}
Vue.prototype.$infoBox = infoBox
new Vue({
router,
store,
// init localstorage, vuex
created: bootstrap,
render: h => h(App)
}).$mount('#app')
import router from './router'
import store from './store'
import storage from '@/utils/jeepayStorageWrapper'
import NProgress from 'nprogress' // progress bar
import '@/components/NProgress/nprogress.less' // progress bar custom style
import { setDocumentTitle } from '@/utils/domUtil'
import { getInfo } from '@/api/login'
import appConfig from '@/config/appConfig'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const allowList = ['login', 'register', 'registerResult'] // no redirect allowList
const loginRoutePath = '/user/login'
// 路由守卫
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${appConfig.APP_TITLE}`)) // 设置浏览器标题
// 如果在免登录页面则直接放行
if (allowList.includes(to.name)) {
// 在免登录名单,直接进入
next()
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
return false
}
// 不包含Token 则直接跳转到登录页面
if (!storage.getToken()) {
next({ path: loginRoutePath, query: { redirect: to.fullPath } })
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
return false
}
// 以下为包含Token的情况
// 如果用户信息不存在, 则重新获取 [用户登录成功 & 强制刷新浏览器时 会执行该函数]
if (!store.state.user.userId) {
// request login userInfo
getInfo().then(bizData => {
store.commit('SET_USER_INFO', bizData) // 调用vuex设置用户基本信息
// 动态添加路由
store.dispatch('GenerateRoutes', {}).then(() => {
router.addRoutes(store.state.asyncRouter.addRouters)
})
next()
}).catch(() => {
// 失败时,获取用户信息失败时,调用登出,来清空历史保留信息
store.dispatch('Logout').then(() => {
next({ path: loginRoutePath, query: { redirect: to.fullPath } })
})
})
} else {
next()
}
})
router.afterEach(() => {
NProgress.done() // finish progress bar
})
import { BasicLayout, BlankLayout, PageView, RouteView } from '@/layouts'
import store from '@/store'
import { asyncRouteDefine } from '@/config/appConfig'
// 前端路由表 = 基础定义 + 动态组件
const constantRouterComponents = Object.assign({
// 基础页面 layout 必须引入
BasicLayout: { component: BasicLayout },
BlankLayout: { component: BlankLayout },
RouteView: { component: RouteView },
PageView: { component: PageView },
'403': () => import('@/views/exception/403'),
'404': () => import('@/views/exception/404'),
'500': () => import('@/views/exception/500')
}, asyncRouteDefine)
// 前端未找到页面路由
const notFoundRouter = {
path: '*', component: () => import('@/views/exception/404')
}
// 根级菜单
const rootRouter = {
name: 'index',
path: '/',
component: BasicLayout,
redirect: redirectFunc, // 根页面【/】默认跳转 地址
children: [],
meta: { title: '主页' }
}
// 动态跳转路径 func
function redirectFunc () {
let mainPageUri = ''
store.state.user.allMenuRouteTree.forEach(item => {
if (item.entId === 'ENT_C_MAIN') { // 当前用户是否拥有主页权限, 如果有直接跳转到该路径
mainPageUri = item.menuUri
return false
}
})
if (mainPageUri) {
return mainPageUri
}
return getOneUri(store.state.user.allMenuRouteTree)
}
// 获取到第一个uri (递归查找)
function getOneUri (item) {
let result = ''
for (let i = 0; i < item.length; i++) {
if (item[i].menuUri && item[i].entType === 'ML') {
return item[i].menuUri
}
if (item[i].children) {
result = getOneUri(item[i].children)
if (result) {
return result
}
}
}
return result
}
/**
* 动态生成菜单
* @param token
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = () => {
return new Promise((resolve, reject) => {
// 根据树状结构生成路由格式
rootRouter.children = generator(store.state.user.allMenuRouteTree)
// 构建完整路由
resolve([rootRouter, notFoundRouter])
})
}
/**
* 格式化树形结构数据 生成 vue-router 层级路由表
*
* @param routerMap
* @returns {*}
*/
export const generator = (allMenuRouteTreeArray) => {
const menuResult = []
// 遍历map
allMenuRouteTreeArray.map(item => {
const defComponent = constantRouterComponents[item.componentName || item.entId]
// 找不到组件 || 其他菜单
if (!defComponent) {
return
}
// 跳转uri
let path = item.menuUri || defComponent.defaultPath
// 没有配置path, 如果为目录则允许为空, 否则不在加载此配置
if (!path) {
if (item.children && item.children.length > 0) {
path = `/${item.entId}`
} else {
return // 不再加载此配置项
}
}
const currentRouter = {
// 如果路由设置了 path,则作为默认 path,否则 路由地址 为默认配置
path: path,
// 路由名称,建议唯一
name: item.entId,
// 该路由对应页面的 组件 :方案2 (动态加载)
component: ((defComponent && defComponent.component) || (() => import(`@/views/${item.componentName}`))),
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: {
title: item.entName,
icon: item.menuIcon,
keepAlive: false
},
hidden: item.entType === 'MO' // 当其他菜单时需要隐藏显示
}
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentRouter.children = generator(item.children)
}
menuResult.push(currentRouter)
})
return menuResult
}
import Vue from 'vue'
import Router from 'vue-router'
import { UserLayout } from '@/layouts'
// hack router push callback
// [解决 vue-router跳转相同路径报错 ]
const originalPush = Router.prototype.push
Router.prototype.push = function push (location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
Vue.use(Router)
// 纯静态路由配置项
const constantRouterMap = [
{
path: '/user',
component: UserLayout,
children: [
{ path: 'login', name: 'login', component: () => import('@/views/user/Login') }
]
}
]
export default new Router({
mode: 'history',
routes: constantRouterMap
})
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
// default router permission control
import asyncRouter from './modules/async-router'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user, // 用户相关状态机
asyncRouter // 动态菜单
},
state: {
// 定义全局loading 为false
globalLoading: false
},
mutations: {
// 显示与关闭 全局 loading
showLoading (state) {
state.globalLoading = true
},
hideLoading (state) {
state.globalLoading = false
}
},
actions: {
}
})
/**
* 向后端请求用户的菜单,动态生成路由
*/
import { generatorDynamicRouter } from '@/router/generator-routers'
const asyncRouter = {
state: {
addRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers
}
},
actions: {
GenerateRoutes ({ commit }, data) {
return new Promise(resolve => {
generatorDynamicRouter().then(routers => {
commit('SET_ROUTERS', routers)
resolve()
})
})
}
}
}
export default asyncRouter
import storage from '@/utils/jeepayStorageWrapper'
import { login, logout } from '@/api/login'
import appConfig from '@/config/appConfig'
const user = {
state: {
token: '',
userName: '', // 真实姓名
userId: '', // 用户ID
avatarImgPath: '', // 头像
allMenuRouteTree: [], // 全部动态 router
accessList: [], // 用户权限集合
isAdmin: '', // 是否是超级管理员
loginUsername: '', // 登录用户名
state: '', // 用户状态
sysType: '', // 所属系统
telphone: '', // 手机号
sex: '' // 性别
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
// 设置头像
SET_AVATAR (state, avatarPath) {
state.avatarImgPath = avatarPath
},
// 设置用户信息
SET_USER_INFO: (state, userInfo) => {
state.userId = userInfo.sysUserId // 用户ID
state.userName = userInfo.realname // 真实姓名
state.avatarImgPath = userInfo.avatarUrl // 头像
state.accessList = userInfo.entIdList // 权限集合
state.allMenuRouteTree = userInfo.allMenuRouteTree // 全部路由集合
state.isAdmin = userInfo.isAdmin // 是否是超级管理员
state.loginUsername = userInfo.loginUsername // 登录用户名
state.state = userInfo.state // 用户状态
state.sysType = userInfo.sysType // 所属系统
state.telphone = userInfo.telphone // 手机号
state.sex = userInfo.sex // 性别
}
},
actions: {
// 登录
Login ({ commit }, { loginParams, isSaveStorage }) {
return new Promise((resolve, reject) => {
login(loginParams).then(bizData => {
storage.setToken(bizData[appConfig.ACCESS_TOKEN_NAME], isSaveStorage)
commit('SET_TOKEN', bizData[appConfig.ACCESS_TOKEN_NAME])
resolve()
}).catch(error => {
reject(error)
})
})
},
// 登出
Logout ({ commit, state }) {
return new Promise((resolve) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
storage.cleanToken()
location.reload() // 退出时 重置缓存
resolve()
}).catch(() => {
resolve()
}).finally(() => {
})
})
}
}
}
export default user
export const setDocumentTitle = function (title) {
document.title = title
const ua = navigator.userAgent
// eslint-disable-next-line
const regex = /\bMicroMessenger\/([\d\.]+)/
if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) {
const i = document.createElement('iframe')
i.src = '/favicon.ico'
i.style.display = 'none'
i.onload = function () {
setTimeout(function () {
i.remove()
}, 9)
}
document.body.appendChild(i)
}
}
import Vue from 'vue'
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
Vue.filter('NumberFormat', function (value) {
if (!value) {
return '0'
}
const intPartFormat = value.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') // 将整数部分逢三一断
return intPartFormat
})
Vue.filter('dayjs', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') {
return moment(dataStr).format(pattern)
})
Vue.filter('moment', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') {
return moment(dataStr).format(pattern)
})
/**
* 通用信息弹层
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/8 07:18
*/
import { Modal } from 'ant-design-vue'
// 确认提示: 标题, 内容, 点击确定回调函数, 取消回调, 扩展参数
export const confirmResult = {
confirm: function (title, content, okFunc, cancelFunc = (() => {}), extConfig = {}) {
return Modal.confirm(
Object.assign({
okText: '确定',
cancelText: '取消',
title: title || '提示',
content: content,
onOk: okFunc,
onCancel: cancelFunc,
confirmLoading: true
}, extConfig))
},
confirmPrimary: function (title, content, okFunc, cancelFunc = (() => {}), extConfig = {}) {
return this.confirm(title, content, okFunc, cancelFunc, Object.assign({ okType: 'primary' }, extConfig))
},
confirmDanger: function (title, content, okFunc, cancelFunc = (() => {}), extConfig = {}) {
return this.confirm(title, content, okFunc, cancelFunc, Object.assign({ okType: 'danger' }, extConfig))
},
modalError: function (title, content, okFunc = (() => {})) {
return Modal.error({ title: title, content: content, onOk: okFunc })
},
modalSuccess: function (title, content, okFunc = (() => {})) {
return Modal.success({ title: title, content: content, onOk: okFunc })
},
modalWarning: function (title, content, okFunc = (() => {})) {
return Modal.warning({ title: title, content: content, onOk: okFunc })
}
}
export default confirmResult
/**
* storage 存储包装类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/8 07:18
*/
import storage from 'store'
import appConfig from '@/config/appConfig'
var SESSION_TOKEN = ''
const wrapper = {
/* 获取当前Token **/
getToken: () => {
return SESSION_TOKEN || storage.get(appConfig.ACCESS_TOKEN_NAME)
},
/* 清空Token **/
cleanToken: () => {
SESSION_TOKEN = ''
storage.remove(appConfig.ACCESS_TOKEN_NAME)
},
/* 设置token信息 **/
setToken (tokenVal, isSaveStorage) {
SESSION_TOKEN = tokenVal
if (isSaveStorage) {
storage.set(appConfig.ACCESS_TOKEN_NAME, tokenVal, 7 * 24 * 60 * 60 * 1000)
}
}
}
export default wrapper
/* eslint-disable */
export const printANSI = () => {
let text = ``
console.log(`%c${text}`, 'color: #fc4d50')
console.log('%c', 'color: #fff; font-size: 14px; font-weight: 300; text-shadow:#000 1px 0 0,#000 0 1px 0,#000 -1px 0 0,#000 0 -1px 0;')
}
export function timeFix () {
const time = new Date()
const hour = time.getHours()
return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好'
}
export function isIE () {
const bw = window.navigator.userAgent
const compare = (s) => bw.indexOf(s) >= 0
const ie11 = (() => 'ActiveXObject' in window)()
return compare('MSIE') || ie11
}
.textOverflow() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
}
.textOverflowMulti(@line: 3, @bg: #fff) {
position: relative;
max-height: @line * 1.5em;
margin-right: -1em;
padding-right: 1em;
overflow: hidden;
line-height: 1.5em;
text-align: justify;
&::before {
position: absolute;
right: 14px;
bottom: 0;
padding: 0 1px;
background: @bg;
content: '...';
}
&::after {
position: absolute;
right: 14px;
width: 1em;
height: 1em;
margin-top: 0.2em;
background: white;
content: '';
}
}
// mixins for clearfix
// ------------------------
.clearfix() {
zoom: 1;
&::before,
&::after {
display: table;
content: ' ';
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
}
}
\ No newline at end of file
<template>
<a-modal
title="修改头像"
:visible="visible"
:maskClosable="false"
:confirmLoading="confirmLoading"
:width="800"
:footer="null"
@cancel="cancelHandel">
<a-row>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<vue-cropper
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
@realTime="realTime"
>
</vue-cropper>
</a-col>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<div class="avatar-upload-preview">
<img :src="previews.url" :style="previews.img"/>
</div>
</a-col>
</a-row>
<br>
<a-row>
<a-col :lg="2" :md="2">
<a-upload name="file" :beforeUpload="beforeUpload" :showUploadList="false">
<a-button icon="upload">选择图片</a-button>
</a-upload>
</a-col>
<a-col :lg="{span: 1, offset: 2}" :md="2">
<a-button icon="plus" @click="changeScale(1)"/>
</a-col>
<a-col :lg="{span: 1, offset: 1}" :md="2">
<a-button icon="minus" @click="changeScale(-1)"/>
</a-col>
<a-col :lg="{span: 1, offset: 1}" :md="2">
<a-button icon="undo" @click="rotateLeft"/>
</a-col>
<a-col :lg="{span: 1, offset: 1}" :md="2">
<a-button icon="redo" @click="rotateRight"/>
</a-col>
<a-col :lg="{span: 2, offset: 6}" :md="2">
<a-button type="primary" @click="finish('blob')">保存</a-button>
</a-col>
</a-row>
</a-modal>
</template>
<script>
import { upload, updateUserInfo } from '@/api/manage'
import store from '@/store'
export default {
data () {
return {
recordId: store.state.user.userId, // 拿到ID
userLoad: upload.icon, // 图片上传地址
visible: false,
id: null,
confirmLoading: false,
fileList: [],
uploading: false,
options: {
// img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
img: '',
autoCrop: true,
autoCropWidth: 200,
autoCropHeight: 200,
fixedBox: true
},
previews: {}
}
},
methods: {
edit (id) {
this.visible = true
this.id = id
/* 获取原始头像 */
},
close () {
this.id = null
this.visible = false
},
cancelHandel () {
this.close()
},
changeScale (num) {
num = num || 1
this.$refs.cropper.changeScale(num)
},
rotateLeft () {
this.$refs.cropper.rotateLeft()
},
rotateRight () {
this.$refs.cropper.rotateRight()
},
beforeUpload (file) {
const reader = new FileReader()
// 把Array Buffer转化为blob 如果是base64不需要
// 转化为base64
reader.readAsDataURL(file)
reader.onload = () => {
this.options.img = reader.result
}
// 转化为blob
// reader.readAsArrayBuffer(file)
return false
},
// 上传图片(点击上传按钮)
finish (type) {
console.log('finish')
const _this = this
// 通过FormData构造函数创建一个空对象
const formData = new FormData()
// 输出
if (type === 'blob') {
this.$refs.cropper.getCropBlob((data) => {
// 通过该方法可以获取当前文件的一个内存URL
const img = window.URL.createObjectURL(data)
this.model = true
this.modelSrc = img
formData.append('avatarUrl', img)
// this.$http.post('https://www.mocky.io/v2/5cc8019d300000980a055e76', formData, { contentType: false, processData: false, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
updateUserInfo(formData)
// .then(res => {
// console.log(res)
// }).catch(err => {
// console.log(err)
// })
.then((response) => {
console.log('upload response:', response)
// var res = response.data
// if (response.status === 'done') {
// _this.imgFile = ''
// _this.headImg = res.realPathList[0] // 完整路径
// _this.uploadImgRelaPath = res.relaPathList[0] // 非完整路径
// _this.$message.success('上传成功')
// this.visible = false
// }
_this.$message.success('上传成功')
_this.$emit('ok', response.url)
_this.visible = false
}).catch(err => {
console.log(err)
})
console.log(_this.userLoad, _this.recordId, this.modelSrc)
})
} else {
this.$refs.cropper.getCropData((data) => {
this.model = true
this.modelSrc = data
})
}
},
okHandel () {
const vm = this
vm.confirmLoading = true
setTimeout(() => {
vm.confirmLoading = false
vm.close()
vm.$message.success('上传头像成功')
}, 2000)
},
realTime (data) {
this.previews = data
}
}
}
</script>
<style lang="less" scoped>
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 180px;
height: 180px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
</style>
<template>
<div style="background: #fff;border-radius:10px">
<a-tabs @change="selectTabs">
<a-tab-pane key="1" tab="基本信息">
<div class="account-settings-info-view">
<a-row :gutter="16">
<a-col :md="16" :lg="16">
<a-form-model ref="infoFormModel" :model="saveObject" :label-col="{span: 9}" :wrapper-col="{span: 10}" :rules="rules">
<a-form-model-item label="用户登录名:">
<a-input v-model="saveObject.loginUsername" disabled/>
</a-form-model-item>
<a-form-model-item label="用户姓名:" prop="realname">
<a-input v-model="saveObject.realname" />
</a-form-model-item>
<a-form-model-item label="手机号:" prop="telphone">
<a-input v-model="saveObject.telphone" disabled/>
</a-form-model-item>
<a-form-model-item label="请选择性别:">
<a-radio-group v-model="saveObject.sex">
<a-radio :value="1"></a-radio>
<a-radio :value="2"></a-radio>
</a-radio-group>
</a-form-model-item>
</a-form-model>
<a-form-item style="display:flex;justify-content:center">
<a-button type="primary" @click="changeInfo" icon="check-circle" :loading="btnLoading">更新基本信息</a-button>
</a-form-item>
</a-col>
<a-col :md="8" :lg="8" :style="{ minHeight: '180px',margin:'0 auto' }">
<!-- 原始的头像上传,带有图片裁剪功能 -->
<!-- <div class="ant-upload-preview" @click="$refs.modal.edit(1)" > -->
<div class="ant-upload-preview" >
<!-- <a-icon type="cloud-upload-o" class="upload-icon"/> -->
<!-- <div class="mask">
<a-icon type="plus" />
</div> -->
<img
:src="saveObject.avatarUrl"
style="border: 1px solid rgba(0,0,0,0.08)"/>
<JeepayUpload
style="
margin-top:10px"
:action="action"
accept=".jpg, .jpeg, .png"
@uploadSuccess="uploadSuccess($event, '')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginLeft:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '更换头像' }} </a-button>
</template>
</JeepayUpload>
</div>
</a-col>
</a-row>
<!-- 图片裁剪组件 <avatar-modal ref="modal" @ok="setavatar"/> -->
</div>
</a-tab-pane>
<a-tab-pane key="2" tab="安全信息">
<div class="account-settings-info-view">
<a-row :gutter="16">
<a-col :md="16" :lg="16">
<a-form-model ref="pwdFormModel" :model="updateObject" :label-col="{span: 9}" :wrapper-col="{span: 10}" :rules="rulesPass">
<a-form-model-item label="原密码:" prop="originalPwd">
<a-input-password autocomplete="new-password" v-model="updateObject.originalPwd" placeholder="请输入原密码" />
</a-form-model-item>
<a-form-model-item label="新密码:" prop="newPwd">
<a-input-password autocomplete="new-password" v-model="updateObject.newPwd" placeholder="请输入新密码" />
</a-form-model-item>
<a-form-model-item label="确认新密码:" prop="confirmPwd">
<a-input-password autocomplete="new-password" v-model="updateObject.confirmPwd" placeholder="确认新密码" />
</a-form-model-item>
</a-form-model>
<a-form-item style="display:flex;justify-content:center">
<a-button type="primary" icon="safety-certificate" @click="confirm" :loading="btnLoading">更新密码</a-button>
</a-form-item>
</a-col>
</a-row>
</div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script>
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import { getUserInfo, updateUserInfo, updateUserPass, upload } from '@/api/manage'
import AvatarModal from './AvatarModal'
import store from '@/store'
import { getInfo } from '@/api/login'
import { Base64 } from 'js-base64'
export default {
components: {
AvatarModal,
JeepayUpload
},
data () {
return {
action: upload.avatar, // 上传图标地址
btnLoading: false,
saveObject: {
loginUsername: '', // 登录名
realname: '', // 真实姓名
telphone: '',
sex: '',
avatarUrl: '' // 用户头像
},
// avatarUrl: store.state.user.avatarImgPath,
updateObject: {
originalPwd: '', // 原密码
newPwd: '', // 新密码
confirmPwd: '' // 确认密码
},
recordId: store.state.user.userId, // 拿到ID
rules: {
realname: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }]
},
rulesPass: {
originalPwd: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
newPwd: [{ min: 6, max: 12, required: true, message: '请输入6-12位新密码', trigger: 'blur' }],
confirmPwd: [{ required: true, message: '请确认输入新密码', trigger: 'blur' }, {
validator: (rule, value, callBack) => {
this.updateObject.newPwd === value ? callBack() : callBack('新密码与确认密码不一致')
}
}]
}
}
},
computed: {
},
created () {
this.detail()
},
methods: {
setavatar (url) {
this.option.img = url
},
detail () { // 获取基本信息
const that = this
getUserInfo().then(res => {
that.saveObject = res
console.log(res)
})
},
changeInfo () { // 更新基本信息事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
this.$infoBox.confirmPrimary('确认更新信息吗?', '', () => {
that.btnLoading = true // 打开按钮上的 loading
that.$store.commit('showLoading') // 关闭全局刷新
// 请求接口
updateUserInfo(that.saveObject).then(res => {
that.btnLoading = false // 关闭按钮刷新
that.$store.commit('hideLoading') // 关闭全局刷新
return getInfo()
}).then(bizData => {
bizData.avatarUrl = that.saveObject.avatarUrl
bizData.realname = that.saveObject.realname
store.commit('SET_USER_INFO', bizData) // 调用vuex设置用户基本信息
that.$message.success('修改成功')
}).catch(err => {
that.$store.commit('hideLoading') // 关闭全局刷新
that.btnLoading = false
console.log(err)
that.$message.error('修改失败')
})
})
}
})
},
confirm (e) { // 确认更新密码
const that = this
this.$refs.pwdFormModel.validate(valid => {
if (valid) { // 验证通过
this.$infoBox.confirmPrimary('确认更新密码吗?', '', () => {
// 请求接口
that.btnLoading = true // 打开按钮上的 loading
that.confirmLoading = true // 显示loading
that.updateObject.recordId = that.recordId // 用户ID
that.updateObject.originalPwd = Base64.encode(that.updateObject.originalPwd)
that.updateObject.confirmPwd = Base64.encode(that.updateObject.confirmPwd)
this.$delete(this.updateObject, 'newPwd')
updateUserPass(that.updateObject).then(res => {
that.$message.success('修改成功')
// 退出登录
this.$store.dispatch('Logout').then(() => {
this.$router.push({ name: 'login' })
})
}).catch(res => {
that.confirmLoading = false
that.btnLoading = false
})
})
}
})
},
selectTabs () { // 清空必填提示
this.updateObject.originalPwd = ''
this.updateObject.newPwd = ''
this.updateObject.confirmPwd = ''
console.log(this.updateObject)
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.saveObject.avatarUrl = value
this.$forceUpdate()
}
}
}
</script>
<style lang="less" scoped>
.avatar-upload-wrapper {
height: 200px;
width: 100%;
}
.ant-upload-preview {
text-align:center ;
position: relative;
margin: 0 auto;
width: 100%;
// max-width: 180px;
border-radius: 50%;
// box-shadow: 0 0 4px #ccc;
.upload-icon {
position: absolute;
top: 0;
right: 10px;
font-size: 1.4rem;
padding: 0.5rem;
background: rgba(222, 221, 221, 0.7);
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.mask {
opacity: 0;
position: absolute;
background: rgba(0,0,0,0.4);
cursor: pointer;
transition: opacity 0.4s;
&:hover {
opacity: 1;
}
i {
font-size: 2rem;
position: absolute;
top: 50%;
left: 50%;
margin-left: -1rem;
margin-top: -1rem;
color: #d6d6d6;
}
}
img, .mask {
width: 150px;
height: 150px;
border-radius: 50%;
overflow: hidden;
}
}
</style>
<template>
<div id="chart-card">
<div class="chart-top">
<div class="chart-item top-left">
<div class="chart-data" style="position:relative">
<!-- 骨架屏与图表有冲突,故不使用内嵌方式。 因为内边距的原因,采取v-if的方式 -->
<a-skeleton active :loading="true" v-if="skeletonIsShow" style="padding:20px" :paragraph="{ rows: 6 }" />
<div v-show="!skeletonIsShow">
<div class="analy-title" style="padding:20px;box-sizing:border-box;padding-bottom:10px">
<span>今日交易金额</span>
<a-tooltip>
<template slot="title">
{{ mainTips.todayAmountTip }}
</template>
<a-icon type="question-circle" />
</a-tooltip>
</div>
<div v-show="ispayAmount">
<div class="pay-amount-text">
<span class="pay-amount">¥{{ mainChart.todayAmount }}
</span>
<span>共{{ mainChart.todayPayCount }}笔</span>
</div>
<div id="payAmount" style="height:60px"></div>
</div>
<div class="payAmountSpan" v-show="ispayAmount">
<span>昨日交易金额:¥{{ this.mainChart.yesterdayAmount }}</span>
<span>近七天交易金额:¥{{ this.mainChart.payWeek }}</span>
</div>
<empty v-show="!ispayAmount"/>
</div>
</div>
</div>
<div class="top-middle">
<div class="middle-top">
<div class="chart-item middle-larger">
<div class="chart-data">
<a-skeleton active :loading="skeletonIsShow" :paragraph="{ rows: 1 }">
<div class="analy-title">
<span>交易总金额</span>
<a-tooltip>
<template slot="title">
{{ mainTips.totalAmountTip }}
</template>
<a-icon type="question-circle" />
</a-tooltip>
</div>
<a-card :bordered="false">
<a-statistic style="margin-top: 10px" :precision="2" :value="'¥' + this.mainChart.totalAmount" />
</a-card>
</a-skeleton>
</div>
</div>
<div class="chart-item middle-smaller">
<div class="chart-data">
<a-skeleton active :loading="skeletonIsShow" :paragraph="{ rows: 1 }">
<div class="analy-title">
<span>服务商数量</span>
<a-tooltip>
<template slot="title">
{{ mainTips.totalIsvTip }}
</template>
<a-icon type="question-circle" />
</a-tooltip>
</div>
<a-card :bordered="false">
<a-statistic style="margin-top: 10px" :value="this.mainChart.totalIsv" />
</a-card>
</a-skeleton>
</div>
</div>
</div>
<div class="middle-bottom">
<div class="chart-item middle-larger">
<div class="chart-data">
<a-skeleton active :loading="skeletonIsShow" :paragraph="{ rows: 1 }">
<div class="analy-title">
<span>交易总笔数</span>
<a-tooltip>
<template slot="title">
{{ mainTips.totalPayCountTip }}
</template>
<a-icon type="question-circle" />
</a-tooltip>
</div>
<a-card :bordered="false">
<a-statistic style="margin-top: 10px" :value="this.mainChart.totalPayCount + ' 笔'" />
</a-card>
</a-skeleton>
</div>
</div>
<div class="chart-item middle-smaller">
<div class="chart-data">
<a-skeleton active :loading="skeletonIsShow" :paragraph="{ rows: 1 }">
<div class="analy-title">
<span>商户数量</span>
<a-tooltip>
<template slot="title">
{{ mainTips.totalMchTip }}
</template>
<a-icon type="question-circle" />
</a-tooltip>
</div>
<a-card :bordered="false">
<a-statistic style="margin-top: 10px" :value="this.mainChart.totalMch" />
</a-card>
</a-skeleton>
</div>
</div>
</div>
</div>
<div class="chart-item top-right">
<div class="chart-data user-greet">
<a-skeleton active avatar :loading="skeletonIsShow" :paragraph="{ rows: 6 }">
<div class="user-greet-title">
<div class="user-greet-all">
<div class="user-greet-img">
<img :src="greetImg" alt="">
</div>
<div class="user-greet-all" >
<span style="">{{ mainTips.helloTitle }}</span>
<div>
<span style="font-size: 12px;" v-if="isAdmin === 1">超级管理员</span>
<span style="font-size: 12px;" v-else>操作员</span>
</div>
</div>
</div>
<!-- <span class="there-spot"></span> -->
<img src="@/assets/svg/more.svg" class="there-spot" alt="更多" slot="extra" @click="showDrawer">
</div>
<div class="quick-start">
<p class="quick-start-title ">快速开始</p>
<ul class="quick-start-ul">
<template v-for="menu in quickMenuList">
<li :key="menu.entId">
<router-link :to="menu.menuUri" tag="span">{{ menu.entName }}</router-link>
</li>
</template>
</ul>
</div>
</a-skeleton>
</div>
</div>
</div>
<div class="chart-bottom">
<div class="chart-item bottom-left">
<div class="chart-data">
<a-skeleton active :loading="skeletonIsShow" :paragraph="{ rows: 6 }"/>
<div v-show="!skeletonIsShow">
<div>
<div class="pay-count-title">
<span class="chart-title">交易统计</span>
<div class="chart-padding" style="border: 1px solid #ddd;" >
<a-range-picker
ref="jeeRange"
style="width:100%"
:ranges="{ '最近一个月': [moment().subtract(1, 'months'),moment()] }"
:default-value="[moment().subtract(7, 'days'),moment()]"
show-time
format="YYYY-MM-DD"
@change="transactionChange"
:disabled-date="disabledDate"
@ok="payCountOk"
:allowClear="false"
>
<div class="change-date-layout">
{{ jeeDate ? jeeDate : '最近七天' }}
<div class="pay-icon">
<div v-if="lastSevenDays" class="change-date-icon"><a-icon type="down" /></div>
<div v-else @click.stop="iconClick" class="change-date-icon" ><a-icon type="close-circle" /></div>
</div>
</div>
</a-range-picker>
</div>
</div>
<div style="position: relative;">
<div v-show="isPayCount">
<div id="payCount" >
</div>
<span style="right: 30px; position: absolute;top: 0;">单位(元)</span>
</div>
<empty v-show="!isPayCount"/>
</div>
</div>
</div>
</div>
</div>
<div class="chart-item bottom-right">
<div class="chart-data">
<a-skeleton active :loading="skeletonIsShow" :paragraph="{ rows: 6 }"/>
<div v-show="!skeletonIsShow">
<div class="pay-count-title">
<span class="chart-title">支付方式</span>
<div class="chart-padding" style="border: 1px solid #ddd;">
<a-range-picker
style="width:100%"
ref="jeeRangePie"
:ranges="{ '最近一个月': [moment().subtract(1, 'months'),moment()] }"
:default-value="[moment().subtract(7, 'days'),moment()]"
@change="payOnChange"
show-time
format="YYYY-MM-DD"
:disabled-date="disabledDate"
@ok="payTypeOk"
:allowClear="false"
>
<div class="change-date-layout">
{{ jeeDatePie ? jeeDatePie : '最近七天' }}
<div class="pay-icon">
<div v-if="!pieDays" class="change-date-icon"><a-icon type="down" /></div>
<div v-else @click.stop="iconPieClick" class="change-date-icon" ><a-icon type="close-circle" /></div>
</div>
</div>
</a-range-picker>
</div>
</div>
<div>
<!-- 如果没数据就展示一个图标 -->
<div v-show="isPayType" id="payType" style="height:300px"></div>
<empty v-show="!isPayType" />
</div>
</div>
</div>
</div>
</div>
<!-- 抽屉 -->
<template>
<div>
<a-drawer
title="用户信息"
placement="right"
:closable="true"
:visible="visible"
@close="onClose"
>
<a-row>
<a-col :span="24">
<a-descriptions>
<a-descriptions-item label="所属系统">
{{ $store.state.user.sysType==="MGR"?"运营平台": $store.state.user.sysType==="MCH"?"商户系统":"未知" }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="24">
<a-descriptions>
<a-descriptions-item label="登录用户名">
{{ $store.state.user.loginUsername }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="24">
<a-descriptions>
<a-descriptions-item label="真实姓名">
{{ $store.state.user.userName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="24">
<a-descriptions>
<a-descriptions-item label="手机号">
{{ $store.state.user.telphone }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="24">
<a-descriptions>
<a-descriptions-item label="性别">
{{ $store.state.user.sex===1?"男":$store.state.user.sex===2?"女":"未知" }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="24">
<a-descriptions>
<a-descriptions-item label="权限">
<a-tag color="#2db7f5" v-if="$store.state.user.isAdmin === 1">超管</a-tag>
<a-tag color="#87d068" v-if="$store.state.user.isAdmin === 0">操作员</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="24">
<a-descriptions>
<a-descriptions-item label="用户状态">
<a-tag color="green" v-if="$store.state.user.state === 1">启用</a-tag>
<a-tag color="red" v-if="$store.state.user.state === 0">停用</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</a-drawer>
</div>
</template>
</div>
</template>
<script>
import { TinyArea, Column, Pie, measureTextWidth } from '@antv/g2plot'
import { getPayAmountWeek, getNumCount, getPayCount, getPayType } from '@/api/manage'
import moment from 'moment'
import store from '@/store'
import { timeFix } from '@/utils/util'
import empty from './empty' // 空数据展示的组件,首页自用
export default {
data () {
return {
skeletonIsShow: true, // 骨架屏是否显示
skeletonReqNum: 0, // 当所有数据请求完毕后关闭骨架屏(共四个请求)
lastSevenDays: true, // 最近七天是否显示
pieDays: false, // 饼状图的关闭按钮是否展示
visible: false,
recordId: store.state.user.userId,
searchData: {}, // 时间选择条件
greetImg: store.state.user.avatarImgPath, // 头像图片地址
isPayType: true, // 支付方式是否存在数据
isPayCount: true, // 交易统计是否存在数据
ispayAmount: true, // 今日交易金额是否存在数据
jeeDate: undefined, // 自定义日期选择框所用状态
jeeDatePie: undefined, // 自定义日期选择框所用状态-支付方式
isAdmin: store.state.user.isAdmin, // 是否为超级管理员
mainTips: { // 主页提示
todayAmountTip: '今日成功交易金额及笔数', // 今日交易提示文字
totalAmountTip: '成功交易总金额', // 交易总金额提示文字
totalPayCountTip: '成功交易总笔数', // 交易总笔数提示文字
totalIsvTip: '服务商数量', // 服务商数量提示文字
totalMchTip: '商户数量', // 商户数量提示文字
helloTitle: ''
},
mainChart: { // 主页统计数据
payAmountData: [], // 近七天交易图表
payCount: [], // 交易统计图表
payType: [], // 支付方式统计图表
todayAmount: 0.00, // 今日交易金额
todayPayCount: 0, // 今日交易笔数
yesterdayAmount: 0.00, // 昨日交易金额
payWeek: 0.00, // 近7天总交易金额
totalPayCount: 0, // 交易总笔数
totalAmount: 0.00, // 交易总金额
totalIsv: 0, // 当前服务商总数
totalMch: 0 // 当前商户总数
},
tinyArea: {},
columnPlot: null, // 柱状图数据
piePlot: null // 环图数据
}
},
components: { empty },
methods: {
init () {
const that = this
if (this.$access('ENT_C_MAIN_PAY_AMOUNT_WEEK')) {
// 周总交易金额
getPayAmountWeek().then(res => {
that.mainChart.payAmountData = res.dataArray
res.dataArray.length === 0 ? this.ispayAmount = false : this.ispayAmount = true
that.mainChart.todayPayCount = res.todayPayCount
that.mainChart.todayAmount = res.todayAmount
that.mainChart.payWeek = res.payWeek
that.mainChart.yesterdayAmount = (res.yesterdayAmount)
that.initPayAmount()
that.skeletonClose(that)
}).catch((err) => {
console.error(err)
that.skeletonClose(that)
this.ispayAmount = false
})
} else {
this.ispayAmount = false
that.skeletonClose(that)
}
if (this.$access('ENT_C_MAIN_NUMBER_COUNT')) {
// 数据统计
getNumCount().then(res => {
that.mainChart.totalMch = res.totalMch
that.mainChart.totalIsv = res.totalIsv
that.mainChart.totalAmount = res.totalAmount
that.mainChart.totalPayCount = res.totalCount
that.skeletonClose(that)
}).catch((err) => {
console.error(err)
that.skeletonClose(that)
})
} else {
that.skeletonClose(that)
}
// 交易统计
if (this.$access('ENT_C_MAIN_PAY_COUNT')) {
getPayCount(that.searchData).then(res => {
that.mainChart.payCount = res
// console.log('交易统计', res)
res.length === 0 ? this.isPayCount = false : this.isPayCount = true
that.initPayCount()
that.skeletonClose(that)
}).catch((err) => {
console.error(err)
this.isPayCount = false
that.skeletonClose(that)
})
} else {
this.isPayCount = false
that.skeletonClose(that)
}
if (this.$access('ENT_C_MAIN_PAY_TYPE_COUNT')) {
// 支付类型统计
getPayType(that.searchData).then(res => {
that.mainChart.payType = res[0]
res[0].length === 0 ? this.isPayType = false : this.isPayType = true
that.initPayType()
that.skeletonClose(that)
}).catch((err) => {
console.error(err)
this.isPayType = false
that.skeletonClose(that)
})
} else {
this.isPayType = false
that.skeletonClose(that)
}
},
initPayAmount () {
this.tinyArea.render()
this.tinyArea.changeData(this.mainChart.payAmountData)
},
initPayCount () {
this.columnPlot.render()
this.columnPlot.changeData(this.mainChart.payCount)
},
initPayType () {
this.piePlot.render()
this.piePlot.changeData(this.mainChart.payType)
},
renderStatistic: function (containerWidth, text, style) {
const { width: textWidth, height: textHeight } = measureTextWidth(text, style)
const R = containerWidth / 2
// r^2 = (w / 2)^2 + (h - offsetY)^2
let scale = 0.7
if (containerWidth < textWidth) {
scale = Math.min(Math.sqrt(Math.abs(Math.pow(R, 2) / (Math.pow(textWidth / 2, 2) + Math.pow(textHeight, 2)))), 1)
}
const textStyleStr = `width:${containerWidth}px`
return `<div style="${textStyleStr};font-size:${scale}em;line-height:${scale < 1 ? 1 : 'inherit'};">${text}</div>`
},
showDrawer () {
this.visible = true
},
onClose () {
this.visible = false
},
payOnChange (date, dateString) {
this.searchData.createdStart = dateString[0] // 开始时间
this.searchData.createdEnd = dateString[1] // 结束时间
this.pieDays = true
this.jeeDatePie = dateString[0] + ' ~ ' + dateString[1]
},
// 交易统计,日期选择器,关闭按钮点击事件
iconClick (dates) {
this.searchData.createdStart = moment().subtract(7, 'days').format('YYYY-MM-DD') // 开始时间
this.searchData.createdEnd = moment().format('YYYY-MM-DD') // 结束时间
this.payCountOk()
this.jeeDate = '最近七天'
this.lastSevenDays = true
},
// 支付方式,日期选择器,关闭按钮点击事件
iconPieClick () {
this.searchData.createdStart = moment().subtract(7, 'days').format('YYYY-MM-DD') // 开始时间
this.searchData.createdEnd = moment().format('YYYY-MM-DD') // 结束时间
this.payTypeOk()
this.jeeDatePie = '最近七天'
this.pieDays = false
},
moment,
disabledDate (current) {
// 当天之前的三十天,可选。 当天也可选
return current < moment().subtract(32, 'days') || current > moment().endOf('day')
},
transactionChange (dates, dateStrings) {
this.searchData.createdStart = dateStrings[0] // 开始时间
this.searchData.createdEnd = dateStrings[1] // 结束时间
this.jeeDate = dateStrings[0] + ' ~ ' + dateStrings[1]
this.lastSevenDays = false
},
payCountOk () {
const that = this
getPayCount(that.searchData).then(res => {
res.length === 0 ? this.isPayCount = false : this.isPayCount = true
that.columnPlot.changeData(res)
})
},
payTypeOk () {
const that = this
getPayType(that.searchData).then(res => {
res[0].length === 0 ? that.isPayType = false : that.isPayType = true
that.piePlot.changeData(res[0])
})
},
skeletonClose (that) {
// 每次请求成功,skeletonReqNum + 1,当大于等于4时, 取消骨架屏展示
that.skeletonReqNum++
that.skeletonReqNum >= 4 ? that.skeletonIsShow = false : that.skeletonIsShow = true
}
},
computed: {
// 快速菜单集合
quickMenuList: function () {
const result = []
const putResult = function (item) {
for (let i = 0; i < item.length; i++) {
if (item[i].menuUri && item[i].quickJump === 1) {
result.push(item[i])
}
if (item[i].children) {
putResult(item[i].children)
}
}
}
putResult(this.$store.state.user.allMenuRouteTree)
return result
}
},
mounted () {
this.tinyArea = new TinyArea('payAmount', {
autoFit: true,
data: this.mainChart.payAmountData,
smooth: true
})
this.columnPlot = new Column('payCount', {
data: this.mainChart.payCount,
xField: 'date',
yField: 'payAmount',
seriesField: 'type',
isGroup: 'true',
marginRatio: 0,
height: 300,
// appendPadding: 16,
appendPadding: [0, 0, 0, 10], // 增加额外的边距,防止左侧的辅助文字被遮挡
theme: {
colors10: ['#FFB238', '#F55536']
},
label: {
// 可配置附加的布局方法
layout: [
],
style: {
fillOpacity: 0
}
}
})
this.piePlot = new Pie('payType', {
width: '100%',
appendPadding: [10, 16, 10, 10], // 增加额外的边距,防止左侧的辅助文字被遮挡
data: this.mainChart.payType,
angleField: 'typeAmount', // 金额 // 笔数 typeCount
colorField: 'typeName',
radius: 1,
innerRadius: 0.64,
autoFit: true,
color: ['#FF6B3B', '#626681', '#FFC100', '#9FB40F', '#76523B', '#DAD5B5', '#0E8E89', '#E19348', '#F383A2', '#247FEA', '#2BCB95', '#B1ABF4', '#1D42C2', '#1D9ED1', '#D64BC0', '#255634', '#8C8C47', '#8CDAE5', '#8E283B', '#791DC9'],
meta: {
value: {
formatter: (v) => `${v} ¥`
}
},
label: {
type: 'inner',
offset: '-50%',
style: {
textAlign: 'center'
},
autoRotate: false,
content: '{value}'
},
statistic: {
title: {
offsetY: -4,
customHtml: (container, view, datum) => {
const { width, height } = container.getBoundingClientRect()
const d = Math.sqrt(Math.pow(width / 2, 2) + Math.pow(height / 2, 2))
const text = datum ? datum.typeName : '总计'
return this.renderStatistic(d, text, { fontSize: 28 })
}
},
content: {
offsetY: 4,
style: {
fontSize: '32px'
},
customHtml: (container, view, datum, data) => {
const { width } = container.getBoundingClientRect()
// 在这里保留小数点后两位
const fixedTwo = data.reduce((r, d) => r + d.typeAmount, 0).toFixed(2)
const text = datum ? ${datum.typeAmount}` : ${fixedTwo}`
return this.renderStatistic(width, text, { fontSize: 32 })
}
}
},
// 添加 中心统计文本 交互
interactions: [{ type: 'element-selected' }, { type: 'element-active' }, { type: 'pie-statistic-active' }]
})
// 用户名信息以及时间问候语句。由于退出登陆才让他更改成功,所以这里的数据先从 vuex中获取
this.mainTips.helloTitle = `${timeFix()},` + this.$store.state.user.userName
this.init()
// 去掉交易统计 日期选择框,原生的边框
this.$refs.jeeRange.$refs.picker.$el.firstChild.style.border = 'none'
this.$refs.jeeRangePie.$refs.picker.$el.firstChild.style.border = 'none'
}
}
</script>
<style lang="less" scoped>
@import './index.less'; // 响应式布局
.user-greet {
font-size: 19px;
font-weight: 500;
.quick-start {
box-sizing: border-box;
padding-top: 20px;
.quick-start-title {
font-size: 16px;
font-weight: 500;
text-align: left;
margin-bottom:0;
}
.quick-start-ul {
font-size: 13px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
padding: 0;
margin-bottom:0;
li {
margin-right: 20px;
margin-top: 10px;
text-align: left;
:hover {
color: @jee-inside-link
}
}
li:hover {
cursor:pointer;
}
}
}
}
.chart-padding {
box-sizing: border-box;
padding: 0 5px;
width:300px;
}
.user-greet-title{
box-sizing:border-box;
padding-bottom:20px;
display:flex;
justify-content:space-between;
border-bottom:1px solid #ddd;
.user-greet-all {
display:flex;
flex-direction:row;
.user-greet-img {
width:60px;
height:60px;
border-radius:50%;
overflow:hidden;
background:#ddd;
margin-right:10px;
img {
width:60px;
height:60px;
border:1px solid rgba(0,0,0,0.08)
}
}
.user-greet-all {
display:flex;
flex-direction:column;
justify-content: space-around;
}
}
}
.analy-title {
display:flex;
justify-content:space-between;
padding-bottom:0;
align-items: center;
}
.there-spot:hover {
cursor:pointer;
}
.ant-calendar-picker-input {
border:none !important
}
.payAmountSpan {
display:flex;
justify-content:space-between;
width: 100%;
box-sizing: border-box;
position: absolute;
bottom:20px;
padding:0 20px;
box-sizing: border-box;
}
.chart-data {
padding:20px;
box-sizing: border-box;
}
.top-left {
.chart-data {
padding:0;
}
}
.pay-amount-text {
display: flex;
padding: 0 20px 0 16px;
box-sizing: border-box;
height: 33px;
line-height: 33px;
align-items: baseline;
margin-bottom: 10px;
.pay-amount {
font-size: 33px;
margin-right: 10px;
}
}
.pay-count-title {
display:flex;
flex-wrap: wrap;
justify-content:space-between;
align-items:center;
.pay-count-date{
display:flex;
justify-content:space-around;
}
}
.chart-padding {
box-sizing: border-box;
max-width:330px;
min-width:260px;
flex-grow: 1;
flex-shrink:1;
margin-bottom: 20px;
}
.change-date-layout {
padding-left: 11px;
align-items: center;
display:flex;
justify-content:space-between;
.change-date-icon {
width:50px;
height:36px;
display:flex;
align-items:center;
justify-content:center;
}
}
.chart-title {
font-size: 16px;
font-weight: 500;
margin-right:20px;
margin-bottom:20px;
}
</style>
<template>
<div class="empty">
<img src="@/assets/svg/empty.svg" alt="" style="width:100px;">
<p style="padding-right:5px;">暂无数据</p>
</div>
</template>
<script>
export default {
name: 'Empty'
}
</script>
<style scoped lang="less">
.empty {
width: 100%;
height:300px; // 与环状图一个高度
display: flex;
justify-content:center;
align-items: center;
flex-direction: column;
text-align:center;
}
</style>
#chart-card {
width: 100%;
}
#chart-card .top-left {
min-height: 250px;
}
#chart-card .chart-data {
min-height: 100px;
height: 100%;
width: 100%;
border-radius: 6px;
background-color: #fff;
}
#chart-card .chart-top,
#chart-card .chart-bottom {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
#chart-card .chart-top .middle-smaller,
#chart-card .chart-bottom .middle-smaller,
#chart-card .chart-top .middle-larger,
#chart-card .chart-bottom .middle-larger {
height: 100%;
}
#chart-card .chart-top .top-left,
#chart-card .chart-bottom .top-left {
min-height: 238px;
}
#chart-card .chart-item {
width: 100%;
box-sizing: border-box;
padding: 12px;
}
@media screen and (max-width: 767px) {
#chart-card .chart-top .top-left {
order: 1;
}
#chart-card .chart-top .top-middle {
order: 2;
width: 100%;
}
#chart-card .chart-top .top-middle .middle-top,
#chart-card .chart-top .top-middle .middle-bottom {
display: flex;
flex-direction: column;
}
#chart-card .chart-top .top-middle .middle-top .middle-larger,
#chart-card .chart-top .top-middle .middle-bottom .middle-larger,
#chart-card .chart-top .top-middle .middle-top .middle-smaller,
#chart-card .chart-top .top-middle .middle-bottom .middle-smaller {
width: 100%;
}
#chart-card .chart-top .top-right {
order: 0;
}
}
@media screen and (min-width: 768px) {
#chart-card .top-left,
#chart-card .top-middle {
order: 1;
}
#chart-card .top-middle {
width: 100%;
}
#chart-card .top-middle .middle-top,
#chart-card .top-middle .middle-bottom {
display: flex;
flex-direction: row;
}
#chart-card .top-middle .middle-top .middle-larger,
#chart-card .top-middle .middle-bottom .middle-larger {
flex-grow: 1;
}
#chart-card .top-middle .middle-top .middle-smaller,
#chart-card .top-middle .middle-bottom .middle-smaller {
max-width: 170px;
min-width: 150px;
}
#chart-card .top-right {
order: 0;
}
}
@media screen and (min-width: 1200px) {
#chart-card .top-left {
order: 1;
width: 50%;
}
#chart-card .top-middle {
width: 50%;
order: 2;
}
#chart-card .top-middle .middle-top,
#chart-card .top-middle .middle-bottom {
display: flex;
flex-direction: row;
}
#chart-card .top-middle .middle-top .middle-larger,
#chart-card .top-middle .middle-bottom .middle-larger {
flex-grow: 1;
}
#chart-card .top-middle .middle-top .middle-smaller,
#chart-card .top-middle .middle-bottom .middle-smaller {
max-width: 170px;
min-width: 150px;
}
#chart-card .top-right {
width: 100%;
order: 0;
}
}
@media screen and (min-width: 1500px) {
#chart-card {
flex-direction: row;
}
#chart-card .chart-top {
width: 100%;
order: 0;
flex-wrap: nowrap;
}
#chart-card .chart-top .top-left,
#chart-card .chart-top .top-middle {
width: 500px;
min-width: 460px;
order: 0;
}
#chart-card .chart-top .top-middle {
order: 1;
}
#chart-card .chart-top .top-middle .middle-top,
#chart-card .chart-top .top-middle .middle-bottom {
display: flex;
width: 100%;
height: 50%;
flex-wrap: nowrap;
}
#chart-card .chart-top .top-middle .middle-smaller {
min-width: 170px;
}
#chart-card .chart-top .top-middle .middle-larger {
width: 200px;
flex-grow: 1;
}
#chart-card .chart-top .top-right {
order: 2;
flex-grow: 1;
}
#chart-card .chart-bottom {
order: 1;
width: 100%;
flex-wrap: nowrap;
}
#chart-card .chart-bottom .bottom-right {
flex-shrink: 1;
max-width: 500px;
min-width: 330px;
}
#chart-card .chart-bottom .bottom-left {
min-width: 900px;
flex-grow: 1;
}
}
#chart-card {
width: 100%;
.top-left {
min-height: 250px;
}
.chart-data {
min-height: 100px;
height: 100%;
width: 100%;
border-radius: 6px;
background-color: #fff;
}
.chart-top, .chart-bottom {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.middle-smaller, .middle-larger { // 较小的区域
height: 100%;
}
.top-left {
min-height: 238px;
}
}
.chart-item {
width: 100%;
box-sizing: border-box;
padding: 12px;
}
}
@media screen and (max-width:767px){
#chart-card {
.chart-top {
.top-left {
order: 1;
}
.top-middle {
order: 2;
width: 100%;
.middle-top, .middle-bottom {
display: flex;
flex-direction: column;
.middle-larger, .middle-smaller {
width: 100%;
}
}
}
.top-right {
order: 0;
}
}
}
}
@media screen and (min-width:768px){
#chart-card {
.top-left, .top-middle {
order: 1;
}
.top-middle {
width: 100%;
.middle-top, .middle-bottom {
display: flex;
flex-direction: row;
.middle-larger {
flex-grow: 1; // 设置为1,存在剩余空间放大
}
.middle-smaller {
max-width: 170px;
min-width: 150px;
}
}
}
.top-right {
order: 0;
}
}
}
@media screen and (min-width:1200px){
#chart-card {
.top-left {
order: 1;
width: 50%;
}
.top-middle {
width: 50%;
order: 2;
.middle-top, .middle-bottom {
display: flex;
flex-direction: row;
.middle-larger {
flex-grow: 1; // 设置为1,存在剩余空间放大
}
.middle-smaller {
max-width: 170px;
min-width: 150px;
}
}
}
.top-right {
width: 100%;
order: 0;
}
}
}
@media screen and (min-width:1500px){
#chart-card {
flex-direction: row;
.chart-top {
width:100%;
order: 0;
flex-wrap: nowrap; // 禁止换行
.top-left, .top-middle {
width:500px;
min-width: 460px;
order: 0;
}
.top-middle {
order: 1;
.middle-top, .middle-bottom {
display: flex;
width: 100%;
height: 50%;
flex-wrap: nowrap; // 禁止换行
}
.middle-smaller { // 较小的区域
min-width: 170px;
}
.middle-larger { // 较大的区域
width: 200px;
flex-grow: 1; // 设置为1,存在剩余空间放大
}
}
.top-right {
order: 2;
flex-grow: 1; // 设置为1,存在剩余空间放大
}
}
.chart-bottom {
order: 1;
width:100%;
flex-wrap: nowrap;
.bottom-right {
flex-shrink: 1;
max-width: 500px;
min-width: 330px;
}
.bottom-left {
min-width: 900px;
flex-grow: 1; // 设置为1,存在剩余空间放大
}
}
}
}
\ No newline at end of file
<template>
<a-modal v-model="isShow" :title=" isAdd ? '新增菜单' : '修改菜单' " @ok="handleOkFunc" :confirmLoading="confirmLoading">
<a-form-model ref="infoFormModel" :model="saveObject" :label-col="{span: 6}" :wrapper-col="{span: 15}" :rules="rules">
<a-form-model-item label="资源名称:" prop="entName">
<a-input v-model="saveObject.entName" />
</a-form-model-item>
<a-form-model-item label="路径地址:" prop="menuUri">
<a-input v-model="saveObject.menuUri" />
</a-form-model-item>
<a-form-model-item label="排序(正序显示):" prop="entSort">
<a-input v-model="saveObject.entSort" />
</a-form-model-item>
<a-form-model-item label="快速开始:" prop="quickJump">
<a-radio-group v-model="saveObject.quickJump" :disabled="saveObject.menuType == 'PB' || !saveObject.menuUri">
<a-radio :value="1"></a-radio>
<a-radio :value="0"></a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="状态:" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">启用</a-radio>
<a-radio :value="0">停用</a-radio>
</a-radio-group>
</a-form-model-item>
</a-form-model>
</a-modal>
</template>
<script>
import { API_URL_ENT_LIST, req, getEntBySysType } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function }
},
data () {
return {
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {}, // 数据对象
recordId: null, // 更新对象ID
sysType: 'MGR', // 菜单类型
rules: {
entName: [
{ required: true, message: '请输入资源名称', trigger: 'blur' }
]
}
}
},
created () {
},
methods: {
show: function (recordId, sysType) { // 弹层打开事件
this.isAdd = !recordId
this.sysType = sysType
this.saveObject = {} // 数据清空
this.confirmLoading = false // 关闭loading
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
if (!this.isAdd) { // 修改信息 延迟展示弹层
that.recordId = recordId
getEntBySysType(recordId, sysType).then(res => { that.saveObject = res })
this.isShow = true
} else {
that.isShow = true // 立马展示弹层信息
}
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
// 请求接口
that.confirmLoading = true // 显示loading
if (that.isAdd) {
} else {
req.updateById(API_URL_ENT_LIST, that.recordId, that.saveObject).then(res => {
that.$message.success('修改成功')
that.isShow = false
that.callbackFunc() // 刷新列表
}).catch(res => { that.confirmLoading = false })
}
}
})
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" style="margin-bottom:30px">
<a-row :gutter="16">
<a-col :sm="18">
<a-row :gutter="16">
<a-col :md="6">
<a-form-item label="">
<a-select v-model="querySysType" placeholder="选择系统菜单" @change="refTable" class="table-head-layout">
<a-select-option value="MGR">显示菜单:运营平台</a-select-option>
<a-select-option value="MCH">显示菜单:商户系统</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-col>
</a-row>
</a-form>
</div>
<a-table
:columns="tableColumns"
:data-source="dataSource"
:pagination="false"
:loading="loading"
rowKey="entId"
:scroll="{ x: 1450 }">
<template slot="stateSlot" slot-scope="record">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_UR_ROLE_ENT_EDIT')" :onChange="(state) => { return updateState(record.entId, state)}"/>
</template>
<template slot="opSlot" slot-scope="record"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a v-if="$access('ENT_UR_ROLE_ENT_EDIT')" @click="editFunc(record.entId)">修改</a>
</JeepayTableColumns>
</template>
</a-table>
</a-card>
<!-- 新增 / 修改 页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="refTable" />
</page-header-wrapper>
</template>
<script>
import { getEntTree, API_URL_ENT_LIST, reqLoad } from '@/api/manage'
import JeepayTableColState from '@/components/JeepayTable/JeepayTableColState'
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import InfoAddOrEdit from './AddOrEdit'
const tableColumns = [
{ title: '资源权限ID', dataIndex: 'entId' }, // key为必填项,用于标志该列的唯一
{ title: '资源名称', dataIndex: 'entName' },
{ title: '图标', dataIndex: 'menuIcon' },
{ title: '路径', dataIndex: 'menuUri' },
{ title: '组件名称', dataIndex: 'componentName' },
{ title: '类型', dataIndex: 'entType' },
{ title: '状态', scopedSlots: { customRender: 'stateSlot' }, align: 'center' },
{ title: '排序', dataIndex: 'entSort' },
{ title: '修改时间', dataIndex: 'updatedAt' },
{ title: '操作', width: '100px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'EntPage',
components: { JeepayTableColState, JeepayTableColumns, InfoAddOrEdit },
data () {
return {
querySysType: 'MGR', // 默认查询运营平台
tableColumns: tableColumns,
dataSource: [],
loading: false
}
},
mounted () {
this.refTable() // 刷新页面
},
methods: {
refTable: function () {
const that = this
that.loading = true
getEntTree(that.querySysType).then(res => {
that.dataSource = res
that.loading = false
})
},
updateState: function (recordId, state) {
const that = this
return reqLoad.updateById(API_URL_ENT_LIST, recordId, { state: state, sysType: that.querySysType }).then(res => {
that.$message.success('更新成功')
that.refTable() // 刷新页面
})
},
editFunc: function (recordId) { // 业务通用【修改】 函数
this.$refs.infoAddOrEdit.show(recordId, this.querySysType)
}
}
}
</script>
<template>
<div class="result-err">
<img src="~@/assets/svg/403.svg" alt="">
<div>
抱歉,您无权访问此页。
</div>
<a-button type="primary" @click="toHome" style="margin-top:30px">
返回首页
</a-button>
</div>
</template>
<script>
export default {
name: 'Exception403',
methods: {
toHome () {
this.$router.push({ path: '/' })
}
}
}
</script>
<template>
<div class="result-err">
<img src="~@/assets/svg/404.svg" alt="">
<div>
抱歉,您访问的页面不存在。
</div>
<a-button type="primary" @click="toHome" style="margin-top:30px">
返回首页
</a-button>
</div>
</template>
<script>
export default {
name: 'Exception404',
methods: {
toHome () {
this.$router.push({ path: '/' })
}
}
}
</script>
<template>
<div class="result-err">
<img src="~@/assets/svg/500.svg" alt="">
<div>
对不起,服务器错误。
</div>
<a-button type="primary" @click="toHome" style="margin-top:30px">
返回首页
</a-button>
</div>
</template>
<script>
export default {
name: 'Exception500',
methods: {
toHome () {
this.$router.push({ path: '/' })
}
}
}
</script>
<template>
<a-drawer
:maskClosable="false"
:visible="visible"
:title=" isAdd ? '新增服务商' : '修改服务商' "
@close="onClose"
:body-style="{ paddingBottom: '80px' }"
width="40%"
>
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="服务商名称" prop="isvName">
<a-input
placeholder="请输入服务商名称"
v-model="saveObject.isvName"
/>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="服务商简称" prop="isvShortName">
<a-input
placeholder="请输入服务商简称"
v-model="saveObject.isvShortName"
/>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="联系人姓名" prop="contactName">
<a-input
placeholder="请输入联系人姓名"
v-model="saveObject.contactName"
/>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="联系人手机号" prop="contactTel">
<a-input
placeholder="请输入联系人手机号"
v-model="saveObject.contactTel"
>
</a-input>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="联系人邮箱" prop="contactEmail">
<a-input
placeholder="请输入联系人邮箱"
v-model="saveObject.contactEmail"
>
</a-input>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state" :defaultValue="1">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
禁用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入备注" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center">
<a-button icon="close" @click="onClose" style="margin-right:8px">
取消
</a-button>
<a-button type="primary" style="margin-right:8px" icon="check" @click="handleOkFunc" :loading="btnLoading">
保存
</a-button>
</div>
</a-drawer>
</template>
<script>
import { API_URL_ISV_LIST, req } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function }
},
data () {
return {
btnLoading: false,
isAdd: true, // 新增 or 修改页面标志
saveObject: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
rules: {
isvName: [ { required: true, message: '请输入服务商名称', trigger: 'blur' } ],
isvShortName: [ { required: true, message: '请输入服务商简称', trigger: 'blur' } ],
contactEmail: [{ required: false, pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: 'blur' }],
contactTel: [{ required: false, pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }]
}
}
},
created () {
},
methods: {
show: function (recordId) { // 弹层打开事件
this.isAdd = !recordId
this.saveObject = { 'state': 1 } // 数据清空
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
if (!this.isAdd) { // 修改信息 延迟展示弹层
that.recordId = recordId
req.getById(API_URL_ISV_LIST, recordId).then(res => {
that.saveObject = res
})
this.visible = true
} else {
that.visible = true // 立马展示弹层信息
}
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
that.btnLoading = true
// 请求接口
if (that.isAdd) {
req.add(API_URL_ISV_LIST, that.saveObject).then(res => {
that.$message.success('新增成功')
that.visible = false
that.callbackFunc() // 刷新列表
that.btnLoading = false
}).catch(res => {
that.btnLoading = false
})
} else {
req.updateById(API_URL_ISV_LIST, that.recordId, that.saveObject).then(res => {
that.$message.success('修改成功')
that.visible = false
that.callbackFunc() // 刷新列表
that.btnLoading = false
}).catch(res => {
that.btnLoading = false
})
}
}
})
},
onClose () {
this.visible = false
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<jeepay-text-up :placeholder="'服务商号'" :msg="searchData.isvNo" v-model="searchData.isvNo" />
<jeepay-text-up :placeholder="'服务商名称'" :msg="searchData.isvName" v-model="searchData.isvName" />
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.state" placeholder="服务商状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons">
<a-button type="primary" icon="search" @click="queryFunc" :loading="btnLoading">搜索</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
<div>
<a-button icon="plus" v-if="$access('ENT_ISV_INFO_ADD')" type="primary" @click="addFunc" class="mg-b-30">新建</a-button>
</div>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="isvNo"
>
<template slot="isvNameSlot" slot-scope="{record}"><b>{{ record.isvName }}</b></template> <!-- 自定义插槽 -->
<template slot="stateSlot" slot-scope="{record}">
<a-badge :status="record.state === 0?'error':'processing'" :text="record.state === 0?'禁用':'启用'" />
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_ISV_INFO_EDIT')" @click="editFunc(record.isvNo)">修改</a-button>
<a-button type="link" v-if="$access('ENT_ISV_PAY_CONFIG_LIST')" @click="showPayIfConfigList(record.isvNo)">支付配置</a-button>
<a-button type="link" v-if="$access('ENT_ISV_INFO_DEL')" style="color: red" @click="delFunc(record.isvNo)">删除</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc"/>
<!-- 支付参数配置页面组件 -->
<IsvPayIfConfigList ref="isvPayIfConfigList" />
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_ISV_LIST, req } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit'
import IsvPayIfConfigList from './IsvPayIfConfigList'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'isvName', width: '200px', title: '服务商名称', fixed: 'left', scopedSlots: { customRender: 'isvNameSlot' } },
{ key: 'isvNo', title: '服务商号', dataIndex: 'isvNo' },
{ key: 'state', title: '服务商状态', scopedSlots: { customRender: 'stateSlot' } },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期' },
{ key: 'op', title: '操作', width: '260px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'IsvListPage',
components: { JeepayTable, JeepayTableColumns, InfoAddOrEdit, IsvPayIfConfigList, JeepayTextUp },
data () {
return {
btnLoading: false,
tableColumns: tableColumns,
searchData: {}
}
},
mounted () {
},
methods: {
queryFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
},
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_ISV_LIST, params)
},
delFunc: function (recordId) {
const that = this
this.$infoBox.confirmDanger('确认删除?', '请确认该服务商下未分配商户', () => {
req.delById(API_URL_ISV_LIST, recordId).then(res => {
that.$refs.infoTable.refTable(false)
this.$message.success('删除成功')
})
})
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
addFunc: function () { // 业务通用【新增】 函数
this.$refs.infoAddOrEdit.show()
},
editFunc: function (recordId) { // 业务通用【修改】 函数
this.$refs.infoAddOrEdit.show(recordId)
},
showPayIfConfigList: function (recordId) { // 支付参数配置
this.$refs.isvPayIfConfigList.show(recordId)
}
}
}
</script>
<template>
<a-drawer
:visible="visible"
title="支付参数列表"
@close="onClose"
:closable="true"
:body-style="{ paddingBottom: '80px' }"
:drawer-style="{ backgroundColor: '#f0f2f5' }"
width="80%"
>
<JeepayCard
ref="infoCard"
:reqCardListFunc="reqCardListFunc"
:span="jeepayCard.span"
:height="jeepayCard.height"
>
<div slot="cardContentSlot" slot-scope="{record}">
<div :style="{'height': jeepayCard.height + 'px'}" class="jeepay-card-content">
<!-- 卡片自定义样式 -->
<div class="jeepay-card-content-header" :style="{backgroundColor: record.bgColor, height: jeepayCard.height/2 + 'px'}">
<img v-if="record.icon" :src="record.icon" :style="{height: jeepayCard.height/5 + 'px'}">
</div>
<div class="jeepay-card-content-body" :style="{height: (jeepayCard.height/2 - 50) + 'px'}">
<div class="title">
{{ record.ifName }}
</div>
<a-badge :status="record.ifConfigState ===1 ? 'processing' : 'error'" :text="record.ifConfigState ===1 ? '启用' : '未开通'" ></a-badge>
</div>
<!-- 卡片底部操作栏 -->
<div class="jeepay-card-ops">
<a v-if="$access('ENT_ISV_PAY_CONFIG_ADD')" @click="editPayIfConfigFunc(record)">填写参数 <a-icon key="right" type="right" style="fontSize: 13px"></a-icon></a>
<a v-else>暂无操作</a>
</div>
</div>
</div>
</JeepayCard>
<a-drawer
title="支付参数配置"
width="40%"
:closable="true"
:visible="childrenVisible"
:body-style="{ paddingBottom: '80px' }"
@close="onChildrenDrawerClose"
:maskClosable="false"
>
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-model-item label="支付接口费率" prop="ifRate">
<a-input v-model="saveObject.ifRate" placeholder="请输入" suffix="%" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<a-divider orientation="left">
<a-tag color="#FF4B33">
{{ saveObject.ifCode }} 服务商参数配置
</a-tag>
</a-divider>
<a-form-model ref="isvParamFormModel" :model="ifParams" layout="vertical" :rules="ifParamsRules">
<a-row :gutter="16">
<a-col v-for="(item, key) in isvParams" :key="key" :span="item.type === 'text' ? 12 : 24">
<a-form-model-item :label="item.desc" :prop="item.name" v-if="item.type === 'text' || item.type === 'textarea'">
<a-input v-if="item.star === '1'" v-model="ifParams[item.name]" :placeholder="ifParams[item.name + '_ph']" :type="item.type" />
<a-input v-else v-model="ifParams[item.name]" placeholder="请输入" :type="item.type" />
</a-form-model-item>
<a-form-model-item :label="item.desc" :prop="item.name" v-else-if="item.type === 'radio'">
<a-radio-group v-model="ifParams[item.name]">
<a-radio v-for="(radioItem, radioKey) in item.values" :key="radioKey" :value="radioItem.value">
{{ radioItem.title }}
</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item :label="item.desc" :prop="item.name" v-else-if="item.type === 'file'">
<a-input v-model="ifParams[item.name]" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams[item.name]"
@uploadSuccess="uploadSuccess($event, item.name)"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center">
<a-button @click="onChildrenDrawerClose" :style="{ marginRight: '8px' }" icon="close">
取消
</a-button>
<a-button type="primary" @click="onSubmit" :loading="btnLoading" icon="check">
保存
</a-button>
</div>
</a-drawer>
<!-- 支付参数配置页面组件 -->
<WxpayPayConfig ref="wxpayPayConfig" :callbackFunc="refCardList" />
<!-- 支付参数配置页面组件 -->
<AlipayPayConfig ref="alipayPayConfig" :callbackFunc="refCardList" />
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import WxpayPayConfig from './custom/WxpayPayConfig'
import AlipayPayConfig from './custom/AlipayPayConfig'
import { API_URL_ISV_PAYCONFIGS_LIST, getIsvPayConfigUnique, req, upload } from '@/api/manage'
export default {
components: {
JeepayCard,
JeepayUpload,
WxpayPayConfig,
AlipayPayConfig
},
data () {
return {
btnLoading: false,
isvNo: null, // 服务商号
action: upload.cert, // 上传文件地址
visible: false, // 一级抽屉开关
childrenVisible: false, // 二级抽屉开关
isvParams: {}, // 支付接口定义描述
saveObject: {}, // 保存的对象
ifParams: {}, // 参数配置对象
jeepayCard: { // 卡片配置
height: 300,
span: { xxl: 6, xl: 4, lg: 4, md: 3, sm: 2, xs: 1 }
},
rules: {
infoId: [{ required: true, trigger: 'blur' }],
ifCode: [{ required: true, trigger: 'blur' }],
ifRate: [{ required: false, pattern: /^(([1-9]{1}\d{0,1})|(0{1}))(\.\d{1,4})?$/, message: '请输入0-100之间的数字,最多四位小数', trigger: 'blur' }]
},
ifParamsRules: {}
}
},
watch: {
ifParams: function (o, n) {
this.$set(this.ifParams, 'appSecret', this.ifParams.appSecret) // 解决appSecret 双向绑定数据不显示的问题
}
},
methods: {
generoterRules () {
const rules = {}
let newItems = []
this.isvParams.forEach(item => {
newItems = []
if (item.verify === 'required' && item.star !== '1') {
newItems.push({
required: true,
message: '请输入' + item.desc,
trigger: 'blur'
})
rules[item.name] = newItems
}
})
this.ifParamsRules = rules
},
// 弹层打开事件
show: function (isvNo) {
this.isvNo = isvNo
this.ifCode = null
this.visible = true
this.refCardList()
},
// 请求支付接口定义数据
reqCardListFunc () {
return req.list(API_URL_ISV_PAYCONFIGS_LIST, { 'isvNo': this.isvNo })
},
// 刷新card列表
refCardList () {
this.$refs.infoCard.refCardList()
},
// 支付参数配置
editPayIfConfigFunc (record) {
if (record.configPageType === 1) { // JSON渲染页面
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
if (this.$refs.isvParamFormModel !== undefined) {
this.$refs.isvParamFormModel.resetFields()
}
this.childrenVisible = true // 打开支付参数配置抽屉
this.saveObject = {} // 要保存的对象
this.ifParams = {} // 参数配置对象
this.isvParams = {} // 支付接口定义描述
this.saveObject.infoId = this.isvNo
this.saveObject.ifCode = record.ifCode
this.saveObject.state = record.ifConfigState === 0 ? 0 : 1
if (!record) {
return
}
const that = this
// 获取支付参数
getIsvPayConfigUnique(this.saveObject.infoId, this.saveObject.ifCode).then(res => {
if (res && res.ifParams) {
this.saveObject = res
this.ifParams = JSON.parse(res.ifParams)
}
const newItems = [] // 重新加载支付接口配置定义描述json
JSON.parse(record.isvParams).forEach(item => {
const radioItems = [] // 存放单选框value title
if (item.type === 'radio') {
const valueItems = item.values.split(',')
const titleItems = item.titles.split(',')
for (const i in valueItems) {
// 检查参数是否为数字类型 然后赋值给radio值
let radioVal = valueItems[i]
if (!isNaN((radioVal))) { radioVal = Number(radioVal) }
radioItems.push({
value: radioVal,
title: titleItems[i]
})
}
}
if (item.star === '1') {
that.ifParams[item.name + '_ph'] = that.ifParams[item.name] ? that.ifParams[item.name] : '请输入'
if (that.ifParams[item.name]) {
that.ifParams[item.name] = ''
}
}
newItems.push({
name: item.name,
desc: item.desc,
type: item.type,
verify: item.verify,
values: radioItems,
star: item.star // 脱敏标识 1-是
})
})
that.isvParams = newItems // 重新赋值接口定义描述
that.generoterRules()
that.$forceUpdate()
})
} else if (record.configPageType === 2) { // 自定义配置页面,页面放在custom目录下,配置模块命名规则:if_code + PayConfig
this.$refs[record.ifCode + 'PayConfig'].show(this.isvNo, record)
}
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
this.$refs.isvParamFormModel.validate(valid2 => {
if (valid && valid2) { // 验证通过
that.btnLoading = true
const reqParams = {}
reqParams.infoId = that.saveObject.infoId
reqParams.ifCode = that.saveObject.ifCode
reqParams.ifRate = that.saveObject.ifRate
reqParams.state = that.saveObject.state
reqParams.remark = that.saveObject.remark
// 支付参数配置不能为空
if (Object.keys(that.ifParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
// 脱敏数据为空时,删除该key
this.isvParams.forEach(item => {
if (item.star === '1' && that.ifParams[item.name] === '') {
that.ifParams[item.name] = undefined
}
that.ifParams[item.name + '_ph'] = undefined
})
reqParams.ifParams = JSON.stringify(that.ifParams)
// 请求接口
req.add(API_URL_ISV_PAYCONFIGS_LIST, reqParams).then(res => {
that.$message.success('保存成功')
that.childrenVisible = false
that.refCardList()
that.btnLoading = false
})
}
})
})
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.ifParams[name] = value
this.$forceUpdate()
},
// 抽屉关闭
onClose () {
this.visible = false
},
onChildrenDrawerClose () {
this.childrenVisible = false
}
}
}
</script>
<style lang="less" scoped>
.jeepay-card-content {
width: 100%;
position: relative;
background-color: @jee-card-back;
border-radius: 6px;
overflow:hidden;
}
.jeepay-card-ops {
width: 100%;
height: 50px;
background-color: @jee-card-back;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
border-top: 1px solid @jee-back;
position: absolute;
bottom: 0;
}
.jeepay-card-content-header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.jeepay-card-content-body {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.title {
font-size: 16px;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
color: #1a1a1a;
letter-spacing: 1px;
}
</style>
<template>
<a-drawer
title="填写参数"
width="40%"
:closable="true"
:maskClosable="false"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
@close="onClose"
>
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-model-item label="支付接口费率" prop="ifRate">
<a-input v-model="saveObject.ifRate" placeholder="请输入" suffix="%" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<a-divider orientation="left">
<a-tag color="#FF4B33">
{{ saveObject.ifCode }} 服务商参数配置
</a-tag>
</a-divider>
<a-form-model ref="isvParamFormModel" :model="ifParams" layout="vertical" :rules="ifParamsRules">
<a-row :gutter="16">
<a-col span="24">
<a-form-model-item label="环境配置" prop="sandbox">
<a-radio-group v-model="ifParams.sandbox">
<a-radio :value="1">沙箱环境</a-radio>
<a-radio :value="0">生产环境</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="合作伙伴身份(PID)" prop="pid">
<a-input v-model="ifParams.pid" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="应用AppID" prop="appId">
<a-input v-model="ifParams.appId" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="应用私钥" prop="privateKey">
<a-input v-model="ifParams.privateKey" :placeholder="ifParams.privateKey_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="支付宝公钥" prop="alipayPublicKey">
<a-input v-model="ifParams.alipayPublicKey" :placeholder="ifParams.alipayPublicKey_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="接口签名方式(推荐使用RSA2)" prop="signType">
<a-radio-group v-model="ifParams.signType" defaultValue="RSA">
<a-radio value="RSA">RSA</a-radio>
<a-radio value="RSA2">RSA2</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="公钥证书" prop="useCert">
<a-radio-group v-model="ifParams.useCert" defaultValue="1">
<a-radio :value="1">使用证书(请使用RSA2私钥)</a-radio>
<a-radio :value="0">不使用证书</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="应用公钥证书(.crt格式)" prop="appPublicCert">
<a-input v-model="ifParams.appPublicCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.appPublicCert"
@uploadSuccess="uploadSuccess($event, 'appPublicCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="支付宝公钥证书(.crt格式)" prop="alipayPublicCert">
<a-input v-model="ifParams.alipayPublicCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.alipayPublicCert"
@uploadSuccess="uploadSuccess($event, 'alipayPublicCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="支付宝根证书(.crt格式)" prop="alipayRootCert">
<a-input v-model="ifParams.alipayRootCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.alipayRootCert"
@uploadSuccess="uploadSuccess($event, 'alipayRootCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center" v-if="$access('ENT_MCH_PAY_CONFIG_ADD')">
<a-button :style="{ marginRight: '8px' }" @click="onClose" icon="close">取消</a-button>
<a-button type="primary" @click="onSubmit" icon="check" :loading="btnLoading">保存</a-button>
</div>
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import { API_URL_ISV_PAYCONFIGS_LIST, req, getIsvPayConfigUnique, upload } from '@/api/manage'
export default {
components: {
JeepayCard,
JeepayUpload
},
props: {
callbackFunc: { type: Function, default: () => ({}) }
},
data () {
return {
btnLoading: false,
visible: false, // 抽屉开关
isAdd: true,
action: upload.cert, // 上传文件地址
saveObject: {}, // 保存的对象
ifParams: {}, // 参数配置对象
rules: {
ifRate: [{ required: false, pattern: /^(([1-9]{1}\d{0,1})|(0{1}))(\.\d{1,4})?$/, message: '请输入0-100之间的数字,最多四位小数', trigger: 'blur' }]
},
ifParamsRules: {
pid: [{ required: true, message: '请输入合作伙伴身份(PID)', trigger: 'blur' }],
appId: [{ required: true, message: '请输入应用AppID', trigger: 'blur' }],
privateKey: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.isAdd && !value) {
callback(new Error('请输入应用私钥'))
}
callback()
} }],
alipayPublicKey: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.useCert === 0 && this.isAdd && !value) {
callback(new Error('请输入支付宝公钥'))
}
callback()
} }],
appPublicCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.useCert === 1 && !this.ifParams.appPublicCert) {
callback(new Error('请上传应用公钥证书(.crt格式)'))
}
callback()
} }],
alipayPublicCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.useCert === 1 && !this.ifParams.alipayPublicCert) {
callback(new Error('请上传支付宝公钥证书(.crt格式)'))
}
callback()
} }],
alipayRootCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.useCert === 1 && !this.ifParams.alipayRootCert) {
callback(new Error('请上传支付宝根证书(.crt格式)'))
}
callback()
} }]
}
}
},
methods: {
// 弹层打开事件
show: function (isvNo, record) {
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
if (this.$refs.isvParamFormModel !== undefined) {
this.$refs.isvParamFormModel.resetFields()
}
// 数据初始化
this.saveObject = {
infoId: isvNo,
ifCode: record.ifCode,
state: record.ifConfigState === 0 ? 0 : 1
}
// 参数配置对象,数据初始化
this.ifParams = {
sandbox: 0,
signType: 'RSA2',
useCert: 0,
privateKey: '',
privateKey_ph: '请输入',
alipayPublicKey: '',
alipayPublicKey_ph: '请输入'
}
this.visible = true
this.getIsvPayConfig()
},
// 支付参数配置
getIsvPayConfig () {
const that = this
// 获取支付参数
getIsvPayConfigUnique(that.saveObject.infoId, that.saveObject.ifCode).then(res => {
if (res && res.ifParams) {
that.saveObject = res
that.ifParams = JSON.parse(res.ifParams)
that.ifParams.privateKey_ph = that.ifParams.privateKey
that.ifParams.privateKey = ''
that.ifParams.alipayPublicKey_ph = that.ifParams.alipayPublicKey
that.ifParams.alipayPublicKey = ''
that.isAdd = false
} else if (res === undefined) {
that.isAdd = true
}
})
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
this.$refs.isvParamFormModel.validate(valid2 => {
if (valid && valid2) { // 验证通过
that.btnLoading = true
const reqParams = {}
reqParams.infoId = that.saveObject.infoId
reqParams.ifCode = that.saveObject.ifCode
reqParams.ifRate = that.saveObject.ifRate
reqParams.state = that.saveObject.state
reqParams.remark = that.saveObject.remark
// 支付参数配置不能为空
if (Object.keys(that.ifParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
// 脱敏数据为空时,删除该key
that.clearEmptyKey('privateKey')
that.clearEmptyKey('alipayPublicKey')
reqParams.ifParams = JSON.stringify(that.ifParams)
// 请求接口
if (Object.keys(reqParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
req.add(API_URL_ISV_PAYCONFIGS_LIST, reqParams).then(res => {
that.$message.success('保存成功')
that.visible = false
that.btnLoading = false
that.callbackFunc()
})
}
})
})
},
// 脱敏数据为空时,删除对应key
clearEmptyKey (key) {
if (!this.ifParams[key]) {
this.ifParams[key] = undefined
}
this.ifParams[key + '_ph'] = undefined
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.ifParams[name] = value
this.$forceUpdate()
},
onClose () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<a-drawer
title="填写参数"
width="40%"
:closable="true"
:maskClosable="false"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
@close="onClose"
>
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-model-item label="支付接口费率" prop="ifRate">
<a-input v-model="saveObject.ifRate" placeholder="请输入" suffix="%" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<a-divider orientation="left">
<a-tag color="#FF4B33">
{{ saveObject.ifCode }} 服务商参数配置
</a-tag>
</a-divider>
<a-form-model ref="isvParamFormModel" :model="ifParams" layout="vertical" :rules="ifParamsRules">
<a-row :gutter="16">
<a-col span="12">
<a-form-model-item label="微信支付商户号" prop="mchId">
<a-input v-model="ifParams.mchId" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="应用AppID" prop="appId">
<a-input v-model="ifParams.appId" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="应用AppSecret" prop="appSecret">
<a-input v-model="ifParams.appSecret" :placeholder="ifParams.appSecret_ph" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="oauth2地址(置空将使用官方)" prop="oauth2Url">
<a-input v-model="ifParams.oauth2Url" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="微信支付API版本" prop="apiVersion">
<a-radio-group v-model="ifParams.apiVersion" defaultValue="V2">
<a-radio value="V2">V2</a-radio>
<a-radio value="V3">V3</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="APIv2密钥" prop="key">
<a-input v-model="ifParams.key" :placeholder="ifParams.key_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="APIv3密钥" prop="apiV3Key">
<a-input v-model="ifParams.apiV3Key" :placeholder="ifParams.apiV3Key_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="序列号" prop="serialNo">
<a-input v-model="ifParams.serialNo" :placeholder="ifParams.serialNo_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="API证书(apiclient_cert.p12)" prop="cert">
<a-input v-model="ifParams.cert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.cert"
@uploadSuccess="uploadSuccess($event, 'cert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="证书文件(apiclient_cert.pem)" prop="apiClientCert">
<a-input v-model="ifParams.apiClientCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.apiClientCert"
@uploadSuccess="uploadSuccess($event, 'apiClientCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="私钥文件(apiclient_key.pem)" prop="apiClientKey">
<a-input v-model="ifParams.apiClientKey" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.apiClientKey"
@uploadSuccess="uploadSuccess($event, 'apiClientKey')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center" v-if="$access('ENT_MCH_PAY_CONFIG_ADD')">
<a-button :style="{ marginRight: '8px' }" @click="onClose" icon="close">取消</a-button>
<a-button type="primary" @click="onSubmit" icon="check" :loading="btnLoading">保存</a-button>
</div>
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import { API_URL_ISV_PAYCONFIGS_LIST, req, getIsvPayConfigUnique, upload } from '@/api/manage'
export default {
components: {
JeepayCard,
JeepayUpload
},
props: {
callbackFunc: { type: Function, default: () => ({}) }
},
data () {
return {
btnLoading: false,
visible: false, // 抽屉开关
isAdd: true,
action: upload.cert, // 上传文件地址
saveObject: {}, // 保存的对象
ifParams: { apiVersion: 'V2' }, // 参数配置对象
rules: {
ifRate: [{ required: false, pattern: /^(([1-9]{1}\d{0,1})|(0{1}))(\.\d{1,4})?$/, message: '请输入0-100之间的数字,最多四位小数', trigger: 'blur' }]
},
ifParamsRules: {
mchId: [{ required: true, message: '请输入微信支付商户号', trigger: 'blur' }],
appId: [{ required: true, message: '请输入应用AppID', trigger: 'blur' }],
appSecret: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.isAdd && !value) {
callback(new Error('请输入应用AppSecret'))
}
callback()
} }],
key: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V2' && this.isAdd && !value) {
callback(new Error('请输入API密钥'))
}
callback()
} }],
apiV3Key: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && !value) {
callback(new Error('请输入API V3秘钥'))
}
callback()
} }],
serialNo: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && !value) {
callback(new Error('请输入序列号'))
}
callback()
} }],
cert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && !value) {
callback(new Error('请上传API证书(apiclient_cert.p12)'))
}
callback()
} }],
apiClientCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && !value) {
callback(new Error('请上传证书文件(apiclient_cert.pem)'))
}
callback()
} }],
apiClientKey: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && !value) {
callback(new Error('请上传私钥文件(apiclient_key.pem)'))
}
callback()
} }]
}
}
},
methods: {
// 弹层打开事件
show: function (isvNo, record) {
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
if (this.$refs.isvParamFormModel !== undefined) {
this.$refs.isvParamFormModel.resetFields()
}
// 数据初始化
this.saveObject = {
infoId: isvNo,
ifCode: record.ifCode,
state: record.ifConfigState === 0 ? 0 : 1
}
// 参数配置对象,数据初始化
this.ifParams = {
apiVersion: 'V2',
appSecret: '',
appSecret_ph: '请输入',
key: '',
key_ph: '请输入',
apiV3Key: '',
apiV3Key_ph: '请输入',
serialNo: '',
serialNo_ph: '请输入'
}
this.visible = true
this.getIsvPayConfig()
},
// 支付参数配置
getIsvPayConfig () {
const that = this
// 获取支付参数
getIsvPayConfigUnique(that.saveObject.infoId, that.saveObject.ifCode).then(res => {
if (res && res.ifParams) {
that.saveObject = res
that.ifParams = JSON.parse(res.ifParams)
that.ifParams.appSecret_ph = that.ifParams.appSecret
that.ifParams.appSecret = ''
that.ifParams.key_ph = that.ifParams.key
that.ifParams.key = ''
that.ifParams.apiV3Key_ph = that.ifParams.apiV3Key
that.ifParams.apiV3Key = ''
that.ifParams.serialNo_ph = that.ifParams.serialNo
that.ifParams.serialNo = ''
that.isAdd = false
} else if (res === undefined) {
that.isAdd = true
}
})
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
this.$refs.isvParamFormModel.validate(valid2 => {
if (valid && valid2) { // 验证通过
that.btnLoading = true
const reqParams = {}
reqParams.infoId = that.saveObject.infoId
reqParams.ifCode = that.saveObject.ifCode
reqParams.ifRate = that.saveObject.ifRate
reqParams.state = that.saveObject.state
reqParams.remark = that.saveObject.remark
// 支付参数配置不能为空
if (Object.keys(that.ifParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
// 脱敏数据为空时,删除该key
that.clearEmptyKey('appSecret')
that.clearEmptyKey('key')
that.clearEmptyKey('apiV3Key')
that.clearEmptyKey('serialNo')
reqParams.ifParams = JSON.stringify(that.ifParams)
// 请求接口
if (Object.keys(reqParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
req.add(API_URL_ISV_PAYCONFIGS_LIST, reqParams).then(res => {
that.$message.success('保存成功')
that.visible = false
that.btnLoading = false
that.callbackFunc()
})
}
})
})
},
// 脱敏数据为空时,删除对应key
clearEmptyKey (key) {
if (!this.ifParams[key]) {
this.ifParams[key] = undefined
}
this.ifParams[key + '_ph'] = undefined
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.ifParams[name] = value
this.$forceUpdate()
},
onClose () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<a-drawer
:maskClosable="false"
:visible="visible"
:title=" isAdd ? '新增商户' : '修改商户' "
@close="onClose"
:body-style="{ paddingBottom: '80px' }"
width="40%"
class="drawer-width"
>
<a-form-model v-if="visible" ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="商户名称" prop="mchName">
<a-input
placeholder="请输入商户名称"
v-model="saveObject.mchName"
/>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="登录名" prop="loginUserName">
<a-input
placeholder="请输入商户登录名"
v-model="saveObject.loginUserName"
:disabled="!this.isAdd"
/>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="商户简称" prop="mchShortName">
<a-input
placeholder="请输入商户简称"
v-model="saveObject.mchShortName"
/>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="联系人姓名" prop="contactName">
<a-input
placeholder="请输入联系人姓名"
v-model="saveObject.contactName"
/>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="联系人邮箱" prop="contactEmail">
<a-input
placeholder="请输入联系人邮箱"
v-model="saveObject.contactEmail"
>
</a-input>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="联系人手机号" prop="contactTel">
<a-input
placeholder="请输入联系人手机号"
v-model="saveObject.contactTel"
>
</a-input>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10" style="position:relative">
<a-form-model-item label="商户类型" prop="type">
<!-- 商户类型 气泡弹窗 -->
<a-radio-group v-model="saveObject.type" :disabled="this.isAdd?false:true">
<a-radio :value="1">
普通商户
</a-radio>
<a-radio :value="2">
特约商户
</a-radio>
</a-radio-group>
</a-form-model-item>
<div id="components-popover-demo-placement">
<div
class="typePopover"
>
<!-- title可省略,就不显示 -->
<a-popover placement="top">
<template slot="content">
<p>普通商户是指商户自行申请入驻微信或支付宝,无服务商协助,单独调接口。</p>
<p>特约商户是指由微信或支付宝的服务商协助商户完成入驻,商户下单走的是服务商接口。</p>
</template>
<template slot="title">
<span>商户类型</span>
</template>
<a-icon type="question-circle" />
</a-popover>
</div>
</div>
</a-col>
<a-col :span="10" v-if="saveObject.type == 2">
<a-form-model-item label="服务商号" prop="isvNo">
<a-select v-model="saveObject.isvNo" placeholder="请选择服务商">
<a-select-option v-for="d in isvList" :value="d.isvNo" :key="d.isvNo">
{{ d.isvName + " [ ID: " + d.isvNo + " ]" }}
</a-select-option>
</a-select>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
禁用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入备注" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
<!-- 重置密码板块 -->
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-divider orientation="left" v-if="resetIsShow">
<a-tag color="#FF4B33">
账户安全
</a-tag>
</a-divider>
</a-col>
</a-row>
<div>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="" v-if="resetIsShow" >
重置密码:<a-checkbox v-model="sysPassword.resetPass"></a-checkbox>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="" v-if="sysPassword.resetPass">
恢复默认密码:<a-checkbox v-model="sysPassword.defaultPass" @click="isResetPass"></a-checkbox>
</a-form-model-item>
</a-col>
</a-row>
</div>
<div v-if="sysPassword.resetPass">
<!-- <div v-else> -->
<div v-show="!this.sysPassword.defaultPass">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="新密码:" prop="newPwd">
<a-input-password autocomplete="new-password" v-model="newPwd" :disabled="sysPassword.defaultPass"/>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="确认新密码:" prop="confirmPwd">
<a-input-password autocomplete="new-password" v-model="sysPassword.confirmPwd" :disabled="sysPassword.defaultPass"/>
</a-form-model-item>
</a-col>
</a-row>
</div>
</div>
</a-form-model>
<div class="drawer-btn-center" >
<a-button icon="close" :style="{ marginRight: '8px' }" @click="onClose" style="margin-right:8px">
取消
</a-button>
<a-button type="primary" icon="check" @click="handleOkFunc" :loading="btnLoading">
保存
</a-button>
</div>
</a-drawer>
</template>
<script>
import { API_URL_MCH_LIST, API_URL_ISV_LIST, req } from '@/api/manage'
import { Base64 } from 'js-base64'
export default {
props: {
callbackFunc: { type: Function }
},
data () {
const checkIsvNo = (rule, value, callback) => { // 校验类型为特约商户是否选择了服务商
if (this.saveObject.type === 2 && !value) {
callback(new Error('请选择服务商'))
}
callback()
}
return {
newPwd: '', // 新密码
resetIsShow: false, // 重置密码是否展现
sysPassword: {
resetPass: false, // 重置密码
defaultPass: true, // 使用默认密码
confirmPwd: '' // 确认密码
},
btnLoading: false,
isAdd: true, // 新增 or 修改页面标志
saveObject: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
isvList: null, // 服务商下拉列表
rules: {
mchName: [{ required: true, message: '请输入商户名称', trigger: 'blur' }],
loginUserName: [{ required: true, pattern: /^[a-zA-Z][a-zA-Z0-9]{5,17}$/, message: '请输入字母开头,长度为6-18位的登录名', trigger: 'blur' }],
mchShortName: [{ required: true, message: '请输入商户简称', trigger: 'blur' }],
contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
isvNo: [{ validator: checkIsvNo, trigger: 'blur' }],
contactEmail: [{ required: false, pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: 'blur' }],
contactTel: [{ required: true, pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }],
newPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value, callBack) => {
if (!this.sysPassword.defaultPass) {
if (this.newPwd.length < 6 || this.newPwd.length > 12) {
callBack('请输入6-12位新密码')
}
}
callBack()
}
}], // 新密码
confirmPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value, callBack) => {
if (!this.sysPassword.defaultPass) {
this.newPwd === this.sysPassword.confirmPwd ? callBack() : callBack('新密码与确认密码不一致')
} else {
callBack()
}
}
}] // 确认新密码
}
}
},
created () {
},
methods: {
show: function (recordId) { // 弹层打开事件
this.isAdd = !recordId
this.saveObject = { 'state': 1, 'type': 1 } // 数据清空
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
req.list(API_URL_ISV_LIST, { 'pageSize': -1, 'state': 1 }).then(res => { // 服务商下拉选择列表
that.isvList = res.records
})
if (!this.isAdd) { // 修改信息 延迟展示弹层
console.log(555)
that.resetIsShow = true // 展示重置密码板块
that.recordId = recordId
req.getById(API_URL_MCH_LIST, recordId).then(res => {
that.saveObject = res
})
this.visible = true
} else {
that.visible = true // 立马展示弹层信息
}
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
// 请求接口
if (that.isAdd) {
this.btnLoading = true
req.add(API_URL_MCH_LIST, that.saveObject).then(res => {
that.$message.success('新增成功')
that.visible = false
that.callbackFunc() // 刷新列表
that.btnLoading = false
}).catch(res => {
that.btnLoading = false
})
} else {
that.sysPassword.confirmPwd = Base64.encode(that.sysPassword.confirmPwd)
console.log(that.sysPassword.confirmPwd)
Object.assign(that.saveObject, that.sysPassword) // 拼接对象
console.log(that.saveObject)
req.updateById(API_URL_MCH_LIST, that.recordId, that.saveObject).then(res => {
that.$message.success('修改成功')
that.visible = false
that.callbackFunc() // 刷新列表
that.btnLoading = false
that.resetIsShow = true // 展示重置密码板块
that.sysPassword.resetPass = false
that.sysPassword.defaultPass = true // 是否使用默认密码默认为true
that.resetPassEmpty(that) // 清空密码
}).catch(res => {
that.btnLoading = false
that.resetIsShow = true // 展示重置密码板块
that.sysPassword.resetPass = false
that.sysPassword.defaultPass = true // 是否使用默认密码默认为true
that.resetPassEmpty(that) // 清空密码
})
}
}
})
},
onClose () {
this.visible = false
this.resetIsShow = false // 取消重置密码板块展示
this.sysPassword.resetPass = false
this.resetPassEmpty(this)
this.sysPassword.defaultPass = true // 是否使用默认密码默认为true
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
// 使用默认密码重置是否为true
isResetPass () {
if (!this.sysPassword.defaultPass) {
this.newPwd = ''
this.sysPassword.confirmPwd = ''
}
},
// 保存后清空密码
resetPassEmpty (that) {
that.newPwd = ''
that.sysPassword.confirmPwd = ''
}
}
}
</script>
<style lang="less">
.typePopover {
position: absolute;
top: 0;
left:62px;
}
</style>
<template>
<a-drawer
:visible="visible"
:title=" true ? '商户详情' : '' "
@close="onClose"
:body-style="{ paddingBottom: '80px' }"
width="40%"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">
{{ detailData.mchNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户名称">
{{ detailData.mchName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="登录名">
{{ detailData.loginUserName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ detailData.mchShortName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12" v-if="detailData.type === 2">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ detailData.isvNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12" v-if="detailData.type === 2">
<a-descriptions>
<a-descriptions-item label="服务商名称">
{{ this.isvName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人姓名">
{{ detailData.contactName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户类型">
{{ detailData.type === 1 ? '普通商户': '特约商户' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人手机号">
{{ detailData.contactTel }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="状态">
<a-tag :color="detailData.state === 1?'green':'volcano'">
{{ detailData.state === 0?'禁用':detailData.state === 1?'启用':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人邮箱">
{{ detailData.contactEmail }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row justify="start" type="flex">
<a-col :sm="24">
<a-form-model-item label="备注">
<a-input
type="textarea"
disabled="disabled"
style="height: 50px"
v-model="detailData.remark"
/>
</a-form-model-item>
</a-col>
</a-row>
</a-drawer>
</template>
<script>
import { API_URL_MCH_LIST, API_URL_ISV_LIST, req } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function }
},
data () {
return {
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
isvList: null, // 服务商下拉列表
isvName: '' // 服务商名称
}
},
created () {
},
methods: {
show: function (recordId) { // 弹层打开事件
this.detailData = { 'state': 1, 'type': 1 } // 数据清空
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
that.recordId = recordId
req.getById(API_URL_MCH_LIST, recordId).then(res => {
that.detailData = res
})
req.list(API_URL_ISV_LIST, { 'pageSize': null }).then(res => { // 服务商下拉选择列表
that.isvList = res.records
for (let i = 0; i < that.isvList.length; i++) {
if (that.detailData.isvNo === that.isvList[i].isvNo) {
that.isvName = that.isvList[i].isvName
}
}
})
this.visible = true
},
onClose () {
this.visible = false
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<jeepay-text-up :placeholder="'商户号'" :msg="searchData.mchNo" v-model="searchData.mchNo"/>
<jeepay-text-up :placeholder="'服务商号'" :msg="searchData.isvNo" v-model="searchData.isvNo"/>
<jeepay-text-up :placeholder="'商户名称'" :msg="searchData.mchName" v-model="searchData.mchName"/>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.state" placeholder="商户状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.type" placeholder="商户类型" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">普通商户</a-select-option>
<a-select-option value="2">特约商户</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons" style="flex-grow: 0; flex-shrink: 0;">
<a-button type="primary" icon="search" @click="queryFunc" :loading="btnLoading">查询</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
<div>
<a-button v-if="$access('ENT_MCH_INFO_ADD')" type="primary" icon="plus" @click="addFunc" class="mg-b-30">新建</a-button>
</div>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="mchNo"
>
<template slot="mchNameSlot" slot-scope="{record}">
<b v-if="!$access('ENT_MCH_INFO_VIEW')">{{ record.mchName }}</b>
<a v-if="$access('ENT_MCH_INFO_VIEW')" @click="detailFunc(record.mchNo)">{{ record.mchName }}</a>
</template> <!-- 自定义插槽 -->
<template slot="stateSlot" slot-scope="{record}">
<a-badge :status="record.state === 0?'error':'processing'" :text="record.state === 0?'禁用':'启用'" />
</template>
<template slot="typeSlot" slot-scope="{record}">
<a-tag :color="record.type === 1 ? 'green' : 'orange'">
{{ record.type === 1 ? '普通商户':'特约商户' }}
</a-tag>
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_MCH_INFO_EDIT')" @click="editFunc(record.mchNo)">修改</a-button>
<a-button type="link" v-if="$access('ENT_MCH_APP_CONFIG')" @click="mchAppConfig(record.mchNo)">应用配置</a-button>
<a-button type="link" v-if="$access('ENT_MCH_INFO_DEL')" style="color: red" @click="delFunc(record.mchNo)">删除</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc"/>
<!-- 新增页面组件 -->
<InfoDetail ref="infoDetail" :callbackFunc="searchFunc"/>
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_MCH_LIST, req, reqLoad } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit'
import InfoDetail from './Detail'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'mchName', fixed: 'left', width: '200px', title: '商户名称', scopedSlots: { customRender: 'mchNameSlot' } },
{ key: 'mchNo', title: '商户号', dataIndex: 'mchNo' },
{ key: 'isvNo', title: '服务商号', dataIndex: 'isvNo' },
{ key: 'state', title: '状态', width: '130px', scopedSlots: { customRender: 'stateSlot' } },
{ key: 'type', title: '商户类型', width: '130px', scopedSlots: { customRender: 'typeSlot' } },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期' },
{ key: 'op', title: '操作', width: '260px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'MchListPage',
components: { JeepayTable, JeepayTableColumns, InfoAddOrEdit, InfoDetail, JeepayTextUp },
data () {
return {
btnLoading: false,
tableColumns: tableColumns,
searchData: {},
value: "''"
}
},
mounted () {
},
methods: {
queryFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
},
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_MCH_LIST, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
addFunc: function () { // 业务通用【新增】 函数
this.$refs.infoAddOrEdit.show()
},
editFunc: function (recordId) { // 业务通用【修改】 函数
this.$refs.infoAddOrEdit.show(recordId)
},
detailFunc: function (recordId) { // 商户详情页
this.$refs.infoDetail.show(recordId)
},
// 删除商户
delFunc: function (recordId) {
const that = this
this.$infoBox.confirmDanger('确认删除?', '该操作将删除商户下所有配置及用户信息', () => {
reqLoad.delById(API_URL_MCH_LIST, recordId).then(res => {
that.$refs.infoTable.refTable(true)
this.$message.success('删除成功')
})
})
},
mchAppConfig: function (recordId) { // 应用配置
this.$router.push({
path: '/apps',
query: { mchNo: recordId }
})
}
}
}
</script>
<template>
<a-drawer
:visible="visible"
:title=" isAdd ? '新增应用' : '修改应用'"
width="40%"
:maskClosable="false"
@close="onClose">
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12" v-if="!isAdd">
<a-form-model-item label="应用 AppId" prop="appId">
<a-input v-model="saveObject.appId" placeholder="请输入" :disabled="!isAdd" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="商户号" prop="mchNo">
<a-input v-model="saveObject.mchNo" placeholder="请输入" :disabled="!isAdd" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="应用名称" prop="appName">
<a-input v-model="saveObject.appName" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="私钥 AppSecret" prop="appSecret" >
<a-input v-model="saveObject.appSecret" :placeholder="saveObject.appSecret_ph" type="textarea" />
<a-button type="primary" ghost @click="randomKey(false, 128, 0)"><a-icon type="file-sync" />随机生成私钥</a-button>
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center">
<a-button @click="onClose" icon="close" :style="{ marginRight: '8px' }">取消</a-button>
<a-button type="primary" @click="onSubmit" icon="check" >保存</a-button>
</div>
</a-drawer>
</template>
<script>
import { API_URL_MCH_APP, req } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function, default: () => () => ({}) }
},
data () {
return {
isAdd: true, // 新增 or 修改
visible: false, // 抽屉开关
appId: '', // 应用AppId
saveObject: {}, // 数据对象
rules: {
mchNo: [{ required: true, message: '请输入商户号', trigger: 'blur' }],
appName: [{ required: true, message: '请输入应用名称', trigger: 'blur' }]
}
}
},
methods: {
// 抽屉显示
show (mchNo, appId) {
this.isAdd = !appId
// 数据清空
this.saveObject = {
'state': 1,
'appSecret': '',
'mchNo': mchNo,
'appSecret_ph': '请输入'
}
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
that.rules.appSecret = []
if (!this.isAdd) { // 修改信息 延迟展示弹层
that.appId = appId
// 拉取详情
req.getById(API_URL_MCH_APP, appId).then(res => {
that.saveObject = res
that.saveObject.appSecret_ph = res.appSecret
that.saveObject.appSecret = ''
})
this.visible = true
} else {
// 新增时,appSecret必填
that.rules.appSecret.push({
required: true,
message: '请输入私钥或点击随机生成私钥',
trigger: 'blur'
})
that.visible = true // 展示弹层信息
}
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
delete that.saveObject.appSecret_ph
// 请求接口
if (that.isAdd) {
req.add(API_URL_MCH_APP, that.saveObject).then(res => {
that.$message.success('新增成功')
that.visible = false
that.callbackFunc() // 刷新列表
})
} else {
if (that.saveObject.appSecret === '') {
delete that.saveObject.appSecret
}
req.updateById(API_URL_MCH_APP, that.appId, that.saveObject).then(res => {
that.$message.success('修改成功')
that.visible = false
that.callbackFunc() // 刷新列表
})
}
}
})
},
randomKey: function (randomFlag, min, max) { // 生成随机128位私钥
let str = ''
let range = min
const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
// 随机产生
if (randomFlag) {
range = Math.round(Math.random() * (max - min)) + min
}
for (var i = 0; i < range; i++) {
var pos = Math.round(Math.random() * (arr.length - 1))
str += arr[ pos ]
}
this.saveObject.appSecret = str
},
onClose () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<a-modal v-model="isShow" title="支付宝子商户扫码授权" @ok="handleOkFunc" @cancel="handleOkFunc">
<div style="text-align: center">
<p>方式1: <br/> 请商家登录【支付宝】APP, 扫描如下二维码, 按提示授权: </p>
<img style="margin-bottom: 10px" :src="apiResData.authQrImgUrl">
<hr/>
<p style="margin-top: 10px">
方式2: <br/> <a-button size="small" class="copy-btn" v-clipboard:copy="apiResData.authUrl" v-clipboard:success="onCopySuccess" >点击复制</a-button>
链接并发送给商户,商户进入链接,按照页面提示自主授权:
</p>
<a target="_blank" :href="apiResData.authUrl">{{ apiResData.authUrl }}</a>
</div>
</a-modal>
</template>
<script>
import { queryAlipayIsvsubMchAuthUrl } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function }
},
data () {
return {
isShow: false, // 是否显示弹层/抽屉
appId: '',
apiResData: {}
}
},
created () {
},
methods: {
show: function (appId) { // 弹层打开事件
this.apiResData = {}
this.appId = appId
const that = this
queryAlipayIsvsubMchAuthUrl(appId).then(res => {
that.apiResData = res
this.isShow = true
})
},
handleOkFunc: function () { // 点击【确认】按钮事件
this.isShow = false
if (this.callbackFunc) {
this.callbackFunc()
}
},
onCopySuccess () {
this.$message.success('复制成功')
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<jeepay-text-up :placeholder="'商户号'" :msg="searchData.mchNo" v-model="searchData.mchNo"/>
<jeepay-text-up :placeholder="'应用AppId'" :msg="searchData.appId" v-model="searchData.appId"/>
<jeepay-text-up :placeholder="'应用名称'" :msg="searchData.appName" v-model="searchData.appName"/>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.state" placeholder="状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons" style="flex-grow: 0; flex-shrink: 0;">
<a-button type="primary" icon="search" @click="queryFunc" :loading="btnLoading">查询</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
<div>
<a-button v-if="$access('ENT_MCH_APP_ADD')" type="primary" icon="plus" @click="addFunc" class="mg-b-30">新建</a-button>
</div>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="false"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="appId"
>
<template slot="appIdSlot" slot-scope="{record}">
<b>{{ record.appId }}</b>
</template> <!-- 自定义插槽 -->
<template slot="stateSlot" slot-scope="{record}">
<a-badge :status="record.state === 0?'error':'processing'" :text="record.state === 0?'禁用':'启用'" />
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_MCH_APP_EDIT')" @click="editFunc(record.appId)">修改</a-button>
<a-button type="link" v-if="$access('ENT_MCH_PAY_CONFIG_LIST')" @click="showPayIfConfigList(record.appId)">支付配置</a-button>
<a-button type="link" v-if="$access('ENT_MCH_APP_DEL')" style="color: red" @click="delFunc(record.appId)">删除</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 新增应用 -->
<MchAppAddOrEdit ref="mchAppAddOrEdit" :callbackFunc="searchFunc"/>
<!-- 支付参数配置页面组件 -->
<MchPayIfConfigList ref="mchPayIfConfigList" />
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_MCH_APP, req } from '@/api/manage'
import MchAppAddOrEdit from './AddOrEdit'
import MchPayIfConfigList from './MchPayIfConfigList'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'appId', fixed: 'left', width: '320px', title: '应用AppId', scopedSlots: { customRender: 'appIdSlot' } },
{ key: 'appName', title: '应用名称', dataIndex: 'appName' },
{ key: 'mchNo', title: '商户号', dataIndex: 'mchNo' },
{ key: 'state', title: '状态', scopedSlots: { customRender: 'stateSlot' } },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期' },
{ key: 'op', title: '操作', width: '260px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'MchAppPage',
components: { JeepayTable, JeepayTableColumns, JeepayTextUp, MchAppAddOrEdit, MchPayIfConfigList },
data () {
return {
btnLoading: false,
tableColumns: tableColumns,
searchData: { 'mchNo': '' }
}
},
mounted () {
this.searchData.mchNo = this.$route.query.mchNo
this.queryFunc()
},
methods: {
queryFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
},
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_MCH_APP, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
addFunc: function () { // 业务通用【新增】 函数
this.$refs.mchAppAddOrEdit.show(this.searchData.mchNo)
},
editFunc: function (recordId) { // 业务通用【修改】 函数
this.$refs.mchAppAddOrEdit.show(this.searchData.mchNo, recordId)
},
delFunc (appId) {
const that = this
this.$infoBox.confirmDanger('确认删除?', '', () => {
req.delById(API_URL_MCH_APP, appId).then(res => {
that.$message.success('删除成功!')
that.searchFunc()
})
})
},
showPayIfConfigList: function (recordId) { // 支付参数配置
this.$refs.mchPayIfConfigList.show(recordId)
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<a-drawer
title="填写参数"
width="40%"
:closable="true"
:maskClosable="false"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
@close="onClose"
>
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<a-divider orientation="left">
<a-tag color="#FF4B33">
{{ saveObject.ifCode }} 商户参数配置
</a-tag>
</a-divider>
<a-form-model ref="mchParamFormModel" :model="ifParams" layout="vertical" :rules="ifParamsRules">
<a-row :gutter="16">
<a-col v-for="(item, key) in mchParams" :key="key" :span="item.type === 'text' ? 12 : 24">
<a-form-model-item :label="item.desc" :prop="item.name" v-if="item.type === 'text' || item.type === 'textarea'">
<a-input v-model="ifParams[item.name]" :placeholder="item.star === '1' ? ifParams[item.name + '_ph'] : '请输入'" :type="item.type" />
</a-form-model-item>
<a-form-model-item :label="item.desc" :prop="item.name" v-else-if="item.type === 'radio'">
<a-radio-group v-model="ifParams[item.name]">
<a-radio v-for="(radioItem, radioKey) in item.values" :key="radioKey" :value="radioItem.value">
{{ radioItem.title }}
</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item :label="item.desc" :prop="item.name" v-else-if="item.type === 'file'">
<a-input v-model="ifParams[item.name]" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams[item.name]"
@uploadSuccess="uploadSuccess($event, item.name)"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center" v-if="$access('ENT_MCH_PAY_CONFIG_ADD')">
<a-button :style="{ marginRight: '8px' }" @click="onClose" icon="close">取消</a-button>
<a-button type="primary" @click="onSubmit" icon="check" :loading="btnLoading">保存</a-button>
</div>
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import { API_URL_MCH_PAYCONFIGS_LIST, req, getMchPayConfigUnique, upload } from '@/api/manage'
export default {
components: {
JeepayCard,
JeepayUpload
},
props: {
callbackFunc: { type: Function, default: () => ({}) }
},
data () {
return {
btnLoading: false,
visible: false, // 抽屉开关
appId: null, // 商户号
ifCode: null, // 接口代码
mchType: null, // 商户类型:1-普通商户 2-特约商户
action: upload.cert, // 上传文件地址
mchParams: {}, // 支付接口定义描述
saveObject: {}, // 保存的对象
ifParams: {}, // 参数配置对象
rules: {
infoId: [{ required: true, trigger: 'blur' }],
ifCode: [{ required: true, trigger: 'blur' }]
// ifRate: [{ required: false, pattern: /^(([1-9]{1}\d{0,1})|(0{1}))(\.\d{1,4})?$/, message: '请输入0-100之间的数字,最多四位小数', trigger: 'blur' }]
},
ifParamsRules: {}
}
},
watch: {
ifParams: function (o, n) {
this.$set(this.ifParams, 'appSecret', this.ifParams.appSecret) // 解决appSecret 双向绑定数据不显示的问题
}
},
methods: {
// 弹层打开事件
show: function (appId, record) {
this.appId = appId
this.ifCode = record.ifCode
this.mchType = record.mchType
this.saveObject = {} // 要保存的对象
this.ifParams = {} // 参数配置对象
this.mchParams = {} // 支付接口定义描述
this.saveObject.infoId = appId
this.saveObject.ifCode = record.ifCode
this.saveObject.state = record.ifConfigState === 0 ? 0 : 1
if (this.$refs.mchParamFormModel !== undefined) {
this.$refs.mchParamFormModel.resetFields()
}
this.getMchPayConfig(record)
},
// 支付参数配置
getMchPayConfig (record) {
const that = this
// 获取支付参数
getMchPayConfigUnique(that.saveObject.infoId, that.saveObject.ifCode).then(res => {
if (res && res.ifParams) {
that.saveObject = res
that.ifParams = JSON.parse(res.ifParams)
}
const newItems = [] // 重新加载支付接口配置定义描述json
let radioItems = [] // 存放单选框value title
const mchParams = this.mchType === 1 ? record.normalMchParams : record.isvsubMchParams // 根据商户类型获取接口定义描述
JSON.parse(mchParams).forEach(item => {
radioItems = []
if (item.type === 'radio') {
const valueItems = item.values.split(',')
const titleItems = item.titles.split(',')
for (const i in valueItems) {
// 检查参数是否为数字类型 然后赋值给radio值
let radioVal = valueItems[i]
if (!isNaN((radioVal))) { radioVal = Number(radioVal) }
radioItems.push({
value: radioVal,
title: titleItems[i]
})
}
}
if (item.star === '1') {
that.ifParams[item.name + '_ph'] = that.ifParams[item.name] ? that.ifParams[item.name] : '请输入'
if (that.ifParams[item.name]) {
that.ifParams[item.name] = ''
}
}
newItems.push({
name: item.name,
desc: item.desc,
type: item.type,
verify: item.verify,
values: radioItems,
star: item.star // 脱敏标识 1-是
})
})
that.mchParams = newItems // 重新赋值接口定义描述
that.visible = true // 打开支付参数配置抽屉
that.generoterRules()
})
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
this.$refs.mchParamFormModel.validate(valid2 => {
if (valid && valid2) { // 验证通过
that.btnLoading = true
const reqParams = {}
reqParams.infoId = that.saveObject.infoId
reqParams.ifCode = that.saveObject.ifCode
// reqParams.ifRate = that.saveObject.ifRate
reqParams.state = that.saveObject.state
reqParams.remark = that.saveObject.remark
// 支付参数配置不能为空
if (Object.keys(that.ifParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
// 脱敏数据为空时,删除该key
that.mchParams.forEach(item => {
if (item.star === '1' && that.ifParams[item.name] === '') {
that.ifParams[item.name] = undefined
}
that.ifParams[item.name + '_ph'] = undefined
})
reqParams.ifParams = JSON.stringify(that.ifParams)
// 请求接口
if (Object.keys(reqParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
req.add(API_URL_MCH_PAYCONFIGS_LIST, reqParams).then(res => {
that.$message.success('保存成功')
that.visible = false
that.btnLoading = false
that.callbackFunc()
})
}
})
})
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.ifParams[name] = value
this.$forceUpdate()
},
// 动态生成支付参数表单检验规则
generoterRules () {
const rules = {}
let newItems = []
this.mchParams.forEach(item => {
newItems = []
if (item.verify === 'required' && item.star !== '1') {
newItems.push({
required: true,
message: '请输入' + item.desc,
trigger: 'blur'
})
rules[item.name] = newItems
}
})
this.ifParamsRules = rules
},
onClose () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
.jeepay-card-content {
width: 100%;
position: relative;
background-color: @jee-card-back;
border-radius: 6px;
overflow:hidden;
}
.jeepay-card-ops {
width: 100%;
height: 50px;
background-color: @jee-card-back;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
border-top: 1px solid @jee-back;
position: absolute;
bottom: 0;
}
.jeepay-card-content-header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.jeepay-card-content-body {
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
}
.title {
font-size: 16px;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
color: #1a1a1a;
letter-spacing: 1px;
}
</style>
<template>
<a-drawer
:visible="visible"
@close="onClose"
:closable="true"
:body-style="{ paddingBottom: '80px' }"
:drawer-style="{ backgroundColor: '#f0f2f5' }"
width="80%"
>
<template slot="title">
<a-steps :current="currentStep" type="navigation" style="width:80%">
<a-step title="支付参数配置" @click="stepChange(0)" />
<a-step title="支付通道配置" @click="stepChange(1)" />
</a-steps>
</template>
<div v-if="currentStep === 0">
<JeepayCard
ref="infoCard"
:reqCardListFunc="reqCardListFunc"
:span="jeepayCard.span"
:height="jeepayCard.height"
>
<div slot="cardContentSlot" slot-scope="{record}">
<div :style="{'height': jeepayCard.height + 'px'}" class="jeepay-card-content">
<!-- 卡片自定义样式 -->
<div class="jeepay-card-content-header" :style="{backgroundColor: record.bgColor, height: jeepayCard.height/2 + 'px'}">
<img v-if="record.icon" :src="record.icon" :style="{height: jeepayCard.height/5 + 'px'}">
</div>
<div class="jeepay-card-content-body" :style="{height: (jeepayCard.height/2 - 50) + 'px'}">
<div class="title">
{{ record.ifName }}
</div>
<a-badge :status="record.ifConfigState ===1 ? 'processing' : 'error'" :text="record.ifConfigState ===1 ? '启用' : '未开通'" ></a-badge>
</div>
<!-- 卡片底部操作栏 -->
<div class="jeepay-card-ops">
<a v-if="record.mchType == 2 && record.ifCode == 'alipay' && $access('ENT_MCH_PAY_CONFIG_ADD')" @click="toAlipayAuthPageFunc(record)">扫码授权 <a-icon key="right" type="right" style="fontSize: 13px"></a-icon></a>
<a v-if="$access('ENT_MCH_PAY_CONFIG_ADD')" @click="editPayIfConfigFunc(record)">填写参数 <a-icon key="right" type="right" style="fontSize: 13px"></a-icon></a>
<a v-else>暂无操作</a>
</div>
</div>
</div>
</JeepayCard>
</div>
<div v-else-if="currentStep === 1">
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline">
<a-row :gutter="10">
<a-col :md="4">
<a-form-item label="">
<a-input placeholder="支付方式代码" v-model="searchData2.wayCode"/>
</a-form-item>
</a-col>
<a-col :md="4">
<a-form-item label="">
<a-input placeholder="支付方式名称" v-model="searchData2.wayName"/>
</a-form-item>
</a-col>
<a-col :sm="6">
<span class="table-page-search-submitButtons">
<a-button type="primary" icon="search" @click="searchFunc(true)">查询</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData2 = {}">重置</a-button>
</span>
</a-col>
</a-row>
</a-form>
</div>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData2"
rowKey="wayCode"
>
<template slot="stateSlot" slot-scope="{record}">
<a-badge :status="record.passageState === 0?'error':'processing'" :text="record.passageState === 0?'禁用':'启用'" />
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_MCH_PAY_PASSAGE_CONFIG')" @click="editPayPassageFunc(record)">配置</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
</div>
<div class="drawer-btn-center ">
<a-button :style="{ marginRight: '8px' }" @click="onClose" icon="close">关闭</a-button>
<a-button type="primary" icon="arrow-left" v-if="$access('ENT_MCH_PAY_CONFIG_LIST') && currentStep ===1" @click="stepChange(0)">上一步</a-button>
<a-button type="primary" icon="arrow-right" v-if="$access('ENT_MCH_PAY_PASSAGE_LIST') && currentStep ===0" @click="stepChange(1)">下一步</a-button>
</div>
<!-- 支付参数配置JSON渲染页面组件 -->
<MchPayConfigAddOrEdit ref="mchPayConfigAddOrEdit" :callbackFunc="refCardList" />
<!-- 支付参数配置自定义页面组件 wxpay -->
<WxpayPayConfig ref="wxpayPayConfig" :callbackFunc="refCardList" />
<!-- 支付参数配置自定义页面组件 alipay -->
<AlipayPayConfig ref="alipayPayConfig" :callbackFunc="refCardList" />
<!-- 支付通道配置页面组件 -->
<MchPayPassageAddOrEdit ref="mchPayPassageAddOrEdit" :callbackFunc="searchFunc"/>
<!-- 支付宝授权弹层 -->
<AlipayAuth ref="alipayAuthPage" :callbackFunc="refCardList"/>
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_MCH_PAYCONFIGS_LIST, API_URL_MCH_PAYPASSAGE_LIST, req, getAvailablePayInterfaceList } from '@/api/manage'
import MchPayConfigAddOrEdit from './MchPayConfigAddOrEdit'
import MchPayPassageAddOrEdit from './MchPayPassageAddOrEdit'
import WxpayPayConfig from './custom/WxpayPayConfig'
import AlipayPayConfig from './custom/AlipayPayConfig'
import AlipayAuth from './AlipayAuth'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'wayCode', title: '支付方式代码', dataIndex: 'wayCode' },
{ key: 'wayName', title: '支付方式名称', dataIndex: 'wayName' },
{ key: 'passageState', title: '状态', scopedSlots: { customRender: 'stateSlot' } },
{ key: 'op', title: '操作', width: '200px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
components: {
JeepayCard,
JeepayTable,
JeepayTableColumns,
MchPayConfigAddOrEdit,
MchPayPassageAddOrEdit,
WxpayPayConfig,
AlipayPayConfig,
AlipayAuth
},
data () {
return {
currentStep: 0, // 当前步骤条index
btnLoading: false,
appId: null, // 应用appId
visible: false, // 抽屉开关
jeepayCard: { // 卡片配置
height: 300,
span: { xxl: 6, xl: 4, lg: 4, md: 3, sm: 2, xs: 1 }
},
tableColumns: tableColumns,
searchData2: {}
}
},
methods: {
// 弹层打开事件
show (appId) {
this.appId = appId
this.ifCode = null
this.currentStep = 0
this.visible = true
this.refCardList()
},
// 步骤条切换
stepChange (current) {
this.currentStep = current
},
// 请求支付接口定义数据
reqCardListFunc () {
return req.list(API_URL_MCH_PAYCONFIGS_LIST, { 'appId': this.appId })
},
// 刷新支付接口card列表
refCardList () {
if (this.$refs.infoCard) {
this.$refs.infoCard.refCardList()
}
},
// 请求支付通道数据
reqTableDataFunc (params) {
const that = this
return req.list(API_URL_MCH_PAYPASSAGE_LIST, Object.assign(params, { appId: that.appId }))
},
searchFunc (isToFirst = false) { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(isToFirst)
},
// 支付参数配置
editPayIfConfigFunc (record) {
if (!record) {
return
}
if (record.subMchIsvConfig === 0) {
this.$error({
title: '提示',
content: '当前应用所属商户为特约商户,请先配置服务商支付参数!'
})
} else if (record.configPageType === 1) {
this.$refs.mchPayConfigAddOrEdit.show(this.appId, record)
} else if (record.configPageType === 2) {
this.$refs[record.ifCode + 'PayConfig'].show(this.appId, record)
}
},
// 支付通道配置
editPayPassageFunc (record) {
const that = this
getAvailablePayInterfaceList(that.appId, record.wayCode).then(resData => {
if (!resData || resData.length === 0) {
that.$error({
title: '提示',
content: '暂无可用支付接口配置'
})
} else {
that.$refs.mchPayPassageAddOrEdit.show(that.appId, record.wayCode)
}
})
},
// 抽屉关闭
onClose () {
this.visible = false
},
// 支付宝子商户 扫码授权
toAlipayAuthPageFunc (record) {
if (!record) {
return
}
if (record.subMchIsvConfig === 0) {
this.$error({
title: '提示',
content: '当前应用所属商户为特约商户,请先配置服务商支付参数!'
})
return
}
this.$refs.alipayAuthPage.show(this.appId)
}
}
}
</script>
<style lang="less" scoped>
.jeepay-card-content {
width: 100%;
position: relative;
background-color: @jee-card-back;
border-radius: 6px;
overflow:hidden;
}
.jeepay-card-ops {
width: 100%;
height: 50px;
background-color: @jee-card-back;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
border-top: 1px solid @jee-back;
position: absolute;
bottom: 0;
}
.jeepay-card-content-header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.jeepay-card-content-body {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.title {
font-size: 16px;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
color: #1a1a1a;
letter-spacing: 1px;
}
</style>
<template>
<a-drawer
:visible="visible"
title="配置支付通道"
@close="onClose"
:closable="true"
:maskClosable="false"
:body-style="{ paddingBottom: '80px' }"
:drawer-style="{ backgroundColor: '#f0f2f5' }"
width="40%"
>
<a-list :data-source="[]" v-if="cardList.length === 0" />
<div v-else>
<a-row :gutter="[24,24]" style="width:100%">
<a-col
v-for="(record, key) in cardList"
:key="key"
:xxl="24/jeepayCard.span.xxl"
:xl="24/jeepayCard.span.xl"
:lg="24/jeepayCard.span.lg"
:md="24/jeepayCard.span.md"
:sm="24/jeepayCard.span.sm"
:xs="24/jeepayCard.span.xs"
>
<div :style="{'height': jeepayCard.height + 'px'}" class="jeepay-card-content">
<!-- 卡片自定义样式 -->
<div class="jeepay-card-content-header" :style="{backgroundColor: record.bgColor, height: (jeepayCard.height-50)/2 + 'px'}">
<img v-if="record.icon" :src="record.icon" :style="{height: (jeepayCard.height-50)/5 + 'px'}">
</div>
<div class="jeepay-card-content-body" :style="{height: ((jeepayCard.height-50)/2) + 'px'}">
<div class="title" :style="{height: ((jeepayCard.height-50)/4) + 'px', lineHeight: ((jeepayCard.height-50)/4) + 'px'}">
{{ record.ifName }}
</div>
<a-form layout="inline" :labelCol="{span:8}" :wrapperCol="{span:14}">
<a-form-item label="费率:" :validate-status="record.error" :help="record.help">
<a-input v-model="record.rate" :disabled="!record.state && record.passageId != ''" suffix="%" />
</a-form-item>
</a-form>
</div>
<!-- 卡片底部操作栏 -->
<div class="jeepay-card-ops">
<a-switch checked-children="启用" un-checked-children="停用" v-model="record.state"></a-switch>
</div>
</div>
</a-col>
</a-row>
<div
:style="{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'center',
zIndex: 1
}"
>
<a-button icon="close" :style="{ marginRight: '8px' }" @click="onClose">
取消
</a-button>
<a-button type="primary" icon="check" v-if="$access('ENT_MCH_PAY_PASSAGE_ADD')" @click="handleOkFunc">
保存
</a-button>
</div>
</div>
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import { API_URL_MCH_PAYPASSAGE_LIST, req, getAvailablePayInterfaceList } from '@/api/manage'
export default {
components: {
JeepayCard
},
props: {
callbackFunc: { type: Function, default: () => ({}) }
},
data () {
return {
cardList: [],
appId: null, // 应用appId
wayCode: null, // 支付方式代码
visible: false, // 是否显示弹层/抽屉
jeepayCard: {
height: 300,
span: { xxl: 3, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }
}
}
},
methods: {
// 弹层打开事件
show: function (appId, wayCode) {
this.appId = appId
this.wayCode = wayCode
this.visible = true
this.cardList = []
this.refCardList()
},
// 请求支付接口定义数据
refCardList () {
const that = this
getAvailablePayInterfaceList(this.appId, this.wayCode).then(resData => {
if (resData === undefined || resData.length === 0) {
that.cardList = []
return
}
const newItems = []
resData.forEach(item => {
newItems.push({
passageId: item.passageId ? item.passageId : '',
ifCode: item.ifCode,
ifName: item.ifName,
icon: item.icon,
bgColor: item.bgColor,
rate: item.rate,
state: item.state === 1
})
})
that.cardList = newItems
that.$forceUpdate()
})
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
const reqParams = []
try {
that.cardList.forEach(item => {
item.error = ''
item.help = ''
const reg = /^(([1-9]{1}\d{0,1})|(0{1}))(\.\d{1,4})?$/
// 状态开启则费率必填
if (item.state) {
if (!item.rate) {
item.error = 'error'
item.help = '请输入费率'
throw new Error('error')
}
if (!reg.test(item.rate) || item.rate > 100) {
item.error = 'error'
item.help = '最多四位小数'
throw new Error('error')
}
}
reqParams.push({
id: item.passageId,
appId: that.appId,
wayCode: that.wayCode,
ifCode: item.ifCode,
rate: item.rate,
state: item.state ? 1 : 0
})
})
} catch (e) {
if (e.message === 'error') {
this.$forceUpdate()
return
}
}
// 请求接口
req.add(API_URL_MCH_PAYPASSAGE_LIST, { 'reqParams': JSON.stringify(reqParams) }).then(res => {
that.$message.success('保存成功')
that.visible = false
that.callbackFunc()
})
},
onClose () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
.jeepay-card-content {
width: 100%;
position: relative;
background-color: @jee-card-back;
border-radius: 6px;
overflow:hidden;
}
.jeepay-card-ops {
width: 100%;
height: 50px;
background-color: @jee-card-back;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
border-top: 1px solid @jee-back;
position: absolute;
bottom: 0;
}
.jeepay-card-content-header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.jeepay-card-content-body {
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
}
.title {
font-size: 16px;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
color: #1a1a1a;
letter-spacing: 1px;
}
</style>
<template>
<a-drawer
title="填写参数"
width="40%"
:closable="true"
:maskClosable="false"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
@close="onClose"
>
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<a-divider orientation="left">
<a-tag color="#FF4B33">
{{ saveObject.ifCode }} 商户参数配置
</a-tag>
</a-divider>
<a-form-model ref="mchParamFormModel" :model="ifParams" layout="vertical" :rules="ifParamsRules">
<a-row :gutter="16" v-if="mchType === 1">
<a-col span="12">
<a-form-model-item label="环境配置" prop="sandbox">
<a-radio-group v-model="ifParams.sandbox">
<a-radio :value="1">沙箱环境</a-radio>
<a-radio :value="0">生产环境</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="应用AppID" prop="appId">
<a-input v-model="ifParams.appId" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="应用私钥" prop="privateKey">
<a-input v-model="ifParams.privateKey" :placeholder="ifParams.privateKey_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="支付宝公钥" prop="alipayPublicKey">
<a-input v-model="ifParams.alipayPublicKey" :placeholder="ifParams.alipayPublicKey_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="接口签名方式(推荐使用RSA2)" prop="signType">
<a-radio-group v-model="ifParams.signType" defaultValue="RSA">
<a-radio value="RSA">RSA</a-radio>
<a-radio value="RSA2">RSA2</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="公钥证书" prop="useCert">
<a-radio-group v-model="ifParams.useCert" defaultValue="1">
<a-radio :value="1">使用证书(请使用RSA2私钥)</a-radio>
<a-radio :value="0">不使用证书</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="应用公钥证书(.crt格式)" prop="appPublicCert">
<a-input v-model="ifParams.appPublicCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.appPublicCert"
@uploadSuccess="uploadSuccess($event, 'appPublicCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="支付宝公钥证书(.crt格式)" prop="alipayPublicCert">
<a-input v-model="ifParams.alipayPublicCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.alipayPublicCert"
@uploadSuccess="uploadSuccess($event, 'alipayPublicCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="支付宝根证书(.crt格式)" prop="alipayRootCert">
<a-input v-model="ifParams.alipayRootCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.alipayRootCert"
@uploadSuccess="uploadSuccess($event, 'alipayRootCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
</a-row>
<a-row :gutter="16" v-else-if="mchType === 2">
<a-col span="12">
<a-form-model-item label="子商户app_auth_token" prop="appAuthToken">
<a-input v-model="ifParams.appAuthToken" placeholder="请输入子商户app_auth_token" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center" v-if="$access('ENT_MCH_PAY_CONFIG_ADD')">
<a-button :style="{ marginRight: '8px' }" @click="onClose" icon="close">取消</a-button>
<a-button type="primary" @click="onSubmit" icon="check" :loading="btnLoading">保存</a-button>
</div>
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import { API_URL_MCH_PAYCONFIGS_LIST, req, getMchPayConfigUnique, upload } from '@/api/manage'
export default {
components: {
JeepayCard,
JeepayUpload
},
props: {
callbackFunc: { type: Function, default: () => ({}) }
},
data () {
return {
btnLoading: false,
visible: false, // 抽屉开关
isAdd: true,
mchType: 1,
action: upload.cert, // 上传文件地址
saveObject: {}, // 保存的对象
ifParams: {}, // 参数配置对象
rules: {
// ifRate: [{ required: false, pattern: /^(([1-9]{1}\d{0,1})|(0{1}))(\.\d{1,4})?$/, message: '请输入0-100之间的数字,最多四位小数', trigger: 'blur' }]
},
ifParamsRules: {
appId: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && !value) {
callback(new Error('请输入应用AppID'))
}
callback()
} }],
privateKey: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && this.isAdd && !value) {
callback(new Error('请输入应用私钥'))
}
callback()
} }],
alipayPublicKey: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && this.isAdd && this.ifParams.useCert === 0 && !value) {
callback(new Error('请输入支付宝公钥'))
}
callback()
} }],
appPublicCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && this.ifParams.useCert === 1 && !this.ifParams.appPublicCert) {
callback(new Error('请上传应用公钥证书(.crt格式)'))
}
callback()
} }],
alipayPublicCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && this.ifParams.useCert === 1 && !this.ifParams.alipayPublicCert) {
callback(new Error('请上传支付宝公钥证书(.crt格式)'))
}
callback()
} }],
alipayRootCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && this.ifParams.useCert === 1 && !this.ifParams.alipayRootCert) {
callback(new Error('请上传支付宝根证书(.crt格式)'))
}
callback()
} }],
appAuthToken: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 2 && !value) {
callback(new Error('请输入子商户app_auth_token'))
}
callback()
} }]
}
}
},
methods: {
// 弹层打开事件
show: function (appId, record) {
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
if (this.$refs.mchParamFormModel !== undefined) {
this.$refs.mchParamFormModel.resetFields()
}
this.mchType = record.mchType
// 数据初始化
this.saveObject = {
infoId: appId,
ifCode: record.ifCode,
state: record.ifConfigState === 0 ? 0 : 1
}
// 参数配置对象,数据初始化
this.ifParams = {
sandbox: 0,
signType: 'RSA2',
useCert: 0,
privateKey: '',
privateKey_ph: '请输入',
alipayPublicKey: '',
alipayPublicKey_ph: '请输入',
appPublicCert: '',
alipayPublicCert: '',
alipayRootCert: ''
}
this.visible = true
this.getMchPayConfig()
},
// 支付参数配置
getMchPayConfig () {
const that = this
// 获取支付参数
getMchPayConfigUnique(that.saveObject.infoId, that.saveObject.ifCode).then(res => {
if (res && res.ifParams) {
that.saveObject = res
that.ifParams = JSON.parse(res.ifParams)
that.ifParams.privateKey_ph = that.ifParams.privateKey
that.ifParams.privateKey = ''
that.ifParams.alipayPublicKey_ph = that.ifParams.alipayPublicKey
that.ifParams.alipayPublicKey = ''
that.isAdd = false
} else if (res === undefined) {
that.isAdd = true
}
})
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
this.$refs.mchParamFormModel.validate(valid2 => {
if (valid && valid2) { // 验证通过
that.btnLoading = true
const reqParams = {}
reqParams.infoId = that.saveObject.infoId
reqParams.ifCode = that.saveObject.ifCode
// reqParams.ifRate = that.saveObject.ifRate
reqParams.state = that.saveObject.state
reqParams.remark = that.saveObject.remark
// 支付参数配置不能为空
if (Object.keys(that.ifParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
// 脱敏数据为空时,删除该key
that.clearEmptyKey('privateKey')
that.clearEmptyKey('alipayPublicKey')
reqParams.ifParams = JSON.stringify(that.ifParams)
// 请求接口
if (Object.keys(reqParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
req.add(API_URL_MCH_PAYCONFIGS_LIST, reqParams).then(res => {
that.$message.success('保存成功')
that.visible = false
that.btnLoading = false
that.callbackFunc()
})
}
})
})
},
// 脱敏数据为空时,删除对应key
clearEmptyKey (key) {
if (!this.ifParams[key]) {
this.ifParams[key] = undefined
}
this.ifParams[key + '_ph'] = undefined
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.ifParams[name] = value
this.$forceUpdate()
},
onClose () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<a-drawer
title="填写参数"
width="40%"
:closable="true"
:maskClosable="false"
:visible="visible"
:body-style="{ paddingBottom: '80px' }"
@close="onClose"
>
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<a-divider orientation="left">
<a-tag color="#FF4B33">
{{ saveObject.ifCode }} 商户参数配置
</a-tag>
</a-divider>
<a-form-model ref="mchParamFormModel" :model="ifParams" layout="vertical" :rules="ifParamsRules">
<a-row :gutter="16" v-if="mchType === 1">
<a-col span="12">
<a-form-model-item label="微信支付商户号" prop="mchId">
<a-input v-model="ifParams.mchId" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="应用AppID" prop="appId">
<a-input v-model="ifParams.appId" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="应用AppSecret" prop="appSecret">
<a-input v-model="ifParams.appSecret" :placeholder="ifParams.appSecret_ph" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="oauth2地址(置空将使用官方)" prop="oauth2Url">
<a-input v-model="ifParams.oauth2Url" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="微信支付API版本" prop="apiVersion">
<a-radio-group v-model="ifParams.apiVersion" defaultValue="V2">
<a-radio value="V2">V2</a-radio>
<a-radio value="V3">V3</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="APIv2密钥" prop="key">
<a-input v-model="ifParams.key" :placeholder="ifParams.key_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="APIv3秘钥" prop="apiV3Key">
<a-input v-model="ifParams.apiV3Key" :placeholder="ifParams.apiV3Key_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="序列号" prop="serialNo">
<a-input v-model="ifParams.serialNo" :placeholder="ifParams.serialNo_ph" type="textarea" />
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="API证书(apiclient_cert.p12)" prop="cert">
<a-input v-model="ifParams.cert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.cert"
@uploadSuccess="uploadSuccess($event, 'cert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="证书文件(apiclient_cert.pem)" prop="apiClientCert">
<a-input v-model="ifParams.apiClientCert" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.apiClientCert"
@uploadSuccess="uploadSuccess($event, 'apiClientCert')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col span="24">
<a-form-model-item label="私钥文件(apiclient_key.pem)" prop="apiClientKey">
<a-input v-model="ifParams.apiClientKey" disabled="disabled" />
<JeepayUpload
:action="action"
:fileUrl="ifParams.apiClientKey"
@uploadSuccess="uploadSuccess($event, 'apiClientKey')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<a-button style="marginTop:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
</a-row>
<a-row :gutter="16" v-else-if="mchType === 2">
<a-col span="12">
<a-form-model-item label="子商户ID" prop="subMchId">
<a-input v-model="ifParams.subMchId" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col span="12">
<a-form-model-item label="子账户appID(线上支付必填)" prop="subMchAppId">
<a-input v-model="ifParams.subMchAppId" placeholder="请输入" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center" v-if="$access('ENT_MCH_PAY_CONFIG_ADD')">
<a-button :style="{ marginRight: '8px' }" @click="onClose" icon="close">取消</a-button>
<a-button type="primary" @click="onSubmit" icon="check" :loading="btnLoading">保存</a-button>
</div>
</a-drawer>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import { API_URL_MCH_PAYCONFIGS_LIST, req, getMchPayConfigUnique, upload } from '@/api/manage'
export default {
components: {
JeepayCard,
JeepayUpload
},
props: {
callbackFunc: { type: Function, default: () => ({}) }
},
data () {
return {
btnLoading: false,
visible: false, // 抽屉开关
isAdd: true,
mchType: 1,
action: upload.cert, // 上传文件地址
saveObject: {}, // 保存的对象
ifParams: { apiVersion: 'V2' }, // 参数配置对象
rules: {
// ifRate: [{ required: false, pattern: /^(([1-9]{1}\d{0,1})|(0{1}))(\.\d{1,4})?$/, message: '请输入0-100之间的数字,最多四位小数', trigger: 'blur' }]
},
ifParamsRules: {
mchId: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && !value) {
callback(new Error('请输入微信支付商户号'))
}
callback()
} }],
appId: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 1 && !value) {
callback(new Error('请输入应用AppID'))
}
callback()
} }],
appSecret: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.isAdd && this.mchType === 1 && !value) {
callback(new Error('请输入应用AppSecret'))
}
callback()
} }],
key: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V2' && this.isAdd && this.mchType === 1 && !value) {
callback(new Error('请输入API密钥'))
}
callback()
} }],
apiV3Key: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && this.mchType === 1 && !value) {
callback(new Error('请输入API V3秘钥'))
}
callback()
} }],
serialNo: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && this.mchType === 1 && !value) {
callback(new Error('请输入序列号'))
}
callback()
} }],
cert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && !value) {
callback(new Error('请上传API证书(apiclient_cert.p12)'))
}
callback()
} }],
apiClientCert: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.isAdd && !value) {
callback(new Error('请上传证书文件(apiclient_cert.pem)'))
}
callback()
} }],
apiClientKey: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.ifParams.apiVersion === 'V3' && this.mchType === 1 && !this.ifParams.apiClientKey) {
callback(new Error('请上传私钥文件(apiclient_key.pem)'))
}
callback()
} }],
subMchId: [{ trigger: 'blur',
validator: (rule, value, callback) => {
if (this.mchType === 2 && !value) {
callback(new Error('请输入子商户ID'))
}
callback()
} }]
}
}
},
methods: {
// 弹层打开事件
show: function (appId, record) {
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
if (this.$refs.mchParamFormModel !== undefined) {
this.$refs.mchParamFormModel.resetFields()
}
this.mchType = record.mchType
// 数据初始化
this.saveObject = {
infoId: appId,
ifCode: record.ifCode,
state: record.ifConfigState === 0 ? 0 : 1
}
// 参数配置对象,数据初始化
this.ifParams = {
apiVersion: 'V2',
appSecret: '',
appSecret_ph: '请输入',
key: '',
key_ph: '请输入',
apiV3Key: '',
apiV3Key_ph: '请输入',
serialNo: '',
serialNo_ph: '请输入'
}
this.visible = true
this.getMchPayConfig()
},
// 支付参数配置
getMchPayConfig () {
const that = this
// 获取支付参数
getMchPayConfigUnique(that.saveObject.infoId, that.saveObject.ifCode).then(res => {
if (res && res.ifParams) {
that.saveObject = res
that.ifParams = JSON.parse(res.ifParams)
that.ifParams.appSecret_ph = that.ifParams.appSecret
that.ifParams.appSecret = ''
that.ifParams.key_ph = that.ifParams.key
that.ifParams.key = ''
that.ifParams.apiV3Key_ph = that.ifParams.apiV3Key
that.ifParams.apiV3Key = ''
that.ifParams.serialNo_ph = that.ifParams.serialNo
that.ifParams.serialNo = ''
that.isAdd = false
} else if (res === undefined) {
that.isAdd = true
}
})
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
this.$refs.mchParamFormModel.validate(valid2 => {
if (valid && valid2) { // 验证通过
that.btnLoading = true
const reqParams = {}
reqParams.infoId = that.saveObject.infoId
reqParams.ifCode = that.saveObject.ifCode
// reqParams.ifRate = that.saveObject.ifRate
reqParams.state = that.saveObject.state
reqParams.remark = that.saveObject.remark
// 支付参数配置不能为空
if (Object.keys(that.ifParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
// 脱敏数据为空时,删除该key
that.clearEmptyKey('appSecret')
that.clearEmptyKey('key')
that.clearEmptyKey('apiV3Key')
that.clearEmptyKey('serialNo')
reqParams.ifParams = JSON.stringify(that.ifParams)
// 请求接口
if (Object.keys(reqParams).length === 0) {
this.$message.error('参数不能为空!')
return
}
req.add(API_URL_MCH_PAYCONFIGS_LIST, reqParams).then(res => {
that.$message.success('保存成功')
that.visible = false
that.btnLoading = false
that.callbackFunc()
})
}
})
})
},
// 脱敏数据为空时,删除对应key
clearEmptyKey (key) {
if (!this.ifParams[key]) {
this.ifParams[key] = undefined
}
this.ifParams[key + '_ph'] = undefined
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.ifParams[name] = value
this.$forceUpdate()
},
onClose () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<a-form-item label="" class="table-head-layout" style="max-width:350px;min-width:300px">
<a-range-picker
@change="onChange"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
:disabled-date="disabledDate"
>
<a-icon slot="suffixIcon" type="sync" />
</a-range-picker>
</a-form-item>
<jeepay-text-up :placeholder="'订单ID'" :msg="searchData.orderId" v-model="searchData.orderId" />
<jeepay-text-up :placeholder="'商户订单号'" :msg="searchData.mchOrderNo" v-model="searchData.mchOrderNo" />
<jeepay-text-up :placeholder="'商户号'" :msg="searchData.mchNo" v-model="searchData.mchNo" />
<jeepay-text-up :placeholder="'服务商号'" :msg="searchData.isvNo" v-model="searchData.isvNo" />
<jeepay-text-up :placeholder="'应用AppId'" :msg="searchData.appId" v-model="searchData.appId"/>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.state" placeholder="通知状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">通知中</a-select-option>
<a-select-option value="2">通知成功</a-select-option>
<a-select-option value="3">通知失败</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.orderType" placeholder="订单类型" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">支付</a-select-option>
<a-select-option value="2">退款</a-select-option>
<a-select-option value="3">转账</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons">
<a-button type="primary" icon="search" @click="queryFunc" :loading="btnLoading">搜索</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:closable="true"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
:rowSelection="rowSelection"
rowKey="orderId"
>
<template slot="stateSlot" slot-scope="{record}">
<a-tag
:key="record.state"
:color="record.state === 1?'orange':record.state === 2?'green':'volcano'"
>
{{ record.state === 1?'通知中':record.state === 2?'通知成功':record.state === 3?'通知失败':'未知' }}
</a-tag>
</template>
<template slot="orderTypeSlot" slot-scope="{record}">
<a-tag
:key="record.orderType"
:color="record.orderType === 1?'green':record.orderType === 2?'volcano': record.orderType === 3? 'blue': 'orange'"
>
{{ record.orderType === 1?'支付':record.orderType === 2?'退款':record.orderType === 3? '转账':'未知' }}
</a-tag>
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_MCH_NOTIFY_VIEW')" @click="detailFunc(record.notifyId)">详情</a-button>
<a-button type="link" v-if="$access('ENT_MCH_NOTIFY_RESEND') && record.state === 3" @click="resendFunc(record.notifyId)">重发通知</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 日志详情抽屉 -->
<template>
<a-drawer
width="40%"
placement="right"
:closable="true"
:visible="visible"
:title="visible === true? '商户通知详情':''"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单ID">
<a-tag color="purple">
{{ detailData.orderId }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ detailData.isvNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户订单号">
{{ detailData.mchOrderNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">
{{ detailData.mchNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单类型">
<a-tag :color="detailData.orderType === 1?'green':detailData.orderType === 2?'volcano': detailData.orderType === 3? 'blue' : 'orange'">
{{ detailData.orderType === 1?'支付':detailData.orderType === 2?'退款':detailData.orderType === 3 ? '转账': '未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用APPID">
{{ detailData.appId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="通知状态">
<a-tag :color="detailData.state === 1?'orange':detailData.state === 2?'green':'volcano'">
{{ detailData.state === 1?'通知中':detailData.state === 2?'通知成功':detailData.state === 3?'通知失败':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="通知次数">
{{ detailData.notifyCount }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="最后通知时间">
{{ detailData.lastNotifyTime }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="创建时间">
{{ detailData.createdAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="更新时间">
{{ detailData.updatedAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-form-model-item label="通知地址">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.notifyUrl"
/>
</a-form-model-item>
</a-col>
<a-col :sm="24">
<a-form-model-item label="响应结果">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.resResult"
/>
</a-form-model-item>
</a-col>
</a-row>
</a-drawer>
</template>
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_MCH_NOTIFY_LIST, req, mchNotifyResend } from '@/api/manage'
import moment from 'moment'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'orderId', title: '订单ID', fixed: 'left', dataIndex: 'orderId' },
{ key: 'mchOrderNo', title: '商户订单号', dataIndex: 'mchOrderNo' },
{ key: 'state', title: '通知状态', width: '130px', scopedSlots: { customRender: 'stateSlot' } },
{ key: 'orderType', title: '订单类型', width: '130px', scopedSlots: { customRender: 'orderTypeSlot' } },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期' },
{ key: 'op', title: '操作', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'IsvListPage',
components: { JeepayTable, JeepayTableColumns, JeepayTextUp },
data () {
return {
btnLoading: true,
tableColumns: tableColumns,
searchData: {},
selectedIds: [], // 选中的数据
createdStart: '', // 选择开始时间
createdEnd: '', // 选择结束时间
visible: false,
detailData: {}
}
},
computed: {
rowSelection () {
const that = this
return {
onChange: (selectedRowKeys, selectedRows) => {
that.selectedIds = [] // 清空选中数组
selectedRows.forEach(function (data) { // 赋值选中参数
that.selectedIds.push(data.payOrderId)
})
}
}
}
},
mounted () {
},
methods: {
queryFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
},
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_MCH_NOTIFY_LIST, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
detailFunc: function (recordId) {
const that = this
req.getById(API_URL_MCH_NOTIFY_LIST, recordId).then(res => {
that.detailData = res
})
this.visible = true
},
moment,
onChange (date, dateString) {
this.searchData.createdStart = dateString[0] // 开始时间
this.searchData.createdEnd = dateString[1] // 结束时间
},
disabledDate (current) { // 今日之后日期不可选
return current && current > moment().endOf('day')
},
onClose () {
this.visible = false
},
resendFunc (notifyId) { // 重发通知
const that = this
this.$infoBox.confirmPrimary('确认重发通知?', '', () => {
mchNotifyResend(notifyId).then(res => {
that.$message.success('任务更新成功,请稍后查看最新状态!')
that.searchFunc()
})
})
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<a-form-item label="" class="table-head-layout" style="max-width:350px;min-width:300px">
<a-range-picker
@change="onChange"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
:disabled-date="disabledDate"
>
<a-icon slot="suffixIcon" type="sync" />
</a-range-picker>
</a-form-item>
<jeepay-text-up :placeholder="'支付/商户/渠道订单号'" :msg="searchData.unionOrderId" v-model="searchData.unionOrderId" />
<!-- <jeepay-text-up :placeholder="'支付订单号'" :msg="searchData.payOrderId" v-model="searchData.payOrderId" />-->
<!-- <jeepay-text-up :placeholder="'商户订单号'" :msg="searchData.mchOrderNo" v-model="searchData.mchOrderNo" />-->
<jeepay-text-up :placeholder="'商户号'" :msg="searchData.mchNo" v-model="searchData.mchNo" />
<jeepay-text-up :placeholder="'服务商号'" :msg="searchData.isvNo" v-model="searchData.isvNo" />
<jeepay-text-up :placeholder="'应用AppId'" :msg="searchData.appId" v-model="searchData.appId"/>
<a-form-item v-if="$access('ENT_PAY_ORDER_SEARCH_PAY_WAY')" label="" class="table-head-layout">
<a-select v-model="searchData.wayCode" placeholder="支付方式" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option :key="item.wayCode" v-for="item in payWayList" :value="item.wayCode">
{{ item.wayName }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.state" placeholder="支付状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">订单生成</a-select-option>
<a-select-option value="1">支付中</a-select-option>
<a-select-option value="2">支付成功</a-select-option>
<a-select-option value="3">支付失败</a-select-option>
<a-select-option value="4">已撤销</a-select-option>
<a-select-option value="5">已退款</a-select-option>
<a-select-option value="6">订单关闭</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.notifyState" placeholder="回调状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未发送</a-select-option>
<a-select-option value="1">已发送</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.divisionState" placeholder="分账状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未发生分账</a-select-option>
<a-select-option value="1">等待分账任务处理</a-select-option>
<a-select-option value="2">分账处理中</a-select-option>
<a-select-option value="3">分账任务已结束(状态请看分账记录)</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons">
<a-button type="primary" icon="search" @click="queryFunc" :loading="btnLoading">搜索</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="payOrderId"
:tableRowCrossColor="true"
>
<template slot="amountSlot" slot-scope="{record}"><b>{{ record.amount/100 }}</b></template> <!-- 自定义插槽 -->
<template slot="refundAmountSlot" slot-scope="{record}">{{ record.refundAmount/100 }}</template> <!-- 自定义插槽 -->
<template slot="stateSlot" slot-scope="{record}">
<a-tag
:key="record.state"
:color="record.state === 0?'blue':record.state === 1?'orange':record.state === 2?'green':record.state === 6?'':'volcano'"
>
{{ record.state === 0?'订单生成':record.state === 1?'支付中':record.state === 2?'支付成功':record.state === 3?'支付失败':record.state === 4?'已撤销':record.state === 5?'已退款':record.state === 6?'订单关闭':'未知' }}
</a-tag>
</template>
<template slot="divisionStateSlot" slot-scope="{record}">
<span v-if="record.divisionState == 0"> - </span>
<a-tag color="orange" v-else-if="record.divisionState == 1">待分账</a-tag>
<a-tag color="red" v-else-if="record.divisionState == 2">分账处理中</a-tag>
<a-tag color="green" v-else-if="record.divisionState == 3">任务已结束</a-tag>
<span v-else>未知</span>
</template>
<template slot="notifySlot" slot-scope="{record}">
<a-badge :status="record.notifyState === 1?'processing':'error'" :text="record.notifyState === 1?'已发送':'未发送'" />
</template>
<template slot="orderSlot" slot-scope="{record}">
<div class="order-list">
<p><span style="color:#729ED5;background:#e7f5f7">支付</span>{{ record.payOrderId }}</p>
<p style="margin-bottom: 0">
<span style="color:#56cf56;background:#d8eadf">商户</span>
<a-tooltip placement="bottom" style="font-weight: normal;" v-if="record.mchOrderNo.length > record.payOrderId.length">
<template slot="title">
<span>{{ record.mchOrderNo }}</span>
</template>
{{ changeStr2ellipsis(record.mchOrderNo, record.payOrderId.length) }}
</a-tooltip>
<span style="font-weight: normal;" v-else>{{ record.mchOrderNo }}</span>
</p>
<p v-if="record.channelOrderNo" style="margin-bottom: 0;margin-top: 10px">
<span style="color:#fff;background:#E09C4D;">渠道</span>
<a-tooltip placement="bottom" style="font-weight: normal;" v-if="record.channelOrderNo.length > record.payOrderId.length">
<template slot="title">
<span>{{ record.channelOrderNo }}</span>
</template>
{{ changeStr2ellipsis(record.channelOrderNo, record.payOrderId.length) }}
</a-tooltip>
<span style="font-weight: normal;" v-else>{{ record.channelOrderNo }}</span>
</p>
</div>
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_PAY_ORDER_VIEW')" @click="detailFunc(record.payOrderId)">详情</a-button>
<a-button type="link" v-if="$access('ENT_PAY_ORDER_REFUND')" style="color: red" v-show="(record.state === 2 && record.refundState !== 2)" @click="openFunc(record, record.payOrderId)">退款</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 退款弹出框 -->
<refund-modal ref="refundModalInfo" :callbackFunc="searchFunc"></refund-modal>
<!-- 日志详情抽屉 -->
<template>
<a-drawer
width="50%"
placement="right"
:closable="true"
:visible="visible"
:title="visible === true? '订单详情':''"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="所属系统">
{{ detailData.mchType === 1?'普通商户':detailData.mchType === 2?'特约商户':'未知' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ detailData.isvNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付订单号">
<a-tag color="purple">
{{ detailData.payOrderId }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">
{{ detailData.mchNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户订单号">
{{ detailData.mchOrderNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户名称">
{{ detailData.mchName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付金额">
<a-tag color="green">
{{ detailData.amount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="手续费"><a-tag color="pink">{{ detailData.mchFeeAmount/100 }}</a-tag></a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="商家费率">{{ (detailData.mchFeeRate*100).toFixed(2) }}%</a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单状态">
<a-tag :color="detailData.state === 0?'blue':detailData.state === 1?'orange':detailData.state === 2?'green':detailData.state === 6?'':'volcano'">
{{ detailData.state === 0?'订单生成':detailData.state === 1?'支付中':detailData.state === 2?'支付成功':detailData.state === 3?'支付失败':detailData.state === 4?'已撤销':detailData.state === 5?'已退款':detailData.state === 6?'订单关闭':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="回调状态">
<a-tag :color="detailData.notifyState === 1?'green':'volcano'">
{{ detailData.notifyState === 0?'未发送':detailData.notifyState === 1?'已发送':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用APPID">
{{ detailData.appId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付错误码">
{{ detailData.errCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付错误描述">
{{ detailData.errMsg }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单失效时间">
{{ detailData.expiredTime }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付成功时间">
{{ detailData.successTime }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="创建时间">
{{ detailData.createdAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="更新时间">
{{ detailData.updatedAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-divider />
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商品标题">
{{ detailData.subject }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商品描述">
{{ detailData.body }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="接口代码">
{{ detailData.ifCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="货币代码">
{{ detailData.currency }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付方式">
{{ detailData.wayCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="客户端IP">
{{ detailData.clientIp }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户标识">
{{ detailData.channelUser }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道订单号">
{{ detailData.channelOrderNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="异步通知地址">
{{ detailData.notifyUrl }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="页面跳转地址">
{{ detailData.returnUrl }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款状态">
<a-tag :color="detailData.refundState === 0?'blue':detailData.refundState === 1?'orange':detailData.refundState === 2?'green':'volcano'">
{{ detailData.refundState === 0?'未发起':detailData.refundState === 1?'部分退款':detailData.refundState === 2?'全额退款':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款次数">
{{ detailData.refundTimes }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款总额">
<a-tag color="cyan" v-if="detailData.refundAmount">
{{ detailData.refundAmount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-divider />
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="订单分账模式">
<span v-if="detailData.divisionMode == 0">该笔订单不允许分账</span>
<span v-else-if="detailData.divisionMode == 1">支付成功按配置自动完成分账</span>
<span v-else-if="detailData.divisionMode == 2">商户手动分账(解冻商户金额)</span>
<span v-else>未知</span>
</a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="分账状态">
<a-tag color="blue" v-if="detailData.divisionState == 0">未发生分账</a-tag>
<a-tag color="orange" v-else-if="detailData.divisionState == 1">待分账</a-tag>
<a-tag color="red" v-else-if="detailData.divisionState == 2">分账处理中</a-tag>
<a-tag color="green" v-else-if="detailData.divisionState == 3">任务已结束</a-tag>
<a-tag color="#f50" v-else>未知</a-tag>
</a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="最新分账发起时间">{{ detailData.divisionLastTime }}</a-descriptions-item></a-descriptions>
</a-col>
</a-row>
<a-divider />
<a-row justify="start" type="flex">
<a-col :sm="24">
<a-form-model-item label="扩展参数">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.extParam"
/>
</a-form-model-item>
</a-col>
</a-row>
</a-drawer>
</template>
</page-header-wrapper>
</template>
<script>
import RefundModal from './RefundModal' // 退款弹出框
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_PAY_ORDER_LIST, API_URL_PAYWAYS_LIST, req } from '@/api/manage'
import moment from 'moment'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'amount', title: '支付金额', ellipsis: true, width: 108, fixed: 'left', scopedSlots: { customRender: 'amountSlot' } },
{ key: 'refundAmount', title: '退款金额', width: 108, scopedSlots: { customRender: 'refundAmountSlot' } },
{ key: 'mchFeeAmount', dataIndex: 'mchFeeAmount', title: '手续费', customRender: (text) => '¥' + (text / 100).toFixed(2), width: 100 },
{ key: 'mchName', title: '商户名称', dataIndex: 'mchName', ellipsis: true, width: 100 },
{ key: 'orderNo', title: '订单号', scopedSlots: { customRender: 'orderSlot' }, width: 210 },
// { key: 'payOrderId', title: '支付订单号', dataIndex: 'payOrderId' },
// { key: 'mchOrderNo', title: '商户订单号', dataIndex: 'mchOrderNo' },
{ key: 'wayName', title: '支付方式', dataIndex: 'wayName', width: 120 },
{ key: 'state', title: '支付状态', scopedSlots: { customRender: 'stateSlot' }, width: 100 },
{ key: 'notifyState', title: '回调状态', scopedSlots: { customRender: 'notifySlot' }, width: 100 },
{ key: 'divisionState', title: '分账状态', scopedSlots: { customRender: 'divisionStateSlot' }, width: 100 },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期', width: 120 },
{ key: 'op', title: '操作', width: 120, fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'IsvListPage',
components: { JeepayTable, JeepayTableColumns, JeepayTextUp, RefundModal },
data () {
return {
btnLoading: false,
tableColumns: tableColumns,
searchData: {},
createdStart: '', // 选择开始时间
createdEnd: '', // 选择结束时间
visible: false,
detailData: {},
payWayList: []
}
},
computed: {
},
mounted () {
if (this.$access('ENT_PAY_ORDER_SEARCH_PAY_WAY')) {
this.initPayWay()
}
},
methods: {
queryFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
},
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_PAY_ORDER_LIST, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(false)
},
// 打开退款弹出框
openFunc (record, recordId) {
if (record.refundState === 2) {
return this.$infoBox.modalError('订单无可退款金额', '')
}
this.$refs.refundModalInfo.show(recordId)
},
detailFunc: function (recordId) {
const that = this
req.getById(API_URL_PAY_ORDER_LIST, recordId).then(res => {
that.detailData = res
})
this.visible = true
},
moment,
onChange (date, dateString) {
this.searchData.createdStart = dateString[0] // 开始时间
this.searchData.createdEnd = dateString[1] // 结束时间
},
disabledDate (current) { // 今日之后日期不可选
return current && current > moment().endOf('day')
},
onClose () {
this.visible = false
},
initPayWay: function () {
const that = this
req.list(API_URL_PAYWAYS_LIST, { 'pageSize': -1 }).then(res => { // 支付方式下拉列表
that.payWayList = res.records
})
},
changeStr2ellipsis (orderNo, baseLength) {
const halfLengh = parseInt(baseLength / 2)
return orderNo.substring(0, halfLengh - 1) + '...' + orderNo.substring(orderNo.length - halfLengh, orderNo.length)
}
}
}
</script>
<style lang="less" scoped>
.order-list {
-webkit-text-size-adjust:none;
font-size: 12px;
display: flex;
flex-direction: column;
p {
white-space:nowrap;
span {
display: inline-block;
font-weight: 800;
height: 16px;
line-height: 16px;
width: 35px;
border-radius: 5px;
text-align: center;
margin-right: 2px;
}
}
}
</style>
<template>
<div>
<a-modal
title="退款"
:visible="visible"
:confirm-loading="confirmLoading"
@ok="handleOk"
@cancel="handleCancel"
:closable="false"
>
<a-row>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="支付订单号">
<a-tag color="purple">
{{ detailData.payOrderId }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="支付金额">
<a-tag color="green">
{{ detailData.amount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="可退金额">
<a-tag color="pink">
{{ nowRefundAmount }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-form-model :rules="rules" :model="refund" ref="refundInfo" >
<a-form-model-item label="退款金额" prop="refundAmount">
<a-input-number v-model="refund.refundAmount" :precision="2" style="width:100%"/>
</a-form-model-item>
<a-form-model-item label="退款原因" prop="refundReason">
<a-input v-model="refund.refundReason" type="textarea" />
</a-form-model-item>
</a-form-model>
</a-modal>
</div>
</template>
<script>
import { API_URL_PAY_ORDER_LIST, req, payOrderRefund } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function, default: () => () => ({}) }
},
data () {
return {
recordId: '',
labelCol: { span: 4 },
wrapperCol: { span: 16 },
visible: false,
confirmLoading: false,
detailData: {
},
refund: {
// refundReason: '', // 退款原因
// refundAmount: '' // 退款金额
},
rules: {
refundReason: [{ min: 0, max: 256, required: true, trigger: 'blur', message: '请输入退款原因,最长不超过256个字符' }],
refundAmount: [{ required: true, message: '请输入金额', trigger: 'blur' },
{
validator: (rule, value, callBack) => {
if (value < 0.01 || value > this.nowRefundAmount) {
callBack('退款金额不能小于0.01,或者大于可退金额')
}
callBack()
}
}]
}
}
},
computed: {
nowRefundAmount () {
return (this.detailData.amount - this.detailData.refundAmount) / 100
}
},
methods: {
show (recordId) {
if (this.$refs.refundInfo !== undefined) {
this.$refs.refundInfo.resetFields()
}
this.recordId = recordId
this.visible = true
this.refund = {}
const that = this
req.getById(API_URL_PAY_ORDER_LIST, recordId).then(res => {
that.detailData = res
})
},
handleOk (e) {
this.$refs.refundInfo.validate(valid => {
if (valid) {
this.confirmLoading = true
const that = this
// 退款接口
payOrderRefund(that.recordId, that.refund.refundAmount, that.refund.refundReason).then(res => {
that.visible = false // 关闭弹窗
that.confirmLoading = false // 取消按钮转圈
if (res.state === 0 || res.state === 3) { // 订单生成 || 失败
const refundErrorModal = that.$infoBox.modalError('退款失败', (h) => that.buildModalText(res, h, () => { refundErrorModal.destroy() }))
} else if (res.state === 1) { // 退款中
const refundErrorModal = that.$infoBox.modalWarning('退款中', (h) => that.buildModalText(res, h, () => { refundErrorModal.destroy() }))
that.callbackFunc()
} else if (res.state === 2) { // 退款成功
that.$message.success('退款成功')
that.callbackFunc()
} else {
const refundErrorModal = that.$infoBox.modalWarning('退款状态未知', (h) => that.buildModalText(res, h, () => { refundErrorModal.destroy() }))
}
}).catch(() => {
that.confirmLoading = false // 取消按钮转圈
})
}
})
},
handleCancel (e) {
this.visible = false
},
buildModalText (res, h, callbackFunc) {
// 跳转退款列表Btn
const toRefundPageBtn = h('a', { on: { click: () => {
callbackFunc()
this.$router.push({ name: 'ENT_REFUND_ORDER' })
} } })
toRefundPageBtn.text = '退款列表'
return h('div', [
h('div', res.errCode ? `错误码:${res.errCode}` : ''),
h('div', res.errMsg ? `错误信息:${res.errMsg}` : ''),
h('div', [h('span', '请到'), toRefundPageBtn, h('span', '中查看详细信息')])
])
}
}
}
</script>
<style scoped lang="less">
</style>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<a-form-item label="" class="table-head-layout" style="max-width:350px;min-width:300px">
<a-range-picker
@change="onChange"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
:disabled-date="disabledDate"
>
<a-icon slot="suffixIcon" type="sync" />
</a-range-picker>
</a-form-item>
<jeepay-text-up :placeholder="'支付/退款列订单号'" :msg="searchData.unionOrderId" v-model="searchData.unionOrderId" />
<!-- <jeepay-text-up :placeholder="'退款订单号'" :msg="searchData.refundOrderId" v-model="searchData.refundOrderId" />-->
<!-- <jeepay-text-up :placeholder="'支付订单号'" :msg="searchData.payOrderId" v-model="searchData.payOrderId" />-->
<!-- <jeepay-text-up :placeholder="'渠道支付订单号'" :msg="searchData.channelPayOrderNo" v-model="searchData.channelPayOrderNo" />-->
<jeepay-text-up :placeholder="'商户号'" :msg="searchData.mchNo" v-model="searchData.mchNo" />
<jeepay-text-up :placeholder="'服务商号'" :msg="searchData.isvNo" v-model="searchData.isvNo" />
<jeepay-text-up :placeholder="'应用AppId'" :msg="searchData.appId" v-model="searchData.appId"/>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.state" placeholder="退款状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">订单生成</a-select-option>
<a-select-option value="1">退款中</a-select-option>
<a-select-option value="2">退款成功</a-select-option>
<a-select-option value="3">退款失败</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.mchType" placeholder="商户类型" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">普通商户</a-select-option>
<a-select-option value="2">特约商户</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons">
<a-button type="primary" icon="search" @click="queryFunc" :loading="btnLoading">搜索</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="refundOrderId"
:tableRowCrossColor="true"
>
<template slot="payAmountSlot" slot-scope="{record}"><b>{{ record.payAmount/100 }}</b></template> <!-- 自定义插槽 -->
<template slot="refundAmountSlot" slot-scope="{record}"><b>{{ record.refundAmount/100 }}</b></template> <!-- 自定义插槽 -->
<template slot="stateSlot" slot-scope="{record}">
<a-tag
:key="record.state"
:color="record.state === 0?'blue':record.state === 1?'orange':record.state === 2?'green':'volcano'"
>
{{ record.state === 0?'订单生成':record.state === 1?'退款中':record.state === 2?'退款成功':record.state === 3?'退款失败':record.state === 4?'任务关闭':'未知' }}
</a-tag>
</template>
<template slot="payOrderSlot" slot-scope="{record}">
<div class="order-list">
<p><span style="color:#729ED5;background:#e7f5f7">支付</span>{{ record.payOrderId }}</p>
<p v-if="record.channelPayOrderNo" style="margin-bottom: 0;">
<span style="color:#fff;background:#E09C4D">渠道</span>
<a-tooltip placement="bottom" style="font-weight: normal;" v-if="record.channelPayOrderNo.length > record.payOrderId.length">
<template slot="title">
<span>{{ record.channelPayOrderNo }}</span>
</template>
{{ changeStr2ellipsis(record.channelPayOrderNo, record.payOrderId.length) }}
</a-tooltip>
<span style="font-weight: normal;" v-else>{{ record.channelPayOrderNo }}</span>
</p>
</div>
</template>
<template slot="refundOrderSlot" slot-scope="{record}">
<div class="order-list">
<p><span style="color:#729ED5;background:#e7f5f7">退款</span>{{ record.refundOrderId }}</p>
<p style="margin-bottom: 0;">
<span style="color:#56cf56;background:#d8eadf">商户</span>
<a-tooltip placement="bottom" style="font-weight: normal;" v-if="record.mchRefundNo.length > record.payOrderId.length">
<template slot="title">
<span>{{ record.mchRefundNo }}</span>
</template>
{{ changeStr2ellipsis(record.mchRefundNo, record.refundOrderId.length) }}
</a-tooltip>
<span style="font-weight: normal;" v-else>{{ record.mchRefundNo }}</span>
</p>
</div>
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_REFUND_ORDER_VIEW')" @click="detailFunc(record.refundOrderId)">详情</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 日志详情抽屉 -->
<template>
<a-drawer
width="50%"
placement="right"
:closable="true"
:visible="visible"
:title="visible === true? '退款订单详情':''"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="所属系统">
{{ detailData.mchType === 1?'普通商户':detailData.mchType === 2?'特约商户':'未知' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ detailData.isvNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款订单号">
<a-tag color="purple">
{{ detailData.refundOrderId }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">
{{ detailData.mchNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付订单号">
{{ detailData.payOrderId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户退款单号">
{{ detailData.mchRefundNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道支付订单号">
{{ detailData.channelPayOrderNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用APPID">
{{ detailData.appId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付金额">
<a-tag color="green">
{{ detailData.payAmount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款金额">
<a-tag color="green">
{{ detailData.refundAmount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单状态">
<a-tag :color="detailData.state === 0?'blue':detailData.state === 1?'orange':detailData.state === 2?'green':'volcano'">
{{ detailData.state === 0?'订单生成':detailData.state === 1?'退款中':detailData.state === 2?'退款成功':detailData.state === 3?'退款失败':detailData.state === 4?'任务关闭':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款成功时间">
{{ detailData.successTime }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="创建时间">
{{ detailData.createdAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="更新时间">
{{ detailData.updatedAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-divider />
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="接口代码">
{{ detailData.ifCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="货币代码">
{{ detailData.currency }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="方式代码">
{{ detailData.wayCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="客户端IP">
{{ detailData.clientIp }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="异步通知地址">
{{ detailData.notifyUrl }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider />
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道订单号">
{{ detailData.channelOrderNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道错误码">
{{ detailData.errCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道错误描述">
{{ detailData.errMsg }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-form-model-item label="渠道额外参数">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.channelExtra"
/>
</a-form-model-item>
</a-col>
<a-divider />
<a-col :sm="24">
<a-form-model-item label="扩展参数">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.extParam"
/>
</a-form-model-item>
</a-col>
<a-col :sm="24">
<a-form-model-item label="备注">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.remark"
/>
</a-form-model-item>
</a-col>
</a-drawer>
</template>
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_REFUND_ORDER_LIST, req } from '@/api/manage'
import moment from 'moment'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'payAmount', title: '支付金额', ellipsis: true, fixed: 'left', scopedSlots: { customRender: 'payAmountSlot' }, width: 100 },
{ key: 'refundAmount', title: '退款金额', ellipsis: true, scopedSlots: { customRender: 'refundAmountSlot' }, width: 100 },
{ key: 'pay', title: '退款订单号', scopedSlots: { customRender: 'refundOrderSlot' }, width: 220 },
{ key: 'refund', title: '支付订单号', scopedSlots: { customRender: 'payOrderSlot' }, width: 220 },
// { key: 'payOrderId', title: '支付订单号', dataIndex: 'payOrderId' },
// { key: 'mchRefundNo', title: '商户退款单号', dataIndex: 'mchRefundNo' },
{ key: 'state', title: '状态', scopedSlots: { customRender: 'stateSlot' }, width: 100 },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期', width: 120 },
{ key: 'op', title: '操作', width: 100, fixed: 'right', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'IsvListPage',
components: { JeepayTable, JeepayTableColumns, JeepayTextUp },
data () {
return {
btnLoading: false,
tableColumns: tableColumns,
searchData: {},
selectedIds: [], // 选中的数据
createdStart: '', // 选择开始时间
createdEnd: '', // 选择结束时间
visible: false,
detailData: {}
}
},
computed: {
},
mounted () {
},
methods: {
queryFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
},
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_REFUND_ORDER_LIST, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
detailFunc: function (recordId) {
const that = this
req.getById(API_URL_REFUND_ORDER_LIST, recordId).then(res => {
that.detailData = res
})
this.visible = true
},
moment,
onChange (date, dateString) {
this.searchData.createdStart = dateString[0] // 开始时间
this.searchData.createdEnd = dateString[1] // 结束时间
},
disabledDate (current) { // 今日之后日期不可选
return current && current > moment().endOf('day')
},
onClose () {
this.visible = false
},
changeStr2ellipsis (orderNo, baseLength) {
const halfLengh = parseInt(baseLength / 2)
return orderNo.substring(0, halfLengh - 1) + '...' + orderNo.substring(orderNo.length - halfLengh, orderNo.length)
}
}
}
</script>
<style lang="less" scoped>
.order-list {
-webkit-text-size-adjust:none;
font-size: 12px;
display: flex;
flex-direction: column;
p {
white-space:nowrap;
span {
display: inline-block;
font-weight: 800;
height: 16px;
line-height: 16px;
width: 35px;
border-radius: 5px;
text-align: center;
margin-right: 2px;
}
}
}
</style>
<!-- 订单详情抽屉 -->
<template>
<a-drawer
width="50%"
placement="right"
:closable="true"
:visible="isShow"
title="转账订单详情"
@close="isShow = false"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户类型">
{{ detailData.mchType === 1?'普通商户':detailData.mchType === 2?'特约商户':'未知' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">{{ detailData.mchNo }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户名称">{{ detailData.mchName }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用APPID">{{ detailData.appId }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">{{ detailData.isvNo }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="转账订单号">
<a-tag color="purple">{{ detailData.transferId }}</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户转账单号">{{ detailData.mchOrderNo }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道订单号">{{ detailData.channelOrderNo }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="金额">
<a-tag color="green">{{ detailData.amount/100 }}</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="货币代码">{{ detailData.currency }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="收款账号">
<a-tag color="green">{{ detailData.accountNo }}</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="收款人姓名">{{ detailData.accountName }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="转账备注">{{ detailData.transferDesc }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单状态">
<a-tag :color="detailData.state === 0?'blue':detailData.state === 1?'orange':detailData.state === 2?'green':'volcano'">
{{ detailData.state === 0?'订单生成':detailData.state === 1?'转账中':detailData.state === 2?'转账成功':detailData.state === 3?'转账失败':detailData.state === 4?'任务关闭':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="转账成功时间">{{ detailData.successTime }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="创建时间">{{ detailData.createdAt }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="更新时间">{{ detailData.updatedAt }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-divider />
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="接口代码">{{ detailData.ifCode }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="入账类型">{{ detailData.entryType }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="客户端IP">{{ detailData.clientIp }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="异步通知地址">{{ detailData.notifyUrl }}</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider />
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道订单号">{{ detailData.channelOrderNo }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道错误码">{{ detailData.errCode }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道错误描述">{{ detailData.errMsg }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-form-model-item label="渠道额外参数">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.channelExtra"
/>
</a-form-model-item>
</a-col>
<a-divider />
<a-col :sm="24">
<a-form-model-item label="扩展参数">
<a-input
type="textarea"
disabled="disabled"
style="height: 100px;color: black"
v-model="detailData.extParam"
/>
</a-form-model-item>
</a-col>
</a-drawer>
</template>
<script>
import { API_URL_TRANSFER_ORDER_LIST, req } from '@/api/manage'
export default {
data () {
return {
detailData: {},
isShow: false, // 是否显示弹层/抽屉
recordId: null // 更新对象ID
}
},
methods: {
show: function (recordId) {
const that = this
req.getById(API_URL_TRANSFER_ORDER_LIST, recordId).then(res => {
that.detailData = res
})
this.isShow = true
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<a-form-item label="" class="table-head-layout" style="max-width:350px;min-width:300px">
<a-range-picker
@change="onChange"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
:disabled-date="disabledDate"
>
<a-icon slot="suffixIcon" type="sync" />
</a-range-picker>
</a-form-item>
<jeepay-text-up :placeholder="'转账/商户/渠道订单号'" :msg="searchData.unionOrderId" v-model="searchData.unionOrderId" />
<!-- <jeepay-text-up :placeholder="'转账订单号'" :msg="searchData.transferId" v-model="searchData.transferId" />-->
<!-- <jeepay-text-up :placeholder="'商户订单号'" :msg="searchData.mchOrderNo" v-model="searchData.mchOrderNo" />-->
<!-- <jeepay-text-up :placeholder="'渠道支付订单号'" :msg="searchData.channelOrderNo" v-model="searchData.channelOrderNo" />-->
<jeepay-text-up :placeholder="'商户号'" :msg="searchData.mchNo" v-model="searchData.mchNo" />
<jeepay-text-up :placeholder="'应用AppId'" :msg="searchData.appId" v-model="searchData.appId"/>
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.state" placeholder="转账状态" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">订单生成</a-select-option>
<a-select-option value="1">转账中</a-select-option>
<a-select-option value="2">转账成功</a-select-option>
<a-select-option value="3">转账失败</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons">
<a-button type="primary" icon="search" @click="queryFunc" :loading="btnLoading">搜索</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="transferId"
:tableRowCrossColor="true"
>
<template slot="transferAmountSlot" slot-scope="{record}"><b>{{ record.amount/100 }}</b></template> <!-- 自定义插槽 -->
<template slot="stateSlot" slot-scope="{record}">
<a-tag
:key="record.state"
:color="record.state === 0?'blue':record.state === 1?'orange':record.state === 2?'green':'volcano'"
>
{{ record.state === 0?'订单生成':record.state === 1?'转账中':record.state === 2?'转账成功':record.state === 3?'转账失败':record.state === 4?'任务关闭':'未知' }}
</a-tag>
</template>
<template slot="orderSlot" slot-scope="{record}">
<div class="order-list">
<p><span style="color:#729ED5;background:#e7f5f7">转账</span>{{ record.transferId }}</p>
<p style="margin-bottom: 0;">
<span style="color:#56cf56;background:#d8eadf">商户</span>
<a-tooltip placement="bottom" style="font-weight: normal;" v-if="record.mchOrderNo.length > record.transferId.length">
<template slot="title">
<span>{{ record.mchOrderNo }}</span>
</template>
{{ changeStr2ellipsis(record.mchOrderNo, record.transferId.length) }}
</a-tooltip>
<span style="font-weight: normal;" v-else>{{ record.mchOrderNo }}</span>
</p>
<p v-if="record.channelOrderNo" style="margin-bottom: 0;margin-top: 10px">
<span style="color:#fff;background:#E09C4D">渠道</span>
<a-tooltip placement="bottom" style="font-weight: normal;" v-if="record.channelOrderNo.length > record.transferId.length">
<template slot="title">
<span>{{ record.channelOrderNo }}</span>
</template>
{{ changeStr2ellipsis(record.channelOrderNo, record.transferId.length) }}
</a-tooltip>
<span style="font-weight: normal;" v-else>{{ record.channelOrderNo }}</span>
</p>
</div>
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_TRANSFER_ORDER_VIEW')" @click="detailFunc(record.transferId)">详情</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 订单详情 页面组件 -->
<TransferOrderDetail ref="transferOrderDetail" />
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import TransferOrderDetail from './TransferOrderDetail'
import { API_URL_TRANSFER_ORDER_LIST, req } from '@/api/manage'
import moment from 'moment'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ title: '转账金额', scopedSlots: { customRender: 'transferAmountSlot' }, width: 108 },
{ title: '商户名称', dataIndex: 'mchName' },
{ key: 'orderNo', title: '订单号', scopedSlots: { customRender: 'orderSlot' }, width: 260 },
// { title: '渠道订单号', dataIndex: 'channelOrderNo' },
{ title: '收款账号', dataIndex: 'accountNo', width: 200 },
{ title: '收款人姓名', dataIndex: 'accountName' },
{ title: '转账备注', dataIndex: 'transferDesc' },
{ title: '状态', scopedSlots: { customRender: 'stateSlot' }, width: 100 },
{ title: '创建日期', dataIndex: 'createdAt' },
{ title: '操作', width: '100px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'IsvListPage',
components: { JeepayTable, JeepayTableColumns, JeepayTextUp, TransferOrderDetail },
data () {
return {
btnLoading: false,
tableColumns: tableColumns,
searchData: {},
createdStart: '', // 选择开始时间
createdEnd: '' // 选择结束时间
}
},
methods: {
queryFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
},
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_TRANSFER_ORDER_LIST, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
detailFunc: function (recordId) {
this.$refs.transferOrderDetail.show(recordId)
},
moment,
onChange (date, dateString) {
this.searchData.createdStart = dateString[0] // 开始时间
this.searchData.createdEnd = dateString[1] // 结束时间
},
disabledDate (current) { // 今日之后日期不可选
return current && current > moment().endOf('day')
},
changeStr2ellipsis (orderNo, baseLength) {
const halfLengh = parseInt(baseLength / 2)
return orderNo.substring(0, halfLengh - 1) + '...' + orderNo.substring(orderNo.length - halfLengh, orderNo.length)
}
}
}
</script>
<style lang="less" scoped>
.order-list {
-webkit-text-size-adjust:none;
font-size: 12px;
display: flex;
flex-direction: column;
p {
white-space:nowrap;
span {
display: inline-block;
font-weight: 800;
height: 16px;
line-height: 16px;
width: 35px;
border-radius: 5px;
text-align: center;
margin-right: 2px;
}
}
}
</style>
<template>
<a-drawer
:visible="visible"
:title=" isAdd ? '新增支付接口' : '修改支付接口'"
width="40%"
:maskClosable="false"
@close="onClose">
<a-form-model ref="infoFormModel" :model="saveObject" layout="vertical" :rules="rules">
<a-row :gutter="16">
<a-col :span="12">
<a-form-model-item label="接口代码" prop="ifCode">
<a-input v-model="saveObject.ifCode" placeholder="请输入" :disabled="!isAdd" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="接口名称" prop="ifName">
<a-input v-model="saveObject.ifName" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="是否支持普通商户模式" prop="isMchMode">
<a-radio-group v-model="saveObject.isMchMode">
<a-radio :value="1">
支持
</a-radio>
<a-radio :value="0">
不支持
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="是否支持服务商子商户模式" prop="isIsvMode">
<a-radio-group v-model="saveObject.isIsvMode">
<a-radio :value="1">
支持
</a-radio>
<a-radio :value="0">
不支持
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="支付参数配置页面类型" prop="configPageType">
<a-radio-group v-model="saveObject.configPageType">
<a-radio :value="1">
根据接口配置定义描述渲染页面
</a-radio>
<a-radio :value="2">
自定义页面
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="24" v-if="saveObject.isIsvMode == 1 && this.saveObject.configPageType === 1">
<a-form-model-item label="服务商接口配置定义描述" prop="isvParams">
<a-input v-model="saveObject.isvParams" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
<a-col :span="24" v-if="saveObject.isIsvMode == 1 && this.saveObject.configPageType === 1">
<a-form-model-item label="特约商户接口配置定义描述" prop="isvsubMchParams">
<a-input v-model="saveObject.isvsubMchParams" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
<a-col :span="24" v-if="saveObject.isMchMode == 1 && this.saveObject.configPageType === 1">
<a-form-model-item label="普通商户接口配置定义描述" prop="normalMchParams">
<a-input v-model="saveObject.normalMchParams" placeholder="请输入" type="textarea" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="状态" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="备注" prop="remark">
<a-input v-model="saveObject.remark" placeholder="请输入" />
</a-form-model-item>
</a-col>
<a-col :span="24">
<a-form-model-item label="支持的支付方式" prop="checkedList">
<a-checkbox-group v-model="checkedList" :options="wayCodesOptions" @change="onWayCodesChange" />
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="页面展示:卡片icon" prop="icon">
<JeepayUpload
:action="action"
accept=".jpg, .jpeg, .png"
@uploadSuccess="uploadSuccess($event, '')"
>
<template slot="uploadSlot" slot-scope="{loading}">
<img :src="saveObject.icon" style="width:80px" />
<a-button style="marginLeft:5px;"> <a-icon :type="loading ? 'loading' : 'upload'" /> {{ loading ? '正在上传' : '点击上传' }} </a-button>
</template>
</JeepayUpload>
</a-form-model-item>
</a-col>
<a-col :span="12">
<a-form-model-item label="页面展示:卡片背景色" prop="bgColor">
<a-input v-model="saveObject.bgColor" placeholder="请输入" />
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<div class="drawer-btn-center">
<a-button @click="onClose" icon="close" :style="{ marginRight: '8px' }">取消</a-button>
<a-button type="primary" @click="onSubmit" icon="check" >保存</a-button>
</div>
</a-drawer>
</template>
<script>
import JeepayUpload from '@/components/JeepayUpload/JeepayUpload'
import { API_URL_IFDEFINES_LIST, API_URL_PAYWAYS_LIST, req, upload } from '@/api/manage'
export default {
components: {
JeepayUpload
},
props: {
callbackFunc: { type: Function, default: () => () => ({}) }
},
data () {
const validateNormalMchParams = (rule, value, callback) => { // 普通商户接口配置定义描述 验证器
if (this.saveObject.isMchMode === 1 && this.saveObject.configPageType === 1 && !value) {
callback(new Error('请输入普通商户接口配置定义描述'))
}
callback()
}
const validateIsvParams = (rule, value, callback) => { // 服务商接口配置定义描述 验证器
if (this.saveObject.isIsvMode === 1 && this.saveObject.configPageType === 1 && !value) {
callback(new Error('请输入服务商接口配置定义描述'))
}
callback()
}
const validateIsvsubMchParams = (rule, value, callback) => { // 特约商户接口配置定义描述 验证器
if (this.saveObject.isIsvMode === 1 && this.saveObject.configPageType === 1 && !value) {
callback(new Error('请输入特约商户接口配置定义描述'))
}
callback()
}
const validateWayCodes = (rule, value, callback) => { // 支付方式 验证器
if (this.checkedList.length <= 0) {
callback(new Error('请选择支付方式'))
}
callback()
}
return {
isAdd: true, // 新增 or 修改
visible: false, // 抽屉开关
action: upload.ifBG, // 上传图标地址
ifCode: '', // 支付接口定义id
saveObject: {}, // 数据对象
rules: {
ifCode: [{ required: true, message: '请输入接口代码', trigger: 'blur' }],
ifName: [{ required: true, message: '请输入接口名称', trigger: 'blur' }],
normalMchParams: [{ validator: validateNormalMchParams, trigger: 'blur' }],
isvParams: [{ validator: validateIsvParams, trigger: 'blur' }],
isvsubMchParams: [{ validator: validateIsvsubMchParams, trigger: 'blur' }],
checkedList: [{ validator: validateWayCodes, trigger: 'blur' }]
},
wayCodesOptions: [], // 支付方式多选框选项列表
checkedList: [] // 选中的数据
}
},
created () {
this.payWayList()
},
methods: {
// 抽屉显示
show (ifCode) {
this.isAdd = !ifCode
// 数据清空
this.saveObject = {
'isMchMode': 1,
'isIsvMode': 1,
'state': 1,
'configPageType': 1
}
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
if (!this.isAdd) { // 修改信息 延迟展示弹层
that.ifCode = ifCode
// 拉取详情
req.getById(API_URL_IFDEFINES_LIST, ifCode).then(res => {
that.saveObject = res
const newItems = [] // 多选框赋值
res.wayCodes.forEach(item => {
newItems.push(item.wayCode)
})
that.checkedList = newItems
})
this.visible = true
} else {
this.checkedList = [] // 多选框设置空
that.visible = true // 展示弹层信息
}
},
onClose () {
this.visible = false
},
// 表单提交
onSubmit () {
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
that.saveObject.wayCodeStrs = this.checkedList.join(',')
// 请求接口
if (that.isAdd) {
req.add(API_URL_IFDEFINES_LIST, that.saveObject).then(res => {
that.$message.success('新增成功')
that.visible = false
that.callbackFunc() // 刷新列表
})
} else {
req.updateById(API_URL_IFDEFINES_LIST, that.ifCode, that.saveObject).then(res => {
that.$message.success('修改成功')
that.visible = false
that.callbackFunc() // 刷新列表
})
}
}
})
},
// 支付方式列表
payWayList () {
const that = this
req.list(API_URL_PAYWAYS_LIST, { 'pageSize': '-1' }).then(res => {
res.records.forEach(item => {
that.wayCodesOptions.push({
label: item.wayName,
value: item.wayCode
})
})
})
},
// 上传文件成功回调方法,参数value为文件地址,name是自定义参数
uploadSuccess (value, name) {
this.saveObject.icon = value
this.$forceUpdate()
},
onWayCodesChange (checkedValues) {
this.checkedList = checkedValues
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<page-header-wrapper>
<template>
<a-card style="width: calc(100% - 24px);margin-bottom: 20px;">
<a-alert message="" type="info">
<template #description>
<!--<p style="display: flex; justify-content: space-between; margin: 0 0 4px;">
</p>-->
</template>
</a-alert>
</a-card>
</template>
<JeepayCard
ref="infoCard"
:reqCardListFunc="reqCardListFunc"
:span="jeepayCard.span"
:height="jeepayCard.height"
:name="jeepayCard.name"
:addAuthority="jeepayCard.addAuthority"
@addJeepayCard="addOrEdit"
>
<div slot="cardContentSlot" slot-scope="{record}">
<div :style="{'height': jeepayCard.height + 'px'}" class="jeepay-card-content">
<!-- 卡片自定义样式 -->
<div class="jeepay-card-content-header" :style="{backgroundColor: record.bgColor, height: jeepayCard.height/2 + 'px'}">
<img v-if="record.icon" :src="record.icon" :style="{height: jeepayCard.height/5 + 'px'}">
</div>
<div class="jeepay-card-content-body" :style="{height: (jeepayCard.height/2 - 50) + 'px'}">
<div class="title">
{{ record.ifName }}
</div>
</div>
<!-- 卡片底部操作栏 -->
<div class="jeepay-card-ops">
<a-tooltip placement="top" title="编辑">
<a-icon key="edit" type="edit" @click="addOrEdit(record.ifCode)" />
</a-tooltip>
<a-tooltip placement="top" title="删除">
<a-icon key="delete" type="delete" @click="del(record.ifCode)" />
</a-tooltip>
</div>
</div>
</div>
</JeepayCard>
<!-- 新增页面组件 -->
<PayIfDefineAddOrEdit ref="payIfDefineAddOrEdit" :callbackFunc="refCardList"/>
</page-header-wrapper>
</template>
<script>
import JeepayCard from '@/components/JeepayCard/JeepayCard'
import { API_URL_IFDEFINES_LIST, req } from '@/api/manage'
import PayIfDefineAddOrEdit from './AddOrEdit'
export default {
name: 'IfDefinePage',
components: {
JeepayCard,
PayIfDefineAddOrEdit
},
data () {
return {
jeepayCard: {
name: '支付接口',
height: 200,
span: { xxl: 8, xl: 4, lg: 4, md: 3, sm: 2, xs: 1 },
addAuthority: this.$access('ENT_PC_IF_DEFINE_ADD')
}
}
},
methods: {
// 请求支付接口定义数据
reqCardListFunc () {
return req.list(API_URL_IFDEFINES_LIST)
},
// 刷新card列表
refCardList () {
this.$refs.infoCard.refCardList()
},
addOrEdit (ifCode) {
this.$refs.payIfDefineAddOrEdit.show(ifCode)
},
del (ifCode) {
const that = this
this.$infoBox.confirmDanger('确认删除?', '', () => {
req.delById(API_URL_IFDEFINES_LIST, ifCode).then(res => {
that.$message.success('删除成功!')
that.refCardList()
})
})
}
}
}
</script>
<style lang="less" scoped>
.jeepay-card-content {
width: 100%;
position: relative;
background-color: @jee-card-back;
border-radius: 6px;
overflow:hidden;
}
.jeepay-card-ops {
width: 100%;
height: 50px;
background-color: @jee-card-back;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
border-top: 1px solid @jee-back;
position: absolute;
bottom: 0;
}
.jeepay-card-content-header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.jeepay-card-content-body {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.title {
font-size: 13px;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
color: #1a1a1a;
letter-spacing: 1px;
}
</style>
<template>
<a-modal v-model="isShow" :title=" isAdd ? '新增支付方式' : '修改支付方式' " @ok="handleOkFunc">
<a-form-model ref="infoFormModel" :model="saveObject" :label-col="{span: 6}" :wrapper-col="{span: 15}" :rules="rules">
<a-form-model-item label="支付方式代码:" prop="wayCode">
<a-input v-model="saveObject.wayCode" :disabled="!isAdd" />
</a-form-model-item>
<a-form-model-item label="支付方式名称:" prop="wayName">
<a-input v-model="saveObject.wayName" />
</a-form-model-item>
</a-form-model>
</a-modal>
</template>
<script>
import { API_URL_PAYWAYS_LIST, req } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function, default: () => () => ({}) }
},
data () {
return {
isAdd: true, // 新增 or 修改页面标志
isShow: false, // 是否显示弹层/抽屉
saveObject: {}, // 数据对象
wayCode: null, // 更新对象ID
rules: {
wayCode: [
{ required: true, message: '请输入支付方式代码', trigger: 'blur' }
],
wayName: [
{ required: true, message: '请输入支付方式名称', trigger: 'blur' }
]
}
}
},
methods: {
show: function (wayCode) { // 弹层打开事件
this.isAdd = !wayCode
this.saveObject = {} // 数据清空
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
if (!this.isAdd) { // 修改信息 延迟展示弹层
that.wayCode = wayCode
req.getById(API_URL_PAYWAYS_LIST, wayCode).then(res => { that.saveObject = res })
this.isShow = true
} else {
that.isShow = true // 立马展示弹层信息
}
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
// 请求接口
if (that.isAdd) {
req.add(API_URL_PAYWAYS_LIST, that.saveObject).then(res => {
that.$message.success('新增成功')
that.isShow = false
that.callbackFunc() // 刷新列表
})
} else {
req.updateById(API_URL_PAYWAYS_LIST, that.wayCode, that.saveObject).then(res => {
that.$message.success('修改成功')
that.isShow = false
that.callbackFunc() // 刷新列表
})
}
}
})
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" v-if="$access('ENT_PC_WAY_SEARCH')" class="table-head-ground">
<div class="table-layer">
<jeepay-text-up :placeholder="'支付方式代码'" :msg="searchData.wayCode" v-model="searchData.wayCode" />
<jeepay-text-up :placeholder="'支付方式名称'" :msg="searchData.wayName" v-model="searchData.wayName" />
<span class="table-page-search-submitButtons">
<a-button type="primary" @click="searchFunc(true)" icon="search" :loading="btnLoading">查询</a-button>
<a-button style="margin-left: 8px;" @click="() => this.searchData = {}" icon="reload">重置</a-button>
</span>
</div>
</a-form>
<a-button v-if="$access('ENT_PC_WAY_ADD')" type="primary" icon="plus" @click="addFunc" class="mg-b-30">新建</a-button>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="wayCode"
>
<template slot="wayCodeSlot" slot-scope="{record}"><b>{{ record.wayCode }}</b></template> <!-- 自定义插槽 -->
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a v-if="$access('ENT_PC_WAY_EDIT')" @click="editFunc(record.wayCode)">修改</a>
<a style="color: red" v-if="$access('ENT_PC_WAY_DEL')" @click="delFunc(record.wayCode)">删除</a>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc"/>
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_PAYWAYS_LIST, req } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{
key: 'wayCode', // key为必填项,用于标志该列的唯一
fixed: 'left',
title: '支付方式代码',
scopedSlots: { customRender: 'wayCodeSlot' }
},
{
key: 'wayName',
title: '支付方式名称',
dataIndex: 'wayName'
},
{
key: 'op',
title: '操作',
width: '200px',
fixed: 'right',
align: 'center',
scopedSlots: { customRender: 'opSlot' }
}
]
export default {
name: 'PayWayPage',
components: { JeepayTable, JeepayTableColumns, InfoAddOrEdit, JeepayTextUp },
data () {
return {
tableColumns: tableColumns,
searchData: {},
btnLoading: false
}
},
methods: {
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_PAYWAYS_LIST, params)
},
searchFunc (isToFirst = false) { // 点击【查询】按钮点击事件
this.btnLoading = true
this.$refs.infoTable.refTable(isToFirst)
},
addFunc: function () { // 业务通用【新增】 函数
this.$refs.infoAddOrEdit.show()
},
editFunc: function (wayCode) { // 业务通用【修改】 函数
this.$refs.infoAddOrEdit.show(wayCode)
},
delFunc: function (wayCode) {
const that = this
this.$infoBox.confirmDanger('确认删除?', '', () => {
req.delById(API_URL_PAYWAYS_LIST, wayCode).then(res => {
that.$message.success('删除成功!')
that.$refs.infoTable.refTable(false)
})
})
}
}
}
</script>
<template>
<a-modal v-model="isShow" title="新增角色" @ok="handleOkFunc">
<a-form-model ref="infoFormModel" :model="saveObject" :label-col="{span: 4}" :wrapper-col="{span: 15}" :rules="rules">
<a-form-model-item label="角色名称:" prop="roleName">
<a-input v-model="saveObject.roleName" />
</a-form-model-item>
</a-form-model>
</a-modal>
</template>
<script>
import { API_URL_ROLE_LIST, req } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function }
},
data () {
return {
isShow: false, // 是否显示弹层/抽屉
saveObject: {}, // 数据对象
rules: {
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
]
}
}
},
created () {
},
methods: {
show: function () { // 弹层打开事件
this.saveObject = {} // 数据清空
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
this.isShow = true
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
// 请求接口
req.add(API_URL_ROLE_LIST, that.saveObject).then(res => {
that.$message.success('新增成功')
that.isShow = false
// 刷新列表
that.callbackFunc()
})
}
})
}
}
}
</script>
<template>
<a-drawer :visible="isShow" :title=" isAdd ? '新增角色' : '修改角色' " width="600" :maskClosable="false" @close="isShow = false">
<a-form-model ref="infoFormModel" :model="saveObject" :label-col="{span: 4}" :rules="rules">
<a-form-model-item label="角色名称:" prop="roleName">
<a-input v-model="saveObject.roleName" />
</a-form-model-item>
</a-form-model>
<!-- 角色权限分配 -->
<RoleDist ref="roleDist" />
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="isShow = false" icon="close">取消</a-button>
<a-button type="primary" @click="handleOkFunc" :loading="confirmLoading" icon="check">保存</a-button>
</div>
</a-drawer>
</template>
<script>
import { API_URL_ROLE_LIST, req } from '@/api/manage'
import RoleDist from './RoleDist'
export default {
components: { RoleDist },
props: {
callbackFunc: { type: Function }
},
data () {
return {
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {}, // 数据对象
recordId: null, // 更新对象ID
rules: {
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
]
}
}
},
created () {
},
methods: {
show: function (recordId) { // 弹层打开事件
this.isAdd = !recordId
this.saveObject = {} // 数据清空
this.confirmLoading = false // 关闭loading
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
const that = this
// 初始化角色权限分配功能
this.$nextTick(() => that.$refs.roleDist.initTree(recordId))
if (!this.isAdd) { // 修改信息 延迟展示弹层
that.recordId = recordId
req.getById(API_URL_ROLE_LIST, recordId).then(res => { that.saveObject = res })
this.isShow = true
} else {
that.isShow = true // 立马展示弹层信息
}
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
// 请求接口
that.confirmLoading = true // 显示loading
// 保存选择的权限信息
const selectedEntIdList = that.$refs.roleDist.getSelectedEntIdList()
that.saveObject.entIdListStr = selectedEntIdList ? JSON.stringify(selectedEntIdList) : ''
if (that.isAdd) {
req.add(API_URL_ROLE_LIST, that.saveObject).then(res => {
that.$message.success('新增成功')
that.isShow = false
that.callbackFunc() // 刷新列表
}).catch(res => { that.confirmLoading = false })
} else {
req.updateById(API_URL_ROLE_LIST, that.recordId, that.saveObject).then(res => {
that.$message.success('修改成功')
that.isShow = false
that.callbackFunc() // 刷新列表
}).catch(res => { that.confirmLoading = false })
}
}
})
}
}
}
</script>
<template>
<div style="padding-bottom:50px">
<p v-if="hasEnt">请选择权限: </p>
<!-- 树状结构 -->
<a-tree :tree-data="treeData" :replaceFields="replaceFields" v-model="checkedKeys" :checkable="true"/>
</div>
</template>
<script>
import { getEntTree, API_URL_ROLE_ENT_RELA_LIST, req } from '@/api/manage'
export default {
data () {
return {
hasEnt: this.$access('ENT_UR_ROLE_DIST'),
recordId: null, // 更新对象ID
treeData: [],
replaceFields: { key: 'entId', title: 'entName' }, // 配置替换字段
checkedKeys: [], // 已选中的节点
allEntList: {} // 由于antd vue关联操作,无法直接获取到父ID, 需要内部自行维护一套数据结构 {entId: {pid, children}}
}
},
methods: {
initTree: function (recordId) { // 弹层打开事件
const that = this
// 判断是否有权限访问
if (!this.hasEnt) {
return false
}
// 重置数据
that.checkedKeys = []
that.treeData = []
that.allEntList = {}
that.recordId = recordId
// 获取全部权限的树状结构
getEntTree('MGR').then(res => {
that.treeData = res
// 存储所有的菜单权限集合
this.recursionTreeData(res, (item) => {
that.allEntList[item.entId] = { pid: item.pid, children: item.children || [] }
})
// 查询所有的已分配的权限集合 (默认为 0 , 无数据)
req.list(API_URL_ROLE_ENT_RELA_LIST, { roleId: recordId || 'NONE', pageSize: -1 }).then(res2 => {
const checkedEntIdList = [] // 所有已分配的权限集合(兼容antd vue 仅保留子节点)
res2.records.map(item => {
if (that.allEntList[item.entId] && that.allEntList[item.entId].children.length <= 0) { // 说明是子节点
checkedEntIdList.push(item.entId)
}
})
that.checkedKeys = checkedEntIdList
})
})
},
getSelectedEntIdList: function () { // 获取已选择的列表集合
// 判断是否有权限访问
if (!this.hasEnt) {
return false
}
const that = this
const reqData = []
this.checkedKeys.map(item => {
const pidList = [] // 当前权限的所有的父节点IDList
that.getAllPid(item, pidList)
pidList.map(pid => {
if (reqData.indexOf(pid) < 0) {
reqData.push(pid)
}
})
})
return reqData
},
// 递归遍历树状结构数据
recursionTreeData (entTreeData, func) {
for (let i = 0; i < entTreeData.length; i++) {
const thisEnt = entTreeData[i]
if (thisEnt.children && thisEnt.children.length > 0) {
this.recursionTreeData(thisEnt.children, func)
}
func(thisEnt)
}
},
getAllPid (entId, array) { // 获取所有的PID
if (this.allEntList[entId] && entId !== 'ROOT') {
array.push(entId)
this.getAllPid(this.allEntList[entId].pid, array)
}
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div v-if="$access('ENT_UR_ROLE_SEARCH')" class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<jeepay-text-up :placeholder="'角色ID'" :msg="searchData.roleId" v-model="searchData.roleId" />
<jeepay-text-up :placeholder="'角色名称'" :msg="searchData.roleName" v-model="searchData.roleName" />
<span class="table-page-search-submitButtons">
<a-button type="primary" @click="searchFunc" icon="search" :loading="btnLoading">查询</a-button>
<a-button style="margin-left: 8px;" @click="() => this.searchData = {}" icon="reload">重置</a-button>
</span>
</div>
</a-form>
<div>
<a-button v-if="$access('ENT_UR_ROLE_ADD')" type="primary" icon="plus" @click="addFunc" class="mg-b-30">新建</a-button>
</div>
</div>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
@btnLoadClose="btnLoading=false"
rowKey="roleName"
>
<template slot="roleIdSlot" slot-scope="{record}"><b>{{ record.roleId }}</b></template> <!-- 自定义插槽 -->
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a v-if="$access('ENT_UR_ROLE_EDIT')" @click="editFunc(record.roleId)">修改</a>
<a style="color: red" v-if="$access('ENT_UR_ROLE_DEL')" @click="delFunc(record.roleId)">删除</a>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 新增 / 修改 页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
</page-header-wrapper>
</template>
<script>
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_ROLE_LIST, req } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{
key: 'roleId', // key为必填项,用于标志该列的唯一
title: '角色ID',
sorter: true,
fixed: 'left',
scopedSlots: { customRender: 'roleIdSlot' }
},
{
key: 'roleName',
title: '角色名称',
dataIndex: 'roleName',
sorter: true
},
{
key: 'op',
title: '操作',
width: '200px',
fixed: 'right',
align: 'center',
scopedSlots: { customRender: 'opSlot' }
}
]
export default {
name: 'RolePage',
components: { JeepayTable, JeepayTableColumns, InfoAddOrEdit, JeepayTextUp },
data () {
return {
tableColumns: tableColumns,
searchData: {},
btnLoading: false
}
},
mounted () {
},
methods: {
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_ROLE_LIST, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.btnLoading = true // 打开查询按钮上的loading
this.$refs.infoTable.refTable(true)
},
addFunc: function () { // 业务通用【新增】 函数
this.$refs.infoAddOrEdit.show()
},
editFunc: function (recordId) { // 业务通用【修改】 函数
this.$refs.infoAddOrEdit.show(recordId)
},
delFunc: function (recordId) { // 业务通用【删除】 函数
const that = this
this.$infoBox.confirmDanger('确认删除?', '', () => {
// 需要【按钮】loading 请返回 promise对象, 不需要请直接返回null
return req.delById(API_URL_ROLE_LIST, recordId).then(res => {
that.$message.success('删除成功!')
that.$refs.infoTable.refTable(false)
})
})
}
}
}
</script>
<template>
<div style="background: #fff">
<a-tabs
@change="selectTabs"
>
<a-tab-pane key="applicationConfig" tab="应用配置">
<div class="account-settings-info-view">
<a-form-model
ref="configFormModel"
>
<a-row>
<a-col :span="8" :offset="1" :key="config" v-for="(item, config) in configData">
<a-form-model-item :label="item.configName">
<a-input :type="item.type==='text'?'text':'textarea'" v-model="item.configVal" autocomplete="off" />
</a-form-model-item>
</a-col>
</a-row>
<a-form-item style="display:flex;justify-content:center">
<a-button type="primary" icon="check-circle" @click="confirm" :loading="btnLoading">确认更新</a-button>
</a-form-item>
</a-form-model>
</div>
</a-tab-pane>
<!--<a-tab-pane key="" tab="···">-->
<!--<div class="account-settings-info-view" style="height: 300px">-->
<!--</div>-->
<!--</a-tab-pane>-->
</a-tabs>
</div>
</template>
<script>
import { API_URL_SYS_CONFIG, req, getConfigs } from '@/api/manage'
export default {
components: {
},
data () {
return {
btnLoading: false,
configData: {},
groupKey: 'applicationConfig'
}
},
created () {
this.detail()
},
methods: {
detail () { // 获取基本信息
const that = this
getConfigs(that.groupKey).then(res => {
that.configData = res
that.groupKey = res[0].groupKey
})
},
selectTabs (key) { // 清空必填提示
if (key) {
this.groupKey = key
this.detail()
}
},
confirm (e) { // 确认更新
const that = this
this.$infoBox.confirmPrimary('确认修改应用配置吗?', '', () => {
that.btnLoading = true // 打开按钮上的 loading
const formData = new FormData()
for (var i in that.configData) {
formData.append(that.configData[i].configKey, that.configData[i].configVal)
}
req.updateById(API_URL_SYS_CONFIG, that.groupKey, formData).then(res => {
that.$message.success('修改成功')
that.btnLoading = false
}).catch(res => {
that.btnLoading = false
})
})
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<a-form-item label="" class="table-head-layout" style="max-width:350px;min-width:300px">
<a-range-picker
@change="onChange"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
:disabled-date="disabledDate"
>
<a-icon slot="suffixIcon" type="sync" />
</a-range-picker>
</a-form-item>
<jeepay-text-up :placeholder="'用户ID'" :msg="searchData.userId" v-model="searchData.userId" />
<jeepay-text-up :placeholder="'用户名'" :msg="searchData.userName" v-model="searchData.userName" />
<a-form-item label="" class="table-head-layout">
<a-select v-model="searchData.sysType" placeholder="所属系统" default-value="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="MGR">运营平台</a-select-option>
<a-select-option value="MCH">商户系统</a-select-option>
</a-select>
</a-form-item>
<span class="table-page-search-submitButtons">
<a-button type="primary" icon="search" @click="queryhFunc" :loading="btnLoading">搜索</a-button>
<a-button style="margin-left: 8px" icon="reload" @click="() => this.searchData = {}">重置</a-button>
</span>
</div>
</a-form>
<div>
<a-button icon="delete" v-if="$access('ENT_SYS_LOG_DEL')" type="danger" @click="delFunc" class="mg-b-30">删除</a-button>
</div>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
:rowSelection="rowSelection"
rowKey="sysLogId"
>
<template slot="userNameSlot" slot-scope="{record}"><b>{{ record.userName }}</b></template> <!-- 自定义插槽 -->
<template slot="sysTypeSlot" slot-scope="{record}">
<a-tag :key="record.sysType" :color="record.sysType === 'MGR'?'green':record.sysType === 'MCH'?'geekblue':'loser'">
{{ record.sysType === 'MGR'?'运营平台':record.sysType === 'MCH'?'商户系统':'其他' }}
</a-tag>
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" v-if="$access('ENT_SYS_LOG_VIEW')" @click="detailFunc(record.sysLogId)">详情</a-button>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 日志详情抽屉 -->
<template>
<a-drawer
width="40%"
placement="right"
:closable="true"
:visible="visible"
:title="visible === true? '日志详情':''"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户ID">
{{ detailData.userId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户IP">
{{ detailData.userIp }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户名">
<b>
{{ detailData.userName }}
</b>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="所属系统">
<a-tag :key="detailData.sysType" :color="detailData.sysType === 'MGR'?'green':detailData.sysType === 'MCH'?'geekblue':'loser'">
{{ detailData.sysType === 'MGR'?'运营平台':detailData.sysType === 'MCH'?'商户系统':'其他' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider />
<a-row justify="space-between" type="flex">
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="操作描述">
{{ detailData.methodRemark }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="请求方法">
{{ detailData.methodName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="请求地址">
{{ detailData.reqUrl }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row justify="start" type="flex">
<a-col :sm="24">
<a-form-model-item label="请求参数">
<a-input
type="textarea"
disabled="disabled"
style="background-color: black;color: #FFFFFF;height: 100px"
v-model="detailData.optReqParam"
/>
</a-form-model-item>
</a-col>
</a-row>
<a-row justify="start" type="flex">
<a-col :sm="24">
<a-form-model-item label="响应参数">
<a-input
type="textarea"
disabled="disabled"
style="background-color: black;color: #FFFFFF;height: 150px"
v-model="detailData.optResInfo"
/>
</a-form-model-item>
</a-col>
</a-row>
</a-drawer>
</template>
</page-header-wrapper>
</template>
<script>
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import { API_URL_SYS_LOG, req } from '@/api/manage'
import moment from 'moment'
import { message, Modal } from 'ant-design-vue'
// eslint-disable-next-line no-unused-vars
const tableColumns = [
{ key: 'userName', title: '用户名', fixed: 'left', scopedSlots: { customRender: 'userNameSlot' } },
{ key: 'userId', title: '用户ID', dataIndex: 'userId' },
{ key: 'userIp', title: '用户IP', dataIndex: 'userIp' },
{ key: 'sysType', title: '所属系统', scopedSlots: { customRender: 'sysTypeSlot' } },
{ key: 'methodRemark', title: '操作描述', ellipsis: true, dataIndex: 'methodRemark' },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期' },
{ key: 'op', title: '操作', width: '100px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
]
export default {
name: 'IsvListPage',
components: { JeepayTable, JeepayTableColumns, JeepayTextUp },
data () {
return {
tableColumns: tableColumns,
searchData: {},
selectedIds: [], // 选中的数据
createdStart: '', // 选择开始时间
createdEnd: '', // 选择结束时间
visible: false,
detailData: {},
btnLoading: false
}
},
computed: {
rowSelection () {
const that = this
return {
onChange: (selectedRowKeys, selectedRows) => {
that.selectedIds = [] // 清空选中数组
selectedRows.forEach(function (data) { // 赋值选中参数
that.selectedIds.push(data.sysLogId)
})
}
}
}
},
mounted () {
},
methods: {
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_SYS_LOG, params)
},
delFunc: function () {
const that = this
if (that.selectedIds.length === 0) {
message.error('请选择要删除的日志')
return false
}
Modal.confirm({
title: '确认删除' + that.selectedIds.length + '条日志吗?',
okType: 'danger',
onOk () {
req.delById(API_URL_SYS_LOG, that.selectedIds).then(res => {
that.selectedIds = [] // 清空选中数组
that.$refs.infoTable.refTable(true)
that.$message.success('删除成功')
})
},
onCance () {
}
})
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.$refs.infoTable.refTable(true)
},
detailFunc: function (recordId) {
const that = this
req.getById(API_URL_SYS_LOG, recordId).then(res => {
that.detailData = res
})
this.visible = true
},
moment,
onChange (date, dateString) {
this.searchData.createdStart = dateString[0] // 开始时间
this.searchData.createdEnd = dateString[1] // 结束时间
},
disabledDate (current) { // 今日之后日期不可选
return current && current > moment().endOf('day')
},
onClose () {
this.visible = false
},
queryhFunc () {
this.btnLoading = true
this.$refs.infoTable.refTable(true)
}
}
}
</script>
<template>
<a-drawer
:title=" isAdd ? '新增操作员' : '修改操作员' "
placement="right"
:closable="true"
@ok="handleOkFunc"
:visible="isShow"
width="600"
@close="onClose"
:maskClosable="false"
>
<!-- <a-modal :confirmLoading="confirmLoading"> -->
<a-form-model
ref="infoFormModel"
:model="saveObject"
layout="vertical"
:rules="rules"
style="padding-bottom:50px">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="用户登录名:" prop="loginUsername">
<a-input v-model="saveObject.loginUsername" :disabled="!isAdd" />
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="用户姓名:" prop="realname">
<a-input v-model="saveObject.realname" />
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="手机号:" prop="telphone">
<a-input v-model="saveObject.telphone" />
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="编号:" prop="userNo">
<a-input v-model="saveObject.userNo" />
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="请选择性别:" prop="sex">
<a-radio-group v-model="saveObject.sex">
<a-radio :value="1"></a-radio>
<a-radio :value="2"></a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="是否为超级管理员:" prop="isAdmin">
<a-radio-group v-model="saveObject.isAdmin">
<a-radio :value="1"></a-radio>
<a-radio :value="0"></a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="状态:" prop="state">
<a-radio-group v-model="saveObject.state">
<a-radio :value="1">启用</a-radio>
<a-radio :value="0">停用</a-radio>
</a-radio-group>
</a-form-model-item>
</a-col>
</a-row>
<a-divider orientation="left" v-if="resetIsShow">
<a-tag color="#FF4B33">
账户安全
</a-tag>
</a-divider>
<div style="display:flex;flex-direction:row;">
<a-row justify="space-between" type="flex" style="width:100%">
<a-col :span="10">
<a-form-model-item label="" v-if="resetIsShow">
重置密码:<a-checkbox v-model="sysPassword.resetPass"></a-checkbox>
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="" v-if="sysPassword.resetPass">
恢复默认密码:<a-checkbox v-model="sysPassword.defaultPass" @click="isResetPass"></a-checkbox>
</a-form-model-item>
</a-col>
</a-row>
</div>
<div v-if="sysPassword.resetPass">
<div v-show="!this.sysPassword.defaultPass">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-model-item label="新密码:" prop="newPwd">
<a-input-password
autocomplete="new-password"
v-model="newPwd"
:disabled="sysPassword.defaultPass" />
</a-form-model-item>
</a-col>
<a-col :span="10">
<a-form-model-item label="确认新密码:" prop="confirmPwd">
<a-input-password
autocomplete="new-password"
v-model="sysPassword.confirmPwd"
:disabled="sysPassword.defaultPass" />
</a-form-model-item>
</a-col>
</a-row>
</div>
</div>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="onClose" icon="close">取消</a-button>
<a-button type="primary" @click="handleOkFunc" icon="check" :loading="confirmLoading">保存</a-button>
</div>
</a-form-model>
</a-drawer>
</template>
<script>
import { req, API_URL_SYS_USER_LIST } from '@/api/manage'
import { Base64 } from 'js-base64'
export default {
props: {
callbackFunc: { type: Function, default: () => ({}) }
},
data () {
return {
newPwd: '', // 新密码
resetIsShow: false, // 重置密码是否展现
sysPassword: {
resetPass: false, // 重置密码
defaultPass: true, // 使用默认密码
confirmPwd: '' // 确认密码
},
loading: false, // 按钮上的loading
value: 1, // 单选框默认的值
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {}, // 数据对象
recordId: null, // 更新对象ID
rules: {
realname: [{ required: true, message: '请输入用户姓名', trigger: 'blur' }],
telphone: [{ required: true, pattern: /^[1][0-9]{10}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
userNo: [{ required: true, message: '请输入编号', trigger: 'blur' }],
loginUsername: [],
newPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value, callBack) => {
if (!this.sysPassword.defaultPass) {
if (this.newPwd.length < 6 || this.newPwd.length > 12) {
callBack('请输入6-12位新密码')
}
}
callBack()
}
}], // 新密码
confirmPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value, callBack) => {
if (!this.sysPassword.defaultPass) {
this.newPwd === this.sysPassword.confirmPwd ? callBack() : callBack('新密码与确认密码不一致')
} else {
callBack()
}
}
}] // 确认新密码
}
}
},
created () {
},
methods: {
show (recordId) { // 弹层打开事件
if (this.$refs.infoFormModel !== undefined) {
this.$refs.infoFormModel.resetFields()
}
this.isAdd = !recordId
// 数据恢复为默认数据
this.saveObject = {
isAdmin: 1,
state: 1,
sex: 1
}
this.rules.loginUsername = []
this.confirmLoading = false // 关闭loading
if (this.isAdd) {
this.rules.loginUsername.push({
required: true,
pattern: /^[a-zA-Z][a-zA-Z0-9]{5,17}$/,
message: '请输入字母开头,长度为6-18位的登录名',
trigger: 'blur'
})
}
const that = this
if (!this.isAdd) { // 修改信息 延迟展示弹层
that.resetIsShow = true // 展示重置密码板块
that.recordId = recordId
req.getById(API_URL_SYS_USER_LIST, recordId).then(res => { that.saveObject = res })
this.isShow = true
} else {
that.isShow = true // 立马展示弹层信息
}
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
this.$refs.infoFormModel.validate(valid => {
if (valid) { // 验证通过
// 请求接口
that.loading = true // 打开按钮上的 loading
that.confirmLoading = true // 显示loading
if (that.isAdd) {
req.add(API_URL_SYS_USER_LIST, that.saveObject).then(res => {
that.$message.success('新增成功')
that.isShow = false
that.loading = false
that.callbackFunc() // 刷新列表
}).catch((res) => {
that.confirmLoading = false
})
} else {
that.sysPassword.confirmPwd = Base64.encode(that.sysPassword.confirmPwd)
Object.assign(that.saveObject, that.sysPassword) // 拼接对象
console.log(that.saveObject)
req.updateById(API_URL_SYS_USER_LIST, that.recordId, that.saveObject).then(res => {
that.$message.success('修改成功')
that.isShow = false
that.callbackFunc() // 刷新列表
that.resetIsShow = false // 取消展示
that.sysPassword.resetPass = false
that.sysPassword.defaultPass = true // 是否使用默认密码默认为true
that.resetPassEmpty(that) // 清空密码
}).catch(res => {
that.confirmLoading = false
that.resetIsShow = false // 取消展示
that.sysPassword.resetPass = false
that.sysPassword.defaultPass = true // 是否使用默认密码默认为true
that.resetPassEmpty(that) // 清空密码
})
}
}
})
},
// 关闭抽屉
onClose () {
this.isShow = false
this.resetIsShow = false // 取消重置密码板块展示
this.resetPassEmpty(this) // 清空密码
this.sysPassword.resetPass = false // 关闭密码输入
this.sysPassword.defaultPass = true // 是否使用默认密码默认为true
},
// 使用默认密码重置是否为true
isResetPass () {
if (!this.sysPassword.defaultPass) {
this.newPwd = ''
this.sysPassword.confirmPwd = ''
}
},
// 保存后清空密码
resetPassEmpty (that) {
that.newPwd = ''
that.sysPassword.confirmPwd = ''
}
}
}
</script>
<template>
<a-drawer :visible="isShow" title="分配角色" width="30%" :maskClosable="true" @close="isShow = false">
<div>
<div :style="{ borderBottom: '1px solid #E9E9E9' }">
<a-checkbox
:indeterminate="checkedVal.length != 0 && allRoleList.length != checkedVal.length"
:checked="checkedVal.length != 0 && allRoleList.length === checkedVal.length"
@change="onCheckAllChange">
全选
</a-checkbox>
</div>
<br />
<a-checkbox-group v-model="checkedVal" :options="allRoleList"/>
</div>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" icon="close" @click="isShow = false">取消</a-button>
<a-button type="primary" @click="handleOkFunc" icon="check" :loading="confirmLoading">保存</a-button>
</div>
</a-drawer>
</template>
<script>
import { uSysUserRoleRela, req, reqLoad, API_URL_ROLE_LIST, API_URL_USER_ROLE_RELA_LIST } from '@/api/manage'
export default {
props: {
callbackFunc: { type: Function }
},
data () {
return {
confirmLoading: false, // 显示确定按钮loading图标
isShow: false, // 是否显示弹层/抽屉
recordId: null, // 更新对象ID
allRoleList: [], // 全部的角色集合 {label: '', value: ''}
checkedVal: [] // 已选择的数据集合
}
},
created () {
},
methods: {
show: function (recordId) { // 弹层打开事件
const that = this
// 重置数据
that.allRoleList = []
that.checkedVal = []
that.confirmLoading = false // 关闭loading
that.recordId = recordId
// 查询所有角色列表
reqLoad.list(API_URL_ROLE_LIST, { pageSize: -1 }).then(res => {
if (res.total <= 0) {
return this.$message.error(`当前暂无角色,请先行添加`)
}
that.allRoleList = []
res.records.map(role => {
that.allRoleList.push({ label: role.roleName, value: role.roleId })
that.isShow = true
})
// 查询已分配的列表
req.list(API_URL_USER_ROLE_RELA_LIST, { pageSize: -1, userId: recordId }).then(relaRes => {
relaRes.records.map(rela => {
that.checkedVal.push(rela.roleId)
})
})
})
},
handleOkFunc: function () { // 点击【确认】按钮事件
const that = this
that.confirmLoading = true // 显示loading
uSysUserRoleRela(this.recordId, this.checkedVal).then(res => {
that.$message.success('更新成功!')
this.isShow = false
if (that.callbackFunc !== undefined) {
that.callbackFunc() // 刷新列表
}
}).catch(res => { that.confirmLoading = false }) // 恢复loading
},
onCheckAllChange: function (e) { // 点击全选/非全选的状态更改
const that = this
this.checkedVal = []
if (e.target.checked) { // 全选
this.allRoleList.map(role => { that.checkedVal.push(role.value) })
}
}
}
}
</script>
<template>
<page-header-wrapper>
<a-card>
<div v-if="$access('ENT_UR_USER_SEARCH')" class="table-page-search-wrapper">
<a-form layout="inline" class="table-head-ground">
<div class="table-layer">
<jeepay-text-up :placeholder="'用户ID'" :msg="searchData.sysUserId" v-model="searchData.sysUserId" />
<jeepay-text-up :placeholder="'用户姓名'" :msg="searchData.realname" v-model="searchData.realname" />
<span class="table-page-search-submitButtons">
<a-button type="primary" @click="searchFunc" icon="search" :loading="btnLoading">查询</a-button>
<a-button style="margin-left: 8px;" @click="() => this.searchData = {}" icon="reload">重置</a-button>
</span>
</div>
</a-form>
<div>
<a-button v-if="$access('ENT_UR_USER_ADD')" type="primary" icon="plus" @click="addFunc" class="mg-b-30">新建</a-button>
</div>
</div>
<!-- 列表渲染 -->
<JeepayTable
@btnLoadClose="btnLoading=false"
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="searchData"
rowKey="sysUserId"
>
<template slot="avatarSlot" slot-scope="{record}">
<a-avatar size="default" :src="record.avatarUrl" />
</template>
<template slot="stateSlot" slot-scope="{record}">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_UR_USER_EDIT')" :onChange="(state) => { return updateState(record.sysUserId, state)}"/>
</template>
<template slot="opSlot" slot-scope="{record}"> <!-- 操作列插槽 -->
<JeepayTableColumns>
<a v-if="$access('ENT_UR_USER_UPD_ROLE')" @click="roleDist(record.sysUserId)" >变更角色</a>
<a v-if="$access('ENT_UR_USER_EDIT')" @click="editFunc(record.sysUserId)">修改</a>
<a v-if="$access('ENT_UR_USER_DELETE')" style="color: red" @click="delFunc(record.sysUserId)">删除</a>
</JeepayTableColumns>
</template>
</JeepayTable>
</a-card>
<!-- 新增 / 修改 页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc"/>
<!-- 分配角色 页面组件 -->
<RoleDist ref="roleDist"/>
</page-header-wrapper>
</template>
<script>
import JeepayTable from '@/components/JeepayTable/JeepayTable'
import JeepayTableColumns from '@/components/JeepayTable/JeepayTableColumns'
import JeepayTableColState from '@/components/JeepayTable/JeepayTableColState'
import { API_URL_SYS_USER_LIST, req, reqLoad } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit'
import RoleDist from './RoleDist'
import JeepayTextUp from '@/components/JeepayTextUp/JeepayTextUp' // 文字上移组件
const tableColumns = [
{ title: '用户ID', dataIndex: 'sysUserId', fixed: 'left' },
{ title: '姓名', dataIndex: 'realname' },
{ title: '性别', dataIndex: 'sex', customRender: (text, record, index) => { return record.sex === 1 ? '男' : record.sex === 2 ? '女' : '未知' } },
{ title: '头像', scopedSlots: { customRender: 'avatarSlot' } },
{ title: '编号', dataIndex: 'userNo' },
{ title: '手机号', dataIndex: 'telphone' },
{ title: '超管', dataIndex: 'isAdmin', customRender: (text, record, index) => { return record.isAdmin === 1 ? '是' : '否' } },
{ title: '状态', scopedSlots: { customRender: 'stateSlot' }, align: 'center' },
{ title: '创建时间', dataIndex: 'createdAt' },
{ title: '修改时间', dataIndex: 'updatedAt' },
{
key: 'op',
title: '操作',
width: 200,
fixed: 'right',
align: 'center',
scopedSlots: { customRender: 'opSlot' }
}
]
export default {
components: { JeepayTable, JeepayTableColumns, InfoAddOrEdit, RoleDist, JeepayTableColState, JeepayTextUp },
data () {
return {
tableColumns: tableColumns,
searchData: {},
btnLoading: false
}
},
mounted () {
},
methods: {
// 请求table接口数据
reqTableDataFunc: (params) => {
return req.list(API_URL_SYS_USER_LIST, params)
},
searchFunc: function () { // 点击【查询】按钮点击事件
this.btnLoading = true // 打开查询按钮的loading
this.$refs.infoTable.refTable(true)
},
addFunc: function () { // 业务通用【新增】 函数
this.$refs.infoAddOrEdit.show()
},
editFunc: function (recordId) { // 业务通用【修改】 函数
this.$refs.infoAddOrEdit.show(recordId)
},
delFunc: function (recordId) { // 业务通用【删除】 函数
const that = this
this.$infoBox.confirmDanger('确认删除?', '', () => {
return req.delById(API_URL_SYS_USER_LIST, recordId).then(res => {
that.$message.success('删除成功!')
that.$refs.infoTable.refTable(false)
})
})
},
roleDist: function (recordId) { // 【分配权限】 按钮点击事件
this.$refs.roleDist.show(recordId)
},
updateState: function (recordId, state) { // 【更新状态】
const that = this
const title = state === 1 ? '确认[启用]该用户?' : '确认[停用]该用户?'
const content = state === 1 ? '启用后用户可进行登陆等一系列操作' : '停用后该用户将立即退出系统并不可再次登陆'
return new Promise((resolve, reject) => {
that.$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_SYS_USER_LIST, recordId, { state: state }).then(res => {
that.searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
}
}
</script>
<template>
<div class="main">
<a-form class="user-layout-login" ref="formLogin" :form="form" @submit="handleSubmit">
<!-- 错误提示信息 -->
<a-alert v-if="showLoginErrorInfo" type="error" showIcon style="margin-bottom: 24px;" :message="showLoginErrorInfo" />
<a-form-item>
<a-input
@focus="usernameIcon = require('@/assets/svg/select-user.svg')"
@blur="usernameIcon = require('@/assets/svg/user.svg')"
size="large"
type="text"
placeholder="请输入账户"
v-decorator="[
'username',
{rules: [{ required: true, message: '请输入帐户名' }], validateTrigger: 'change'}
]"
>
<img :src="usernameIcon" slot="prefix" class="user" alt="user" >
</a-input>
</a-form-item>
<a-form-item>
<a-input-password
@focus="passwordIcon = require('@/assets/svg/select-lock.svg')"
@blur="passwordIcon = require('@/assets/svg/lock.svg')"
size="large"
placeholder="请输入密码"
v-decorator="[
'password',
{rules: [{ required: true, message: '请输入密码' }], validateTrigger: 'change'}
]"
>
<!-- <a-icon src="../../assets/svg/user.svg" alt=""> -->
<img :src="passwordIcon" slot="prefix" class="user" alt="user">
</a-input-password>
</a-form-item>
<div class="code">
<a-form-item>
<a-input
@focus="vercodeIcon = require('@/assets/svg/select-code.svg')"
@blur="vercodeIcon = require('@/assets/svg/code.svg')"
class="code-input"
size="large"
type="text"
placeholder="请输入人机验证码"
v-decorator="[
'usercode',
{rules: [{ required: true, message: '请输入人机验证码' }], validateTrigger: 'blur'}
]"
>
<img :src="vercodeIcon" slot="prefix" class="user" alt="user" />
</a-input>
</a-form-item>
<div class="code-img" style="position: relative;background:#ddd">
<img v-show="vercodeImgSrc" :src="vercodeImgSrc" @click="refVercode()"/>
<div class="vercode-mask" v-show="isOverdue" @click="refVercode()">已过期 请刷新</div>
</div>
</div>
<a-form-item>
<!-- 自动登录 -->
<!-- <a-checkbox v-decorator="['rememberMe', { valuePropName: 'checked' }]">自动登录</a-checkbox> -->
<a-checkbox v-model="isAutoLogin">自动登录</a-checkbox>
<!-- 忘记密码 -->
<a class="forge-password" style="float: right;">忘记密码?</a>
</a-form-item>
<a-form-item class="submit">
<a-button
size="large"
type="primary"
htmlType="submit"
class="login-button"
:loading="loginBtnLoadingFlag"
>登录
</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script>
// import Initializer from '@/core/bootstrap'
import { mapActions } from 'vuex'
import { timeFix } from '@/utils/util'
import { vercode } from '@/api/login'
export default {
components: {
},
data () {
return {
isOverdue: false, // 设置过期样式
isAutoLogin: true, // 是否是自动登录
loginBtnLoadingFlag: false, // 登录按钮是否显示 加载状态
showLoginErrorInfo: '', // 是否显示登录错误面板信息
form: this.$form.createForm(this),
usernameIcon: require('@/assets/svg/user.svg'), // 三个icon图标
passwordIcon: require('@/assets/svg/lock.svg'),
vercodeIcon: require('@/assets/svg/code.svg'),
vercodeImgSrc: '', // 验证码图片
vercodeToken: '' // 验证码验证token
}
},
mounted () {
this.refVercode()
},
methods: {
...mapActions(['Login', 'Logout']),
// handler
handleSubmit (e) {
e.preventDefault() // 通知 Web 浏览器不要执行与事件关联的默认动作
const that = this
that.form.validateFields({ force: true }, (err, values) => {
if (!err) {
const loginParams = { ...values }
loginParams.username = values.username
loginParams.password = values.password
loginParams.vercode = values.usercode
loginParams.vercodeToken = that.vercodeToken
that.loginBtnLoadingFlag = true // 登录按钮显示加载loading
that.Login({ loginParams: loginParams, isSaveStorage: that.isAutoLogin }) // 打开自动登录将保存在localStorage中,否则保存在内存中。
.then((res) => {
this.loginSuccess(res)
})
.catch(err => {
that.showLoginErrorInfo = (err.msg || JSON.stringify(err))
that.loginBtnLoadingFlag = false
})
}
})
},
loginSuccess (res) {
this.$router.push({ path: '/' })
// 延迟 1 秒显示欢迎信息
setTimeout(() => {
this.$notification.success({
message: '欢迎',
description: `${timeFix()},欢迎回来`
})
}, 1000)
this.showLoginErrorInfo = ''
},
refVercode () { // 刷新图片验证码
const that = this
// 获取图形验证码
vercode().then(res => {
that.vercodeImgSrc = res.imageBase64Data
that.vercodeToken = res.vercodeToken
this.isOverdue = false
if (this.timer) clearInterval(this.timer) // 如果多次点击则清除已有的定时器
// 超过60秒提示过期刷新
this.timer = setInterval(() => {
res.expireTime--
if (res.expireTime <= 0) {
that.isOverdue = true
clearInterval(this.timer)
}
}, 1000)
})
}
}
}
</script>
<style lang="less" scoped>
.user-layout-login {
label {
font-size: 14px;
}
.forge-password {
font-size: 14px;
color: @jee-theme;
}
button.login-button {
padding: 0 15px;
font-size: 16px;
height: 40px;
width: 100%;
}
.user-login-other {
text-align: left;
margin-top: 24px;
line-height: 22px;
.item-icon {
font-size: 24px;
color: rgba(0, 0, 0, 0.2);
margin-left: 16px;
vertical-align: middle;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #1890ff;
}
}
.register {
float: right;
}
}
.code {
display: flex;
justify-content: space-between;
.code-input {
width: 216px;
}
.code-img {
width: 137px;
height: 40px;
background-color: #ddd;
img{
width: 137px;
height: 40px;
}
}
}
.submit {
margin-top: 50px;
}
}
.vercode-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0.8;
text-align:center;
line-height: 40px;
color:#fff;
&:hover {
cursor: pointer;
}
}
</style>
module.exports = {
env: {
jest: true
}
}
const path = require('path')
const webpack = require('webpack')
const GitRevisionPlugin = require('git-revision-webpack-plugin')
const GitRevision = new GitRevisionPlugin()
const buildDate = JSON.stringify(new Date().toLocaleString())
function resolve (dir) {
return path.join(__dirname, dir)
}
// check Git
function getGitHash () {
try {
return GitRevision.version()
} catch (e) { }
return 'unknown'
}
// const isProd = process.env.NODE_ENV === 'production'
// vue.config.js
const vueConfig = {
publicPath: process.env.VUE_APP_BASE_URL, // 前端资源访问根目录, 可配置到cdn目录下。 建议使用命令行环境变量进行替换。
configureWebpack: {
// webpack plugins
plugins: [
// Ignore all locale files of moment.js
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.DefinePlugin({
APP_VERSION: `"${require('./package.json').version}"`,
GIT_HASH: JSON.stringify(getGitHash()),
BUILD_DATE: buildDate
})
],
// if prod, add externals
externals: {}
},
chainWebpack: (config) => {
config.resolve.alias
.set('@$', resolve('src'))
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule
.oneOf('inline')
.resourceQuery(/inline/)
.use('vue-svg-icon-loader')
.loader('vue-svg-icon-loader')
.end()
.end()
.oneOf('external')
.use('file-loader')
.loader('file-loader')
.options({
limit: 100000,
name: 'assets/[name].[hash:8].[ext]'
})
},
css: {
loaderOptions: {
less: {
modifyVars: {
'primary-color': '#1a53ff',
'link-color': '#1A79FF',
'border-radius-base': '4px'
},
javascriptEnabled: true
}
}
},
devServer: {
// development server port 8000
port: 8000
// If you want to turn on the proxy, please remove the mockjs /src/main.jsL11
// proxy: {
// '/api': {
// target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro',
// ws: false,
// changeOrigin: true
// }
// }
},
// disable source map in production
productionSourceMap: false,
lintOnSave: undefined,
// babel-loader no-ignore node_modules/*
transpileDependencies: [],
pluginOptions: { // 第三方插件配置
'style-resources-loader': {
preProcessor: 'less',
patterns: [path.resolve(__dirname, 'src/assets/styles/color.less')] // less所在文件路径
}
}
}
module.exports = vueConfig
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment