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 AppMain } from './AppMain' |
||||
export { default as Navbar } from './Navbar' |
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 Settings } from './Settings' |
||||
export { default as TagsView } from './TagsView/index' |
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