提交 374e23cf authored 作者: pengxiaohui's avatar pengxiaohui

init

上级 d9129967
VITE_LOGIN_URL=https://login.ezijing.com/auth/login/index
\ No newline at end of file
VITE_LOGIN_URL=https://login2.ezijing.com/auth/login/index
\ No newline at end of file
VITE_LOGIN_URL=https://login2.ezijing.com/auth/login/index
\ No newline at end of file
module.exports = {
env: {
node: true
},
extends: ['plugin:vue/essential', 'standard'],
rules: {
'vue/comment-directive': 'off',
'space-before-function-paren': 'off'
}
}
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9MjUExxOj6tfMUaU+t/k0+VokVkh1GVZACLPdxR2VcjjOkaI
qloY4TK4/uybncLhoqu9ggyiJFNmXVLxe2TxReGXhR8tAc0RTCuaFv0oHJ7HXI48
CZ/zc8sgjyd7XsuyRurKwpHWXfy9FMHE7r/9R+IUHAkIPmZ17qlwqphlePK8I1fn
DU/LOvglXPIlR55TUe6NKVoCPZXQEHHPZClH0qdnXFiUq5I1f6GMUoGCbV5DLECb
OAndCP/snqakw8oeLmoBGSig/FGrc/41l2DJxyIxm/CfaEhfnSN4hvOTAFXcro9c
gyy88H1BfT/bEhM7OO+RRaKUrV5CieeIOEmvGQIDAQABAoIBAGmkcsJ8qPsgPskJ
aSqMjjlU/Lgd+5eq1apVW6xMzHVhaY+w+TJsB+jI90Yt30tK3A5UiEkkIqYCyF7m
eQmEGwzJu5bcSZRJaHmzJ6FcSH9xlyC+0fJlcbA7riWaKIhU6O/qTO+D+Tw+42ud
5NwVR75KN9uRmlkz5xnFTraRZtm3MJmA7dwXK3hrN+dFJR2vLO3KBAtpgtpPdkK1
ObpJQ1Q7jsnEmODVRZ7n1CKZEDmXd8GBPA/jCVqgiEbVVCdkhHkyxyIMQenBReyy
tJIPf7CdL3O3PPsThhMa1P2CP/xehS4bcQSLw9wtNTJcvVPHTvffHKOKUfhUxkHu
0cpl+zECgYEA/jzySW/br7W+xS2e4VBHzY+UZJwxd/3mY3d/kasMV1zuipr6WOhQ
FVsd5uJXPRr+rHBypwyOIlP205V2K5oQEK0yT+tF+IBvKGdJv89wskCgrXcD3Kfb
dCFbt014pHw89A8jb8LBbGOPH6jhZhGkxP33CJdVPtncUc4m0hj4HHcCgYEA9nsc
KcCZOIYRlZmJ93DoukhjxaouGFDTOZoujaqasrfXUaWRnpZYekDZWa1NneOzLBEz
h1RwPcmeYLCVRmXtpRzLOKXfJY0gGSJr979I0AVkzj8A9NZcU/HxUP0GqpwBbzAp
EEShQVhjYppQ62KAwZ1tbsVWX2V1SBsa3McExO8CgYEA6kVy5aTDhOgugDeHnguB
/rN9hDBBjVZTQ/jLfolld+NUlDg21FJN6T/rD+Qli1MitfdwTupM1ukUGugw2gC/
KP7Py8D62wBObaav2KXoLPlMlkuDLYMnv501jHVA5CDvcd25Q7Ts01nyerP97zX2
5Oc5CZuZm67ZTDBwqU0E5AUCgYBIC2wL+DPRBb8WDy74mJQt/wLKwBeBG/7hk2OQ
HRHis0HIp7CMvj1WXqYpRDKvt+KjOtPo9pFoPgqBEJxRW3G/FU+BW1qCS2HadulA
HTVXOHxinJ/W8OFD2DBFD/Bm5fq1WUpnaugHhaJnK9wDMWOZND7MZfn9IFbLoMCV
T8bhGQKBgG7qsZhI9ldAqooZQ1xSua/2SBc8GI8d03g7y8kZkkx/XclbEz6X6wUu
U3PVL+neY8Qw3JxC1cHS++KIdHR2ZSoTpF00A4QvDJL0+eo1KgI88vRV8QaWLxPB
ahvXwmkKW2+jgvCAqFtepZx/KsKpQW+x3GOJyhl2tIT8sZwRmE6u
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIFhzCCBG+gAwIBAgIQCzEi4VmynSzbyBV1UEXGojANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
RFYgVExTIENBIC0gRzEwHhcNMjAxMTA5MDAwMDAwWhcNMjExMTA5MjM1OTU5WjAa
MRgwFgYDVQQDEw9kZXYuZXppamluZy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQD0yNQTHE6Pq18xRpT63+TT5WiRWSHUZVkAIs93FHZVyOM6Roiq
WhjhMrj+7JudwuGiq72CDKIkU2ZdUvF7ZPFF4ZeFHy0BzRFMK5oW/SgcnsdcjjwJ
n/NzyyCPJ3tey7JG6srCkdZd/L0UwcTuv/1H4hQcCQg+ZnXuqXCqmGV48rwjV+cN
T8s6+CVc8iVHnlNR7o0pWgI9ldAQcc9kKUfSp2dcWJSrkjV/oYxSgYJtXkMsQJs4
Cd0I/+yepqTDyh4uagEZKKD8Uatz/jWXYMnHIjGb8J9oSF+dI3iG85MAVdyuj1yD
LLzwfUF9P9sSEzs475FFopStXkKJ54g4Sa8ZAgMBAAGjggJzMIICbzAfBgNVHSME
GDAWgBRVdE+yck/1YLpQ0dfmUVyaAYca1zAdBgNVHQ4EFgQUkRHkmubxZAvEWtCY
IBT9sw/3Yb8wGgYDVR0RBBMwEYIPZGV2LmV6aWppbmcuY29tMA4GA1UdDwEB/wQE
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTAYDVR0gBEUwQzA3
BglghkgBhv1sAQIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
Y29tL0NQUzAIBgZngQwBAgEwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx
LmNydDAJBgNVHRMEAjAAMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYA9lyUL9F3
MCIUVBgIMJRWjuNNExkzv98MLyALzE7xZOMAAAF1qpa6jQAABAMARzBFAiAwHOcp
Ua1H0WK4OZUHiQ1rndqnYxPHhP9XWunwpRMoagIhAOB2MPSW9M4qj6Yih7eQkydl
lgawpoBZzRzhisU+TN67AHYAXNxDkv7mq0VEsV6a1FbmEDf71fpH3KFzlLJe5vbH
DsoAAAF1qpa63gAABAMARzBFAiEA92ZeW0PgyWW3j+3wypLS0O/wI63C+x0WTvMZ
Vngp6AMCIBoThjaKif+XY11YbaV89ndqs1nDlzbEfBrFftoB9fchMA0GCSqGSIb3
DQEBCwUAA4IBAQA2geo9wQAd+vx+lwAafVRxCBQyBiS0qT413ewYpZYDnSkLX0l1
5kRdxDGWQhPzOio0ckj/jOtOlbbSsiovBBVTyYPB8WfkNjMd0psMNx2e6Wy/WKkQ
X3DqEOB4XGg0RwpebiAmz6lWxyFwIAbCrwCntkkaIF4LnIvczn6pvPFBtK2nXJJC
HL0Igbxo+xJLt3Hql7TcwkFDXz/LIB8AwhhkkhhwW45r3Eyjw8eOyzvflDPwSNH+
ByadQ+AH4H4vYYVo0ILNIPCdaokLQ+u4FttB9VQ+iGmpJ56Yg2muxWh8Qckca+vH
40RbC5aK1RSy2RIRpC5fwvq2JuV/CksP5G5Q
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
-----END CERTIFICATE-----
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const OSS = require('ali-oss')
const log = console.log
const client = new OSS({
region: 'oss-cn-beijing',
accessKeyId: 'LTAIOTuuLTaWoGJj',
accessKeySecret: 'dE5tTGm2lh35eItct2krW2DeH2lf2I',
bucket: 'webapp-pub'
})
async function uploadTarget(src, dist) {
try {
const result = await client.put(dist, path.join(__dirname, src))
log(chalk.green('上传成功', result.url))
} catch (e) {
log(chalk.red('上传失败', src))
log(e)
}
}
function generateUploadTarget(src, dist) {
fs.readdir(path.join(__dirname, src), function (err, files) {
if (err) {
log(err)
return
}
files.forEach(function (file) {
const _src = src + '/' + file
const _dist = dist + '/' + file
const stats = fs.statSync(path.join(__dirname, _src))
// 判断是否为文件
stats.isFile() && uploadTarget(_src, _dist)
// 判断是否为文件夹
stats.isDirectory() && generateUploadTarget(_src, _dist)
})
})
}
generateUploadTarget('./dist', '/website/prod/accounts')
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const OSS = require('ali-oss')
const log = console.log
const client = new OSS({
region: 'oss-cn-beijing',
accessKeyId: 'LTAIOTuuLTaWoGJj',
accessKeySecret: 'dE5tTGm2lh35eItct2krW2DeH2lf2I',
bucket: 'webapp-pub'
})
async function uploadTarget(src, dist) {
try {
const result = await client.put(dist, path.join(__dirname, src))
log(chalk.green('上传成功', result.url))
} catch (e) {
log(chalk.red('上传失败', src))
log(e)
}
}
function generateUploadTarget(src, dist) {
fs.readdir(path.join(__dirname, src), function (err, files) {
if (err) {
log(err)
return
}
files.forEach(function (file) {
const _src = src + '/' + file
const _dist = dist + '/' + file
const stats = fs.statSync(path.join(__dirname, _src))
// 判断是否为文件
stats.isFile() && uploadTarget(_src, _dist)
// 判断是否为文件夹
stats.isDirectory() && generateUploadTarget(_src, _dist)
})
})
}
generateUploadTarget('../dist', '/website/prod/marketing-admin')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="https://zws-imgs-pub.ezijing.com/pc/base/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>大客户管理系统-紫荆教育</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"version": "0.0.0",
"config": {},
"scripts": {
"dev": "vite --mode dev",
"build": "cross-env BUILD_ENV=prod vite build && cross-env BUILD_ENV=prod npm run deploy",
"build:pre": "vite build",
"build:test": "vite build --mode test",
"preview": "vite preview",
"deploy": "node ./deploy/oss.js"
},
"dependencies": {
"axios": "^0.21.1",
"element-ui": "^2.15.5",
"query-string": "^7.0.1",
"vue": "^2.6.14",
"vue-router": "^3.5.2",
"vuex": "^3.6.2"
},
"devDependencies": {
"ali-oss": "^6.16.0",
"chalk": "^4.1.2",
"cross-env": "^7.0.3",
"sass": "^1.37.5",
"vite": "^2.4.4",
"vite-plugin-vue2": "^1.8.0",
"vue-template-compiler": "^2.6.14"
}
}
<template>
<div id="app">
<router-view />
</div>
</template>
import httpRequest from '@/utils/axios'
// 获取用户信息
export function getUser() {
return httpRequest.get('/api/passport/account/get-user-info')
}
// 退出登录
export function logout() {
return httpRequest.get('/api/passport/rest/logout')
}
// 获取oss token
export function getToken() {
return httpRequest.get('/api/usercenter/aliyun/assume-role')
}
// 获取oss signature
export function getSignature() {
return httpRequest.get('/api/usercenter/aliyun/get-signature')
}
// 图片上传
export function uploadFile(data) {
return httpRequest.post('https://webapp-pub.oss-cn-beijing.aliyuncs.com', data, {
withCredentials: false,
headers: { 'Content-Type': 'multipart/form-data' }
})
}
* {
margin: 0;
padding: 0;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
<template>
<div class="app-card">
<div class="app-card-hd">
<slot name="header">
<h2 class="app-card-hd__title" v-if="title">{{ title }}</h2>
<slot name="header-aside"></slot>
</slot>
</div>
<div class="app-card-bd">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'AppCard',
props: { title: String }
}
</script>
<style>
.app-card {
background: #ffffff;
box-shadow: 0 1px 6px 0 rgb(228 232 235 / 20%);
border-radius: 8px;
margin-bottom: 20px;
padding: 32px;
}
.app-card-hd {
display: flex;
}
.app-card-hd__title {
flex: 1;
font-size: 18px;
font-weight: 700;
margin-bottom: 16px;
}
</style>
<template>
<div class="table-list">
<div class="table-list-hd">
<!-- 筛选 -->
<div class="table-list-filter" v-if="filters.length">
<el-form :inline="true" :model="params" ref="filterForm">
<template v-for="item in filters">
<el-form-item :label="item.label" :prop="item.prop" :key="item.prop">
<template v-if="item.slots">
<slot :name="item.slots" v-bind="{ params }"></slot>
</template>
<template v-else>
<!-- input -->
<el-input v-model="params[item.prop]" v-bind="item" clearable v-if="item.type === 'input'" />
<!-- select -->
<el-select
v-model="params[item.prop]"
clearable
v-bind="item"
v-if="item.type === 'select'"
@change="search"
>
<template v-for="(option, index) in item.options">
<el-option
:label="option[item.labelKey] || option.label"
:value="option[item.valueKey] || option.value"
:key="index"
></el-option>
</template>
</el-select>
</template>
</el-form-item>
</template>
<el-form-item class="filter-buttons">
<el-button type="primary" icon="el-icon-search" @click="search">搜索</el-button>
<el-button icon="el-icon-refresh-left" @click="reset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="table-list-hd-aside"><slot name="header-aside" /></div>
</div>
<slot></slot>
<div class="table-list-bd">
<slot name="body" v-bind="{ data: dataList }">
<el-table
:data="dataList"
v-loading="loading"
v-bind="$attrs"
v-on="$listeners"
style="height: 100%"
ref="table"
>
<template v-for="item in columns">
<el-table-column v-bind="item" :key="item.prop" v-if="visible(item)">
<template v-slot:default="scope" v-if="item.slots || item.computed">
<slot :name="item.slots" v-bind="scope" v-if="item.slots"></slot>
<div v-html="item.computed(scope)" v-if="item.computed"></div>
</template>
</el-table-column>
</template>
</el-table>
</slot>
</div>
<div class="table-list-ft">
<div>
<slot name="footer"></slot>
</div>
<el-pagination
class="table-list-pagination"
layout="total, prev, pager, next, sizes, jumper"
:page-sizes="[10, 20, 30, 50, 100]"
:page-size="page.size"
:total="page.total"
:current-page.sync="page.currentPage"
@size-change="pageSizeChange"
@current-change="fetchList()"
v-if="hasPagination"
>
</el-pagination>
</div>
</div>
</template>
<script>
export default {
name: 'AppList',
props: {
// 接口请求
remote: { type: Object, default: () => ({}) },
// 筛选
filters: { type: Array, default: () => [] },
// 列表项
columns: { type: Array, default: () => [] },
// 列表数据
data: { type: Array, default: () => [] },
// 是否含有翻页
hasPagination: { type: Boolean, default: true },
// 每页多少条数据
limit: { type: Number, default: 20 }
},
data() {
return {
loading: false,
params: this.remote.params || {},
dataList: this.data,
page: { total: 0, size: this.limit, currentPage: 1 }
}
},
watch: {
'remote.params': {
immediate: true,
handler(data) {
this.params = data || {}
}
},
data: {
immediate: true,
handler(data) {
this.dataList = data
}
}
},
computed: {
table() {
return this.$refs.table
}
},
methods: {
fetchList(isReset) {
/**
* @param function httpRequest api接口
* @param function beforeRequest 接口请求之前
* @param function callback 接口请求成功回调
*/
const { httpRequest, beforeRequest, callback } = this.remote
if (!httpRequest) {
return
}
// 参数设置
let params = this.params
// 翻页参数设置
if (this.hasPagination) {
params.page = (this.page.currentPage - 1).toString()
params.page_size = this.page.size.toString()
}
// 接口请求之前
if (beforeRequest) {
params = beforeRequest(params, isReset)
}
// for (const key in params) {
// if (params[key] === '' || params[key] === undefined || params[key] === undefined) {
// delete params[key]
// }
// }
this.loading = true
httpRequest(params)
.then(res => {
const { data = [], page_info: pageInfo = {} } = res || {}
this.page.total = parseInt(pageInfo.total_number || '')
this.dataList = callback ? callback(data) : data
})
.catch(() => {
this.page.total = 0
this.dataList = []
})
.finally(() => {
this.loading = false
})
},
// 搜索
search() {
this.page.currentPage = 1
this.fetchList()
},
// 重置
reset() {
// 清空筛选条件
this.$refs.filterForm && this.$refs.filterForm.resetFields()
// 初始化页码
this.page.currentPage = 1
// 刷新列表
this.fetchList(true)
},
// 刷新
refetch(isForce) {
isForce ? this.reset() : this.fetchList()
},
// 页数改变
pageSizeChange(value) {
this.page.currentPage = 1
this.page.size = value
this.fetchList()
},
visible(item) {
return Object.prototype.hasOwnProperty.call(item, 'visible') ? item.visible : true
}
},
beforeMount() {
this.fetchList()
}
}
</script>
<style lang="scss">
.table-list {
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.table-list-hd {
display: flex;
}
.table-list-filter {
flex: 1;
}
.table-list-bd {
flex: 1;
}
.table-list-ft {
display: flex;
align-items: center;
justify-content: space-between;
}
.table-list-pagination {
padding: 10px 0;
text-align: right;
}
.el-table-column--selection .cell {
padding: 0 14px !important;
}
</style>
<template>
<aside class="app-aside">
<nav class="nav">
<el-menu :default-active="defaultActive" :router="true">
<template v-for="item in menuList">
<el-submenu :index="item.path" :key="item.path" v-if="item.children">
<template #title><i :class="item.icon"></i>{{ item.name }}</template>
<el-menu-item :index="subitem.path" v-for="subitem in item.children" :key="subitem.path">
{{ subitem.name }}
</el-menu-item>
</el-submenu>
<el-menu-item :index="item.path" :key="item.path" v-else>
<i :class="item.icon"></i>{{ item.name }}
</el-menu-item>
</template>
</el-menu>
</nav>
</aside>
</template>
<script>
export default {
name: 'AppAside',
data() {
return {
menuList: [
{
name: '大客户管理',
path: '/customer-manage',
icon: 'el-icon-user'
},
{
name: '系统管理',
path: '/system/customer-group',
icon: 'el-icon-setting',
children: [{ name: '客户组管理', path: '/system/customer-group' }]
}
]
}
},
computed: {
defaultActive() {
return this.$route.path
}
}
}
</script>
<style lang="scss">
.app-aside {
position: sticky;
top: 0;
width: 240px;
z-index: 100;
background: #fff;
border-right: 1px solid rgba(0, 0, 0, 0.12);
overflow-x: hidden;
overflow-y: auto;
}
.nav {
margin: 20px 0;
.el-menu {
border-right: 0;
i {
margin-right: 14px;
font-size: 24px;
}
.el-icon-arrow-down {
margin-right: 0;
font-size: 16px;
}
}
.el-menu-item {
display: flex;
align-items: center;
margin: 0 16px;
font-size: 16px;
border-radius: 8px;
}
.el-submenu .el-menu-item {
min-width: auto;
padding-left: 58px !important;
}
.el-submenu__title {
display: flex;
align-items: center;
margin: 0 16px;
font-size: 16px;
border-radius: 8px;
&:hover {
background-color: rgba(86, 100, 210, 0.04);
}
}
}
</style>
<template>
<div class="app-breadcrumb" v-if="routes.length">
<el-breadcrumb>
<el-breadcrumb-item v-for="route in routes" :key="route.path">
<router-link :to="route.path">{{ route.meta.title }}</router-link>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
name: 'AppBreadcrumb',
computed: {
routes() {
return this.$route.matched.filter(route => route.meta.title)
}
}
}
</script>
<style lang="scss">
.app-breadcrumb {
padding: 6px 0 16px;
.el-breadcrumb {
font-size:20px;
font-weight: 400;
line-height: 24px;
}
.el-breadcrumb__inner a {
font-weight: normal;
// color: #1a1b1c;
}
.router-link-exact-active {
color: #5b91fd !important;
font-size: 24px;
}
}
</style>
<template>
<header class="app-header">
<div class="logo">
<router-link to="/"><img src="https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo-white.svg" /></router-link>
</div>
<div class="app-header-right">
<el-dropdown>
<div class="avatar">
<img :src="user.avatar || 'https://zws-imgs-pub.ezijing.com/pc/base/logo.png'" />
</div>
<el-dropdown-menu slot="dropdown">
<div class="user">
<div class="user-name">{{ user.realname || user.nickname }}</div>
<el-button round size="medium" class="btn-logout" @click="logout">退出登录</el-button>
</div>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
</template>
<script>
export default {
name: 'AppHeader',
computed: {
user() {
return this.$store.state.user
}
},
methods: {
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
})
}
}
}
</script>
<style lang="scss">
.app-header {
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
background-color: #3276fc;
color: #fff;
.logo {
width: 120px;
}
}
.app-header-right {
display: flex;
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
overflow: hidden;
}
}
}
.user {
padding: 16px;
}
.user-name {
margin-bottom: 20px;
}
.btn-logout {
width: 200px;
}
</style>
\ No newline at end of file
<template>
<div class="app-layout">
<app-header></app-header>
<div class="app-layout-container">
<app-aside></app-aside>
<app-main></app-main>
</div>
</div>
</template>
<script>
import AppHeader from './Header.vue'
import AppAside from './Aside.vue'
import AppMain from './Main.vue'
export default {
name: 'AppLayout',
components: { AppHeader, AppAside, AppMain }
}
</script>
<style>
.app-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #f4f5f7;
}
.app-layout-container {
flex: 1;
display: flex;
}
</style>
\ No newline at end of file
<template>
<section class="app-main">
<div class="app-main-inner">
<div class="app-main-header">
<app-breadcrumb v-if="hasBreadcrumb"></app-breadcrumb>
</div>
<div class="app-main-container">
<router-view></router-view>
</div>
</div>
</section>
</template>
<script>
import AppBreadcrumb from './Breadcrumb.vue'
export default {
name: 'AppMain',
props: { hasBreadcrumb: { type: Boolean, default: true } },
components: { AppBreadcrumb }
}
</script>
<style>
.app-main {
position: relative;
flex: 1;
padding: 16px;
}
.app-main-inner {
margin: 0 auto;
}
.app-main-container::after {
content: '';
display: table;
clear: both;
}
.el-form--label-top .el-form-item__label {
padding-bottom: 0;
}
</style>
<template>
<editor :init="init" v-bind="$attrs" v-on="$listeners" @onChange="onChange" @onBlur="onBlur" />
</template>
<script>
import Editor from '@tinymce/tinymce-vue'
import ImageUpload from './imageUpload'
export default {
components: {
editor: Editor
},
data() {
return {
init: {
min_height: 600,
max_height: 600,
menubar: false,
statusbar: false,
plugins: 'table autoresize charmap fullscreen hr lists link code preview quickbars',
toolbar:
'undo redo | fontsizeselect lineheight bold italic underline strikethrough forecolor backcolor | link quickimage image media table | align hangingindent indent outdent numlist bullist | charmap blockquote hr fullscreen | code preview',
// font_formats:
// '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Times New Roman',
fontsize_formats: '8px 10px 12px 14px 15px 16px 17px 18px 20px 24px',
lineheight_formats: '0.5 1 1.2 1.5 2',
images_upload_handler: ImageUpload,
automatic_uploads: true,
quickbars_insert_toolbar: false,
// style_formats: [{ title: '悬挂缩进', block: 'p', styles: { textIndent: '-2em', paddingLeft: '2em' } }],
content_style: 'img {max-width:100%;}'
}
}
},
methods: {
onChange(event, editor) {
this.dispatch('ElFormItem', 'el.form.change', editor.getContent())
},
onBlur(event, editor) {
this.dispatch('ElFormItem', 'el.form.blur', editor.getContent())
},
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root
var name = parent.$options.componentName
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.componentName
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
}
}
}
</script>
<style>
.tox .tox-tbtn--bespoke .tox-tbtn__select-label {
width: 4em !important;
}
</style>
import { getSignature, uploadFile } from '@/api/base'
import md5 from 'blueimp-md5'
export default function (blobInfo, succFun, failFun) {
const file = blobInfo.blob()
getSignature()
.then(response => {
const prefix = 'upload/admin/'
const fileName = file.name
const key = prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
const { accessid, policy, signature, host } = response
const data = { key, OSSAccessKeyId: accessid, policy, signature, success_action_status: '200', file }
const fileUrl = `${host}/${key}`
uploadFile(data)
.then(() => {
succFun(fileUrl)
})
.catch(() => {
failFun('上传失败')
})
})
.catch(response => {
failFun('获取Signature失败')
})
}
<template>
<div class="app-upload">
<draggable
v-model="fileList"
group="people"
class="file-list el-upload-list--picture-card"
@end="onEnd"
v-if="showFileList"
>
<div class="file-item" v-for="(file, index) in fileList" :key="index">
<!-- <el-image style="width: 100%; height: 100%" :src="file.url" :preview-src-list="fileList.map(item => item.url)"> -->
<img :src="file.url" />
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-preview" @click="handlePreview(file)">
<i class="el-icon-zoom-in"></i>
</span>
<span v-if="!$attrs.disabled" class="el-upload-list__item-delete" @click="handleRemove(index)">
<i class="el-icon-delete"></i>
</span>
</span>
</div>
</draggable>
<el-upload
action="https://webapp-pub.oss-cn-beijing.aliyuncs.com"
type="drag"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:show-file-list="false"
:on-exceed="handleExceed"
:data="data"
v-bind="$attrs"
v-on="$listeners"
style="display: inline"
>
<slot>
<i class="el-icon-plus"></i>
</slot>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="previewFile.url" />
</el-dialog>
</div>
</template>
<script>
import { getSignature } from '@/api/base'
import md5 from 'blueimp-md5'
import draggable from 'vuedraggable'
export default {
name: 'AppUpload',
components: { draggable },
props: {
value: { type: [String, Array] },
prefix: { type: String, default: 'upload/shop-admin/' },
showFileList: { type: Boolean, default: true }
},
data() {
return {
data: {}, // 请求参数
fileList: [], // 文件列表
dialogVisible: false,
previewFile: {},
message: null
}
},
watch: {
value: {
immediate: true,
handler(value) {
if (!value) {
return
}
let fileList = []
if (this.isMultiple) {
fileList = value.map(item => {
return { name: item.name || item, url: item.url || item }
})
} else {
fileList.push({ name: '附件', url: value })
}
this.fileList = [...fileList]
}
}
},
computed: {
isMultiple() {
return Array.isArray(this.value)
}
},
methods: {
beforeUpload(file) {
const limit = this.$attrs.limit
if (limit && this.fileList.length >= limit) {
this.message && this.message.close()
this.message = this.$message({ type: 'error', message: '文件超出个数限制' })
return false
}
const fileName = file.name
const key = this.prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
return new Promise((resolve, reject) => {
getSignature()
.then(response => {
const { accessid, policy, signature, host } = response
this.data = { key, OSSAccessKeyId: accessid, policy, signature, success_action_status: '200' }
file.url = `${host}/${key}`
resolve(true)
})
.catch(err => {
console.log(err)
reject(err)
})
})
},
handleSuccess(response, file) {
let value = null
if (this.isMultiple) {
const limit = this.$attrs.limit
if (limit && this.fileList.length >= limit) {
return
}
this.fileList.push({ name: file.name, url: file.raw.url })
value = this.fileList
} else {
this.fileList = [file.raw.url]
value = file.raw.url
}
this.$emit('input', value)
this.dispatch('ElFormItem', 'el.form.change', value)
},
// 删除
handleRemove(index) {
this.fileList.splice(index, 1)
this.$emit('input', this.fileList)
this.dispatch('ElFormItem', 'el.form.change', this.fileList)
},
// 预览
handlePreview(file) {
this.previewFile = file
this.dialogVisible = true
},
handleExceed() {
this.$message({ type: 'error', message: '文件超出个数限制' })
},
onEnd() {
this.$emit('input', this.fileList)
},
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root
var name = parent.$options.componentName
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.componentName
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
}
}
}
</script>
<style lang="scss">
.el-upload-list--picture-card .el-upload-list__item-thumbnail {
object-fit: cover;
}
.el-upload-list__item {
transition: none !important;
}
.file-list {
margin: 0;
display: inline;
vertical-align: top;
}
.file-item {
position: relative;
overflow: hidden;
background-color: #fff;
border: 1px solid #c0ccda;
border-radius: 6px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 148px;
height: 148px;
margin: 0 8px 8px 0;
display: inline-block;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>
<template>
<app-upload class="avatar-uploader" accept="image/*" :show-file-list="false" v-bind="$attrs" v-on="$listeners">
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</app-upload>
</template>
<script>
import AppUpload from './Upload.vue'
export default {
name: 'AppUploadImage',
components: { AppUpload },
data() {
return {}
},
computed: {
imageUrl() {
return this.$attrs.value
}
}
}
</script>
<style lang="scss">
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 148px;
height: 148px;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
line-height: 148px;
text-align: center;
}
.avatar {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
</style>
<template>
<app-upload class="video-uploader" accept="video/*" :show-file-list="false" v-bind="$attrs" v-on="$listeners">
<img v-if="videoUrl" :src="`${videoUrl}?x-oss-process=video/snapshot,t_10,f_jpg,w_0,h_0,m_fast`" class="avatar" />
<i v-else class="el-icon-plus video-uploader-icon"></i>
</app-upload>
</template>
<script>
import AppUpload from './Upload.vue'
export default {
name: 'AppUploadVideo',
components: { AppUpload },
data() {
return {}
},
computed: {
videoUrl() {
return this.$attrs.value
}
}
}
</script>
<style lang="scss">
.video-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 148px;
height: 148px;
}
.video-uploader .el-upload:hover {
border-color: #409eff;
}
.video-uploader-icon {
font-size: 28px;
color: #8c939d;
line-height: 148px;
text-align: center;
}
.avatar {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
</style>
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import modules from './modules'
import beforeEnter from './utils/beforeEnter'
// 公共css
import '@/assets/css/base.css'
// 注册模块
modules({ router, store })
// Element-UI
import '@/assets/theme/index.css'
import ElementUI from 'element-ui'
Vue.use(ElementUI)
// 路由钩子函数
router.beforeEach(beforeEnter)
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
import httpRequest from '@/utils/axios'
// 获取用户信息
export function getUser() {
return httpRequest.get('/api/passport/account/get-user-info')
}
// const routes = [
// {
// path: '/customer-manage',
// component: () => import('./views/Index.vue')
// }
// ]
const routes = [
{
path: '/customer-manage',
redirect: '/customer-manage',
component: () => import('@/components/layout/Index.vue'),
meta: { title: '大客户管理' },
children: [
{
path: '',
component: () => import('./views/Index.vue'),
meta: { title: '' }
}
]
}
]
export { routes }
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template #header-aside>
<el-button type="primary">创建客户</el-button>
</template>
<!-- 筛选 -->
<template v-slot:filter-date="{ params }">
<el-date-picker
v-model="params.date"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期时间"
end-placeholder="结束日期时间"
>
</el-date-picker>
</template>
<!-- 操作 -->
<template v-slot:table-operate="{ row }">
<template>
<el-button type="text" @click="handleDetails(row)" size="mini">查看</el-button>
<el-button type="text" @click="handleDelete(row)" size="mini">删除</el-button>
</template>
</template>
</app-list>
</app-card>
</template>
<script>
// 组件
import AppList from '@/components/base/AppList.vue'
import AppCard from '@/components/base/AppCard.vue'
// 接口
// import { getRoleList } from '../api'
export default {
components: { AppCard, AppList },
data() {
return {}
},
computed: {
// 列表配置
tableOptions() {
return {
// remote: {
// httpRequest: getRoleList,
// params: { },
// },
filters: [
{
type: 'input',
prop: 'name',
placeholder: '请输入活动名'
},
{ prop: 'date', slots: 'filter-date' }
],
data: [
{
name: '北京大学',
source: '公司资源',
category: '普通客户',
address: '北京市-北京市-海淀区',
time: '2021-12-12 12:12',
project: '2',
contacts: '2',
creator: '员工A',
created_time: '2021-12-12 12:12'
}
],
columns: [
// { type: 'selection', minWidth: '40px' },
{ prop: 'name', label: '客户名称', minWidth: '120px' },
{ prop: 'source', label: '客户来源', minWidth: '80px' },
{ prop: 'category', label: '客户分类', minWidth: '150px' },
{ prop: 'address', label: '所在地区', minWidth: '150px' },
{ prop: 'time', label: '最近跟进时间', minWidth: '140px' },
{ prop: 'project', label: '合作项目', minWidth: '140px' },
{ prop: 'contacts', label: '联系人', minWidth: '140px' },
{ prop: 'creator', label: '创建员工', minWidth: '140px' },
{ prop: 'created_time', label: '创建时间', minWidth: '140px' },
{ label: '操作', minWidth: '140px', slots: 'table-operate' }
]
}
}
},
methods: {
// 查看
handleDetails(row) {
console.log(row)
},
// 删除弹窗确认
handleRemove(data) {
this.$confirm('角色删除请谨慎操作,确定删除?', '删除角色', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(this.handleRemove(data))
},
// 删除请求
fetchRemove(data) {
console.log(data)
}
}
}
</script>
<style lang="scss" scoped>
.sign-item {
margin-bottom: 20px;
border: 1px solid #ebeef5;
}
.sign-item-hd {
display: flex;
padding: 14px;
border-bottom: 1px solid #ebeef5;
}
.sign-item-bd {
display: flex;
align-items: center;
padding: 14px;
}
.sign-item-bd-content {
flex: 1;
padding: 0 14px;
p {
line-height: 1.4;
}
}
</style>
const modules = Object.values(import.meta.globEager('./**/index.js'))
export default function ({ router }) {
modules.forEach(({ routes }) => {
// 注册路由
routes.forEach(route => {
router.addRoute(route)
})
})
}
import httpRequest from '@/utils/axios'
.app-card-item {
position: relative;
margin-left: 24px;
display: flex;
align-items: center;
padding: 16px 24px 16px 0;
border-top: 1px solid #dadce0;
cursor: pointer;
&:hover {
margin-left: 0;
padding-left: 24px;
background-color: rgba(128, 134, 139, 0.04);
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1px;
border-top: 1px solid #dadce0;
}
}
}
.app-card-item-label {
flex-basis: 156px;
margin-right: 24px;
font-weight: 500;
color: #5f6368;
}
.app-card-item-content {
flex: 1;
}
.avatar {
position: relative;
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cover {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 20px;
background-color: rgba(32, 33, 36, 0.6);
color: #fff;
text-align: center;
}
}
.avatar-dialog-body {
text-align: center;
::v-deep .el-upload {
width: 288px;
height: 288px;
border-radius: 50%;
}
::v-deep .avatar-uploader-icon {
font-size: 40px;
line-height: 288px;
}
h2 {
margin-top: 24px;
line-height: 2rem;
font-size: 1.5rem;
letter-spacing: 0;
font-weight: 400;
}
p {
margin-top: 4px;
line-height: 1.5rem;
font-size: 1rem;
letter-spacing: 0.00625em;
font-weight: 400;
}
}
.avatar-dialog-footer {
display: flex;
::v-deep .el-button {
flex: 1;
}
}
::v-deep .el-dialog {
width: 90%;
max-width: 490px;
border-radius: 8px;
}
::v-deep .el-dialog__headerbtn {
font-size: 24px;
}
::v-deep .el-dialog__headerbtn .el-dialog__close {
color: #3c4043;
}
.dialog-footer {
text-align: right;
}
const routes = [
{
path: '/system',
redirect: '/system/customer-group',
component: () => import('@/components/layout/Index.vue'),
meta: { title: '系统管理' },
children: [
{
path: 'customer-group',
component: () => import('./views/Index.vue'),
meta: { title: '客户组管理' },
}
]
}
]
export { routes }
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template #header-aside>
<el-button type="primary">新建客户组</el-button>
</template>
<!-- 筛选 -->
<template v-slot:filter-date="{ params }">
<el-date-picker
v-model="params.date"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期时间"
end-placeholder="结束日期时间"
>
</el-date-picker>
</template>
<!-- 操作 -->
<template v-slot:table-operate="{ row }">
<template>
<el-button type="text" @click="handleDetails(row)" size="mini">查看</el-button>
<el-button type="text" @click="handleEdit(row)" size="mini">编辑</el-button>
<el-button type="text" @click="handleDelete(row)" size="mini">删除</el-button>
</template>
</template>
</app-list>
</app-card>
</template>
<script>
// 组件
import AppList from '@/components/base/AppList.vue'
import AppCard from '@/components/base/AppCard.vue'
// 接口
// import { getRoleList } from '../api'
export default {
components: { AppCard, AppList },
data() {
return {}
},
computed: {
// 列表配置
tableOptions() {
return {
// remote: {
// httpRequest: getRoleList,
// params: { },
// },
filters: [
{
type: 'input',
prop: 'name',
placeholder: '请输入活动名'
},
{ prop: 'date', slots: 'filter-date' }
],
data: [
{
name: '1+x客户组',
customer_num: 2,
staff_num: 2,
creator: '员工A',
created_time: '2021-12-12 12:12'
}
],
columns: [
// { type: 'selection', minWidth: '40px' },
{ prop: 'name', label: '客户名称', minWidth: '120px' },
{ prop: 'customer_num', label: '合作项目', minWidth: '140px' },
{ prop: 'staff_num', label: '联系人', minWidth: '140px' },
{ prop: 'creator', label: '创建员工', minWidth: '140px' },
{ prop: 'created_time', label: '创建时间', minWidth: '140px' },
{ label: '操作', minWidth: '140px', slots: 'table-operate' }
]
}
}
},
methods: {
// 查看
handleDetails(row) {
console.log(row)
},
// 编辑
handleEdit(row) {
console.log(row)
},
// 删除弹窗确认
handleRemove(data) {
this.$confirm('角色删除请谨慎操作,确定删除?', '删除角色', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(this.handleRemove(data))
},
// 删除请求
fetchRemove(data) {
console.log(data)
}
}
}
</script>
<style lang="scss" scoped>
.sign-item {
margin-bottom: 20px;
border: 1px solid #ebeef5;
}
.sign-item-hd {
display: flex;
padding: 14px;
border-bottom: 1px solid #ebeef5;
}
.sign-item-bd {
display: flex;
align-items: center;
padding: 14px;
}
.sign-item-bd-content {
flex: 1;
padding: 0 14px;
p {
line-height: 1.4;
}
}
</style>
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [{ path: '*', redirect: '/customer-manage' }]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
import Vue from 'vue'
import Vuex from 'vuex'
import { getUser, logout } from '@/api/base'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: {}
},
mutations: {
setUser(state, user) {
state.user = user
}
},
actions: {
// 获取用户信息
getUser({ commit }) {
getUser().then(response => {
commit('setUser', response)
})
},
// 退出登录
logout({ commit }) {
return logout().then(response => {
commit('setUser', {})
return response
})
},
async checkLogin({ commit }) {
const isLogin = await getUser()
.then(response => {
if (response.code === 0) {
commit('setUser', response.data)
return true
} else {
commit('setUser', {})
return false
}
})
.catch(() => {
commit('setUser', {})
return false
})
return isLogin
}
}
})
import axios from 'axios'
import queryString from 'query-string'
import { Message } from 'element-ui'
const httpRequest = axios.create({
timeout: 60000,
withCredentials: true
})
// 请求拦截
httpRequest.interceptors.request.use(
function (config) {
if (config.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
config.data = queryString.stringify(config.data)
}
if (config.headers['Content-Type'] === 'multipart/form-data') {
const formData = new window.FormData()
for (const key in config.data) {
formData.append(key, config.data[key])
}
config.data = formData
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截
httpRequest.interceptors.response.use(
function (response) {
const { data } = response
if (data.code) {
return Promise.reject(data)
}
return data
},
function (error) {
if (error.response) {
const { status, message } = error.response.data
// 未登录
if (status === 403) {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
} else {
Message.error(message || error.response.data)
}
return Promise.reject(error.response)
} else {
console.log(error)
}
return Promise.reject(error)
}
)
export default httpRequest
import store from '@/store'
const UA = navigator.userAgent
const isMobile = /iphone/i.test(UA) || (/android/i.test(UA) && /mobile/i.test(UA))
const isIe = window.ActiveXObject || 'ActiveXObject' in window
const isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)
// let chromeVersion = 0
// if (isChrome) {
// const temp = UA.match(/Chrome\/([\d.]+)/)
// chromeVersion = temp ? parseFloat(temp[1]) : 0
// }
// const notSupport = !isMobile && (isIe || (isChrome && chromeVersion < 70))
const notSupport = !isMobile && isIe
// https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
export default async function (to, from, next) {
// 浏览器不支持
if (notSupport && to.name !== 'browser') {
next({ path: '/browser' })
return
}
// 登录白名单
const whiteList = []
if (whiteList.includes(to.path)) {
next()
return
}
const isLogin = store.state.user.id || (await store.dispatch('checkLogin'))
if (!isLogin) {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
return
}
next()
}
import fs from 'fs'
import path from 'path'
import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
export default defineConfig({
base: process.env.BUILD_ENV === 'prod' ? 'https://webapp-pub.ezijing.com/website/prod/marketing-admin/' : '/',
plugins: [createVuePlugin()],
server: {
open: true,
host: 'dev.ezijing.com',
https: {
key: fs.readFileSync(path.join(__dirname, './certs/dev.ezijing.com.key')),
cert: fs.readFileSync(path.join(__dirname, './certs/dev.ezijing.com.pem'))
},
proxy: {
'/api': 'https://shop-admin.ezijing.com'
}
},
resolve: {
alias: [
{
find: '@',
replacement: path.resolve(__dirname, 'src')
}
]
}
})
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论