提交 ad28a6bc authored 作者: 王鹏飞's avatar 王鹏飞

update

上级
module.exports = {
domain: 'dev.ezijing.com',
url: 'https://learn-api.ezijing.com/api',
webpack: {
externals: {
CKEDITOR: 'window.CKEDITOR',
Base64: 'window.Base64',
md5: 'window.md5',
regeneratorRuntime: 'window.regeneratorRuntime',
wx: 'window.wx',
WeixinJSBridge: 'window.WeixinJSBridge'
},
devServer: {
proxy: {
'/api/shop': {
target: 'http://172.16.3.11:8089',
changeOrigin: true,
pathRewrite: { '^/api/shop': '' }
}
}
}
},
ProvidePlugin: {},
others: {
baseUrl: 'https://learn-api.ezijing.com',
loginUrl: 'https://login.ezijing.com/auth/login/index'
}
}
module.exports = {
url: '/',
DesDir: './client-dist',
isUploadStatic: false,
webpack: {
externals: {
CKEDITOR: 'window.CKEDITOR',
Base64: 'window.Base64',
md5: 'window.md5',
regeneratorRuntime: 'window.regeneratorRuntime',
wx: 'window.wx',
WeixinJSBridge: 'window.WeixinJSBridge'
}
},
ProvidePlugin: {},
others: {
baseUrl: 'https://learn-api.ezijing.com',
loginUrl: 'https://login.ezijing.com/auth/login/index'
}
}
module.exports = {
url: '/',
DesDir: './client-dist',
isUploadStatic: false,
webpack: {
externals: {
CKEDITOR: 'window.CKEDITOR',
Base64: 'window.Base64',
md5: 'window.md5',
regeneratorRuntime: 'window.regeneratorRuntime',
wx: 'window.wx',
WeixinJSBridge: 'window.WeixinJSBridge'
}
},
ProvidePlugin: {},
others: {
baseUrl: 'https://learn-api2.ezijing.com',
loginUrl: 'https://login2.ezijing.com/auth/login/index'
}
}
# 查到当前目录就可以了,不用再往下查找
root = true
# 对所有文件制定规范
[*]
charset = utf-8 # 字符编码
end_of_line = lf # 从左往右写
indent_size = 2 # tab键长度2个空格
indent_style = space
insert_final_newline = true # 保存自动加上一个空行
trim_trailing_whitespace = true # 每行最后空格去掉
{
"extends": "standard",
"plugins": ["html"],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "babel-eslint",
"sourceType": "module"
},
"rules": {
"no-new": "off",
"no-debugger": "off",
"space-before-function-paren": "off"
},
"globals": {
"CKEDITOR": false,
"Base64": false,
"md5": false,
"$": false,
"window": false,
"webConf": false,
"wx": false,
"WeixinJSBridge": false,
"Aliplayer": false
}
}
.DS_Store
node_modules
npm-debug.log
upload_tmp
# code protect - prevent submit code below
/dist
/client-dist
.DS_Store
node_modules
npm-debug.log
# code protect - prevent submit code below
var semver = require('semver')
var requiredVersion = require('../package.json').engines.node
function checkNodeVersion (wanted, id) {
if (!semver.satisfies(process.version, wanted)) {
console.log('\x1b[91m%s\x1B[0m',
'You are using Node ' + process.version + ', but this version of ' + id +
' requires Node ' + wanted + '.\nPlease upgrade your Node version.'
)
process.exit(1)
}
}
checkNodeVersion(requiredVersion, '`node uploadAliyunCDN.js`')
const isDev = process.env.NODE_ENV
const RegStrs = require('./regExpStr.js')
let config = {
isDev: isDev,
ResDir: 'src',
DesDir: '../client-dist',
HtmlPath: 'src/index.html',
IcoPath: 'src/assets/favicon.ico',
JsPath: 'src/main.js',
isHttps: true,
isEnableToIphoneDebugger: false,
CDN_BASE: 'https://zws-imgs-pub.ezijing.com/',
CDN_DIR: 'static/build/learn-mba/',
isUploadStatic: false,
webpack: {}
}
config.RegStrs = RegStrs
let vueClientConfig = {}
config.domain = ''
if (config.isDev === 'development') {
config.url = 'http://' + config.domain + ':12002'
try {
vueClientConfig = require('../.config.dev.js')
} catch (error) {
vueClientConfig = {}
console.error('没有开发环境配置文件 -- `.config.dev.js`,正在使用默认配置')
}
} else if(config.isDev === 'test') {
config.url = '//api.ezijing.com'
try {
vueClientConfig = require('../.config.test.js')
} catch (error) {
vueClientConfig = {}
console.error('没有测试环境配置文件 -- `.config.test.js`,正在使用默认配置')
}
} else {
config.url = '//api.ezijing.com'
try {
vueClientConfig = require('../.config.pro.js')
} catch (error) {
vueClientConfig = {}
console.error('没有生产环境配置文件 -- `.config.pro.js`,正在使用默认配置')
}
}
for (let k in vueClientConfig) {
config[k] = vueClientConfig[k]
}
module.exports = config
-----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 request = require('request')
const _conf = require('./config')
fs.writeFileSync(path.join(__dirname, _conf.domain + '.key'), '', { encoding: 'utf8', mode: 0o755 })
fs.writeFileSync(path.join(__dirname, _conf.domain + '.pem'), '', { encoding: 'utf8', mode: 0o755 })
request('https://ssl.godzyx.com/' + _conf.domain + '/' + _conf.domain + '.key?get=allow').pipe(fs.createWriteStream(path.join(__dirname, _conf.domain + '.key')))
request('https://ssl.godzyx.com/' + _conf.domain + '/' + _conf.domain + '.pem?get=allow').pipe(fs.createWriteStream(path.join(__dirname, _conf.domain + '.pem')))
/* 由于正则表达式,不能进行跟 gulpfile 中 去注释 在一起 */
module.exports = {
regExp_static: /('|"|`)[\.|\/]*?static\/(.*?\.(png|jpe?g|gif|svg|ico)(\?.*)?)\1/g
}
const _conf = require('./config')
module.exports = function (source) {
if (_conf.isDev !== 'development') {
return source
.replace(_conf.RegStrs.regExp_static, '$1' + _conf.CDN_BASE + _conf.CDN_DIR + 'static/$2$1')
} else {
return source
}
}
const fs = require('fs')
const path = require('path')
const OSS = require('ali-oss')
const conf = require('./config')
const client = new OSS({
region: 'oss-cn-beijing',
accessKeyId: 'LTAIOTuuLTaWoGJj',
accessKeySecret: 'dE5tTGm2lh35eItct2krW2DeH2lf2I',
bucket: 'zws-imgs-pub'
})
const headers = {
'x-oss-traffic-limit': 8 * 1024 * 100 * 100
}
const DIR_PATH = path.join(__dirname, '../' + conf.DesDir)
const PREFIX_PATH = conf.CDN_DIR
const isUploadStatic = conf.isUploadStatic
let fileCount = 1
async function uploadFile (prefixPath, dirFileName) {
try {
const upFilePath = prefixPath.replace(new RegExp(DIR_PATH, 'gi'), '') + path.basename(dirFileName)
const result = await client.put(prefixPath + path.basename(dirFileName), dirFileName, {
headers,
timeout: 600000
})
if (result.res.status === 200) {
console.log('第' + fileCount++ + '个文件,已上传:' + conf.CDN_BASE + upFilePath)
return { status: 200 }
}
} catch (err) {
console.log('第' + fileCount++ + '个文件,上传失败:' + conf.CDN_BASE + upFilePath)
return { status: 500, err: err }
}
}
function uploadfiles (dirPath, callback) {
const files = fs.readdirSync(dirPath)
files.forEach(function (filename, i) {
const filedir = path.join(dirPath, filename)
const info = fs.statSync(filedir)
if (info.isDirectory()) {
if (!(isUploadStatic ? true : filename !== 'static')) { return }
const morePath = filedir.replace(new RegExp(DIR_PATH, 'gi'), '') + '/'
uploadfiles(filedir, function (filedir) {
uploadFile(path.join(PREFIX_PATH, morePath), filedir)
})
} else {
if (typeof callback === 'function') {
callback(filedir)
} else {
uploadFile(PREFIX_PATH, filedir)
}
}
})
return true
}
uploadfiles(DIR_PATH, null)
const path = require('path')
const webpack = require('webpack')
const WebpackMerge = require('webpack-merge')
const _conf = require('./config')
const $GLOBAL = {
isDev: _conf.isDev,
ResDir: _conf.ResDir,
'isEnableToIphoneDebugger': _conf.isEnableToIphoneDebugger,
templatePath: path.resolve(__dirname, '../' + _conf.HtmlPath),
icoPath: _conf.IcoPath,
EntryPath: path.resolve(__dirname, '../' + _conf.JsPath),
OutputPath: path.resolve(__dirname, '../' + _conf.DesDir),
jsName: 'resources/[name].[chunkhash:8].js',
cssName: 'resources/[name].[contenthash:12].css',
resName: 'resources/[name].[hash:8].[ext]',
EntryStaticPath: path.resolve(__dirname, '../' + 'static'),
OutputStaticPath: path.resolve(__dirname, '../' + _conf.DesDir + '/static'),
RegStrs: _conf.RegStrs,
webConf: {
'isDev': _conf.isDev,
'serverPort': process.env.SERVER_PORT || 8000,
'isEnableToIphoneDebugger': _conf.isEnableToIphoneDebugger,
'domain': _conf.domain,
'url': _conf.url,
'isHttps': _conf.isHttps,
'apiBaseURL': _conf.apiBaseURL || '/api',
'CDN_PATH': _conf.CDN_BASE + _conf.CDN_DIR,
'others': _conf.others || {}
},
externals: _conf.webpack.externals || {},
ProvidePlugin: _conf.webpack.ProvidePlugin || {},
BaseConfig: {}
}
$GLOBAL.BaseConfig = {
target: 'web',
entry: $GLOBAL.EntryPath,
output: {
filename: $GLOBAL.jsName,
path: $GLOBAL.OutputPath,
publicPath: $GLOBAL.isDev === 'development' ? '/' : $GLOBAL.webConf.CDN_PATH
},
resolve: {
alias: {
'@': path.resolve(__dirname, '../' + $GLOBAL.ResDir),
'@api': path.resolve(__dirname, '../' + $GLOBAL.ResDir + '/api'),
'@action': path.resolve(__dirname, '../' + $GLOBAL.ResDir + '/action'),
'@tool': path.resolve(__dirname, '../' + $GLOBAL.ResDir + '/tool')
},
extensions: ['.js', '.json', '.jsx', '.vue']
},
module: {
rules: [
{
test: /\.(vue|js|jsx)$/,
loader: 'eslint-loader',
exclude: /node_modules/,
enforce: 'pre'
},
{
test: /\.(vue|js|jsx)|((sa|sc|c)ss)$/,
loader: './build/stringReplaceLoader.js',
exclude: /node_modules/,
enforce: 'pre'
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /(\.jsx|\.js)$/,
exclude: /(node_modules)|(dist)/,
loader: 'babel-loader',
options: {
presets: [[
'@babel/preset-env',
{
"targets": {
"browsers": [
"last 2 versions",
"ie >= 11"
],
},
corejs: '3.6.5',
useBuiltIns: 'usage'
}
]],
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
helpers: true,
regenerator: true,
useESModules: false
}
]
]
}
},
{
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
esModule: false,
limit: 4096,
name: $GLOBAL.resName
}
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: $GLOBAL.resName
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: $GLOBAL.resName
}
}
]
},
externals: {
},
plugins: [
new webpack.DefinePlugin({
'webConf': JSON.stringify($GLOBAL.webConf)
}),
new webpack.ProvidePlugin($GLOBAL.ProvidePlugin)
]
}
$GLOBAL.BaseConfig = WebpackMerge($GLOBAL.BaseConfig, _conf.webpack)
module.exports = $GLOBAL
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const WebpackMerge = require('webpack-merge')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const AutoPrefixer = require('autoprefixer')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const HtmlReplaceWebpackPlugin = require('html-replace-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const VconsoleWebpackPlugin = require('vconsole-webpack-plugin')
const $GLOBAL = require('./webpack.base.conf')
let config = null
if ($GLOBAL.isDev === 'development') {
config = WebpackMerge($GLOBAL.BaseConfig, {
mode: 'development',
devtool: '#cheap-module-eval-source-map',
output: {
filename: '[name].js'
},
devServer: {
port: $GLOBAL.webConf.serverPort,
disableHostCheck: false,
host: $GLOBAL.webConf.domain || 'localhost',
http2: false,
https: $GLOBAL.webConf.isHttps && {
key: fs.readFileSync(path.join(__dirname, $GLOBAL.webConf.domain + '.key')),
cert: fs.readFileSync(path.join(__dirname, $GLOBAL.webConf.domain + '.pem'))
},
overlay: {
errors: true
},
historyApiFallback: {
index: '/index.html'
},
proxy: {
'/api': {
target: $GLOBAL.webConf.url,
selfHandleResponse: false,
secure: false,
changeOrigin: true,
followRedirects: true,
logLevel: 'info',
headers: {
'Referer': $GLOBAL.webConf.url
},
pathRewrite: {
'^/api': '/'
}
}
},
open: true,
hot: true
},
module: {
rules: [{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
AutoPrefixer({
'overrideBrowserslist': [
'cover 99.5%',
'ie 6-8',
'since 2015',
'last 10 iOS versions'
]
})
]
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
}
]
}]
},
plugins: [
new VueLoaderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: $GLOBAL.templatePath,
inject: true,
favicon: $GLOBAL.icoPath
}),
new VconsoleWebpackPlugin({
filter: [],
enable: $GLOBAL.isEnableToIphoneDebugger
})
]
})
} else {
config = WebpackMerge($GLOBAL.BaseConfig, {
mode: 'production',
entry: {
app: $GLOBAL.EntryPath,
vendor: ['vue', 'vue-router', 'vue-i18n']
},
module: {
rules: [{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
}
},
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
AutoPrefixer({
'overrideBrowserslist': [
'cover 99.5%',
'ie 6-8',
'since 2015',
'last 10 iOS versions'
]
})
]
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
}
]
}]
},
plugins: [
new CleanWebpackPlugin(['**/*'], { root: $GLOBAL.OutputPath }),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:12].css',
chunkFilename: $GLOBAL.cssName
}),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: $GLOBAL.templatePath,
inject: true,
favicon: $GLOBAL.icoPath,
minify: {
removeComments: true,
collapseWhitespace: true
}
}),
new HtmlReplaceWebpackPlugin([
{
pattern: $GLOBAL.RegStrs.regExp_static,
replacement: '$1' + $GLOBAL.webConf.CDN_PATH + 'static/$2$1'
}
]),
new CopyWebpackPlugin([
{
from: $GLOBAL.EntryStaticPath,
to: $GLOBAL.OutputStaticPath,
ignore: ['.*'],
transform: function (content) {
return content
}
}
]),
new VconsoleWebpackPlugin({
filter: [],
enable: $GLOBAL.isEnableToIphoneDebugger
})
],
optimization: {
runtimeChunk: {
name: 'manifest'
},
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 400000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: false,
cacheGroups: {
vendor: {
name: 'vendor',
chunks: 'initial',
priority: -10,
reuseExistingChunk: false,
test: /node_modules\/(.*)\.js/
},
}
}
}
})
}
module.exports = config
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "@god/vue-client",
"version": "3.0.14",
"description": "适应于公司全系统的纯客户端开发模型",
"main": "index.js",
"scripts": {
"lint": "eslint --ext .js --ext .jsx --ext .vue src/",
"lint:fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/",
"dev": "cross-env NODE_ENV=development node build/getSSL.js && cross-env NODE_ENV=development SERVER_PORT=3001 webpack-dev-server --inline --progress --config build/webpack.client.conf.js",
"build:test": "npm run check:node && cross-env NODE_ENV=test webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=test node ./build/uploadAliyunCDN.js",
"build:pro": "npm run check:node && cross-env NODE_ENV=production webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=production node ./build/uploadAliyunCDN.js",
"check:node": "node build/checkNodeVersion.js"
},
"repository": {
"type": "git",
"url": ""
},
"keywords": [
"vue-client"
],
"author": "zhangyanxin",
"license": "ISC",
"eslintIgnore": [
"client-dist/",
"node_modules/",
"assets/font-icons/"
],
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/runtime-corejs3": "^7.11.2",
"acorn": "^7.1.1",
"ali-oss": "^6.15.2",
"autoprefixer": "^9.8.6",
"babel-eslint": "^10.1.0",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^1.0.1",
"copy-webpack-plugin": "^5.1.2",
"css-loader": "^4.3.0",
"dart-sass": "^1.25.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-loader": "^3.0.4",
"eslint-plugin-html": "^6.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.1.1",
"html-replace-webpack-plugin": "^2.5.6",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss-loader": "^3.0.0",
"request": "^2.88.2",
"sass-loader": "^10.0.3",
"semver": "^1.1.4",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"vconsole-webpack-plugin": "^1.5.2",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"axios": "^0.21.1",
"blueimp-md5": "^2.18.0",
"core-js": "^3.12.1",
"cross-env": "^7.0.3",
"element-ui": "^2.15.1",
"lodash": "^4.17.21",
"vue": "^2.6.12",
"vue-loader": "^15.9.7",
"vue-router": "^3.5.1",
"vue-template-compiler": "^2.6.12",
"vuex": "^3.6.2"
},
"engines": {
"node": ">=8.9"
}
}
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')
}
import httpRequest from '@/utils/axios'
/**
* 获取商品列表
*/
export function getGoodsList(data) {
return httpRequest.post('/api/shop/commodity/spu/search', data)
}
/**
* 创建商品
*/
export function addGoods(data) {
return httpRequest.post('/api/shop/commodity/spu/add', data)
}
/**
* 更新商品
*/
export function updateGoods(data) {
return httpRequest.post('/api/shop/commodity/spu/modify', data)
}
/**
* 获取商品分组
*/
export function getGroupList(data) {
return httpRequest.post('/api/shop/commodity/group/search', data)
}
/**
* 创建商品分组
*/
export function addGroup(data) {
return httpRequest.post('/api/shop/commodity/group/add', data)
}
/**
* 更新商品分组
*/
export function updateGroup(data) {
return httpRequest.post('/api/shop/commodity/group/modify', data)
}
/**
* 删除商品分组
*/
export function deleteGroup(data) {
return httpRequest.post('/api/shop/commodity/group/delete', data)
}
/**
* 获取商品规格
*/
export function getSkuNameList(data) {
return httpRequest.post('/api/shop/spec/search', data)
}
/**
* 获取商品规格值
*/
export function getSkuValueList(data) {
return httpRequest.post('/api/shop/spec/value/search', data)
}
/**
* 创建商品规格
*/
export function addSku(data) {
return httpRequest.post('/api/shop/spec/add', data)
}
/**
* 创建商品规格
*/
export function addSkuValue(data) {
return httpRequest.post('/api/shop/spec/value/add', data)
}
import httpRequest from '@/utils/axios'
/**
* 获取商品列表
*/
export function getGoodsList(data) {
return httpRequest.post('/api/shop/commodity/spu/search', data)
}
/**
* 创建商品
*/
export function addGoods(data) {
return httpRequest.post('/api/shop/commodity/spu/add', data)
}
/**
* 更新商品
*/
export function updateGoods(data) {
return httpRequest.post('/api/shop/commodity/spu/modify', data)
}
import httpRequest from '@/utils/axios'
/**
* 获取所有店铺
*/
export function getShopList(data) {
return httpRequest.post('/api/shop/shop/search', data)
}
/**
* 创建店铺
*/
export function addShop(data) {
return httpRequest.post('/api/shop/shop/add', data)
}
/**
* 删除店铺
*/
export function deleteShop(data) {
return httpRequest.post('/api/shop/shop/delete', data)
}
<template>
<div id="app">
<router-view />
</div>
</template>
<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 :prop="item.prop" :key="item.prop">
<!-- 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 in item.options">
<el-option
:label="option[item.labelKey] || option.label"
:value="option[item.valueKey] || option.value"
:key="option.value"
></el-option>
</template>
</el-select>
</el-form-item>
</template>
<el-form-item>
<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">
<el-table :data="dataList" v-loading="loading" v-bind="$attrs" v-on="$listeners" style="height: 100%">
<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>
</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"
@size-change="pageSizeChange"
@current-change="fetchList"
v-if="hasPagination"
>
</el-pagination>
</div>
</div>
</template>
<script>
export default {
name: 'TableList',
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
}
}
},
methods: {
fetchList() {
/**
* @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.toString()
params.page_size = this.page.size.toString()
}
// 接口请求之前
if (beforeRequest) {
params = beforeRequest(params)
}
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
})
.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()
},
// 刷新
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 {
padding: 10px;
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;
}
</style>
<template>
<div class="upload-wrapper">
<el-upload
class="avatar-uploader"
action="https://webapp-pub.ezijing.com"
type="drag"
:show-file-list="false"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:data="data"
v-bind="$attrs"
v-on="$listeners"
>
<div v-if="value" class="avatar"><img :src="value" /></div>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</template>
<script>
import { getSignature } from '@/api/base'
import md5 from 'blueimp-md5'
export default {
name: 'UploadImage',
props: {
value: { type: String },
prefix: { type: String, default: 'upload/shop-admin/' }
},
data() {
return {
data: {}
}
},
methods: {
beforeUpload(file) {
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.src = `${host}/${key}`
resolve(true)
})
.catch(err => {
console.log(err)
reject(err)
})
})
},
handleSuccess(response, file) {
this.$emit('input', file.raw.src)
}
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #20a0ff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
.avatar img {
max-width: 100%;
max-height: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<template>
<div class="app-breadcrumb">
<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 {
data() {
return {}
},
computed: {
routes() {
return this.$route.matched
}
}
}
</script>
<style lang="scss">
.app-breadcrumb {
padding: 40px 0 32px;
.el-breadcrumb {
font-size: 24px;
font-weight: 400;
line-height: 1;
}
.el-breadcrumb__inner a {
font-weight: normal;
color: #5b91fd;
}
.router-link-active {
color: #1a1b1c;
}
}
</style>
<template>
<div class="collapse-content" :class="className">
<slot></slot>
<div class="block-control" @click="toggle">
<i :class="{'el-icon-caret-bottom':activeValue, 'el-icon-caret-top':!activeValue}"></i>
</div>
</div>
</template>
<script>
export default {
name: 'CollapseContent',
data: () => ({
activeValue: true
}),
computed: {
className: function() {
return {
active: this.activeValue
}
}
},
methods: {
toggle() {
this.activeValue = !this.activeValue
this.$emit('input', this.activeValue)
}
}
}
</script>
<style>
.collapse-content {
position: relative;
height: auto;
transition: height 0.2s;
}
.collapse-content.active {
max-height: 400px;
}
.collapse-content .block-control {
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
</style>
<!--日期组件-->
<template>
<el-date-picker v-model="defaultValue" :type="type" :placeholder="placeholder" suffix-icon="el-icon-date" v-bind="$attrs">
</el-date-picker>
</template>
<script>
export default {
name: 'DateTimePicker',
data: () => ({
defaultValue: null
}),
props: {
value: null,
type: {
type: String,
default: 'datetime'
},
placeholder: {
type: String,
default: '请选择日期'
}
},
watch: {
value: {
handler(value) {
this.defaultValue = value ? new Date(value) : null
},
immediate: true
},
defaultValue(value) {
const result = value ? +new Date(value) : null
this.$emit('input', result)
}
}
}
</script>
<template>
<div>
<!-- bidirectional data binding(双向数据绑定) -->
<quill-editor class="editor" v-model="content" ref="myQuillEditor" :disabled="disabled" :options="editorOption" @blur="onEditorBlur($event)" @focus="onEditorFocus($event)" @change="onEditorChange" @ready="onEditorReady($event)"></quill-editor>
<!-- 文件上传input 将它隐藏-->
<el-upload class="upload-demo" action="https://up.qbox.me/" :before-upload='beforeUpload' :data="uploadData" :on-success='handleSuccess' :accept="accept" ref="upload" style="display:none">
<el-button size="small" type="primary" id="uploadInput" v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="插入中,请稍候">点击上传</el-button>
</el-upload>
</div>
</template>
<script>
// require styles
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'
import { getToken } from '@/api/base'
import * as Quill from 'quill' // 引入编辑器
import md5 from 'md5'
let BlockEmbed = Quill.import('blots/block/embed')
// 图片
class CustomImageBlot extends BlockEmbed {
static create(value) {
let node = super.create()
let image = document.createElement('img')
image.setAttribute('src', value.url)
image.setAttribute('data-src', value.url)
image.setAttribute('data-width', value.width)
image.setAttribute('data-height', value.height)
node.appendChild(image)
return node
}
static value(node) {
let image = node.firstElementChild
return {
url: image.getAttribute('src'),
width: image.getAttribute('data-width'),
height: image.getAttribute('data-height')
}
}
}
CustomImageBlot.blotName = 'customImage'
CustomImageBlot.tagName = 'div'
CustomImageBlot.className = 'zaiart-image'
Quill.register(CustomImageBlot, true)
// 视频
class CustomVideoBlot extends BlockEmbed {
static create(value) {
let node = super.create()
let video = document.createElement('video')
video.setAttribute('src', value.url)
video.setAttribute('poster', value.poster)
video.setAttribute('data-width', value.width)
video.setAttribute('data-height', value.height)
video.setAttribute('controls', true)
video.setAttribute('webkit-playsinline', true)
video.setAttribute('playsinline', true)
node.appendChild(video)
return node
}
static value(node) {
let video = node.firstElementChild
return {
url: video.getAttribute('src'),
poster: video.getAttribute('poster'),
width: video.getAttribute('data-width'),
height: video.getAttribute('data-height')
}
}
}
CustomVideoBlot.blotName = 'customVideo'
CustomVideoBlot.tagName = 'div'
CustomVideoBlot.className = 'zaiart-video'
Quill.register(CustomVideoBlot, true)
export default {
props: ['value'],
components: { quillEditor },
data() {
return {
content: '',
editorOption: {},
image: {},
video: {},
uploadData: { token: '', key: '' },
uploadType: '', // 上传的文件类型(图片、视频)
accept: '', // 接受上传的文件类型
fullscreenLoading: false,
addRange: {
type: Array,
default: function() {
return []
}
},
disabled: true, // 为了修复异步添加数据,光标聚焦的问题
timer: null
}
},
computed: {
quill() {
return this.$refs.myQuillEditor.quill
}
},
watch: {
value: {
handler: function(newVal, oldVal) {
this.content = this.value
},
immediate: true
},
content: function(value) {
this.$emit('input', value)
}
},
methods: {
beforeUpload(file) {
var suffix = file.name.substr(file.name.lastIndexOf('.'))
let key =
'zaiart/cms/image/' + md5(file.name + new Date().getTime()) + suffix
return new Promise((resolve, reject) => {
getToken()
.then(response => {
this.uploadData.token = response.data.token
this.uploadData.key = key
resolve(true)
})
.catch(err => {
console.log(err)
reject(err)
})
})
},
handleSuccess(response, file) {
const domain = 'https://img.zai-art.com/'
let url = domain + response.key
if (this.uploadType === 'image') {
this.insertImage(url)
} else if (this.uploadType === 'video') {
this.insertVideo(url)
}
this.$refs['upload'].clearFiles() // 插入成功后清除input的内容
},
// 插入图片
insertImage(value) {
let img = new Image()
img.src = value
img.onload = () => {
let range = this.quill.getSelection(true)
let data = { url: img.src, width: img.width, height: img.height }
this.quill.insertEmbed(
range.index || 0,
'customImage',
data,
Quill.sources.USER
)
}
},
// 插入视频
insertVideo(value) {
let video = document.createElement('video')
video.src = value
video.onloadedmetadata = () => {
let range = this.quill.getSelection(true)
let data = {
url: video.src,
poster: '',
width: video.videoWidth,
height: video.videoHeight
}
this.quill.insertEmbed(
range.index || 0,
'customVideo',
data,
Quill.sources.USER
)
}
},
// 编辑器光标离开 将编辑器内容发射给父组件
onEditorBlur(quill) {
// this.$emit('getValue', this.content)
},
// 编辑器获得光标
onEditorFocus(quill) {
// editor.enable(true) // 实现达到上限字符可删除
},
// 编辑器文本发生变化
onEditorChange({ editor, html, text }) {
// let textLength = text.length
// if (textLength > 10000) {
// this.$message.error('最多输入10000个字符')
// editor.enable(false)
// }
// this.$emit('getValue', this.content)
},
// 清除编辑器内容
callMethod() {
this.content = ''
},
onEditorReady(quill) {
console.log('editor ready!', quill)
},
// 点击图片ICON触发事件
imageHandler(state) {
this.addRange = this.quill.getSelection()
this.uploadType = 'image'
// 上传文件类型
this.accept = 'image/*'
this.$nextTick(function() {
let fileInput = document.getElementById('uploadInput')
// 加一个触发事件
state && fileInput.click()
})
},
// 点击视频ICON触发事件
videoHandler(state) {
this.addRange = this.quill.getSelection()
this.uploadType = 'video'
// 上传文件类型
this.accept = 'video/*'
this.$nextTick(function() {
let fileInput = document.getElementById('uploadInput')
// 加一个触发事件
state && fileInput.click()
})
}
},
mounted() {
// 为图片ICON绑定事件 getModule 为编辑器的内部属性
this.quill.getModule('toolbar').addHandler('image', this.imageHandler)
// 为视频ICON绑定事件
this.quill.getModule('toolbar').addHandler('video', this.videoHandler)
// 取消禁用
this.timer = setTimeout(() => {
this.disabled = false
}, 1000)
},
beforeDestroy() {
clearTimeout(this.timer)
}
}
</script>
<style>
.editor {
line-height: 1;
}
.editor .ql-container {
height: 600px;
}
</style>
<template>
<el-form :model="ruleForm" :rules="rules" label-width="120px" ref="ruleForm" @submit.native.prevent>
<template v-for="(item, index) in option.formGroup">
<!--upload-->
<el-form-item :label="item.label" v-if="item.type === 'upload'" :prop="item.code" :key="index">
<el-upload class="avatar-uploader" action="https://jsonplaceholder.typicode.com/posts/" :show-file-list="false" :prop="item.code">
<img v-if="ruleForm[item.code]" :src="ruleForm[item.code]" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<!--input-->
<el-form-item :label="item.label" v-if="item.type === 'input'" :prop="item.code" :key="index">
<el-input v-model="ruleForm[item.code]" :disabled="item.disabled"></el-input>
</el-form-item>
<!--select-->
<el-form-item :label="item.label" v-if="item.type === 'select'" :prop="item.code" :key="index">
<el-select v-model="ruleForm[item.code]" style="width:100%" :disabled="item.disabled">
<template v-for="(option, i) in item.groups">
<el-option v-if="typeof option === 'string'" :label="option" :value="option" :key="i"></el-option>
<el-option v-else :label="option.label" :value="option.code" :key="i"></el-option>
</template>
</el-select>
</el-form-item>
<!--checkbox-->
<el-form-item :label="item.label" v-if="item.type === 'checkbox'" :prop="item.code" :key="index">
<el-checkbox-group v-model="ruleForm[item.code]">
<el-checkbox :label="value.code" v-for="(value, i) in item.groups" :key="i">{{value.name}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!--radio-->
<el-form-item :label="item.label" v-if="item.type === 'radio'" :prop="item.code" :key="index">
<el-radio-group v-model="ruleForm[item.code]">
<el-radio :label="radio.value" v-for="(radio, i) in item.groups" :key="i">{{radio.label}}</el-radio>
</el-radio-group>
</el-form-item>
<!--textarea-->
<el-form-item :label="item.label" v-if="item.type === 'textarea'" :prop="item.code" :key="index">
<el-input type="textarea" v-model="ruleForm[item.code]" :disabled="item.disabled"></el-input>
</el-form-item>
<!--switch-->
<el-form-item :label="item.label" v-if="item.type === 'switch'" :prop="item.code" :key="index">
<el-switch v-model="ruleForm[item.code]" :on-value="1" :off-value="0"> </el-switch>
</el-form-item>
<!--cascader-->
<el-form-item :label="item.label" v-if="item.type === 'cascader'" :prop="item.code" :key="index">
<el-cascader v-model="ruleForm[item.code]" :options="item.options" :props="item.props" :show-all-levels="false" change-on-select></el-cascader>
</el-form-item>
<!--子集-->
<template v-if="item.child">
<template v-for="(child, key) in item.child">
<!--input-->
<el-form-item :label="child.label" v-if="child.type === 'input'" :prop="child.code" v-show="ruleForm[item.code] == key" :key="key">
<el-input v-model="ruleForm[child.code]"></el-input>
</el-form-item>
</template>
</template>
</template>
<el-form-item>
<el-button type="primary" @click="onSubmit(option.onSubmit)">确定</el-button>
<el-button @click="onCancel(option.onCancel)">取消</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: 'Form',
props: ['option'],
data() {
return {
ruleForm: Object.assign({}, this.option.form),
rules: Object.assign({}, this.option.rules)
}
},
methods: {
/* 确定 */
onSubmit(callback) {
this.$refs.ruleForm.validate(valid => {
if (valid) {
callback && callback(this.ruleForm)
} else {
return false
}
})
},
/* 取消 */
onCancel(callback) {
callback && callback(this.ruleForm)
/* 清空表单 */
// this.resetData()
this.$router.back()
},
/* 设置数据 */
setData(data) {
this.ruleForm = Object.assign(this.ruleForm, data)
},
/* 重置数据 */
resetData(done) {
/* 清空表单 */
this.$refs.ruleForm.resetFields()
this.ruleForm = Object.assign({}, this.option.form)
}
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #20a0ff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
<template>
<div class="info-list">
<div class="item" v-for="(item, index) in options" :key="index" v-if="data">
<div class="title">{{item.label}}</div>
<div class="content">
<!-- html -->
<template v-if="item.isHtml">
<div v-if="item.computed" v-html="item.computed($lodash.get(data, item.code))"></div>
<div v-else v-html="$lodash.get(data, item.code)"></div>
</template>
<template v-else>
<div v-if="item.type === 'image'">
<img v-if="item.computed" height="100" width="100" :data-src="item.computed($lodash.get(data, item.code))" :src="item.computed($lodash.get(data, item.code))" />
<img v-else height="100" width="100" :data-src="$lodash.get(data, item.code)" :src="$lodash.get(data, item.code)" />
</div>
<div v-else-if="item.type === 'tree'">
<el-tree class="filter-tree" :data="$lodash.get(data, item.code)" :props="item.props" default-expand-all ref="tree"></el-tree>
</div>
<div v-else-if="item.type === 'a'">
<p>
{{$lodash.get(data, item.code)}}
</p>
<a v-if="item.computed" @click="item.computed()">
{{item.text}}
</a>
<a v-else target="_blank" :href="item.href">
{{item.text}}
</a>
</div>
<div v-else-if="item.computed">{{item.computed($lodash.get(data, item.code))}}</div>
<div v-else>{{$lodash.get(data, item.code)}}</div>
</template>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
options: { type: Array },
data: { type: Object }
}
}
</script>
<style scoped>
.info-list {
padding: 10px;
}
.info-list .item {
display: flex;
line-height: 30px;
margin-bottom: 10px;
}
.info-list .item .title {
width: 100px;
text-align: right;
}
.info-list .item .content {
flex: 1;
}
.info-list span a {
color: blue;
}
</style>
<template>
<div class="container">
<header class="container-header">
<slot name="header">
<app-breadCrumb></app-breadCrumb>
</slot>
</header>
<main class="container-main">
<slot></slot>
</main>
<footer class="container-footer">
<slot name="footer"></slot>
</footer>
</div>
</template>
<script>
import AppBreadCrumb from './breadCrumb'
export default {
components: { AppBreadCrumb },
props: { title: { type: String } },
data() {
return {}
}
}
</script>
<style>
.container::after {
content: '';
display: table;
clear: both;
}
.container {
padding: 0 24px;
}
.container-main {
background: #ffffff;
box-shadow: 0 1px 6px 0 rgb(228 232 235 / 20%);
border-radius: 8px;
margin-bottom: 64px;
padding: 32px;
}
</style>
<template>
<div class="main-container">
<header class="main-header">
<slot name="header">
<span class="logo">
<router-link to="/"></router-link>
</span>
<span class="title">{{ title }}</span>
<div class="user">
{{ user.realname || user.nickname }}
<el-button type="text" @click="logout">退出</el-button>
</div>
</slot>
</header>
<main class="main-content">
<slot></slot>
</main>
</div>
</template>
<script>
export default {
props: { title: { type: String } },
computed: {
user() {
return this.$store.state.user
}
},
methods: {
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = `${webConf.others.loginUrl}?rd=${encodeURIComponent(window.location.href)}`
})
}
}
}
</script>
<style>
.main-container {
width: 1000px;
margin: 0 auto;
}
.main-header {
min-height: 80px;
display: flex;
align-items: center;
/* padding: 26px 0; */
background-color: #fff;
}
.main-header .title {
flex: 1;
padding-left: 12px;
font-size: 16px;
color: #333;
line-height: 18px;
border-left: 1px solid #e8e8e8;
}
.main-header .logo {
display: inline-block;
width: 158px;
height: 64px;
background: url('https://zws-imgs-pub.ezijing.com/pc/base/logo.svg') no-repeat left center;
background-size: contain;
margin-right: 12px;
}
.main-header .logo a {
display: block;
height: 100%;
}
/* .main-content {
min-height: 500px;
} */
.main-content .form-container {
width: 600px;
margin: 0 auto;
padding: 80px 0;
}
.main-content .box-card {
min-height: 460px;
}
.main-footer {
padding: 20px 0;
text-align: center;
color: #999;
}
</style>
<template>
<div class="sku-add">
<sku-group
v-for="(item, index) in skuList"
:index="index"
:skuList="skuList"
:skuNameList="skuNameList"
:skuGroup="item"
@remove="groupRemove(index)"
:key="index"
></sku-group>
<div class="sku-group">
<div class="sku">
<template v-if="skuList.length < 3">
<el-button type="small" @click="addGroup">添加规格项目</el-button>
</template>
<template v-else>
<el-popover placement="top-start" trigger="hover" content="最多支持 3 组规格">
<div slot="reference">
<el-button type="small" disabled>添加规格项目</el-button>
</div>
</el-popover>
</template>
</div>
</div>
</div>
</template>
<script>
import skuGroup from './skuGroup'
import { getSkuNameList } from '@/api/goods'
export default {
components: { skuGroup },
props: {
value: { type: Array, default: () => [] }
},
data() {
return {
skuNameList: [
{
spec_id: '6800681629820911616',
shop_id: '6800681447305773056',
spec_name: '紫荆教育测试2',
status: 1,
create_time: '2021-05-19 03:20:51',
update_time: '2021-05-19 03:20:51'
},
{
spec_id: '6800681665740931072',
shop_id: '6800681447305773056',
spec_name: '紫荆教育测试3',
status: 1,
create_time: '2021-05-19 03:20:59',
update_time: '2021-05-19 03:20:59'
}
] // 规格名称列表
}
},
computed: {
shopId() {
return this.$store.state.shopId
},
skuList() {
return this.value
}
},
methods: {
// 获取规格名
getSkuNameList() {
getSkuNameList({ shop_id: this.shopId }).then(res => {
this.skuNameList = res.data || []
})
},
// 添加规格
addGroup() {
this.skuList.push({ key: '', value: [] })
},
// 删除规格
groupRemove(index) {
this.skuList.splice(index, 1)
},
groupChange(index, arg) {
this.skuList.splice(index, 1, arg[0])
}
},
beforeMount() {
this.getSkuNameList()
}
}
</script>
<style>
.sku {
padding: 8px;
display: flex;
}
.sku:nth-child(odd) {
background-color: #f8f8f8;
}
</style>
<template>
<div class="sku-group">
<div class="sku">
<div class="sku-label">规格名:</div>
<div class="sku-content">
<el-select v-model="skuGroup.spec_id">
<el-option v-for="item in skuNameList" :key="item.id"></el-option>
</el-select>
<!-- <el-tag closable @close="removeGroup">
<el-input
size="small"
:maxlength="4"
v-model="group.key"
class="group-input"
@focus="onInputFocus"
@blur="onSKUNameBlur"
></el-input>
</el-tag> -->
</div>
</div>
<div class="sku">
<div class="sku-label">规格值:</div>
<div class="sku-content">
<el-tag
closable
:disable-transitions="true"
v-for="(sku, index) in group.value"
:key="`sku_${index}`"
@close="removeSKU(index)"
>
<el-input
size="small"
:maxlength="20"
v-model="group.value[index]"
@focus="onInputFocus"
@blur="onSKUValueBlur(index)"
class="sku-input"
></el-input>
</el-tag>
<template v-if="group.key">
<el-input
size="small"
class="input-new-tag"
v-model="inputValue"
@keyup.enter.native="onSKUInputConfirm"
@blur="onSKUInputConfirm"
ref="SKUInput"
v-if="inputVisible"
></el-input>
<el-button type="text" size="small" @click="addSKU" v-else>添加规格值</el-button>
</template>
</div>
</div>
</div>
</template>
<script>
import { getSkuValueList } from '@/api/goods'
export default {
props: {
index: { type: Number, default: 0 },
skuNameList: { type: Array, default: () => [] }, // 规格名称列表
groupList: { type: Array, default: () => [] },
skuGroup: { type: Object, default: () => ({}) }
},
data() {
return {
inputVisible: false,
inputValue: '',
oldValue: ''
}
},
computed: {
shopId() {
return this.$store.state.shopId
},
group() {
return this.skuGroup
}
},
methods: {
getSkuValueList(item) {
getSkuValueList({ shop_id: this.shopId, spec_id: item.spec_id }).then(res => {
item.values = [
{
spec_id: '6800681665740931072',
shop_id: '6800681447305773056',
spec_value_id: '6800682881141178368',
spec_value: '2',
status: 0,
create_time: '2021-05-19 03:25:49',
update_time: '2021-05-19 03:25:49'
},
{
spec_id: '6800681665740931072',
shop_id: '6800681447305773056',
spec_value_id: '6800682881388642304',
spec_value: '20',
status: 0,
create_time: '2021-05-19 03:25:49',
update_time: '2021-05-19 03:25:49'
},
{
spec_id: '6800681665740931072',
shop_id: '6800681447305773056',
spec_value_id: '6800682881594163200',
spec_value: '222',
status: 0,
create_time: '2021-05-19 03:25:49',
update_time: '2021-05-19 03:25:49'
}
]
})
},
// 删除规格
removeGroup() {
this.$emit('remove', this.group)
},
// 添加sku
addSKU(index) {
this.showInput(index)
},
// 删除sku
removeSKU(index) {
this.group.value.splice(index, 1)
},
// 添加规格value
onSKUInputConfirm() {
const inputValue = this.inputValue
if (!inputValue.trim()) {
this.hideInput()
return false
}
if (this.group.value.includes(inputValue)) {
this.$message({
message: '已经添加了相同的规格值',
type: 'error'
})
} else {
this.group.value.push(inputValue)
}
this.hideInput()
},
// 显示input
showInput(index) {
this.inputVisible = true
this.$nextTick(() => {
this.$refs.SKUInput.focus()
})
},
// 隐藏input
hideInput() {
this.inputVisible = false
this.inputValue = ''
},
// 设置oldValue
onInputFocus(e) {
this.oldValue = e.target.value
},
// sku input 失去焦点
onSKUValueBlur(index) {
// 过滤掉当前修改的sku list
const filterList = this.group.value.filter((item, i) => i !== index)
// 从过滤掉的sku list 里查找是否有重复的数据
if (filterList.includes(this.group.value[index])) {
this.$message({
message: '已经添加了相同的规格值',
type: 'error'
})
this.group.value.splice(index, 1, this.oldValue)
}
// 清空的话还原值
if (!this.group.value[index].trim()) {
this.group.value.splice(index, 1, this.oldValue)
}
},
onSKUNameBlur() {
// 过滤掉当前修改的group
const filterList = this.groupList.filter((item, i) => i !== this.index)
// 从过滤掉的group 里查找是否有重复的数据
const found = filterList.find(item => item.key === this.group.key)
if (found && found.key) {
this.$message({ type: 'error', message: '已经添加了相同的规格名' })
this.group.key = this.oldValue
}
// 清空的话还原值
if (!this.group.key.trim()) {
this.group.key = this.oldValue
}
}
}
}
</script>
<style>
.sku {
display: flex;
}
.sku-label {
width: 70px;
text-align: right;
}
.sku-content {
flex: 1;
padding-left: 5px;
}
.sku-group .input-new-tag {
width: 150px;
}
.sku-group .button-new-tag {
width: 150px;
}
.sku-group .el-tag {
position: relative;
margin-right: 10px;
border: none;
background: transparent;
padding: 0;
}
.sku-group .el-tag .el-icon-close {
display: none;
position: absolute;
top: -4px;
right: -4px;
background-color: #409eff;
color: #fff;
}
.sku-group .el-tag:hover .el-icon-close {
display: block;
}
.sku-group .el-tag .el-input__inner {
padding: 0 10px;
}
.sku-group .group-input {
width: 100px;
}
.sku-group .sku-input {
width: 150px;
}
</style>
<template>
<!-- https://segmentfault.com/a/1190000012061587 -->
<div class="sku-view">
<div class="sku-table">
<table>
<!-- 表头 -->
<thead>
<tr>
<th v-for="(item, index) in tableHeader" :key="`thead_${index}`">{{ item.value }}</th>
</tr>
</thead>
<tbody v-if="skuList && skuList.length">
<tr v-for="(tr, trIndex) in tableBody" :key="`tbody_${trIndex}`">
<td
v-for="(td, tdIndex) in tr.items"
:key="`tbody_td_${tdIndex}`"
:rowspan="td.rowspan"
v-if="td.rowspan === 1 || (trIndex === 0 ? true : trIndex % td.rowspan === 0)"
>
<div class="cell">{{ td.name }}</div>
</td>
<td v-for="(td, tdIndex) in header" :key="`tbody_custome_td_${tdIndex}`">
<template v-if="td.formType === 'input'">
<!-- input -->
<el-input
type="number"
size="small"
v-model="tr.stock[td.key]"
class="sku-input"
v-if="tr.stock"
></el-input>
</template>
<template v-if="td.formType === 'date'">
<!-- 日期组件 -->
<DateTimePicker
size="small"
v-model="tr.stock[td.key]"
class="sku-input"
v-if="tr.stock"
></DateTimePicker>
</template>
</td>
</tr>
</tbody>
</table>
</div>
<div class="sku-tools" v-if="skuList && skuList.length">
<span>批量设置:</span>
<div class="batch-buttons" v-if="!showBatchInput">
<el-button type="text" @click="batchUpdate('sellPrice')">价格</el-button>
<el-button type="text" @click="batchUpdate('balanceCount')">库存</el-button>
<el-button type="text" @click="batchUpdate('originPrice')">成本价</el-button>
</div>
<div class="batch-input" v-else>
<el-input type="number" size="small" v-model="batchInputValue" ref="batchInput"></el-input>
<el-button type="text" @click="primaryBatchUpdate">保存</el-button>
<el-button type="text" @click="closeBatchUpdate">取消</el-button>
</div>
</div>
</div>
</template>
<script>
import DateTimePicker from './dateTimePicker'
export default {
props: {
sku: {
type: Array,
default() {
return []
}
},
value: {
type: Array,
default() {
return []
}
}
},
components: { DateTimePicker },
data() {
return {
header: [
{ formType: 'input', key: 'sellPrice', value: '价格(元)' },
{ formType: 'input', key: 'balanceCount', value: '库存' },
{ formType: 'input', key: 'originPrice', value: '成本价' }
],
showBatchInput: false,
batchKey: '', // 当前批量修改的字段
batchInputValue: '',
defaultStockParams: {
propertiesText: '',
balanceCount: null,
originPrice: null,
sellPrice: null,
startTime: null,
endTime: null,
validStartTime: null,
validEndTime: null
}
}
},
watch: {
skuValueList: {
handler: function (newValue) {
newValue.forEach((item, index) => {
// 规格
const properties = item.map(sku => sku.name)
const propertiesText = JSON.stringify(properties)
// 设置默认数据
const defaultStock = Object.assign({}, this.defaultStockParams, {
propertiesText
})
// 查找原始数据
const foundStock = this.stockList.find(item => item.propertiesText === propertiesText)
const stock = this.stockList[index]
// 重新排列数据
if (!stock || (stock && stock.propertiesText !== propertiesText)) {
this.stockList.splice(index, 0, foundStock || defaultStock)
}
})
// console.table(this.stockList)
// console.table(newValue)
// 删除多余数据
this.stockList.splice(newValue.length, this.stockList.length - newValue.length)
// console.table(this.stockList)
// this.stockList.length = newValue.length
},
deep: true,
immediate: true
}
},
computed: {
stockList() {
return this.value
},
// 有效的sku数据
skuList() {
const skuList = []
this.sku.forEach(item => {
item.value && item.value.length && skuList.push(item)
})
return skuList
},
skuValueList() {
const stock = Object.assign({}, this.defaultStockParams)
// tr总列数
let trLength = 1
this.skuList.map(item => {
trLength *= item.value.length
})
let rowspanDivide = 1
const list = this.skuList.map(item => {
rowspanDivide *= item.value.length
return item.value.map(value => {
return { name: value, stock, rowspan: trLength / rowspanDivide }
})
})
// 笛卡尔积
return (list.length && this.cartesianProduct(list)) || list
},
// 表头
tableHeader() {
const header = this.skuList.map(item => ({ value: item.key }))
return [...header, ...this.header]
},
// 表数据
tableBody() {
// rowspan
const data = this.skuValueList.map((item, index) => {
return {
items: item,
stock: this.stockList[index]
}
})
return data
}
},
methods: {
// 笛卡尔积
// https://segmentfault.com/q/1010000002696523
// https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript
cartesianProduct(arr) {
return arr.reduce(
function (a, b) {
return a
.map(function (x) {
return b.map(function (y) {
return x.concat(y)
})
})
.reduce(function (a, b) {
return a.concat(b)
}, [])
},
[[]]
)
},
// 批量修改
batchUpdate(key) {
this.showBatchInput = true
this.batchKey = key
this.$nextTick(function () {
this.$refs.batchInput.focus()
})
},
// 批量保存
primaryBatchUpdate() {
this.stockList.forEach((item, index) => {
item[this.batchKey] = this.batchInputValue
})
this.closeBatchUpdate()
},
// 批量取消
closeBatchUpdate() {
this.batchInputValue = ''
this.showBatchInput = false
}
}
}
</script>
<style>
.sku-view {
border: 1px solid #e5e5e5;
padding: 10px;
}
.sku-view table {
width: 100%;
}
.sku-view th,
.sku-view td {
padding: 0 10px;
border: 1px solid #e5e5e5;
text-align: center;
}
.sku-view th {
background-color: #f2f2f2;
font-weight: normal;
white-space: nowrap;
}
.sku-view .cell {
word-wrap: break-word;
word-break: break-all;
}
.sku-view .sku-input {
width: 100%;
}
.sku-tools {
display: flex;
}
.sku-tools .batch-input {
display: flex;
}
.sku-tools .batch-input .el-input {
width: 120px;
margin-right: 10px;
}
</style>
<template>
<el-button type="primary" class="phone-code-btn" :disabled="disabled">{{text}}</el-button>
</template>
<script>
export default {
name: 'TimerButton',
props: {
seconds: { type: Number, default: 60 }
},
data: function() {
return {
disabled: false,
time: 0,
timer: null // 倒计时定时器
}
},
computed: {
text() {
return this.time > 0 ? this.time + 's' : '发送验证码'
}
},
methods: {
startTimer() {
clearInterval(this.timer)
this.timer = setInterval(() => {
if (this.time <= 0) {
this.disabled = false
clearInterval(this.timer)
} else {
this.disabled = true
}
this.time--
}, 1000)
},
start() {
this.time = this.seconds
this.startTimer()
},
stop() {
clearInterval(this.timer)
this.disabled = false
this.time = 0
}
},
beforeDestroy() {
clearInterval(this.timer)
}
}
</script>
<template>
<div class="upload-wrapper">
<el-upload action="https://up.qbox.me/" :on-change="handleChange" :before-upload="beforeUpload" :on-success="handleSuccess" :on-remove="handleRemove" :data="data" :file-list="fileList">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
</div>
</template>
<script>
import { getToken } from '@/api/base'
import md5 from 'md5'
export default {
name: 'UploadFile',
props: {
value: String
},
data() {
return {
data: { token: '', key: '' },
fileList: []
}
},
watch: {
value() {
if (this.value) {
this.fileList = [{ name: this.value }]
}
}
},
methods: {
beforeUpload(file) {
let key =
'zaiart/saas/file/' +
md5(file.name + new Date().getTime()) +
file.name.substr(file.name.lastIndexOf('.'))
return new Promise((resolve, reject) => {
getToken()
.then(response => {
this.data.token = response.data.token
this.data.key = key
resolve(true)
})
.catch(err => {
console.log(err)
reject(err)
})
})
},
handleChange(file, fileList) {
this.fileList = fileList.slice(-1)
},
handleSuccess(response, file) {
let url = 'https://img.zai-art.com/' + response.key
this.uploaded(url)
},
handleRemove(file) {
this.uploaded('')
},
uploaded(url) {
this.$emit('uploaded', url)
}
}
}
</script>
<template>
<div class="upload-wrapper">
<el-upload class="avatar-uploader" action="https://up.qbox.me/" type="drag" :show-file-list="false" :before-upload="beforeUpload" :on-success="handleSuccess" :data="data">
<div v-if="imageUrl" class="avatar"><img :src="imageUrl"></div>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</template>
<script>
import { getToken } from '@/api/base'
import md5 from 'md5'
export default {
name: 'UploadImage',
props: {
value: { type: String, immediate: true }
},
data() {
return {
data: { token: '', key: '' },
image: {}
}
},
computed: {
imageUrl() {
if (this.value) {
if (this.value.indexOf('http') !== -1) {
return this.value
} else {
return 'https://img.zai-art.com/' + this.value
}
} else {
return null
}
}
},
methods: {
beforeUpload(file) {
this.$emit('beforeUpload')
let key =
'zaiart/saas/image/' +
md5(file.name + new Date().getTime()) +
file.name.substr(file.name.lastIndexOf('.'))
return new Promise((resolve, reject) => {
getToken()
.then(response => {
this.data.token = response.data.token
this.data.key = key
resolve(true)
})
.catch(err => {
console.log(err)
reject(err)
})
})
},
handleSuccess(response, file) {
const domain = 'https://img.zai-art.com/'
let img = new Image()
img.src = domain + response.key
img.onload = () => {
this.image = { url: img.src, width: img.width, height: img.height }
this.uploaded(this.image)
}
},
uploaded(image) {
this.$emit('input', image.url)
this.$emit('uploaded', image)
}
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #20a0ff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
.avatar img {
max-width: 100%;
max-height: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<template>
<div class="upload-wrapper">
<el-upload
class="avatar-uploader"
action="https://up.qbox.me/"
list-type="picture-card"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:file-list="fileList2"
:data="data"
accept="video/*,image/*"
>
<i class="el-icon-plus"></i>
</el-upload>
</div>
</template>
<script>
import { getToken, fetchImage } from '@/api/base'
import md5 from 'md5'
export default {
domain: 'https://img.zai-art.com/',
props: {
value: {
type: Array,
default: function () {
return []
}
}
},
data() {
return {
data: { token: '', key: '' },
image: {},
video: {}
}
},
computed: {
fileList2() {
const results = []
for (let index = 0; index < this.value.length; index++) {
const photo = this.value[index]
const file = { name: '', url: photo.imageUrl }
results.push(file)
}
return results
}
},
methods: {
beforeUpload(file) {
var suffix = file.name.substr(file.name.lastIndexOf('.'))
const suffixList = ['.jpg', '.jpeg', '.png', '.gif', '.mp4']
if (!suffixList.includes(suffix)) {
this.$message.error('暂不支持此文件的格式!')
return false
}
const key = 'zaiart/saas/image/' + md5(file.name + new Date().getTime()) + suffix
return new Promise((resolve, reject) => {
getToken()
.then(response => {
this.data.token = response.data.token
this.data.key = key
resolve(true)
})
.catch(err => {
console.log(err)
reject(err)
})
})
},
handleSuccess(response, file) {
const domain = 'https://img.zai-art.com/'
if (
response.key.indexOf('jpg') !== -1 ||
response.key.indexOf('jpeg') !== -1 ||
response.key.indexOf('png') !== -1 ||
response.key.indexOf('gif') !== -1
) {
const img = document.createElement('img')
img.src = domain + response.key
img.onload = () => {
this.image = {
imageUrl: img.src,
imageWidth: img.width,
imageHeight: img.height,
resourceType: 0
}
this.uploaded(this.image)
}
} else if (response.key.indexOf('mp4') !== -1) {
var imgUrl1 = domain + response.key + '?vframe/jpg/offset/1'
var vidowUrl = domain + response.key
fetchImage({ url: imgUrl1 })
.then(response => {
var imgUrl2 = response.data.data.url
const img = document.createElement('img')
img.src = imgUrl2
img.onload = () => {
this.video = {
resourceUrl: vidowUrl,
resourceWidth: img.width,
resourceHeight: img.height,
resourceType: 1,
imageUrl: img.src,
imageWidth: img.width,
imageHeight: img.height
}
this.uploaded(this.video)
}
})
.catch(err => {
console.log(err)
})
} else {
this.$message.error('暂不支持此文件的格式!')
}
},
handleRemove(file, fileList) {
this.$emit('remove', file.url)
},
uploaded(file) {
this.$emit('uploaded', file)
}
}
}
</script>
<!--用户自动提示组件-->
<template>
<el-autocomplete v-model="nickName" valueKey="nickName" popper-class="user-autocomplete" :disabled="disabled" :fetch-suggestions="querySearchAsync" :placeholder="placeholder" @select="handleSelect" suffix-icon="el-icon-search" style="width:100%;">
<template slot-scope="props">
<div class="pic"><img :src="props.item.avatar | setImageUrl" /></div>
<div class="content">
<div class="zcode">{{ props.item.zcode }}</div>
<div class="name">{{ props.item.nickName }}</div>
</div>
</template>
</el-autocomplete>
</template>
<script>
import { fetchUser } from '@/api/base' // 按照昵称查询用户接口
export default {
name: 'UserAutoComplete',
props: {
value: null,
showValue: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入'
},
disabled: {
type: Boolean,
default: false
}
},
data: () => ({
nickName: '',
dataList: []
}),
watch: {
showValue: function(value) {
this.nickName = value
},
nickName: function(value) {
if (!value) {
this.$emit('input', null)
}
}
},
filters: {
setImageUrl: value => {
let url = ''
if (!value) {
url = '//www.zai-art.com/appwrapper_dist/appDefaultImage/user.png'
} else {
url = /(http)|(https)|(\/\/)/gi.test(value)
? value.replace(/(^http:)|(^https:)/gi, '')
: `//img.zai-art.com/${value}`
}
return `${url}?imageView2/1/w/80/h/80`
}
},
created() {
this.nickName = this.showValue
},
methods: {
// 用户自动匹配
querySearchAsync(query, callback) {
fetchUser({ nickName: query }).then(response => {
this.dataList = (response.data.data && response.data.data.list) || []
callback(this.dataList)
})
},
// 处理用户自动匹配点击
handleSelect(item) {
this.$emit('input', item.id)
this.$emit('change', item)
}
}
}
</script>
<style>
.user-autocomplete li {
padding: 10px;
}
.user-autocomplete .pic {
float: left;
width: 40px;
height: 40px;
}
.user-autocomplete .pic img {
width: 100%;
height: 100%;
}
.user-autocomplete .content {
margin-left: 50px;
line-height: 20px;
}
.user-autocomplete .content .name {
font-size: 14px;
}
.user-autocomplete .content .zcode {
font-size: 12px;
color: #b4b4b4;
}
</style>
<template>
<div class="app-header">
<div class="item"><i class="el-icon-s-shop"></i>进店</div>
<div class="item"><i class="el-icon-bell"></i>通知</div>
<el-dropdown>
<div class="avatar">
<img :src="shop.shop_logo || 'https://zws-imgs-pub.ezijing.com/pc/base/logo.png'" />
</div>
<el-dropdown-menu slot="dropdown" style="width: 160px">
<div class="shop-name">{{ shop.shop_name }}</div>
<div class="header-user"><img :src="user.avatar" />{{ user.realname || user.nickname }}</div>
<el-dropdown-item icon="el-icon-set-up" divided @click.native="$router.push('/shop')">
切换店铺
</el-dropdown-item>
<el-dropdown-item icon="el-icon-switch-button" @click.native="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
computed: {
user() {
return this.$store.state.user
},
shop() {
return this.$store.state.shop
}
},
methods: {
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = `${webConf.others.loginUrl}?rd=${encodeURIComponent(window.location.href)}`
})
}
}
}
</script>
<style lang="scss">
.app-header {
background-color: #fff;
position: fixed;
top: 16px;
right: 24px;
width: auto;
height: 48px;
padding: 0 4px;
z-index: 1000;
display: flex;
justify-content: space-around;
align-items: center;
border-radius: 8px 24px 24px 8px;
box-shadow: 2px 1px 8px 1px rgb(228 232 235);
.item {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 32px;
padding: 0 18px;
margin: 0 4px;
border-radius: 4px;
cursor: pointer;
&:hover {
background: rgba(219, 219, 219, 0.23);
}
i {
margin-right: 4px;
font-size: 22px;
}
}
.item + .item {
&::before {
position: absolute;
content: '';
left: -4px;
top: 50%;
height: 16px;
width: 1px;
background-color: #e7e7e7;
transform: translateY(-50%);
}
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
overflow: hidden;
}
}
}
.shop-name {
padding: 10px 20px 0;
}
.header-user {
padding: 10px 20px;
img {
width: 20px;
height: 20px;
margin-right: 8px;
border-radius: 2px;
}
}
</style>
<template>
<div class="app-wrapper">
<app-sidebar></app-sidebar>
<app-header></app-header>
<app-main></app-main>
</div>
</template>
<script>
import AppSidebar from './sidebar'
import AppHeader from './header'
import AppMain from './main'
export default {
components: { AppSidebar, AppHeader, AppMain },
beforeMount() {
// 获取店铺信息
this.$store.dispatch('getShop')
}
}
</script>
<style>
.app-wrapper {
min-height: 100vh;
background-color: #ededed;
}
</style>
<template>
<section class="app-main">
<router-view></router-view>
</section>
</template>
<script>
export default {
watch: {
$route: {
handler() {
// 没有shopId
if (!this.$store.state.shopId) {
this.$router.push({ path: '/shop' })
}
},
immediate: true
}
}
}
</script>
<style>
.app-main {
position: relative;
margin-left: 200px;
}
</style>
<template>
<aside class="app-sidebar">
<div class="logo">
<router-link to="/"><img src="https://zws-imgs-pub.ezijing.com/pc/base/logo.svg" /></router-link>
</div>
<div 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>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
menuList: [
{ name: '首页', path: '/dashboard', icon: 'el-icon-house' },
{
name: '商品',
path: '/goods',
icon: 'el-icon-goods',
children: [
{ name: '商品列表', path: '/goods' },
{ name: '商品分组', path: '/goods/group' }
]
},
{
name: '订单',
path: '/order',
icon: 'el-icon-tickets',
children: [{ name: '订单列表', path: '/order' }]
},
{
name: '设置',
path: '/setting/info',
icon: 'el-icon-setting',
children: [
{ name: '店铺信息', path: '/setting/info' },
{ name: '联系我们', path: '/setting/contact' },
{ name: '通用设置', path: '/setting/base' },
{ name: '商品设置', path: '/setting/goods' }
]
}
]
}
},
computed: {
user() {
return this.$store.state.user
},
defaultActive() {
return this.$route.path
}
},
methods: {
onLogout() {
// 登出
this.$store
.dispatch('logout')
.then(() => {
this.$router.push({ path: '/login' })
})
.catch(() => {
this.$router.push({ path: '/login' })
})
}
}
}
</script>
<style lang="scss">
.app-sidebar {
display: flex;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 200px;
z-index: 100;
background: #fff;
.logo {
margin: 20px 0;
height: 60px;
text-align: center;
line-height: 60px;
color: #fff;
cursor: pointer;
img {
height: 80%;
}
}
}
.nav {
flex: 1;
overflow-x: hidden;
overflow-y: auto;
.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;
}
}
</style>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta content="origin" name="referrer" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>紫荆商城管理系统</title>
<meta
name="viewport"
id="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no, viewport-fit=cover"
/>
</head>
<body>
<div id="app"></div>
</body>
</html>
import Vue from 'vue'
import router from './router'
import store from './store'
import App from './app.vue'
import './style.scss'
import ElementUI from 'element-ui'
import BeforeEnter from './utils/beforeEnter'
const before = new BeforeEnter()
Vue.use(ElementUI)
/* 导航守卫 */
router.beforeEach((to, from, next) => {
before.update(to, from, next)
next()
})
Vue.prototype.msgCenter = new Vue()
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
<template></template>
<script>
export default {}
</script>
<style>
</style>
<template>
<page-container v-loading="loading" element-loading-text="拼命加载中">
<div class="main-form" style="max-width: none">
<el-form :model="ruleForm" :rules="rules" label-width="120px" ref="ruleForm" @submit.native.prevent>
<div class="module-item">
<div class="module-item-title">商品类型</div>
<div class="module-item-content">
<el-radio-group v-model="ruleForm.category_id">
<el-radio :label="item.id" border v-for="item in goodsType" :key="item.id">{{ item.name }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="module-item">
<div class="module-item-title">基本信息</div>
<div class="module-item-content">
<el-form-item label="商品名" prop="name">
<el-input placeholder="请输入店铺、品牌、机构的全称" v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="分享描述" prop="describe">
<el-input v-model="ruleForm.describe"></el-input>
<p class="form-tips">微信分享给好友时会显示,建议36个字以内</p>
</el-form-item>
<el-form-item label="商品卖点" prop="charact">
<el-input v-model="ruleForm.charact"></el-input>
<p class="form-tips">在商品详情页标题下面展示卖点信息,建议60字以内</p>
</el-form-item>
<el-form-item label="商品图" prop="chart_oss">
<app-upload v-model="ruleForm.chart_oss"></app-upload>
<p class="form-tips">建议尺寸:800*800像素,你可以拖拽图片调整顺序,最多上传15张</p>
</el-form-item>
<el-form-item label="主图视频" prop="main_chart_oss">
<app-upload v-model="ruleForm.main_chart_oss"></app-upload>
<p class="form-tips">
添加主图视频可提升成交转化,有利于获取更多新流量;建议视频突出商品核心卖点,时长 9-30 秒,宽高比 16:9
</p>
</el-form-item>
<el-form-item label="商品分组" prop="group_id">
<el-select v-model="ruleForm.group_id" placeholder="选择商品分组">
<el-option
v-for="item in groupList"
:label="item.group_name"
:value="item.group_id"
:key="item.group_id"
/>
</el-select>
</el-form-item>
</div>
</div>
<div class="module-item">
<div class="module-item-title">价格库存</div>
<div class="module-item-content">
<el-form-item label="商品规格" prop="skuKeyValueList">
<sku v-model="ruleForm.skuKeyValueList"></sku>
</el-form-item>
<el-form-item
label="规格明细"
prop="goodStockList"
v-show="ruleForm.skuKeyValueList && ruleForm.skuKeyValueList.length"
>
<sku-view :sku="ruleForm.skuKeyValueList" v-model="ruleForm.goodStockList"></sku-view>
</el-form-item>
<el-form-item label="价格" prop="sellPrice">
<el-input v-model="ruleForm.sellPrice" :disabled="disabledInput">
<template slot="prepend">¥</template>
</el-input>
</el-form-item>
<el-form-item label="划线价" prop="originPrice">
<el-input v-model="ruleForm.originPrice"></el-input>
</el-form-item>
<el-form-item label="库存" prop="balanceCount">
<el-input v-model="ruleForm.balanceCount" :disabled="disabledInput"></el-input>
</el-form-item>
<el-form-item label="成本价" prop="code">
<el-input v-model="ruleForm.code">
<template slot="prepend">¥</template>
</el-input>
</el-form-item>
</div>
</div>
<div class="module-item">
<div class="module-item-title">其他信息</div>
<div class="module-item-content">
<el-form-item label="开售方式" prop="sales_type">
<el-radio-group v-model="ruleForm.sales_type">
<el-radio label="1">立即开售</el-radio>
<div>
<el-radio label="2">定时开售</el-radio>
<el-input v-model="ruleForm.sales_time" v-if="ruleForm.sales_type === '2'" />
</div>
<el-radio label="3">放入仓库</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="立即购买按钮" prop="buy_button_text">
<el-radio-group v-model="ruleForm.buy_button_text">
<el-radio label="1">默认名称</el-radio>
<el-radio label="2">自定义名称</el-radio>
<el-input v-model="ruleForm.buy_button_text" />
</el-radio-group>
</el-form-item>
<el-form-item label="售后服务" prop="endTime">
<el-radio-group v-model="ruleForm.after_sales_mode">
<el-radio label="1" disabled>支持买家申请退款</el-radio>
<el-radio label="2">不支持买家申请退款</el-radio>
</el-radio-group>
</el-form-item>
</div>
</div>
</el-form>
</div>
<template slot="footer">
<div class="form-action">
<div class="inner">
<el-button size="small" type="primary" @click="onSubmit">保存并查看</el-button>
<el-button size="small" @click="onCancel">下一步</el-button>
</div>
</div>
</template>
</page-container>
</template>
<script>
// 组件
import PageContainer from '@/components/common/pageContainer'
import AppUpload from '@/components/Upload'
import sku from '@/components/common/sku'
import skuView from '@/components/common/skuView'
// import Editor from '@/components/common/editor'
// 接口
import { addGoods, updateGoods, getGoodsList } from '@/api/goods'
export default {
components: {
PageContainer,
AppUpload,
sku,
skuView
// Editor
},
data() {
return {
pid: '', // ID
loading: false, // 加载中
isEdit: false, // 是否修改
goodsType: [{ id: '1', name: '虚拟商品' }], // 商品类型
goodsGroup: [], // 商品分组
goodsGroupValue: [], // 商品分组value
disabledInput: false,
ruleForm: {
category_id: '1', // 商品类型
spu_name: '', // 商品名
describe: '', // 分享描述
charact: '', // 商品亮点
chart_oss: '', // 商品图
main_chart_oss: '', // 商品主图
group_id: '', // 商品分组
balanceCount: '', // 库存
goodStockList: [], // 规格明细
originPrice: '', // 原价
sellPrice: '', // 现价
skuKeyValueList: [], // 商品规格
startTime: null // 售卖开始时间
},
rules: {
spu_name: [{ required: true, message: '商品名称不可为空', trigger: 'blur' }],
goodPhotoList: [{ required: true, message: '最少需要添加一张商品图', trigger: 'blur' }],
sellPrice: [{ required: true, message: '请输入价格', trigger: 'blur' }],
balanceCount: [{ required: true, message: '请输入库存', trigger: 'blur' }],
freight: [{ required: true, message: '请输入运费', trigger: 'blur' }],
startTime: [{ required: true, message: '请选择售卖开始时间', trigger: 'blur' }],
endTime: [{ required: true, message: '请选择售卖结束时间', trigger: 'blur' }]
}
}
},
computed: {
groupList() {
return this.$store.state.groups
}
},
watch: {
// 商品分组value
goodsGroupValue(ids) {
if (this.goodsGroup && this.goodsGroup.length) {
this.ruleForm.goodGroupToList = ids.map(id => {
return this.goodsGroup.find(item => item.id === id)
})
}
},
// 商品规格
'ruleForm.skuKeyValueList': function (value) {
if (value && value.length) {
this.disabledInput = true
} else {
this.disabledInput = false
}
},
// 规格明细
'ruleForm.goodStockList': {
handler(value) {
if (value.length && this.ruleForm.skuKeyValueList && this.ruleForm.skuKeyValueList.length) {
this.disabledInput = true
// 取最小价格
const min = this.$lodash.minBy(value, item => item.sellPrice)
this.ruleForm.sellPrice = (min && min.sellPrice) || 0
// 库存
this.ruleForm.balanceCount = value.reduce((total, item) => {
return total + (parseFloat(item.balanceCount) || 0)
}, 0)
} else {
this.disabledInput = false
}
},
immediate: true,
deep: true
}
},
methods: {
// 初始化
init() {
this.pid = this.$route.params.id
this.isEdit = !!this.pid
// 获取商品分组
this.$store.dispatch('getGroups')
if (this.isEdit) {
// 修改渲染
this.editRender()
} else {
const query = this.$route.query
if (query.params) {
try {
const params = JSON.parse(decodeURIComponent(query.params))
Object.assign(this.ruleForm, params)
if (params.goodTypeId === '21') {
this.isTicket = true
}
} catch (error) {
console.log(error)
}
}
this.loading = false
}
},
// 修改渲染
editRender(data) {
// 请求接口
getGoodsList({ id: this.pid }).then(response => {
const data = response.data
Object.assign(this.ruleForm, data)
// 商品分组
this.goodsGroupValue = data.goodGroupToList.map(item => item.id)
// 隐藏加载动画
this.loading = false
})
},
// 确定
onSubmit() {
let isPass = true
const [firstImage = {}] = this.ruleForm.goodPhotoList
const { imageUrl = '', imageWidth = null, imageHeight = null } = firstImage
Object.assign(this.ruleForm, { imageUrl, imageWidth, imageHeight })
// 基本信息表单校验
this.$refs.ruleForm.validate(valid => {
if (!valid) {
isPass = false
return false
}
})
// 规格明细校验
if (
isPass &&
this.ruleForm.skuKeyValueList &&
this.ruleForm.skuKeyValueList.length &&
this.ruleForm.goodStockList &&
this.ruleForm.goodStockList.length
) {
this.$lodash.forEach(this.ruleForm.goodStockList, item => {
if (isNaN(parseFloat(item.sellPrice)) || isNaN(parseFloat(item.balanceCount))) {
this.$message({ message: '请完善规格明细表单', type: 'error' })
isPass = false
return false
}
})
if (!isPass) {
return false
}
}
if (isPass) {
this.isEdit ? this.onEdit() : this.onAdd()
} else {
this.$message({ message: '请完善表单信息', type: 'error' })
}
},
// 取消
onCancel() {
// 返回上一页
this.$router.back()
},
// 成功回调
onSuccess(data) {
const redirectUri = this.$route.query.redirect_uri
if (redirectUri) {
this.$router.push({
path: decodeURIComponent(redirectUri),
query: { goods_id: data.id }
})
} else {
// 返回商品列表
this.$router.push('/goods')
}
},
// 确定添加商品
onAdd() {
addGoods(this.ruleForm).then(response => {
this.$message({
message: '保存商品成功',
type: 'success'
})
// 成功回调
this.onSuccess(response.data)
})
},
// 确定修改商品
onEdit() {
updateGoods(this.ruleForm).then(response => {
this.$message({
message: '保存商品成功',
type: 'success'
})
// 成功回调
this.onSuccess(response.data)
})
},
imageListUploaded(file) {
this.ruleForm.goodPhotoList.push(file)
},
imageListRemove(url) {
const index = this.ruleForm.goodPhotoList.findIndex(item => item.imageUrl === url)
this.ruleForm.goodPhotoList.splice(index, 1)
}
},
beforeMount() {
this.init()
}
}
</script>
<style lang="scss" scoped>
.module-item {
max-width: 800px;
}
.module-item-title {
font-size: 18px;
font-weight: 700;
margin-bottom: 16px;
}
.module-item-content {
}
</style>
<template>
<page-container v-loading="loading" element-loading-text="拼命加载中">
<!-- 基本信息 -->
<info-list :options="options" :data="detail"></info-list>
</page-container>
</template>
<script>
// 组件
import PageContainer from '@/components/common/pageContainer'
import InfoList from '@/components/common/infoList'
// 接口
import { viewGoodsGroup } from '@/api/goods'
export default {
components: { PageContainer, InfoList },
data() {
return {
pid: '', // ID
loading: true, // 加载中
options: [{ label: '分组名称', code: 'name' }],
detail: {} // 详情页数据
}
},
created() {
this.init()
},
methods: {
// 初始化
init() {
this.pid = this.$route.params.id
this.getDetail()
},
// 获取详情数据
getDetail() {
viewGoodsGroup({ id: this.pid }).then(response => {
this.detail = response.data
this.loading = false
})
}
}
}
</script>
<template>
<el-form
:model="ruleForm"
:rules="rules"
:hide-required-asterisk="true"
label-width="70px"
ref="ruleForm"
@submit.native.prevent
>
<el-form-item label="分组名称" prop="group_name">
<el-input placeholder="请输入商品分组名称" v-model="ruleForm.group_name"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="onCancel">取消</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</el-form-item>
</el-form>
</template>
<script>
// 接口
import { addGroup, updateGroup } from '@/api/goods'
export default {
props: { isEdit: { type: Boolean, default: false }, data: { type: Object, default: () => {} } },
data() {
return {
ruleForm: { group_name: '', shop_id: this.$store.state.shopId },
rules: {
group_name: [{ required: true, message: '商品分组不能少于一个字或者多余50个字', trigger: 'blur' }]
}
}
},
methods: {
// 初始化
init() {
if (this.isEdit) {
// 修改渲染
this.editRender()
}
},
// 修改渲染
editRender() {
Object.assign(this.ruleForm, this.data)
},
// 取消
onCancel() {
this.$refs.ruleForm.resetFields()
this.$emit('cancel')
},
// 确定
onSubmit() {
this.$refs.ruleForm.validate().then(() => {
this.isEdit ? this.onEdit() : this.onAdd()
})
},
// 确定添加商品
onAdd() {
addGroup(this.ruleForm).then(this.handleSuccess)
},
// 确定修改商品
onEdit() {
updateGroup(this.ruleForm).then(this.handleSuccess)
},
// 成功
handleSuccess(response) {
this.$message({ message: '保存成功', type: 'success' })
this.$emit('success', response)
}
},
beforeMount() {
this.init()
}
}
</script>
<template>
<page-container>
<table-list v-bind="tableOptions" :hasPagination="false" ref="list">
<template #header-aside>
<el-button type="primary" @click="handleAdd">新建商品分组</el-button>
</template>
<template v-slot:table-x="{ row }">
<el-button type="text" @click="handleUpdate(row)">编辑</el-button>
<el-popconfirm confirm-button-text="确认" title="确定要删除该分组吗?" @confirm="handleRemove(row)">
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
<el-button type="text" @click="handlePromote(row)">推广</el-button>
</template>
</table-list>
<el-dialog :title="title" width="30%" :visible.sync="dialogVisible">
<group-edit
:isEdit="isEdit"
:data="editRaw"
@success="handleSuccess"
@cancel="dialogVisible = false"
v-if="dialogVisible"
/>
</el-dialog>
</page-container>
</template>
<script>
// 组件
import PageContainer from '@/components/common/pageContainer'
import TableList from '@/components/TableList'
import GroupEdit from './edit'
// 接口
import { getGroupList, deleteGroup } from '@/api/goods'
export default {
components: { PageContainer, TableList, GroupEdit },
data() {
return {
dialogVisible: false,
isEdit: false,
editRaw: {}
}
},
computed: {
shopId() {
return this.$store.state.shopId
},
groupList() {
return this.$store.state.groups
},
title() {
return this.isEdit ? '编辑商品分组' : '新建商品分组'
},
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getGroupList,
params: { shop_id: this.shopId, group_name: '' }
},
filters: [{ type: 'input', prop: 'group_name', placeholder: '搜索分组' }],
columns: [
{ label: '分组名称', prop: 'group_name' },
{ label: '商品数', prop: 'count', width: 100 },
{ label: '创建时间', prop: 'create_time', width: 200 },
{ label: '操作', slots: 'table-x', align: 'right' }
],
data: this.groupList
}
}
},
methods: {
handleAdd() {
this.isEdit = false
this.dialogVisible = true
},
// 编辑
handleUpdate(row) {
this.isEdit = true
this.dialogVisible = true
this.editRaw = row
},
handleSuccess() {
this.dialogVisible = false
this.$refs.list.refetch()
},
// 删除
handleRemove(row) {
deleteGroup({ shop_id: this.shopId, group_id: row.group_id }).then(res => {
this.$refs.list.refetch()
})
},
// 推广
handlePromote() {}
},
beforeMount() {
// 获取商品分组
this.$store.dispatch('getGroups')
}
}
</script>
<template>
<page-container>
<!--列表-->
<table-list v-bind="tableOptions" ref="list">
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane :label="item.label" :name="item.name" v-for="item in tabList" :key="item.name"></el-tab-pane>
</el-tabs>
<template #header-aside>
<router-link to="/goods/add">
<el-button type="primary">发布商品</el-button>
</router-link>
</template>
<template v-slot:table-x="{ row }">
<el-button type="text" @click="handleUpdate(row)">编辑</el-button>
<el-button type="text" @click="handlePromote(row)">推广</el-button>
<el-button type="text" @click="handleCopy(row)">复制</el-button>
</template>
</table-list>
</page-container>
</template>
<script>
// 组件
import TableList from '@/components/TableList'
import PageContainer from '@/components/common/pageContainer'
// 接口
import { getGoodsList } from '@/api/goods'
export default {
components: { PageContainer, TableList },
data() {
return {
activeName: '1',
tabList: [
{ label: '全部', name: '1' },
{ label: '销售中', name: '2' },
{ label: '已售罄', name: '3' },
{ label: '仓库中', name: '4' }
]
}
},
computed: {
shopId() {
return this.$store.state.shopId
},
groupList() {
return this.$store.state.groups
},
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getGoodsList,
params: { shop_id: this.shopId, spu_name: '贵州空气', group_id: '' }
},
filters: [
{
type: 'input',
prop: 'spu_name',
placeholder: '请输入商品名称'
},
{
type: 'select',
prop: 'group_id',
options: this.groupList,
labelKey: 'group_name',
valueKey: 'group_id'
}
],
columns: [
{ label: '商品名', prop: 'spu_name' },
{ label: '价格(元)', prop: 'price' },
{ label: '访问量', prop: 'view' },
{ label: '库存', prop: 'stock' },
{ label: '销量', prop: 'goodSocialCount' },
{ label: '创建时间', prop: 'create_time' },
{
label: '商品状态',
prop: 'status',
computed({ row }) {
const map = { 1: '仓库中', 2: '销售中', 3: '已售罄', 4: '下架', 5: '已删除' }
return map[row.status]
}
},
{ label: '操作', slots: 'table-x' }
]
}
}
},
methods: {
handleClick() {
this.$refs.list.refetch(true)
},
// 编辑
handleUpdate() {},
// 推广
handlePromote() {},
// 复制
handleCopy() {}
},
beforeMount() {
// 获取商品分组
this.$store.dispatch('getGroups')
}
}
</script>
<template>
<page-container v-loading="loading" element-loading-text="拼命加载中">
<el-container>
<el-aside width="300px">
<div style="text-align: left; margin-left: 20px">
<div>
<br />
<h2>订单信息</h2>
</div>
<div style="font-size: 10px; color: #999">
<br />
订单编号: {{ dataDetail.id }} <br /><br />订单类型: 普通订单 <br /><br />订单来源: 其他 <br />
<br />付款方式: 微信安全支付-代销 <br /><br />买家: {{ dataDetail.buyerSubject }}
<br /><br />---------------------------------- <br /><br />配送方式: 快递配送 <br /><br />收货信息:
{{ address }} <br /><br />买家留言: - <br /><br />
</div>
</div>
</el-aside>
<el-main>
<div style="text-align: left; margin-left: 20px">
<div>
<h2>状态信息</h2>
</div>
<br />
<div style="font-size: 12px; color: #999">
<a>下单时间:{{ dataDetail.createTime | formatDateTime }}</a>
<a><br /><br />支付时间:{{ dataDetail.payTime | formatDateTime }}</a>
<a><br /><br />支付状态:{{ updatepayState(dataDetail.payState) }}</a>
<a v-if="dataDetail.tradeState > 3"><br /><br />发货时间:{{ dataDetail.shipTime | formatDateTime }}</a>
<a v-if="dataDetail.payState === 110"
><br /><br />完成时间:{{ dataDetail.completeTime | formatDateTime }}</a
>
<a v-if="dataDetail.payState === 110"><br /><br />退款时间:{{ dataDetail.refundTime | formatDateTime }}</a>
<a><br /><br />订单状态:{{ updateTradeState(dataDetail.tradeState) }}</a>
<br /><br />
<el-button v-if="dataDetail.tradeState === 3" @click="Clickshipment(dataDetail)" type="text"
>发货</el-button
>
<el-button
v-if="dataDetail.payState === 60 && dataDetail.goodAmount > 0"
@click="Clickrefund(dataDetail)"
type="text"
>退款</el-button
>
<el-button
v-if="dataDetail.payState === 1 || dataDetail.payState === 110"
@click="Clickclose(dataDetail)"
type="text"
>关闭订单</el-button
>
</div>
</div>
</el-main>
<div>
<el-dialog title="发货" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="订单编号" :label-width="formLabelWidth">
<el-input v-model="form.id" auto-complete="off" :disabled="true"></el-input>
</el-form-item>
<el-form-item label="快递公司" :label-width="formLabelWidth">
<el-select v-model="form.logisticsCompanyName">
<el-option
v-for="(company, index) in expressList"
:key="`express_${index}`"
:label="company.name"
:value="company.name"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="快递单号" :label-width="formLabelWidth">
<el-input v-model="form.logisticsNo" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="Btnshipment">确 定</el-button>
</div>
</el-dialog>
</div>
</el-container>
<br />
<div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="itemImageUrl" label="" width="80">
<template slot-scope="scope">
<img :src="scope.row.itemImageUrl" alt="" style="width: 60px; height: 60px" />
</template>
</el-table-column>
<el-table-column prop="itemName" label="商品" width="280"></el-table-column>
<el-table-column prop="itemPrice" label="价格" width="180"></el-table-column>
<el-table-column prop="itemCount" label="数量" width="180"></el-table-column>
<el-table-column prop="youhui" label="优惠" width="180"></el-table-column>
<el-table-column prop="xiaoji" label="小计"></el-table-column>
<!-- <el-table-column prop="status" label="状态"></el-table-column> -->
</el-table>
</div>
<br />
<div style="text-align: right; margin-left: 20px">
订单共{{ tableData.length }}件商品,总计: <a style="color: red">¥{{ dataDetail.goodAmount }}</a
>(含运费 ¥0.00)
</div>
</page-container>
</template>
<script>
import PageContainer from '@/components/common/pageContainer'
import { getOrderDetail, closeOrder, refundOrder, Shipment } from '@/api/order'
import * as utils from '@/utils/index'
export default {
components: { PageContainer },
data() {
return {
loading: false,
dataDetail: {},
tableData: [],
dialogFormVisible: false,
form: {
id: null,
logisticsCompanyName: null,
logisticsNo: null
},
formLabelWidth: '120px',
expressList: [
{ name: '圆通快递' },
{ name: '中通快递' },
{ name: '顺丰快递' },
{ name: '韵达快递' },
{ name: '申通快递' },
{ name: '天天快递' },
{ name: '百世快递' },
{ name: '邮政' },
{ name: '德邦物流' }
]
}
},
computed: {
// 收货地址
address() {
let tradeAddress = this.dataDetail.tradeAddress || null
let results = ''
if (tradeAddress) {
results =
tradeAddress.province +
' ' +
tradeAddress.city +
' ' +
tradeAddress.district +
' ' +
tradeAddress.address +
' ' +
tradeAddress.name +
' ' +
tradeAddress.phone
}
return results
}
},
filters: {
formatDateTime: utils.formatDateTime
},
created() {
this.pid = this.$route.params.id
this.getDetail()
},
methods: {
// 获取详情数据
getDetail() {
getOrderDetail({ id: this.pid }).then(response => {
this.dataDetail = response.data
this.tableData = response.data.tradeItemList.map(item => {
let xiaoji = item.itemPrice * item.itemCount
return Object.assign({}, item, {
xiaoji,
youhui: '--',
status: '完成'
})
})
})
},
// 关闭订单
Clickclose(data) {
this.$confirm('确定要关闭订单?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
this.closeOrder0(data.id)
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消关闭'
})
})
},
// 关闭
closeOrder0(ArticleId) {
closeOrder({ id: ArticleId }).then(response => {
this.$message({
message: response.message,
type: 'success'
})
// 更改发货状态
this.dataDetail.tradeState = response.data.tradeState
this.dataDetail.payState = response.data.payState
})
},
// 确定退款
Clickrefund(data) {
this.$confirm('确定要退款?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
this.Clickrefund0(data.id)
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消退款'
})
})
},
// 退款
Clickrefund0(ArticleId) {
refundOrder({ id: ArticleId }).then(response => {
this.$message({
message: response.message,
type: 'success'
})
// 更改发货状态
this.dataDetail.tradeState = response.data.tradeState
this.dataDetail.payState = response.data.payState
// 退款提醒
this.$alert('后台操作完成,请前往手机客户端完成退款流程', '退款提醒', {
confirmButtonText: '确定',
callback: action => {
this.$message({
type: 'success',
message: response.message
})
}
})
})
},
// 发货表单
Clickshipment(data) {
this.form.id = data.id
this.dialogFormVisible = true
},
// 确定发货
Btnshipment() {
this.dialogFormVisible = false
Shipment(this.form).then(response => {
this.$message({
message: response.message,
type: 'success'
})
// 更改发货状态
this.dataDetail.tradeState = response.data.tradeState
})
},
// 转换订单状态
updateTradeState(val) {
switch (val) {
case 0:
return '待付款'
case 3:
return '待发货'
case 6:
return '待收货'
case 10:
return '取消'
case 20:
return '完成'
case 40:
return '已退款'
}
},
// 转换支付状态
updatepayState(val) {
switch (val) {
case 1:
return '未支付'
case 30:
return ''
case 40:
return '用户正在支付'
case 50:
return '支付错误'
case 60:
return '支付成功'
case 70:
return '支付关闭'
case 80:
return '交易结束'
case 100:
return '退款中'
case 110:
return '已退款'
}
}
}
}
</script>
<style scoped>
.el-header,
.el-footer {
background-color: #b3c0d1;
color: #333;
text-align: center;
line-height: 60px;
}
.el-aside {
text-align: center;
border: 1px solid #e5e5e5;
/* line-height: 200px; */
}
.el-main {
border: 1px solid #e5e5e5;
text-align: center;
/* line-height: 160px; */
}
body > .el-container {
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
line-height: 320px;
}
.el-table th {
background: #f4f4f4 !important;
font-size: 10px;
}
</style>
<template>
<page-container>
<!--列表-->
<table-list v-bind="tableOptions" ref="list">
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane :label="item.label" :name="item.name" v-for="item in tabList" :key="item.name"></el-tab-pane>
</el-tabs>
<template #header-aside>
<router-link to="/goods/add">
<el-button type="primary">新增订单</el-button>
</router-link>
</template>
<template v-slot:table-x="{ row }">
<el-button type="text" @click="handleUpdate(row)">编辑</el-button>
<el-button type="text" @click="handlePromote(row)">推广</el-button>
<el-button type="text" @click="handleCopy(row)">复制</el-button>
</template>
</table-list>
</page-container>
</template>
<script>
// 组件
import TableList from '@/components/TableList'
import PageContainer from '@/components/common/pageContainer'
// 接口
import { getGoodsList } from '@/api/goods'
export default {
components: { PageContainer, TableList },
data() {
return {
activeName: '1',
tabList: [
{ label: '全部', name: '1' },
{ label: '代付款', name: '2' },
{ label: '待发货', name: '3' },
{ label: '已发货', name: '4' },
{ label: '已完成', name: '5' },
{ label: '已关闭', name: '6' },
{ label: '售后中', name: '7' }
]
}
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getGoodsList,
params: { shop_id: this.$store.state.shopId }
},
filters: [
{
type: 'input',
prop: 'name',
placeholder: '请输入商品名称'
},
{
type: 'select',
prop: 'group_id',
options: [
{ label: '活动', value: 1 },
{ label: '报名费', value: 2 },
{ label: '学费', value: 3 },
{ label: '活动', value: 4 }
]
}
],
columns: [
{ label: '商品', prop: 'name' },
{ label: '单价(元)/数量', prop: 'sellPrice' },
{ label: '售后', prop: 'sellPrice' },
{ label: '买家 / 收货人', prop: 'balanceCount' },
{ label: '配送方式', prop: 'sellCount' },
{ label: '实收金额(元)', prop: 'createTime' },
{ label: '订单状态', prop: 'createTime' },
{ label: '操作', slots: 'table-x' }
]
}
}
},
methods: {
handleClick() {
this.$refs.list.refetch(true)
},
// 编辑
handleUpdate() {},
// 推广
handlePromote() {},
// 复制
handleCopy() {}
}
}
</script>
<template>
<div>
<div class="order-list-header" id="searchBar">
<div class="inner" :class="{fixed: isFixed}">
<div class="item goods-cell">商品</div>
<div class="item price-cell">单价/数量</div>
<div class="item aftermarket-cell">售后</div>
<div class="item customer-cell">买家</div>
<div class="item time-cell">下单时间</div>
<div class="item state-cell">订单状态</div>
<div class="item check-cell">核销状态</div>
<div class="item pay-price-cell">实付金额</div>
</div>
</div>
<div v-for="(item,index) in dataList" :key="index" class="order-list-item">
<div class="order-list-item-header">
<div class="order-list-item-header-row">
<div class="order-no">订单号:{{item.id}}</div>
<div class="order-tools">
<el-button @click="ClickDetail(item)" type="text" size="small">查看详情</el-button>
<el-button v-if="item.tradeState===3" @click="Clickshipment(item)" type="text" size="small">发货</el-button>
<el-button v-if="item.payState===60&&item.goodAmount>0" @click="Clickrefund(item)" type="text" size="small">退款</el-button>
<el-button v-if="item.payState===1||item.payState===110" @click="Clickclose(item)" type="text" size="small">关闭订单</el-button>
</div>
</div>
</div>
<table class="order-list-item-table">
<tbody class="order-list-item-body">
<tr class="trlis">
<td class="goods-item-cell-td">
<div class="goods-item-cell">
<div class="pic"><img :src="item.tradeItemList[0].itemImageUrl" /></div>
<div class="content">
<h3>
<router-link :to="`/goods/edit/${item.tradeItemList[0].itemId}`" target="_blank">{{item.tradeItemList[0].itemName}}</router-link>
</h3>
</div>
<div class="tools">
<p>{{item.tradeItemList[0].itemPrice}}</p>
<p>({{item.tradeItemList[0].itemCount}}件)</p>
</div>
</div>
</td>
<td class="aftermarket-cell" :rowspan="item.tradeItemList.length">
<div class="order-td-cell">{{item.shouhou}}</div>
</td>
<td class="customer-cell" :rowspan="item.tradeItemList.length">
<div class="order-td-cell">{{item.buyer.id}}<br />{{item.buyer.nickName}}<br />{{item.buyer.phone}}</div>
</td>
<td class="time-cell" :rowspan="item.tradeItemList.length">
<div class="order-td-cell">{{item.createTime | formatDateTime}}</div>
</td>
<td class="state-cell" :rowspan="item.tradeItemList.length">
<div class="order-td-cell">{{updateTradeState(item.tradeState)}}</div>
</td>
<td class="check-cell" :rowspan="item.tradeItemList.length">
<div class="order-td-cell">{{(item.tradeTicket && item.tradeTicket.ticketState || '') | formatTicketState}}</div>
</td>
<td class="pay-price-cell" :rowspan="item.tradeItemList.length">
<div class="order-td-cell">{{item.goodAmount}}</div>
</td>
</tr>
<tr v-for="(goods, index) in item.tradeItemList" :key="goods.id" v-if="index !== 0">
<td class="goods-item-cell-td">
<div class="goods-item-cell">
<div class="pic"><img :src="goods.itemImageUrl" /></div>
<div class="content">
<h3>
<router-link :to="`/goods/edit/${goods.itemId}`" target="_blank">{{goods.itemName}}</router-link>
</h3>
</div>
<div class="tools">
<p>{{goods.itemPrice}}</p>
<p>({{goods.itemCount}}件)</p>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="main-page" v-if="pageTotal">
<el-pagination @size-change="handleSizeChange " @current-change="handleCurrentChange " :current-page="currentPage " :page-sizes="[10] " :page-size='pagesize' layout="total, sizes, prev, pager, next, jumper " :total='pageTotal'>
</el-pagination>
</div>
<!-- 发货 modal -->
<el-dialog title="发货" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="订单编号" :label-width="formLabelWidth">
<el-input v-model="form.id" auto-complete="off" :disabled="true"></el-input>
</el-form-item>
<el-form-item label="快递公司" :label-width="formLabelWidth">
<el-select v-model="form.logisticsCompanyName">
<el-option v-for="(company, index) in expressList" :key="`express_${index}`" :label="company.name" :value="company.name"></el-option>
</el-select>
</el-form-item>
<el-form-item label="快递单号" :label-width="formLabelWidth">
<el-input v-model="form.logisticsNo" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="Btnshipment">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script type="text/ecmascript-6">
import { getOrderList, closeOrder, refundOrder, Shipment } from '@/api/order'
import * as utils from '@/utils/index'
import { Loading } from 'element-ui'
export default {
components: {},
data() {
return {
dataList: [],
dataList0: [],
currentPage: 1,
pagesize: 10,
pageTotal: 0,
param: {
id: null,
tradeState: null,
startTime: null,
endTime: null,
goodName: null,
logisticsNo: null,
tradeAddressName: null,
tradeAddressPhone: null,
logisticsCompanyName: null,
page: {
index: 1,
size: 10
}
},
dialogFormVisible: false,
form: {
id: null,
logisticsCompanyName: null,
logisticsNo: null
},
formLabelWidth: '120px',
expressList: [
{ name: '圆通快递' },
{ name: '中通快递' },
{ name: '顺丰快递' },
{ name: '韵达快递' },
{ name: '申通快递' },
{ name: '天天快递' },
{ name: '百世快递' },
{ name: '邮政' },
{ name: '德邦物流' }
],
isFixed: false,
loadingInstance: null
}
},
filters: {
// 日期
formatDateTime: utils.formatDateTime,
// 核销状态
formatTicketState(value) {
let options = { 0: '未使用', 1: '已使用' }
return options[value] || ''
}
},
methods: {
objectSpanMethod({ row, column, rowIndex, columnIndex }) {},
// 更改一页显示个数
handleSizeChange(pagesize) {
this.pagesize = pagesize
this.freshPage()
},
// 跳转页面
handleCurrentChange(currentPage) {
this.currentPage = currentPage
this.dataList = []
this.param.page.index = currentPage
this.getListData()
},
// 获取列表数据
getListData() {
this.btnloading()
getOrderList(this.param).then(response => {
this.currentPage = this.param.page.index
this.dataList = response.data.list
this.pageTotal = parseInt(response.data.totalCount + '') || 0
this.closeloading()
})
},
// 转换订单状态
updateTradeState(val) {
switch (val) {
case 0:
return '待付款'
case 3:
return '待发货'
case 6:
return '待收货'
case 10:
return '取消'
case 20:
return '完成'
case 40:
return '已退款'
}
},
// 刷新页面
getfresh(remote) {
this.param = JSON.parse(
JSON.stringify(this.$lodash.defaults(remote, this.param))
)
if (remote.tradeState === '-1') {
this.param.tradeState = null
}
this.toRegion(remote.region, remote.ding)
this.getListData()
},
toRegion(val, val1) {
switch (val) {
case 'id':
return (this.param.id = val1)
case 'logisticsNo':
return (this.param.logisticsNo = val1)
case 'logisticsCompanyName':
return (this.param.logisticsCompanyName = val1)
case 'tradeAddressName':
return (this.param.tradeAddressName = val1)
case 'tradeAddressPhone':
return (this.param.tradeAddressPhone = val1)
}
},
// 跳转详情页
ClickDetail(val) {
this.$router.push('/order/detail/' + val.id)
},
// 关闭订单
Clickclose(data) {
this.$confirm('确定要关闭订单?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
this.closeOrder0(data.id)
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消关闭'
})
})
},
// 关闭
closeOrder0(ArticleId) {
closeOrder({ id: ArticleId }).then(response => {
this.$message({
message: response.message,
type: 'success'
})
// 刷新列表
this.dataList = []
this.getListData()
})
},
// 确定退款
Clickrefund(data) {
this.$confirm('确定要退款?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
this.Clickrefund0(data.id)
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消退款'
})
})
},
// 退款
Clickrefund0(ArticleId) {
refundOrder({ id: ArticleId }).then(response => {
this.$message({
message: response.message,
type: 'success'
})
// 刷新列表
this.dataList = []
this.getListData()
})
},
// 发货表单
Clickshipment(data) {
this.form = Object.assign({}, this.form, data)
this.dialogFormVisible = true
},
// 确定发货
Btnshipment() {
this.dialogFormVisible = false
Shipment(this.form).then(response => {
this.$message({
message: response.message,
type: 'success'
})
// 刷新列表
this.dataList = []
this.getListData()
})
},
handleScroll() {
// 改变元素#searchBar的top值
let scrollTop =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop
let offsetTop = document.querySelector('#searchBar').offsetTop
if (scrollTop >= offsetTop) {
this.isFixed = true
} else {
this.isFixed = false
}
},
btnloading() {
this.loadingInstance = Loading.service({
class: 'order-list-item-table',
body: false
})
},
closeloading() {
this.$nextTick(() => {
// 以服务的方式调用的 Loading 需要异步关闭
this.loadingInstance.close()
})
}
},
created() {
this.dataList = []
this.getListData()
},
mounted() {
// 给window添加一个滚动滚动监听事件
window.addEventListener('scroll', this.handleScroll)
},
destroyed() {
// 离开该页面需要移除这个监听的事件
window.removeEventListener('scroll', this.handleScroll)
}
}
</script>
<style>
/* 表头 */
.order-list-header {
position: relative;
height: 40px;
margin-bottom: 10px;
}
.order-list-header .inner {
display: flex;
position: sticky;
top: 0;
background-color: #f8f8f8;
align-items: center;
height: 40px;
z-index: 1;
}
.order-list-header .inner.fixed {
position: fixed;
left: 200px;
right: 40px;
}
.order-list-header .item {
text-align: center;
padding: 10px;
}
.order-list-header .goods-cell {
flex: 1;
text-align: left;
}
.order-list-header .price-cell {
width: 10%;
text-align: right;
}
.order-list-header .aftermarket-cell {
width: 10%;
}
.order-list-header .customer-cell {
width: 10%;
word-break: break-all;
}
.order-list-header .time-cell {
width: 10%;
}
.order-list-header .state-cell {
width: 15%;
}
.order-list-header .check-cell {
width: 15%;
}
.order-list-header .pay-price-cell {
width: 10%;
}
/* table */
.order-list-item {
font-size: 12px;
margin: 10px 0;
}
.order-list-item-header {
background-color: #f8f8f8;
border: 1px solid #e5e5e5;
border-bottom: none;
padding: 10px;
}
.order-list-item-header .order-list-item-header-row {
display: flex;
justify-content: space-between;
}
.order-list-item-header .order-no {
line-height: 32px;
}
.order-list-item-table {
width: 100%;
}
.order-list-item-table td {
border: 1px solid #e5e5e5;
}
.order-td-cell {
padding: 10px;
text-align: center;
}
.goods-item-cell {
display: flex;
padding: 10px;
}
.goods-item-cell .pic {
width: 60px;
height: 60px;
}
.goods-item-cell .pic img {
width: 100%;
height: 100%;
}
.goods-item-cell .content {
flex: 1;
padding: 0 10px;
}
.goods-item-cell .content h3 {
font-weight: normal;
color: #0000ff;
}
.order-list-item-body .aftermarket-cell {
width: 10%;
}
.order-list-item-body .customer-cell {
width: 10%;
word-break: break-all;
}
.order-list-item-body .time-cell {
width: 10%;
}
.order-list-item-body .state-cell {
width: 15%;
}
.order-list-item-body .check-cell {
width: 15%;
}
.order-list-item-body .pay-price-cell {
width: 10%;
}
</style>
<template>
<page-container>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" style="width: 400px">
<el-form-item label="客户电话" prop="tel">
<el-input v-model="ruleForm.tel"></el-input>
</el-form-item>
<el-form-item label="详细地址" prop="address">
<el-input v-model="ruleForm.address">
<template #append>
<el-button>搜索地图</el-button>
</template>
</el-input>
</el-form-item>
</el-form>
</page-container>
</template>
<script>
import PageContainer from '@/components/common/pageContainer'
export default {
components: { PageContainer },
data() {
return {
ruleForm: { tel: '', address: '' },
rules: {
tel: [{ required: true, message: '必填', trigger: 'blur' }],
address: [{ required: true, message: '必填', trigger: 'blur' }]
}
}
},
methods: {}
}
</script>
<style lang="scss" scoped></style>
<template>
<page-container>
<div class="module-item">
<div class="module-item-hd">
<h2 class="module-item-hd__title">商品规格</h2>
</div>
<div class="module-item-bd">
<div class="sku-item" v-for="(item, index) in skuList" :key="index">
<div class="sku-item-row">
<div class="sku-item-row-left">
<label class="label">规格{{ index + 1 }}</label>
<el-input v-model="item.spec_name" placeholder="规格名" />
</div>
<div class="sku-item-row-right is-right">
<el-button type="text" @click="removeSku(item, index)">删除规格</el-button>
</div>
</div>
<div class="sku-item-row">
<div class="sku-item-row-left">
<el-input placeholder="规格值" />
</div>
<div class="sku-item-row-right">
<div class="tools">
<i class="el-icon-circle-plus-outline" @click="addSkuValue(item, index)"></i>
<i class="el-icon-delete" @click="removeSkuValue(item, index)"></i>
</div>
</div>
</div>
</div>
<el-button type="text" @click="addSku">
<i class="el-icon-circle-plus-outline"></i>&nbsp;&nbsp;创建新规格
</el-button>
</div>
</div>
</page-container>
</template>
<script>
import PageContainer from '@/components/common/pageContainer'
import { getSkuList } from '@/api/goods'
export default {
components: { PageContainer },
data() {
return {
ruleForm: { shop_name: '', shop_logo: '', shop_desc: '', shop_tel: '', shop_type: '1' },
rules: {
shop_name: [{ required: true, message: '必填', trigger: 'blur' }],
shop_logo: [{ required: true, message: '请上传', trigger: 'change' }],
shop_desc: [{ required: true, message: '必填', trigger: 'blur' }],
shop_tel: [{ required: true, message: '必填', trigger: 'blur' }]
},
skuList: []
}
},
computed: {
shopId() {
return this.$store.state.shopId
}
},
methods: {
getSkuList() {
getSkuList({ shop_id: this.shopId }).then(res => {
this.skuList = res.data
})
},
// 添加规格
addSku() {
this.skuList.push({ spec_name: '' })
},
// 删除规格
removeSku(item) {},
addSkuValue() {},
// 删除规格值
removeSkuValue(item) {}
},
beforeMount() {
this.getSkuList()
}
}
</script>
<style lang="scss" scoped>
.module-item {
max-width: 800px;
}
.module-item-hd {
display: flex;
}
.module-item-hd__title {
flex: 1;
font-size: 18px;
font-weight: 700;
margin-bottom: 16px;
}
.sku-item {
background-color: #f9f9f9;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
}
.sku-item-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.sku-item-row + .sku-item-row {
margin-top: 10px;
}
.sku-item-row-left {
position: relative;
flex: 1;
padding-left: 60px;
.label {
position: absolute;
left: 0;
top: 50%;
font-size: 16px;
transform: translateY(-50%);
}
}
.sku-item-row-right {
flex: 1;
margin-left: 20px;
.tools {
float: left;
display: flex;
align-items: center;
justify-content: center;
height: 40px;
background: rgba(255, 255, 255, 0.9);
border-radius: 4px;
i {
margin: 5px;
padding: 5px;
font-size: 20px;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: rgba(0, 0, 0, 0.03);
}
}
}
}
.is-right {
text-align: right;
}
</style>
<template>
<page-container>
<div class="module-item">
<div class="module-item-hd">
<h2 class="module-item-hd__title">基础信息</h2>
<el-button type="text" @click="handleUpdate">编辑</el-button>
</div>
<div class="module-item-bd shop-info">
<img :src="shop.shop_logo" class="shop-logo" />
<div class="shop-info-content">
<h3 class="title">{{ shop.shop_name }}</h3>
<div class="item">
<label class="label">店铺编号:</label><span class="content">{{ shop.shop_id }}</span>
</div>
<div class="item">
<label class="label">创建时间:</label><span class="content">{{ shop.create_time }}</span>
</div>
<div class="item">
<label class="label">联系电话:</label><span class="content">{{ shop.tel }}</span>
</div>
</div>
</div>
</div>
<el-dialog title="店铺信息编辑" width="800px" :visible.sync="dialogVisible">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px">
<el-form-item label="店铺名称" prop="shop_name">
<el-input v-model="ruleForm.shop_name" placeholder="请输入店铺、品牌、机构的全称"></el-input>
</el-form-item>
<el-form-item label="店铺头像" prop="shop_logo">
<app-upload v-model="ruleForm.shop_logo"></app-upload>
</el-form-item>
<el-form-item label="店铺有效期" prop="end_time">
<el-date-picker
v-model="ruleForm.end_time"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择店铺有效期"
>
</el-date-picker>
</el-form-item>
<el-form-item label="店铺简介" prop="shop_desc">
<el-input type="textarea" v-model="ruleForm.shop_desc" :autosize="{ minRows: 4, maxRows: 6 }"></el-input>
</el-form-item>
<el-form-item label="联系电话" prop="shop_tel">
<el-input v-model="ruleForm.shop_tel"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="onSubmit">确认</el-button>
</el-form-item>
</el-form>
</el-dialog>
</page-container>
</template>
<script>
import PageContainer from '@/components/common/pageContainer'
import AppUpload from '@/components/Upload'
export default {
components: { PageContainer, AppUpload },
data() {
return {
ruleForm: { shop_name: '', shop_logo: '', shop_desc: '', shop_tel: '', shop_type: '1' },
rules: {
shop_name: [{ required: true, message: '必填', trigger: 'blur' }],
shop_logo: [{ required: true, message: '请上传', trigger: 'change' }],
shop_desc: [{ required: true, message: '必填', trigger: 'blur' }],
shop_tel: [{ required: true, message: '必填', trigger: 'blur' }]
},
dialogVisible: false
}
},
computed: {
shop() {
return this.$store.state.shop
}
},
methods: {
handleUpdate() {
this.dialogVisible = true
this.ruleForm = Object.assign({}, this.shop)
},
onSubmit() {
this.$refs.ruleForm.validate().then(this.updateShop)
},
updateShop() {}
}
}
</script>
<style lang="scss" scoped>
.module-item {
}
.module-item-hd {
display: flex;
}
.module-item-hd__title {
flex: 1;
font-size: 18px;
font-weight: 700;
margin-bottom: 16px;
}
.shop-info {
display: flex;
}
.shop-logo {
width: 72px;
height: 72px;
border-radius: 50%;
overflow: hidden;
object-fit: cover;
}
.shop-info-content {
flex: 1;
.title {
margin-left: 30px;
margin-bottom: 20px;
font-size: 16px;
font-weight: 700;
color: #323233;
}
.item {
display: flex;
font-size: 14px;
margin-bottom: 12px;
.label {
text-align: right;
white-space: nowrap;
min-width: 100px;
line-height: 20px;
color: #646566;
}
.content {
flex: 1;
word-wrap: break-word;
word-break: break-all;
color: #323233;
}
}
}
</style>
<template>
<page-main title="创建店铺">
<el-card class="box-card">
<div class="form-container">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px">
<el-form-item label="店铺名称" prop="shop_name">
<el-input v-model="ruleForm.shop_name" placeholder="请输入店铺、品牌、机构的全称"></el-input>
</el-form-item>
<el-form-item label="店铺头像" prop="shop_logo">
<app-upload v-model="ruleForm.shop_logo"></app-upload>
</el-form-item>
<el-form-item label="店铺有效期" prop="end_time">
<el-date-picker
v-model="ruleForm.end_time"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择店铺有效期"
>
</el-date-picker>
</el-form-item>
<el-form-item label="店铺简介" prop="shop_desc">
<el-input type="textarea" v-model="ruleForm.shop_desc" :autosize="{ minRows: 4, maxRows: 6 }"></el-input>
</el-form-item>
<el-form-item label="联系电话" prop="shop_tel">
<el-input v-model="ruleForm.shop_tel"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">创建店铺</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
</page-main>
</template>
<script>
// 组件
import PageMain from '@/components/common/pageMain'
import AppUpload from '@/components/Upload'
// 接口
import { addShop } from '@/api/shop'
export default {
components: { PageMain, AppUpload },
data() {
return {
ruleForm: { shop_name: '', shop_logo: '', shop_desc: '', shop_tel: '', shop_type: '1' },
rules: {
shop_name: [{ required: true, message: '必填', trigger: 'blur' }],
shop_logo: [{ required: true, message: '请上传', trigger: 'change' }],
shop_desc: [{ required: true, message: '必填', trigger: 'blur' }],
shop_tel: [{ required: true, message: '必填', trigger: 'blur' }]
}
}
},
methods: {
onSubmit() {
this.$refs.ruleForm.validate().then(this.addShop)
},
addShop() {
addShop(this.ruleForm).then(response => {
// 进入店铺列表
this.$router.push({ path: '/shop' })
})
}
}
}
</script>
<template>
<page-container v-loading="loading" element-loading-text="拼命加载中">
<!-- 基本信息 -->
<info-list :options="options" :data="detailData"></info-list>
</page-container>
</template>
<script>
// 组件
import PageContainer from '@/components/common/pageContainer'
import InfoList from '@/components/common/infoList'
import { getShopDetail } from '@/api/shop'
export default {
components: { PageContainer, InfoList },
data() {
return {
pid: '', // ID
loading: true, // 加载中
options: [
{ label: '编号', code: 'id' },
{ label: '店铺名称', code: 'name' }
],
detailData: {} // 详情页数据
}
},
created() {
this.pid = this.$route.params.id
this.getDetail()
},
methods: {
// 获取详情数据
getDetail() {
this.loading = true
// 请求接口
getShopDetail({ id: this.pid }).then(response => {
this.detailData = response.data
this.loading = false
})
}
}
}
</script>
<template>
<page-main title="选择店铺">
<el-card class="box-card shop-card">
<template #header>
<div class="header">
<el-button type="primary" size="mini" @click="addShop">创建店铺</el-button>
</div>
</template>
<div class="shop-list" v-if="list.length">
<div class="item" v-for="item in list" :key="item.shop_id" @click="entryShop(item)">
<h2 class="title">{{ item.shop_name }}</h2>
<p>主体信息:{{ item.business_entity }}</p>
<p>有效期至:{{ item.end_time }}</p>
<span class="type">{{ typeName(item.shop_type) }}</span>
</div>
</div>
<div class="shop-tips" v-else>
<p><router-link to="/shop/add">创建</router-link>你的专属店铺</p>
</div>
</el-card>
</page-main>
</template>
<script>
// 组件
import PageMain from '@/components/common/pageMain'
// 接口
import { getShopList } from '@/api/shop'
export default {
components: { PageMain },
data() {
return {
list: []
}
},
computed: {
user() {
return this.$store.state.user
}
},
methods: {
typeName(value) {
const map = { 1: '微商城' }
return map[value]
},
// 获取店铺
getShop() {
getShopList().then(res => {
this.list = res.data || []
})
},
entryShop(item) {
this.$store.dispatch('setShopId', item.shop_id)
// 进入店铺概括
this.$router.push({ path: '/dashboard' })
},
// 创建店铺
addShop() {
this.$router.push('/shop/add')
}
},
beforeMount() {
this.getShop()
}
}
</script>
<style lang="scss">
.shop-card {
border: 0;
border-radius: 0;
}
.shop-card .el-card__header {
padding: 0 40px;
background: #f8f8f8;
border-bottom: 0;
}
.shop-card .header {
height: 70px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.shop-list {
overflow: hidden;
display: flex;
flex-wrap: wrap;
margin-left: -22px;
.item {
width: 300px;
height: 130px;
padding: 0 20px;
margin-left: 25px;
margin-bottom: 25px;
border-radius: 2px;
border: 1px solid #e5e5e5;
border-top: 3px solid #d5d7db;
background: #fff;
cursor: pointer;
color: #999;
box-sizing: border-box;
p {
padding-bottom: 5px;
}
}
.title {
margin-top: 18px;
line-height: 20px;
padding-bottom: 5px;
font-size: 14px;
color: #111;
font-weight: 400;
}
.type {
display: inline-block;
padding: 0 3px;
font-size: 12px;
line-height: 1.5;
color: var(--main-color);
border: 1px solid var(--main-color);
border-radius: 2px;
}
}
.shop-tips {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
</style>
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(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)
}
export default new Router({
routes,
mode: 'history', // 还有一个 hash 默认
fallback: true // 浏览器不支持 history时,自动改成 hash方式
})
/* layout */
import Layout from '@/components/layout/layout'
// /* 商品 */
// const GoodsEdit = () => import(/* webpackChunkName: "goods" */ '@/pages/goods/edit')
// /* 商品分组 */
// const GoodsGroup = () => import(/* webpackChunkName: "goods" */ '@/pages/goods/group/index')
// const GoodsGroupEdit = () => import(/* webpackChunkName: "goods" */ '@/pages/goods/group/edit')
// const GoodsGroupDetail = () => import(/* webpackChunkName: "goods" */ '@/pages/goods/group/detail')
// /* 订单 */
const orderList = () => import(/* webpackChunkName: "order" */ '@/pages/order/orderList')
// const orderDetail = () => import(/* webpackChunkName: "order" */ '@/pages/order/orderDetail')
export default [
{ path: '*', redirect: '/dashboard' },
{
path: '/dashboard',
component: Layout,
children: [
{
path: '',
component: () => import(/* webpackChunkName: "shop" */ '@/pages/dashboard/index'),
meta: { title: '首页' }
}
]
},
{
path: '/shop',
component: () => import(/* webpackChunkName: "shop" */ '@/pages/shop/list'),
meta: { title: '选择店铺' }
},
{
path: '/shop/add',
component: () => import(/* webpackChunkName: "shop" */ '@/pages/shop/add'),
meta: { title: '新建店铺' }
},
// 商品管理
{
path: '/goods',
component: Layout,
meta: { title: '商品' },
children: [
{
path: '',
component: () => import(/* webpackChunkName: "goods" */ '@/pages/goods/index'),
meta: { title: '商品列表' }
},
{
path: 'add',
component: () => import(/* webpackChunkName: "goods" */ '@/pages/goods/edit'),
meta: { title: '发布商品' }
},
{
path: 'edit/:id',
component: () => import(/* webpackChunkName: "goods" */ '@/pages/goods/edit'),
meta: { title: '编辑商品' }
}
]
},
// 商品分组
{
path: '/goods/group',
component: Layout,
meta: { title: '商品' },
children: [
{
path: '',
component: () => import(/* webpackChunkName: "goods-group" */ '@/pages/goods/group/index'),
meta: { title: '商品分组' }
}
// {
// path: 'add',
// component: GoodsGroupEdit,
// meta: { title: '新建商品分组' }
// },
// {
// path: 'edit/:id',
// component: GoodsGroupEdit,
// meta: { title: '编辑商品分组' }
// },
// {
// path: 'detail/:id',
// component: GoodsGroupDetail,
// meta: { title: '查看商品分组' }
// }
]
},
// 订单管理
{
path: '/order',
component: Layout,
meta: { title: '订单' },
children: [
{ path: '', component: orderList, meta: { title: '订单列表' } }
// {
// path: 'detail/:id',
// component: orderDetail,
// meta: { title: '订单详情' }
// }
]
},
// 设置
{
path: '/setting',
component: Layout,
meta: { title: '设置' },
children: [
{ path: '', redirect: 'info' },
{
path: 'info',
component: () => import(/* webpackChunkName: "setting" */ '@/pages/setting/info'),
meta: { title: '店铺信息' }
},
{
path: 'contact',
component: () => import(/* webpackChunkName: "setting" */ '@/pages/setting/contact'),
meta: { title: '联系我们' }
},
{
path: 'goods',
component: () => import(/* webpackChunkName: "setting" */ '@/pages/setting/goods'),
meta: { title: '商品设置' }
}
]
}
]
import Vue from 'vue'
import Vuex from 'vuex'
import { getUser, logout } from '@/api/base'
import { getShopList } from '@/api/shop'
import { getGroupList } from '@/api/goods'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
user: {},
shopId: window.localStorage.getItem('shopId') || '', // 店铺ID
shop: {}, // 店铺信息
groups: [] // 商品分组
},
mutations: {
setUser(state, user) {
state.user = user
},
setShopId(state, shopId) {
state.shopId = shopId
window.localStorage.setItem('shopId', shopId)
},
setShop(state, data) {
state.shop = data
},
setGroups(state, list) {
state.groups = list
}
},
actions: {
// 获取用户信息
getUser({ commit }) {
getUser().then(response => {
commit('setUser', response)
})
},
// 获取店铺信息
getShop({ commit, state }) {
getShopList({ shop_id: state.shopId }).then(response => {
const [first = {}] = response.data || []
commit('setShop', first)
})
},
// 获取商品分组列表
getGroups({ commit, state }) {
getGroupList({ shop_id: state.shopId }).then(response => {
console.log(response)
const { data = [] } = response
commit('setGroups', data)
})
},
// 设置店铺id
setShopId({ commit }, shopId) {
commit('setShopId', shopId)
},
// 退出登录
logout({ commit }) {
return logout().then(response => {
commit('setUser', {})
return response
})
},
// 检测登录状态
async checkLogin({ commit }) {
const isLogin = await getUser()
.then(response => {
commit('setUser', response.data)
return true
})
.catch(() => {
commit('setUser', {})
return false
})
return isLogin
}
}
})
export default store
$--color-primary: #3276fc;
// $--border-radius-base: 8px;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import '~element-ui/packages/theme-chalk/src/index';
body,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
pre,
form,
fieldset,
legend,
button,
input,
textarea,
th,
td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
}
ul,
ol,
li {
list-style: none;
}
em,
i {
font-style: normal;
}
img {
border: none;
}
input,
img {
vertical-align: middle;
}
a {
color: inherit;
text-decoration: none;
}
input,
button,
select,
textarea {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-appearance: none;
border: 0;
border-radius: 0;
}
textarea:focus {
outline: 0;
}
html {
font-size: 100px;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
:root {
--main-color: #5b91fd;
}
body {
font-size: 14px;
line-height: 1.4;
color: #222;
font-family: 'PingFang SC', 'Source Han Sans CN', -apple-system, 'Microsoft YaHei', 'Helvetica', 'Arial', Verdana,
'Hiragino Sans GB', 'Wenquanyi Micro Hei', sans-serif;
}
.empty {
padding: 120px;
text-align: center;
font-size: 18px;
color: #999;
}
import axios from 'axios'
import qs from 'qs'
import { Message } from 'element-ui'
const httpRequest = axios.create({
// baseURL: webConf.others.baseUrl,
timeout: 60000,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截
httpRequest.interceptors.request.use(
function(config) {
if (config.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
config.data = qs.stringify(config.data)
}
return config
},
function(error) {
return Promise.reject(error)
}
)
// 响应拦截
httpRequest.interceptors.response.use(
function(response) {
const { data } = response
if (data.code) {
Message.error(data.msg || data.message)
return Promise.reject(data)
}
return data
},
function(error) {
if (error.response) {
const { status, message } = error.response.data
// 未登录
if (status === 403) {
window.location.href = `${webConf.others.loginUrl}?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'
export default class BeforeEnter {
constructor(opt) {
this.opt = opt || {}
}
async update(to, from, next) {
// 登录白名单
const whiteList = []
if (whiteList.includes(to.path)) {
next()
return
}
const isLogin = store.state.user.id || (await store.dispatch('checkLogin'))
if (!isLogin) {
window.location.href = `${webConf.others.loginUrl}?rd=${encodeURIComponent(window.location.href)}`
return
}
next()
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论