32 changed files with 2093 additions and 37 deletions
-
33src/api/generator/generator.js
-
2src/api/system/code.js
-
29src/components/Dict/Dict.js
-
29src/components/Dict/index.js
-
16src/components/Doc/index.vue
-
54src/components/GithubCorner/index.vue
-
186src/components/HeaderSearch/index.vue
-
140src/components/PanThumb/index.vue
-
60src/components/Screenfull/index.vue
-
57src/components/SizeSelect/index.vue
-
1src/layout/components/Navbar.vue
-
2src/layout/components/index.js
-
9src/layout/index.vue
-
3src/main.js
-
181src/views/dashboard/PanelGroup.vue
-
107src/views/home.vue
-
36src/views/nested/menu1/menu1-1/index.vue
-
5src/views/nested/menu1/menu1-2/index.vue
-
5src/views/nested/menu2/index.vue
-
110src/views/system/job/module/form.vue
-
36src/views/system/user/index.vue
-
98src/views/tools/aliPay/config.vue
-
48src/views/tools/aliPay/index.vue
-
86src/views/tools/aliPay/toPay.vue
-
91src/views/tools/email/config.vue
-
41src/views/tools/email/index.vue
-
142src/views/tools/email/send.vue
-
36src/views/tools/storage/index.vue
-
184src/views/tools/storage/local/index.vue
-
98src/views/tools/storage/qiniu/form.vue
-
189src/views/tools/storage/qiniu/index.vue
-
16src/views/tools/swagger/index.vue
@ -0,0 +1,33 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
export function getAllTable() { |
|||
return request({ |
|||
url: 'api/generator/tables/all', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
export function generator(tableName, type) { |
|||
return request({ |
|||
url: 'api/generator/' + tableName + '/' + type, |
|||
method: 'post', |
|||
responseType: type === 2 ? 'blob' : '' |
|||
}) |
|||
} |
|||
|
|||
export function save(data) { |
|||
return request({ |
|||
url: 'api/generator', |
|||
data, |
|||
method: 'put' |
|||
}) |
|||
} |
|||
|
|||
export function sync(tables) { |
|||
return request({ |
|||
url: 'api/generator/sync', |
|||
method: 'post', |
|||
data: tables |
|||
}) |
|||
} |
|||
|
@ -0,0 +1,29 @@ |
|||
import Vue from 'vue' |
|||
import { get as getDictDetail } from '@/api/system/dictDetail' |
|||
|
|||
export default class Dict { |
|||
constructor(dict) { |
|||
this.dict = dict |
|||
} |
|||
|
|||
async init(names, completeCallback) { |
|||
if (names === undefined || name === null) { |
|||
throw new Error('need Dict names') |
|||
} |
|||
const ps = [] |
|||
names.forEach(n => { |
|||
Vue.set(this.dict.dict, n, {}) |
|||
Vue.set(this.dict.label, n, {}) |
|||
Vue.set(this.dict, n, []) |
|||
ps.push(getDictDetail(n).then(data => { |
|||
this.dict[n].splice(0, 0, ...data.content) |
|||
data.content.forEach(d => { |
|||
Vue.set(this.dict.dict[n], d.value, d) |
|||
Vue.set(this.dict.label[n], d.value, d.label) |
|||
}) |
|||
})) |
|||
}) |
|||
await Promise.all(ps) |
|||
completeCallback() |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
import Dict from './Dict' |
|||
|
|||
const install = function(Vue) { |
|||
Vue.mixin({ |
|||
data() { |
|||
if (this.$options.dicts instanceof Array) { |
|||
const dict = { |
|||
dict: {}, |
|||
label: {} |
|||
} |
|||
return { |
|||
dict |
|||
} |
|||
} |
|||
return {} |
|||
}, |
|||
created() { |
|||
if (this.$options.dicts instanceof Array) { |
|||
new Dict(this.dict).init(this.$options.dicts, () => { |
|||
this.$nextTick(() => { |
|||
this.$emit('dictReady') |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
export default { install } |
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<div> |
|||
<svg-icon icon-class="doc" @click="click" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Doc', |
|||
methods: { |
|||
click() { |
|||
window.open('https://www.aiyxlib.com', '_blank') |
|||
} |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,54 @@ |
|||
<template> |
|||
<a href="https://www.aiyxlib.com" target="_blank" class="github-corner" aria-label="View source on Github"> |
|||
<svg |
|||
width="80" |
|||
height="80" |
|||
viewBox="0 0 250 250" |
|||
style="fill:#40c9c6; color:#fff;" |
|||
aria-hidden="true" |
|||
> |
|||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" /> |
|||
<path |
|||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" |
|||
fill="currentColor" |
|||
style="transform-origin: 130px 106px;" |
|||
class="octo-arm" |
|||
/> |
|||
<path |
|||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" |
|||
fill="currentColor" |
|||
class="octo-body" |
|||
/> |
|||
</svg> |
|||
</a> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
.github-corner:hover .octo-arm { |
|||
animation: octocat-wave 560ms ease-in-out |
|||
} |
|||
|
|||
@keyframes octocat-wave { |
|||
0%, |
|||
100% { |
|||
transform: rotate(0) |
|||
} |
|||
20%, |
|||
60% { |
|||
transform: rotate(-25deg) |
|||
} |
|||
40%, |
|||
80% { |
|||
transform: rotate(10deg) |
|||
} |
|||
} |
|||
|
|||
@media (max-width:500px) { |
|||
.github-corner:hover .octo-arm { |
|||
animation: none |
|||
} |
|||
.github-corner .octo-arm { |
|||
animation: octocat-wave 560ms ease-in-out |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,186 @@ |
|||
<template> |
|||
<div :class="{'show':show}" class="header-search"> |
|||
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> |
|||
<el-select |
|||
ref="headerSearchSelect" |
|||
v-model="search" |
|||
:remote-method="querySearch" |
|||
filterable |
|||
default-first-option |
|||
remote |
|||
placeholder="Search" |
|||
class="header-search-select" |
|||
@change="change" |
|||
> |
|||
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" /> |
|||
</el-select> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import Fuse from 'fuse.js' |
|||
import path from 'path' |
|||
|
|||
export default { |
|||
name: 'HeaderSearch', |
|||
data() { |
|||
return { |
|||
search: '', |
|||
options: [], |
|||
searchPool: [], |
|||
show: false, |
|||
fuse: undefined |
|||
} |
|||
}, |
|||
computed: { |
|||
routes() { |
|||
return this.$store.state.permission.routers |
|||
} |
|||
}, |
|||
watch: { |
|||
routes() { |
|||
this.searchPool = this.generateRoutes(this.routes) |
|||
}, |
|||
searchPool(list) { |
|||
this.initFuse(list) |
|||
}, |
|||
show(value) { |
|||
if (value) { |
|||
document.body.addEventListener('click', this.close) |
|||
} else { |
|||
document.body.removeEventListener('click', this.close) |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.searchPool = this.generateRoutes(this.routes) |
|||
}, |
|||
methods: { |
|||
click() { |
|||
this.show = !this.show |
|||
if (this.show) { |
|||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus() |
|||
} |
|||
}, |
|||
close() { |
|||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur() |
|||
this.options = [] |
|||
this.show = false |
|||
}, |
|||
change(val) { |
|||
if (this.ishttp(val.path)) { |
|||
// http(s):// 路径新窗口打开 |
|||
window.open(val.path, '_blank') |
|||
} else { |
|||
this.$router.push(val.path) |
|||
} |
|||
this.search = '' |
|||
this.options = [] |
|||
this.$nextTick(() => { |
|||
this.show = false |
|||
}) |
|||
}, |
|||
initFuse(list) { |
|||
this.fuse = new Fuse(list, { |
|||
shouldSort: true, |
|||
threshold: 0.4, |
|||
location: 0, |
|||
distance: 100, |
|||
maxPatternLength: 32, |
|||
minMatchCharLength: 1, |
|||
keys: [{ |
|||
name: 'title', |
|||
weight: 0.7 |
|||
}, { |
|||
name: 'path', |
|||
weight: 0.3 |
|||
}] |
|||
}) |
|||
}, |
|||
// Filter out the routes that can be displayed in the sidebar |
|||
// And generate the internationalized title |
|||
generateRoutes(routes, basePath = '/', prefixTitle = []) { |
|||
let res = [] |
|||
|
|||
for (const router of routes) { |
|||
// skip hidden router |
|||
if (router.hidden) { continue } |
|||
|
|||
const data = { |
|||
path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path, |
|||
title: [...prefixTitle] |
|||
} |
|||
|
|||
if (router.meta && router.meta.title) { |
|||
data.title = [...data.title, router.meta.title] |
|||
|
|||
if (router.redirect !== 'noRedirect') { |
|||
// only push the routes with title |
|||
// special case: need to exclude parent router without redirect |
|||
res.push(data) |
|||
} |
|||
} |
|||
|
|||
// recursive child routes |
|||
if (router.children) { |
|||
const tempRoutes = this.generateRoutes(router.children, data.path, data.title) |
|||
if (tempRoutes.length >= 1) { |
|||
res = [...res, ...tempRoutes] |
|||
} |
|||
} |
|||
} |
|||
return res |
|||
}, |
|||
querySearch(query) { |
|||
if (query !== '') { |
|||
this.options = this.fuse.search(query) |
|||
} else { |
|||
this.options = [] |
|||
} |
|||
}, |
|||
ishttp(url) { |
|||
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.header-search { |
|||
font-size: 0 !important; |
|||
|
|||
.search-icon { |
|||
cursor: pointer; |
|||
font-size: 18px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.header-search-select { |
|||
font-size: 18px; |
|||
transition: width 0.2s; |
|||
width: 0; |
|||
overflow: hidden; |
|||
background: transparent; |
|||
border-radius: 0; |
|||
display: inline-block; |
|||
vertical-align: middle; |
|||
|
|||
::v-deep .el-input__inner { |
|||
border-radius: 0; |
|||
border: 0; |
|||
padding-left: 0; |
|||
padding-right: 0; |
|||
box-shadow: none !important; |
|||
border-bottom: 1px solid #d9d9d9; |
|||
vertical-align: middle; |
|||
} |
|||
} |
|||
|
|||
&.show { |
|||
.header-search-select { |
|||
width: 210px; |
|||
margin-left: 10px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,140 @@ |
|||
<template> |
|||
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item"> |
|||
<div class="pan-info"> |
|||
<div class="pan-info-roles-container"> |
|||
<slot /> |
|||
</div> |
|||
</div> |
|||
<img :src="image" class="pan-thumb"> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'PanThumb', |
|||
props: { |
|||
image: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
zIndex: { |
|||
type: Number, |
|||
default: 1 |
|||
}, |
|||
width: { |
|||
type: String, |
|||
default: '150px' |
|||
}, |
|||
height: { |
|||
type: String, |
|||
default: '150px' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.pan-item { |
|||
width: 200px; |
|||
height: 200px; |
|||
border-radius: 50%; |
|||
display: inline-block; |
|||
position: relative; |
|||
cursor: default; |
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); |
|||
} |
|||
|
|||
.pan-info-roles-container { |
|||
padding: 20px; |
|||
text-align: center; |
|||
} |
|||
|
|||
.pan-thumb { |
|||
width: 100%; |
|||
height: 100%; |
|||
background-size: 100%; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
position: absolute; |
|||
transform-origin: 95% 40%; |
|||
transition: all 0.3s ease-in-out; |
|||
} |
|||
|
|||
.pan-thumb:after { |
|||
content: ''; |
|||
width: 8px; |
|||
height: 8px; |
|||
position: absolute; |
|||
border-radius: 50%; |
|||
top: 40%; |
|||
left: 95%; |
|||
margin: -4px 0 0 -4px; |
|||
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%); |
|||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9); |
|||
} |
|||
|
|||
.pan-info { |
|||
position: absolute; |
|||
width: inherit; |
|||
height: inherit; |
|||
border-radius: 50%; |
|||
overflow: hidden; |
|||
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.pan-info h3 { |
|||
color: #fff; |
|||
text-transform: uppercase; |
|||
position: relative; |
|||
letter-spacing: 2px; |
|||
font-size: 18px; |
|||
margin: 0 60px; |
|||
padding: 22px 0 0 0; |
|||
height: 85px; |
|||
font-family: 'Open Sans', Arial, sans-serif; |
|||
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3); |
|||
} |
|||
|
|||
.pan-info p { |
|||
color: #fff; |
|||
padding: 10px 5px; |
|||
font-style: italic; |
|||
margin: 0 30px; |
|||
font-size: 12px; |
|||
border-top: 1px solid rgba(255, 255, 255, 0.5); |
|||
} |
|||
|
|||
.pan-info p a { |
|||
display: block; |
|||
color: #333; |
|||
width: 80px; |
|||
height: 80px; |
|||
background: rgba(255, 255, 255, 0.3); |
|||
border-radius: 50%; |
|||
color: #fff; |
|||
font-style: normal; |
|||
font-weight: 700; |
|||
text-transform: uppercase; |
|||
font-size: 9px; |
|||
letter-spacing: 1px; |
|||
padding-top: 24px; |
|||
margin: 7px auto 0; |
|||
font-family: 'Open Sans', Arial, sans-serif; |
|||
opacity: 0; |
|||
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s; |
|||
transform: translateX(60px) rotate(90deg); |
|||
} |
|||
|
|||
.pan-info p a:hover { |
|||
background: rgba(255, 255, 255, 0.5); |
|||
} |
|||
|
|||
.pan-item:hover .pan-thumb { |
|||
transform: rotate(-110deg); |
|||
} |
|||
|
|||
.pan-item:hover .pan-info p a { |
|||
opacity: 1; |
|||
transform: translateX(0px) rotate(0deg); |
|||
} |
|||
</style> |
@ -0,0 +1,60 @@ |
|||
<template> |
|||
<div> |
|||
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import screenfull from 'screenfull' |
|||
|
|||
export default { |
|||
name: 'Screenfull', |
|||
data() { |
|||
return { |
|||
isFullscreen: false |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.init() |
|||
}, |
|||
beforeDestroy() { |
|||
this.destroy() |
|||
}, |
|||
methods: { |
|||
click() { |
|||
if (!screenfull.enabled) { |
|||
this.$message({ |
|||
message: 'you browser can not work', |
|||
type: 'warning' |
|||
}) |
|||
return false |
|||
} |
|||
screenfull.toggle() |
|||
}, |
|||
change() { |
|||
this.isFullscreen = screenfull.isFullscreen |
|||
}, |
|||
init() { |
|||
if (screenfull.enabled) { |
|||
screenfull.on('change', this.change) |
|||
} |
|||
}, |
|||
destroy() { |
|||
if (screenfull.enabled) { |
|||
screenfull.off('change', this.change) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.screenfull-svg { |
|||
display: inline-block; |
|||
cursor: pointer; |
|||
fill: #5a5e66;; |
|||
width: 20px; |
|||
height: 20px; |
|||
vertical-align: 10px; |
|||
} |
|||
</style> |
@ -0,0 +1,57 @@ |
|||
<template> |
|||
<el-dropdown trigger="click" @command="handleSetSize"> |
|||
<div> |
|||
<svg-icon class-name="size-icon" icon-class="size" /> |
|||
</div> |
|||
<el-dropdown-menu slot="dropdown"> |
|||
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value"> |
|||
{{ |
|||
item.label }} |
|||
</el-dropdown-item> |
|||
</el-dropdown-menu> |
|||
</el-dropdown> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
sizeOptions: [ |
|||
{ label: 'Default', value: 'default' }, |
|||
{ label: 'Medium', value: 'medium' }, |
|||
{ label: 'Small', value: 'small' }, |
|||
{ label: 'Mini', value: 'mini' } |
|||
] |
|||
} |
|||
}, |
|||
computed: { |
|||
size() { |
|||
return this.$store.getters.size |
|||
} |
|||
}, |
|||
methods: { |
|||
handleSetSize(size) { |
|||
this.$ELEMENT.size = size |
|||
this.$store.dispatch('app/setSize', size) |
|||
this.refreshView() |
|||
this.$message({ |
|||
message: '布局设置成功', |
|||
type: 'success' |
|||
}) |
|||
}, |
|||
refreshView() { |
|||
// In order to make the cached page re-rendered |
|||
this.$store.dispatch('tagsView/delAllCachedViews', this.$route) |
|||
|
|||
const { fullPath } = this.$route |
|||
|
|||
this.$nextTick(() => { |
|||
this.$router.replace({ |
|||
path: '/redirect' + fullPath |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
} |
|||
</script> |
@ -1,5 +1,5 @@ |
|||
export { default as AppMain } from './AppMain' |
|||
export { default as Navbar } from './Navbar' |
|||
export { default as Sidebar } from './Sidebar' |
|||
export { default as Sidebar } from './Sidebar/index' |
|||
export { default as Settings } from './Settings' |
|||
export { default as TagsView } from './TagsView/index' |
@ -0,0 +1,181 @@ |
|||
<template> |
|||
<el-row :gutter="40" class="panel-group"> |
|||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> |
|||
<div class="card-panel" @click="handleSetLineChartData('newVisitis')"> |
|||
<div class="card-panel-icon-wrapper icon-people"> |
|||
<svg-icon icon-class="peoples" class-name="card-panel-icon" /> |
|||
</div> |
|||
<div class="card-panel-description"> |
|||
<div class="card-panel-text"> |
|||
New Visits |
|||
</div> |
|||
<count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" /> |
|||
</div> |
|||
</div> |
|||
</el-col> |
|||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> |
|||
<div class="card-panel" @click="handleSetLineChartData('messages')"> |
|||
<div class="card-panel-icon-wrapper icon-message"> |
|||
<svg-icon icon-class="message" class-name="card-panel-icon" /> |
|||
</div> |
|||
<div class="card-panel-description"> |
|||
<div class="card-panel-text"> |
|||
Messages |
|||
</div> |
|||
<count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" /> |
|||
</div> |
|||
</div> |
|||
</el-col> |
|||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> |
|||
<div class="card-panel" @click="handleSetLineChartData('purchases')"> |
|||
<div class="card-panel-icon-wrapper icon-money"> |
|||
<svg-icon icon-class="money" class-name="card-panel-icon" /> |
|||
</div> |
|||
<div class="card-panel-description"> |
|||
<div class="card-panel-text"> |
|||
Purchases |
|||
</div> |
|||
<count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" /> |
|||
</div> |
|||
</div> |
|||
</el-col> |
|||
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> |
|||
<div class="card-panel" @click="handleSetLineChartData('shoppings')"> |
|||
<div class="card-panel-icon-wrapper icon-shopping"> |
|||
<svg-icon icon-class="shopping" class-name="card-panel-icon" /> |
|||
</div> |
|||
<div class="card-panel-description"> |
|||
<div class="card-panel-text"> |
|||
Shoppings |
|||
</div> |
|||
<count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" /> |
|||
</div> |
|||
</div> |
|||
</el-col> |
|||
</el-row> |
|||
</template> |
|||
|
|||
<script> |
|||
import CountTo from 'vue-count-to' |
|||
|
|||
export default { |
|||
components: { |
|||
CountTo |
|||
}, |
|||
methods: { |
|||
handleSetLineChartData(type) { |
|||
this.$emit('handleSetLineChartData', type) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.panel-group { |
|||
margin-top: 18px; |
|||
|
|||
.card-panel-col { |
|||
margin-bottom: 32px; |
|||
} |
|||
|
|||
.card-panel { |
|||
height: 108px; |
|||
cursor: pointer; |
|||
font-size: 12px; |
|||
position: relative; |
|||
overflow: hidden; |
|||
color: #666; |
|||
background: #fff; |
|||
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05); |
|||
border-color: rgba(0, 0, 0, .05); |
|||
|
|||
&:hover { |
|||
.card-panel-icon-wrapper { |
|||
color: #fff; |
|||
} |
|||
|
|||
.icon-people { |
|||
background: #40c9c6; |
|||
} |
|||
|
|||
.icon-message { |
|||
background: #36a3f7; |
|||
} |
|||
|
|||
.icon-money { |
|||
background: #f4516c; |
|||
} |
|||
|
|||
.icon-shopping { |
|||
background: #34bfa3 |
|||
} |
|||
} |
|||
|
|||
.icon-people { |
|||
color: #40c9c6; |
|||
} |
|||
|
|||
.icon-message { |
|||
color: #36a3f7; |
|||
} |
|||
|
|||
.icon-money { |
|||
color: #f4516c; |
|||
} |
|||
|
|||
.icon-shopping { |
|||
color: #34bfa3 |
|||
} |
|||
|
|||
.card-panel-icon-wrapper { |
|||
float: left; |
|||
margin: 14px 0 0 14px; |
|||
padding: 16px; |
|||
transition: all 0.38s ease-out; |
|||
border-radius: 6px; |
|||
} |
|||
|
|||
.card-panel-icon { |
|||
float: left; |
|||
font-size: 48px; |
|||
} |
|||
|
|||
.card-panel-description { |
|||
float: right; |
|||
font-weight: bold; |
|||
margin: 26px; |
|||
margin-left: 0px; |
|||
|
|||
.card-panel-text { |
|||
line-height: 18px; |
|||
color: rgba(0, 0, 0, 0.45); |
|||
font-size: 16px; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.card-panel-num { |
|||
font-size: 20px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media (max-width:550px) { |
|||
.card-panel-description { |
|||
display: none; |
|||
} |
|||
|
|||
.card-panel-icon-wrapper { |
|||
float: none !important; |
|||
width: 100%; |
|||
height: 100%; |
|||
margin: 0 !important; |
|||
|
|||
.svg-icon { |
|||
display: block; |
|||
margin: 14px auto !important; |
|||
float: none !important; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,107 @@ |
|||
<template> |
|||
<div class="dashboard-container"> |
|||
<div class="dashboard-editor-container"> |
|||
<github-corner class="github-corner" /> |
|||
|
|||
<panel-group @handleSetLineChartData="handleSetLineChartData" /> |
|||
|
|||
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;"> |
|||
<line-chart :chart-data="lineChartData" /> |
|||
</el-row> |
|||
<el-row :gutter="32"> |
|||
<el-col :xs="24" :sm="24" :lg="8"> |
|||
<div class="chart-wrapper"> |
|||
<radar-chart /> |
|||
</div> |
|||
</el-col> |
|||
<el-col :xs="24" :sm="24" :lg="8"> |
|||
<div class="chart-wrapper"> |
|||
<pie-chart /> |
|||
</div> |
|||
</el-col> |
|||
<el-col :xs="24" :sm="24" :lg="8"> |
|||
<div class="chart-wrapper"> |
|||
<bar-chart /> |
|||
</div> |
|||
</el-col> |
|||
</el-row> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import GithubCorner from '@/components/GithubCorner' |
|||
import PanelGroup from './dashboard/PanelGroup' |
|||
import LineChart from './dashboard/LineChart' |
|||
import RadarChart from '@/components/Echarts/RadarChart' |
|||
import PieChart from '@/components/Echarts/PieChart' |
|||
import BarChart from '@/components/Echarts/BarChart' |
|||
|
|||
const lineChartData = { |
|||
newVisitis: { |
|||
expectedData: [100, 120, 161, 134, 105, 160, 165], |
|||
actualData: [120, 82, 91, 154, 162, 140, 145] |
|||
}, |
|||
messages: { |
|||
expectedData: [200, 192, 120, 144, 160, 130, 140], |
|||
actualData: [180, 160, 151, 106, 145, 150, 130] |
|||
}, |
|||
purchases: { |
|||
expectedData: [80, 100, 121, 104, 105, 90, 100], |
|||
actualData: [120, 90, 100, 138, 142, 130, 130] |
|||
}, |
|||
shoppings: { |
|||
expectedData: [130, 140, 141, 142, 145, 150, 160], |
|||
actualData: [120, 82, 91, 154, 162, 140, 130] |
|||
} |
|||
} |
|||
|
|||
export default { |
|||
name: 'Dashboard', |
|||
components: { |
|||
GithubCorner, |
|||
PanelGroup, |
|||
LineChart, |
|||
RadarChart, |
|||
PieChart, |
|||
BarChart |
|||
}, |
|||
data() { |
|||
return { |
|||
lineChartData: lineChartData.newVisitis |
|||
} |
|||
}, |
|||
methods: { |
|||
handleSetLineChartData(type) { |
|||
this.lineChartData = lineChartData[type] |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style rel="stylesheet/scss" lang="scss" scoped> |
|||
.dashboard-editor-container { |
|||
padding: 32px; |
|||
background-color: rgb(240, 242, 245); |
|||
position: relative; |
|||
|
|||
.github-corner { |
|||
position: absolute; |
|||
top: 0; |
|||
border: 0; |
|||
right: 0; |
|||
} |
|||
|
|||
.chart-wrapper { |
|||
background: #fff; |
|||
padding: 16px 16px 0; |
|||
margin-bottom: 32px; |
|||
} |
|||
} |
|||
|
|||
@media (max-width:1024px) { |
|||
.chart-wrapper { |
|||
padding: 8px; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,36 @@ |
|||
<template> |
|||
<div class="app-container"> |
|||
<el-alert :closable="false" title="三级菜单1" type="success" /> |
|||
<el-form label-width="170px" style="margin-top: 20px"> |
|||
<el-form-item label="三级菜单缓存功能测试区"> |
|||
<el-input v-model="input" placeholder="请输入内容" style="width: 360px;" /> |
|||
</el-form-item> |
|||
</el-form> |
|||
<div> |
|||
<blockquote class="my-blockquote"> 三级菜单缓存配置</blockquote> |
|||
<pre class="my-code"> |
|||
1、将前后端代码更新为最新版版本,或对照提交记录修改,点击查看-> <a href="https://www.aiyxlib.com/XXXX" target="_blank">提交(1)</a>、<a href="https://www.aiyxlib.com" target="_blank">提交(2)</a>、<a href="https://www.aiyxlib.com" target="_blank">提交(3)</a> |
|||
2、将 二级菜单 的 菜单类型 设置为 目录 级别,并且原有的 组件路径 需要清空 |
|||
3、将 三级菜单 的 菜单缓存 设置为 是,最后将 组件名称 填写正确 |
|||
4、具体设置可参考 菜单管理 的 多级菜单 配置进行进行相应的修改 |
|||
</pre> |
|||
<blockquote class="my-blockquote">更多帮助请联系管理员</blockquote> |
|||
<pre class="my-code">联系QQ:421691338</pre> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
name: 'Test', |
|||
data() { |
|||
return { |
|||
input: '' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
.my-code a{ |
|||
color:#009688; |
|||
} |
|||
</style> |
@ -0,0 +1,5 @@ |
|||
<template> |
|||
<div style="padding:30px;"> |
|||
<el-alert :closable="false" title="三级菜单2" type="success" /> |
|||
</div> |
|||
</template> |
@ -0,0 +1,5 @@ |
|||
<template> |
|||
<div style="padding:30px;"> |
|||
<el-alert :closable="false" title="二级菜单" /> |
|||
</div> |
|||
</template> |
@ -0,0 +1,110 @@ |
|||
<template> |
|||
<el-dialog |
|||
append-to-body |
|||
:close-on-click-modal="false" |
|||
:before-close="crud.cancelCU" |
|||
:visible="crud.status.cu > 0" |
|||
:title="crud.status.title" |
|||
width="500px" |
|||
> |
|||
<el-form |
|||
ref="form" |
|||
:model="form" |
|||
:rules="rules" |
|||
size="small" |
|||
label-width="80px" |
|||
> |
|||
<el-form-item |
|||
label="名称" |
|||
prop="name" |
|||
> |
|||
<el-input |
|||
v-model="form.name" |
|||
style="width: 370px;" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item |
|||
label="排序" |
|||
prop="jobSort" |
|||
> |
|||
<el-input-number |
|||
v-model.number="form.jobSort" |
|||
:min="0" |
|||
:max="999" |
|||
controls-position="right" |
|||
style="width: 370px;" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item |
|||
v-if="form.pid !== 0" |
|||
label="状态" |
|||
prop="enabled" |
|||
> |
|||
<el-radio |
|||
v-for="item in jobStatus" |
|||
:key="item.id" |
|||
v-model="form.enabled" |
|||
:label="item.value === 'true'" |
|||
> |
|||
{{ item.label }} |
|||
</el-radio> |
|||
</el-form-item> |
|||
</el-form> |
|||
<div |
|||
slot="footer" |
|||
class="dialog-footer" |
|||
> |
|||
<el-button |
|||
type="text" |
|||
@click="crud.cancelCU" |
|||
> |
|||
取消 |
|||
</el-button> |
|||
<el-button |
|||
:loading="crud.status.cu === 2" |
|||
type="primary" |
|||
@click="crud.submitCU" |
|||
> |
|||
确认 |
|||
</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
import { form } from '@crud/crud' |
|||
|
|||
const defaultForm = { |
|||
id: null, |
|||
name: '', |
|||
jobSort: 999, |
|||
enabled: true |
|||
} |
|||
export default { |
|||
mixins: [form(defaultForm)], |
|||
props: { |
|||
jobStatus: { |
|||
type: Array, |
|||
required: true |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
rules: { |
|||
name: [ |
|||
{ required: true, message: '请输入名称', trigger: 'blur' } |
|||
], |
|||
jobSort: [ |
|||
{ required: true, message: '请输入序号', trigger: 'blur', type: 'number' } |
|||
] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style rel="stylesheet/scss" lang="scss" scoped> |
|||
::v-deep .el-input-number .el-input__inner { |
|||
text-align: left; |
|||
} |
|||
</style> |
@ -0,0 +1,98 @@ |
|||
<template> |
|||
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px"> |
|||
<el-form-item label="appID" prop="appId"> |
|||
<el-input v-model="form.appId" style="width: 40%" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">应用APPID,收款账号既是APPID对应支付宝账号</span> |
|||
</el-form-item> |
|||
<el-form-item label="商家账号" prop="sysServiceProviderId"> |
|||
<el-input v-model="form.sysServiceProviderId" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">商家账号</span> |
|||
</el-form-item> |
|||
<el-form-item label="商户私钥" prop="privateKey"> |
|||
<el-input v-model="form.privateKey" type="password" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">商户私钥,你的PKCS8格式RSA2私钥</span> |
|||
</el-form-item> |
|||
<el-form-item label="支付宝公钥" prop="publicKey"> |
|||
<el-input v-model="form.publicKey" type="password" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">支付宝公钥</span> |
|||
</el-form-item> |
|||
<el-form-item label="回调地址" prop="returnUrl"> |
|||
<el-input v-model="form.returnUrl" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">订单完成后返回的地址</span> |
|||
</el-form-item> |
|||
<el-form-item label="异步通知" prop="notifyUrl"> |
|||
<el-input v-model="form.notifyUrl" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">支付结果异步通知地址</span> |
|||
</el-form-item> |
|||
<el-form-item label=""> |
|||
<el-button :loading="loading" size="medium" type="primary" @click="doSubmit">保存配置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</template> |
|||
|
|||
<script> |
|||
import { get, update } from '@/api/tools/alipay' |
|||
export default { |
|||
name: 'Config', |
|||
data() { |
|||
return { |
|||
loading: false, |
|||
form: { appId: '', sysServiceProviderId: '', privateKey: '', publicKey: '', returnUrl: '', notifyUrl: '' }, |
|||
rules: { |
|||
appId: [ |
|||
{ required: true, message: '请输入appID', trigger: 'blur' } |
|||
], |
|||
sysServiceProviderId: [ |
|||
{ required: true, message: '请输入商家账号', trigger: 'blur' } |
|||
], |
|||
privateKey: [ |
|||
{ required: true, message: '商户私钥不能为空', trigger: 'blur' } |
|||
], |
|||
publicKey: [ |
|||
{ required: true, message: '支付宝公钥不能为空', trigger: 'blur' } |
|||
], |
|||
returnUrl: [ |
|||
{ required: true, message: '回调地址不能为空', trigger: 'blur' } |
|||
], |
|||
notifyUrl: [ |
|||
{ required: true, message: '回调地址不能为空', trigger: 'blur' } |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
this.init() |
|||
}, |
|||
methods: { |
|||
init() { |
|||
get().then(res => { |
|||
this.form = res |
|||
}) |
|||
}, |
|||
doSubmit() { |
|||
this.$refs['form'].validate((valid) => { |
|||
if (valid) { |
|||
this.loading = true |
|||
update(this.form).then(res => { |
|||
this.$notify({ |
|||
title: '修改成功', |
|||
type: 'success', |
|||
duration: 2500 |
|||
}) |
|||
this.loading = false |
|||
}).catch(err => { |
|||
this.loading = false |
|||
console.log(err.response.data.message) |
|||
}) |
|||
} else { |
|||
return false |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
|
|||
</style> |
@ -0,0 +1,48 @@ |
|||
<template> |
|||
<el-tabs v-model="activeName" style="padding-left: 5px;"> |
|||
<el-tab-pane label="参数配置" name="first"> |
|||
<Config /> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="支付测试" name="second"> |
|||
<ToPay /> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="使用说明" name="third"> |
|||
<div> |
|||
<blockquote class="my-blockquote">注意</blockquote> |
|||
<pre class="my-code"> |
|||
测试所用参数都是沙箱环境,仅供测试使用,申请地址:<a style="color: #00a0e9" href="https://openhome.alipay.com/platform/appDaily.htm?tab=info" target="_blank">支付宝开发平台</a> |
|||
如需付款测试,请使用 |
|||
账号:uuxesw9745@sandbox.com |
|||
密码与支付密码:111111</pre> |
|||
<blockquote class="my-blockquote"> 支付设置</blockquote> |
|||
<pre class="my-code"> |
|||
// 支付提供两个接口, |
|||
// PC端与手机端,并且在前端使用代码识别 |
|||
if (/(Android)/i.test(navigator.userAgent)){ // 判断是否为Android手机 |
|||
url = "/aliPay/toPayAsWeb" |
|||
}else if(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)){ // 判断是否为苹果手机 |
|||
url = "/aliPay/toPayAsWeb" |
|||
} else { |
|||
url = "/aliPay/toPayAsPC" |
|||
}</pre> |
|||
</div> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
</template> |
|||
|
|||
<script> |
|||
import Config from './config' |
|||
import ToPay from './toPay' |
|||
export default { |
|||
name: 'AliPay', |
|||
components: { Config, ToPay }, |
|||
data() { |
|||
return { |
|||
activeName: 'second' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
</style> |
@ -0,0 +1,86 @@ |
|||
<template> |
|||
<div> |
|||
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="90px"> |
|||
<el-form-item label="商品名称" prop="subject"> |
|||
<el-input v-model="form.subject" style="width: 35%" /> |
|||
</el-form-item> |
|||
<el-form-item label="商品价格" prop="totalAmount"> |
|||
<el-input v-model="form.totalAmount" style="width: 35%" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">测试允许区间(0,5000]</span> |
|||
</el-form-item> |
|||
<el-form-item label="商品描述" prop="body"> |
|||
<el-input v-model="form.body" style="width: 35%" rows="8" type="textarea" /> |
|||
</el-form-item> |
|||
<el-form-item label=""> |
|||
<el-button :loading="loading" size="medium" type="primary" @click="doSubmit">去支付</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { toAliPay } from '@/api/tools/alipay' |
|||
export default { |
|||
data() { |
|||
return { |
|||
url: '', |
|||
// 新窗口的引用 |
|||
newWin: null, |
|||
loading: false, form: { subject: '', totalAmount: '', body: '' }, |
|||
rules: { |
|||
subject: [ |
|||
{ required: true, message: '商品名称不能为空', trigger: 'blur' } |
|||
], |
|||
totalAmount: [ |
|||
{ required: true, message: '商品价格不能为空', trigger: 'blur' } |
|||
], |
|||
body: [ |
|||
{ required: true, message: '商品描述不能为空', trigger: 'blur' } |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
url(newVal, oldVal) { |
|||
if (newVal && this.newWin) { |
|||
this.newWin.sessionStorage.clear() |
|||
this.newWin.location.href = newVal |
|||
// 重定向后把url和newWin重置 |
|||
this.url = '' |
|||
this.newWin = null |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
doSubmit() { |
|||
this.$refs['form'].validate((valid) => { |
|||
if (valid) { |
|||
this.loading = true |
|||
// 先打开一个空的新窗口,再请求 |
|||
this.newWin = window.open() |
|||
let url = '' |
|||
if (/(Android)/i.test(navigator.userAgent)) { // 判断是否为Android手机 |
|||
url = 'aliPay/toPayAsWeb' |
|||
} else if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { // 判断是否为苹果手机 |
|||
url = 'aliPay/toPayAsWeb' |
|||
} else { |
|||
url = 'aliPay/toPayAsPC' |
|||
} |
|||
toAliPay(url, this.form).then(res => { |
|||
this.loading = false |
|||
this.url = res |
|||
}).catch(err => { |
|||
this.loading = false |
|||
console.log(err.response.data.message) |
|||
}) |
|||
} else { |
|||
return false |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
</style> |
@ -0,0 +1,91 @@ |
|||
<template> |
|||
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px"> |
|||
<el-form-item label="发件人邮箱" prop="fromUser"> |
|||
<el-input v-model="form.fromUser" style="width: 40%" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">Sender mailbox</span> |
|||
</el-form-item> |
|||
<el-form-item label="发件用户名" prop="user"> |
|||
<el-input v-model="form.user" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">Sender usernamex</span> |
|||
</el-form-item> |
|||
<el-form-item label="邮箱密码" prop="pass"> |
|||
<el-input v-model="form.pass" type="password" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">email Password</span> |
|||
</el-form-item> |
|||
<el-form-item label="SMTP地址" prop="host"> |
|||
<el-input v-model="form.host" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">SMTP address</span> |
|||
</el-form-item> |
|||
<el-form-item label="SMTP端口" prop="port"> |
|||
<el-input v-model="form.port" style="width: 40%;" /> |
|||
<span style="color: #C0C0C0;margin-left: 10px;">SMTP port</span> |
|||
</el-form-item> |
|||
<el-form-item label=""> |
|||
<el-button :loading="loading" size="medium" type="primary" @click="doSubmit">保存配置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</template> |
|||
|
|||
<script> |
|||
import { get, update } from '@/api/tools/email' |
|||
export default { |
|||
name: 'Config', |
|||
data() { |
|||
return { |
|||
loading: false, form: { id: 1, fromUser: '', user: '', pass: '', host: '', port: '', sslEnable: '' }, |
|||
rules: { |
|||
fromUser: [ |
|||
{ required: true, message: '请输入发件人邮箱', trigger: 'blur' }, |
|||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' } |
|||
], |
|||
user: [ |
|||
{ required: true, message: '请输入发件用户名', trigger: 'blur' } |
|||
], |
|||
pass: [ |
|||
{ required: true, message: '密码不能为空', trigger: 'blur' } |
|||
], |
|||
host: [ |
|||
{ required: true, message: 'SMTP地址不能为空', trigger: 'blur' } |
|||
], |
|||
port: [ |
|||
{ required: true, message: 'SMTP端口不能为空', trigger: 'blur' } |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
this.init() |
|||
}, |
|||
methods: { |
|||
init() { |
|||
get().then(res => { |
|||
this.form = res |
|||
}) |
|||
}, |
|||
doSubmit() { |
|||
this.$refs['form'].validate((valid) => { |
|||
if (valid) { |
|||
this.loading = true |
|||
update(this.form).then(res => { |
|||
this.$notify({ |
|||
title: '修改成功', |
|||
type: 'success', |
|||
duration: 2500 |
|||
}) |
|||
this.loading = false |
|||
}).catch(err => { |
|||
this.loading = false |
|||
console.log(err.response.data.message) |
|||
}) |
|||
} else { |
|||
return false |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
|
|||
</style> |
@ -0,0 +1,41 @@ |
|||
<template> |
|||
<el-tabs v-model="activeName" style="padding-left: 8px;"> |
|||
<el-tab-pane label="邮箱配置" name="first"> |
|||
<Config /> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="发送邮件" name="second"> |
|||
<Send /> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="使用说明" name="third"> |
|||
<div> |
|||
<blockquote class="my-blockquote"> 邮件服务器配置</blockquote> |
|||
<pre class="my-code"> |
|||
# 邮件服务器的SMTP地址,可选,默认为smtp |
|||
# 邮件服务器的SMTP端口,可选,默认465或者25 |
|||
# 发件人(必须正确,否则发送失败) |
|||
# 用户名,默认为发件人邮箱前缀 |
|||
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,如QQ和163等等) |
|||
# 是否开启ssl,默认开启</pre> |
|||
<blockquote class="my-blockquote">更多帮助</blockquote> |
|||
<pre class="my-code">更多帮助请查看文档:<a style="color:#009688" href="http://hutool.mydoc.io/#text_319499" target="_black">hutool工具包</a></pre> |
|||
</div> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
</template> |
|||
|
|||
<script> |
|||
import Config from './config' |
|||
import Send from './send' |
|||
export default { |
|||
name: 'Email', |
|||
components: { Config, Send }, |
|||
data() { |
|||
return { |
|||
activeName: 'second' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
</style> |
@ -0,0 +1,142 @@ |
|||
<template> |
|||
<div> |
|||
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="100px"> |
|||
<el-form-item label="邮件标题" prop="subject"> |
|||
<el-input v-model="form.subject" style="width: 646px" /> |
|||
</el-form-item> |
|||
<el-form-item |
|||
v-for="(domain, index) in tos" |
|||
:key="domain.key" |
|||
:label="'收件邮箱' + (index === 0 ? '': index)" |
|||
> |
|||
<el-input v-model="domain.value" style="width: 550px" /> |
|||
<el-button icon="el-icon-plus" @click="addDomain" /> |
|||
<el-button style="margin-left:0;" icon="el-icon-minus" @click.prevent="removeDomain(domain)" /> |
|||
</el-form-item> |
|||
<div ref="editor" class="editor" /> |
|||
<el-button :loading="loading" style="margin-left:1.6%;" size="medium" type="primary" @click="doSubmit">发送邮件</el-button> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { send } from '@/api/tools/email' |
|||
import { upload } from '@/utils/upload' |
|||
import { validEmail } from '@/utils/validate' |
|||
import { mapGetters } from 'vuex' |
|||
import E from 'wangeditor' |
|||
export default { |
|||
name: 'Index', |
|||
data() { |
|||
return { |
|||
loading: false, form: { subject: '', tos: [], content: '' }, |
|||
tos: [{ |
|||
value: '' |
|||
}], |
|||
rules: { |
|||
subject: [ |
|||
{ required: true, message: '标题不能为空', trigger: 'blur' } |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'imagesUploadApi' |
|||
]) |
|||
}, |
|||
mounted() { |
|||
const _this = this |
|||
var editor = new E(this.$refs.editor) |
|||
// 自定义菜单配置 |
|||
editor.customConfig.zIndex = 10 |
|||
// 文件上传 |
|||
editor.customConfig.customUploadImg = function(files, insert) { |
|||
// files 是 input 中选中的文件列表 |
|||
// insert 是获取图片 url 后,插入到编辑器的方法 |
|||
files.forEach(image => { |
|||
files.forEach(image => { |
|||
upload(_this.imagesUploadApi, image).then(data => { |
|||
insert(data.data.url) |
|||
}) |
|||
}) |
|||
}) |
|||
} |
|||
editor.customConfig.onchange = (html) => { |
|||
this.form.content = html |
|||
} |
|||
editor.create() |
|||
}, |
|||
methods: { |
|||
removeDomain(item) { |
|||
var index = this.tos.indexOf(item) |
|||
if (index !== -1 && this.tos.length !== 1) { |
|||
this.tos.splice(index, 1) |
|||
} else { |
|||
this.$message({ |
|||
message: '请至少保留一位联系人', |
|||
type: 'warning' |
|||
}) |
|||
} |
|||
}, |
|||
addDomain() { |
|||
this.tos.push({ |
|||
value: '', |
|||
key: Date.now() |
|||
}) |
|||
}, |
|||
doSubmit() { |
|||
const _this = this |
|||
this.$refs['form'].validate((valid) => { |
|||
this.form.tos = [] |
|||
if (valid) { |
|||
let sub = false |
|||
this.tos.forEach(function(data, index) { |
|||
if (data.value === '') { |
|||
_this.$message({ |
|||
message: '收件邮箱不能为空', |
|||
type: 'warning' |
|||
}) |
|||
sub = true |
|||
} else if (validEmail(data.value)) { |
|||
_this.form.tos.push(data.value) |
|||
} else { |
|||
_this.$message({ |
|||
message: '收件邮箱格式错误', |
|||
type: 'warning' |
|||
}) |
|||
sub = true |
|||
} |
|||
}) |
|||
if (sub) { return false } |
|||
this.loading = true |
|||
send(this.form).then(res => { |
|||
this.$notify({ |
|||
title: '发送成功', |
|||
type: 'success', |
|||
duration: 2500 |
|||
}) |
|||
this.loading = false |
|||
}).catch(err => { |
|||
this.loading = false |
|||
console.log(err.response.data.message) |
|||
}) |
|||
} else { |
|||
return false |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.editor{ |
|||
text-align:left; |
|||
margin: 20px; |
|||
width: 730px; |
|||
} |
|||
::v-deep .w-e-text-container { |
|||
height: 360px !important; |
|||
} |
|||
</style> |
@ -0,0 +1,36 @@ |
|||
<template> |
|||
<el-tabs v-model="activeName" style="padding-left: 8px;" @tab-click="tabClick"> |
|||
<el-tab-pane label="本地存储" name="first"> |
|||
<Local ref="local" /> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="七牛云存储" name="second"> |
|||
<QiNiu ref="qiNiu" /> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
</template> |
|||
|
|||
<script> |
|||
import QiNiu from './qiniu/index' |
|||
import Local from './local/index' |
|||
export default { |
|||
name: 'Storage', |
|||
components: { QiNiu, Local }, |
|||
data() { |
|||
return { |
|||
activeName: 'first' |
|||
} |
|||
}, |
|||
methods: { |
|||
tabClick(name) { |
|||
if (this.activeName === 'first') { |
|||
this.$refs.local.crud.toQuery() |
|||
} else { |
|||
this.$refs.qiNiu.crud.toQuery() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
</style> |
@ -0,0 +1,184 @@ |
|||
<template> |
|||
<div class="app-container" style="padding: 8px;"> |
|||
<!--工具栏--> |
|||
<div class="head-container"> |
|||
<div v-if="crud.props.searchToggle"> |
|||
<!-- 搜索 --> |
|||
<el-input v-model="query.blurry" clearable size="small" placeholder="输入内容模糊搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" /> |
|||
<date-range-picker v-model="query.createTime" class="date-item" /> |
|||
<rrOperation /> |
|||
</div> |
|||
<crudOperation :permission="permission"> |
|||
<!-- 新增 --> |
|||
<el-button |
|||
slot="left" |
|||
v-permission="['admin','storage:add']" |
|||
class="filter-item" |
|||
size="mini" |
|||
type="primary" |
|||
icon="el-icon-upload" |
|||
@click="crud.toAdd" |
|||
>上传 |
|||
</el-button> |
|||
</crudOperation> |
|||
</div> |
|||
<!--表单组件--> |
|||
<el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.add ? '文件上传' : '编辑文件'" width="500px"> |
|||
<el-form ref="form" :model="form" size="small" label-width="80px"> |
|||
<el-form-item label="文件名"> |
|||
<el-input v-model="form.name" style="width: 370px;" /> |
|||
</el-form-item> |
|||
<!-- 上传文件 --> |
|||
<el-form-item v-if="crud.status.add" label="上传"> |
|||
<el-upload |
|||
ref="upload" |
|||
:limit="1" |
|||
:before-upload="beforeUpload" |
|||
:auto-upload="false" |
|||
:headers="headers" |
|||
:on-success="handleSuccess" |
|||
:on-error="handleError" |
|||
:action="fileUploadApi + '?name=' + form.name" |
|||
> |
|||
<div class="eladmin-upload"><i class="el-icon-upload" /> 添加文件</div> |
|||
<div slot="tip" class="el-upload__tip">可上传任意格式文件,且不超过100M</div> |
|||
</el-upload> |
|||
</el-form-item> |
|||
</el-form> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button type="text" @click="crud.cancelCU">取消</el-button> |
|||
<el-button v-if="crud.status.add" :loading="loading" type="primary" @click="upload">确认</el-button> |
|||
<el-button v-else :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
<!--表格渲染--> |
|||
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column prop="name" label="文件名"> |
|||
<template slot-scope="scope"> |
|||
<el-popover |
|||
:content="'file/' + scope.row.type + '/' + scope.row.realName" |
|||
placement="top-start" |
|||
title="路径" |
|||
width="200" |
|||
trigger="hover" |
|||
> |
|||
<a |
|||
slot="reference" |
|||
:href="baseApi + '/file/' + scope.row.type + '/' + scope.row.realName" |
|||
class="el-link--primary" |
|||
style="word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color: #1890ff;font-size: 13px;" |
|||
target="_blank" |
|||
> |
|||
{{ scope.row.name }} |
|||
</a> |
|||
</el-popover> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="path" label="预览图"> |
|||
<template slot-scope="{row}"> |
|||
<el-image |
|||
:src=" baseApi + '/file/' + row.type + '/' + row.realName" |
|||
:preview-src-list="[baseApi + '/file/' + row.type + '/' + row.realName]" |
|||
fit="contain" |
|||
lazy |
|||
class="el-avatar" |
|||
> |
|||
<div slot="error"> |
|||
<i class="el-icon-document" /> |
|||
</div> |
|||
</el-image> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="suffix" label="文件类型" /> |
|||
<el-table-column prop="type" label="类别" /> |
|||
<el-table-column prop="size" label="大小" /> |
|||
<el-table-column prop="operate" label="操作人" /> |
|||
<el-table-column prop="createTime" label="创建日期" /> |
|||
</el-table> |
|||
<!--分页组件--> |
|||
<pagination /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapGetters } from 'vuex' |
|||
import { getToken } from '@/utils/auth' |
|||
import crudFile from '@/api/tools/localStorage' |
|||
import CRUD, { presenter, header, form, crud } from '@crud/crud' |
|||
import rrOperation from '@crud/RR.operation' |
|||
import crudOperation from '@crud/CRUD.operation' |
|||
import pagination from '@crud/Pagination' |
|||
import DateRangePicker from '@/components/DateRangePicker' |
|||
|
|||
const defaultForm = { id: null, name: '' } |
|||
export default { |
|||
components: { pagination, crudOperation, rrOperation, DateRangePicker }, |
|||
cruds() { |
|||
return CRUD({ title: '文件', url: 'api/localStorage', crudMethod: { ...crudFile }}) |
|||
}, |
|||
mixins: [presenter(), header(), form(defaultForm), crud()], |
|||
data() { |
|||
return { |
|||
delAllLoading: false, |
|||
loading: false, |
|||
headers: { 'Authorization': getToken() }, |
|||
permission: { |
|||
edit: ['admin', 'storage:edit'], |
|||
del: ['admin', 'storage:del'] |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'baseApi', |
|||
'fileUploadApi' |
|||
]) |
|||
}, |
|||
created() { |
|||
this.crud.optShow.add = false |
|||
}, |
|||
methods: { |
|||
// 上传文件 |
|||
upload() { |
|||
this.$refs.upload.submit() |
|||
}, |
|||
beforeUpload(file) { |
|||
let isLt2M = true |
|||
isLt2M = file.size / 1024 / 1024 < 100 |
|||
if (!isLt2M) { |
|||
this.loading = false |
|||
this.$message.error('上传文件大小不能超过 100MB!') |
|||
} |
|||
this.form.name = file.name |
|||
return isLt2M |
|||
}, |
|||
handleSuccess(response, file, fileList) { |
|||
this.crud.notify('上传成功', CRUD.NOTIFICATION_TYPE.SUCCESS) |
|||
this.$refs.upload.clearFiles() |
|||
this.crud.status.add = CRUD.STATUS.NORMAL |
|||
this.crud.resetForm() |
|||
this.crud.toQuery() |
|||
}, |
|||
// 监听上传失败 |
|||
handleError(e, file, fileList) { |
|||
const msg = JSON.parse(e.message) |
|||
this.$notify({ |
|||
title: msg.message, |
|||
type: 'error', |
|||
duration: 2500 |
|||
}) |
|||
this.loading = false |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
::v-deep .el-image__error, .el-image__placeholder{ |
|||
background: none; |
|||
} |
|||
::v-deep .el-image-viewer__wrapper{ |
|||
top: 55px; |
|||
} |
|||
</style> |
@ -0,0 +1,98 @@ |
|||
<template> |
|||
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" title="七牛云配置" append-to-body width="580px"> |
|||
<el-form ref="form" :model="form" :rules="rules" style="margin-top: 6px;" size="small" label-width="110px"> |
|||
<el-form-item label="Access Key" prop="accessKey"> |
|||
<el-input v-model="form.accessKey" style="width: 95%" placeholder="accessKey,在安全中心,秘钥管理中查看" /> |
|||
</el-form-item> |
|||
<el-form-item label="Secret Key" prop="secretKey"> |
|||
<el-input v-model="form.secretKey" type="password" style="width: 95%;" placeholder="secretKey,在安全中心,秘钥管理中查看" /> |
|||
</el-form-item> |
|||
<el-form-item label="空间名称" prop="bucket"> |
|||
<el-input v-model="form.bucket" style="width: 95%;" placeholder="存储空间名称作为唯一的 Bucket 识别符" /> |
|||
</el-form-item> |
|||
<el-form-item label="外链域名" prop="host"> |
|||
<el-input v-model="form.host" style="width: 95%;" placeholder="外链域名,可自定义,需在七牛云绑定" /> |
|||
</el-form-item> |
|||
<el-form-item label="存储区域"> |
|||
<el-select v-model="form.zone" placeholder="请选择存储区域"> |
|||
<el-option |
|||
v-for="item in zones" |
|||
:key="item" |
|||
:label="item" |
|||
:value="item" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="空间类型" prop="type"> |
|||
<el-radio v-model="form.type" label="公开">公开</el-radio> |
|||
<el-radio v-model="form.type" label="私有">私有</el-radio> |
|||
</el-form-item> |
|||
</el-form> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button type="text" @click="dialog = false">取消</el-button> |
|||
<el-button :loading="loading" type="primary" @click="doSubmit">确认</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
import { get, update } from '@/api/tools/qiniu' |
|||
export default { |
|||
data() { |
|||
return { |
|||
zones: ['华东', '华北', '华南', '北美', '东南亚'], dialog: false, |
|||
loading: false, form: { accessKey: '', secretKey: '', bucket: '', host: '', zone: '', type: '' }, |
|||
rules: { |
|||
accessKey: [ |
|||
{ required: true, message: '请输入accessKey', trigger: 'blur' } |
|||
], |
|||
secretKey: [ |
|||
{ required: true, message: '请输入secretKey', trigger: 'blur' } |
|||
], |
|||
bucket: [ |
|||
{ required: true, message: '请输入空间名称', trigger: 'blur' } |
|||
], |
|||
host: [ |
|||
{ required: true, message: '请输入外链域名', trigger: 'blur' } |
|||
], |
|||
type: [ |
|||
{ required: true, message: '空间类型不能为空', trigger: 'blur' } |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
init() { |
|||
get().then(res => { |
|||
this.form = res |
|||
}) |
|||
}, |
|||
doSubmit() { |
|||
this.$refs['form'].validate((valid) => { |
|||
if (valid) { |
|||
this.loading = true |
|||
update(this.form).then(res => { |
|||
this.$notify({ |
|||
title: '修改成功', |
|||
type: 'success', |
|||
duration: 2500 |
|||
}) |
|||
this.$parent.crud.toQuery() |
|||
this.loading = false |
|||
this.dialog = false |
|||
}).catch(err => { |
|||
this.loading = false |
|||
console.log(err.response.data.message) |
|||
}) |
|||
} else { |
|||
return false |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
|
|||
</style> |
@ -0,0 +1,189 @@ |
|||
<template> |
|||
<div class="app-container" style="padding: 8px;"> |
|||
<!--表单组件--> |
|||
<eForm ref="form" /> |
|||
<!-- 工具栏 --> |
|||
<div class="head-container"> |
|||
<div v-if="crud.props.searchToggle"> |
|||
<!-- 搜索 --> |
|||
<el-input v-model="query.key" clearable size="small" placeholder="输入文件名称搜索" style="width: 200px;" class="filter-item" @keyup.enter.native="toQuery" /> |
|||
<date-range-picker v-model="query.createTime" class="date-item" /> |
|||
<rrOperation /> |
|||
</div> |
|||
<crudOperation :permission="permission"> |
|||
<template slot="left"> |
|||
<!-- 上传 --> |
|||
<el-button class="filter-item" size="mini" type="primary" icon="el-icon-upload" @click="dialog = true">上传</el-button> |
|||
<!-- 同步 --> |
|||
<el-button :icon="icon" class="filter-item" size="mini" type="warning" @click="synchronize">同步</el-button> |
|||
<!-- 配置 --> |
|||
<el-button |
|||
class="filter-item" |
|||
size="mini" |
|||
type="success" |
|||
icon="el-icon-s-tools" |
|||
@click="doConfig" |
|||
>配置</el-button> |
|||
</template> |
|||
</crudOperation> |
|||
<!-- 文件上传 --> |
|||
<el-dialog :visible.sync="dialog" :close-on-click-modal="false" append-to-body width="500px" @close="doSubmit"> |
|||
<el-upload |
|||
:before-remove="handleBeforeRemove" |
|||
:on-success="handleSuccess" |
|||
:on-error="handleError" |
|||
:file-list="fileList" |
|||
:headers="headers" |
|||
:action="qiNiuUploadApi" |
|||
class="upload-demo" |
|||
multiple |
|||
> |
|||
<el-button size="small" type="primary">点击上传</el-button> |
|||
<div slot="tip" style="display: block;" class="el-upload__tip">请勿上传违法文件,且文件不超过15M</div> |
|||
</el-upload> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button type="primary" @click="doSubmit">确认</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
<!--表格渲染--> |
|||
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="crud.selectionChangeHandler"> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column prop="name" :show-overflow-tooltip="true" label="文件名"> |
|||
<template slot-scope="scope"> |
|||
<a href="JavaScript:" class="el-link el-link--primary" target="_blank" type="primary" @click="download(scope.row.id)">{{ scope.row.key }}</a> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column :show-overflow-tooltip="true" prop="suffix" label="文件类型" @selection-change="crud.selectionChangeHandler" /> |
|||
<el-table-column prop="bucket" label="空间名称" /> |
|||
<el-table-column prop="size" label="文件大小" /> |
|||
<el-table-column prop="type" label="空间类型" /> |
|||
<el-table-column prop="updateTime" label="创建日期" /> |
|||
</el-table> |
|||
<!--分页组件--> |
|||
<pagination /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import crudQiNiu from '@/api/tools/qiniu' |
|||
import { mapGetters } from 'vuex' |
|||
import { getToken } from '@/utils/auth' |
|||
import eForm from './form' |
|||
import CRUD, { presenter, header, crud } from '@crud/crud' |
|||
import rrOperation from '@crud/RR.operation' |
|||
import crudOperation from '@crud/CRUD.operation' |
|||
import pagination from '@crud/Pagination' |
|||
import DateRangePicker from '@/components/DateRangePicker' |
|||
|
|||
export default { |
|||
components: { eForm, pagination, crudOperation, rrOperation, DateRangePicker }, |
|||
cruds() { |
|||
return CRUD({ title: '七牛云文件', url: 'api/qiNiuContent', crudMethod: { ...crudQiNiu }}) |
|||
}, |
|||
mixins: [presenter(), header(), crud()], |
|||
data() { |
|||
return { |
|||
permission: { |
|||
del: ['admin', 'storage:del'] |
|||
}, |
|||
title: '文件', dialog: false, |
|||
icon: 'el-icon-refresh', |
|||
url: '', headers: { 'Authorization': getToken() }, |
|||
dialogImageUrl: '', dialogVisible: false, fileList: [], files: [], newWin: null |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'qiNiuUploadApi' |
|||
]) |
|||
}, |
|||
watch: { |
|||
url(newVal, oldVal) { |
|||
if (newVal && this.newWin) { |
|||
this.newWin.sessionStorage.clear() |
|||
this.newWin.location.href = newVal |
|||
// 重定向后把url和newWin重置 |
|||
this.url = '' |
|||
this.newWin = null |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
this.crud.optShow.add = false |
|||
this.crud.optShow.edit = false |
|||
}, |
|||
methods: { |
|||
// 七牛云配置 |
|||
doConfig() { |
|||
const _this = this.$refs.form |
|||
_this.init() |
|||
_this.dialog = true |
|||
}, |
|||
handleSuccess(response, file, fileList) { |
|||
const uid = file.uid |
|||
const id = response.id |
|||
this.files.push({ uid, id }) |
|||
}, |
|||
handleBeforeRemove(file, fileList) { |
|||
for (let i = 0; i < this.files.length; i++) { |
|||
if (this.files[i].uid === file.uid) { |
|||
crudQiNiu.del([this.files[i].id]).then(res => {}) |
|||
return true |
|||
} |
|||
} |
|||
}, |
|||
handlePictureCardPreview(file) { |
|||
this.dialogImageUrl = file.url |
|||
this.dialogVisible = true |
|||
}, |
|||
// 刷新列表数据 |
|||
doSubmit() { |
|||
this.fileList = [] |
|||
this.dialogVisible = false |
|||
this.dialogImageUrl = '' |
|||
this.dialog = false |
|||
this.crud.toQuery() |
|||
}, |
|||
// 监听上传失败 |
|||
handleError(e, file, fileList) { |
|||
const msg = JSON.parse(e.message) |
|||
this.crud.notify(msg.message, CRUD.NOTIFICATION_TYPE.ERROR) |
|||
}, |
|||
// 下载文件 |
|||
download(id) { |
|||
this.downloadLoading = true |
|||
// 先打开一个空的新窗口,再请求 |
|||
this.newWin = window.open() |
|||
crudQiNiu.download(id).then(res => { |
|||
this.downloadLoading = false |
|||
this.url = res.url |
|||
}).catch(err => { |
|||
this.downloadLoading = false |
|||
console.log(err.response.data.message) |
|||
}) |
|||
}, |
|||
// 同步数据 |
|||
synchronize() { |
|||
this.icon = 'el-icon-loading' |
|||
crudQiNiu.sync().then(res => { |
|||
this.icon = 'el-icon-refresh' |
|||
this.$message({ |
|||
showClose: true, |
|||
message: '数据同步成功', |
|||
type: 'success', |
|||
duration: 1500 |
|||
}) |
|||
this.crud.toQuery() |
|||
}).catch(err => { |
|||
this.icon = 'el-icon-refresh' |
|||
console.log(err.response.data.message) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
|
|||
</style> |
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<elFrame :src="swaggerApi" /> |
|||
</template> |
|||
<script> |
|||
import { mapGetters } from 'vuex' |
|||
import elFrame from '@/components/Iframe/index' |
|||
export default { |
|||
name: 'Swagger', |
|||
components: { elFrame }, |
|||
computed: { |
|||
...mapGetters([ |
|||
'swaggerApi' |
|||
]) |
|||
} |
|||
} |
|||
</script> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue