mirror of https://github.com/halo-dev/halo-admin
refactor: use js-sdk/admin-api (#378)
* 1.3.0-beta.2 * fix: token expire. * pref: #291 * refactor: use js-sdk/admin-api * chore: remove unnecessary files * chore: remove unnecessary files * chore: remove unnecessary files * chore: remove unnecessary files * refactor: developer/Environment.vue * chore: remove unnecessary files * refactor: login auth * refactor: login auth * refactor: remove api url setting * refactor: custom sheet list * style: reformat code * refactor: logout * refactor: remove setTimeout when fetch api * fix: auto login error * fix: post update error * fix: backup * fix: turn on developer mode error * fix: mfa setting error * chore: remove unnecessary files * feat: add interceptors * refactor: api client * refactor: api client * chore(deps): upgrade admin-api * refactor: 重构认证 * fix: 修复认证请求头参数 * refactor: login * feat: add error handle * refactor: api client * refactor: refresh token * refactor: attachment upload * refactor: upload component * refactor: upload * fix: tag save * fix: github api request * fix: installation page * feat: add version field for html * fix: option.list to option.listAsMapView * fix: directory base path of static storage * chore: upgrade halo sdk version Co-authored-by: guqing <1484563614@qq.com>pull/381/head
parent
760fffd605
commit
48d145f053
|
@ -1,2 +1,3 @@
|
|||
NODE_ENV=development
|
||||
PUBLIC_PATH=/
|
||||
PUBLIC_PATH=/
|
||||
VUE_APP_API_URL=http://localhost:8090
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
NODE_ENV=production
|
||||
PUBLIC_PATH=https://cdn.jsdelivr.net/npm/halo-admin@1.4.13/dist/
|
||||
VUE_APP_API_URL=/
|
||||
|
|
|
@ -21,3 +21,8 @@ pnpm-debug.log*
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# NodeJs package manager
|
||||
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
|
|
@ -24,8 +24,9 @@
|
|||
"@codemirror/basic-setup": "^0.19.0",
|
||||
"@codemirror/lang-html": "^0.19.3",
|
||||
"@codemirror/lang-java": "^0.19.1",
|
||||
"@halo-dev/admin-api": "^1.0.0-alpha.44",
|
||||
"ant-design-vue": "^1.7.8",
|
||||
"axios": "^0.21.4",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.10.7",
|
||||
"enquire.js": "^2.1.6",
|
||||
"filepond": "^4.30.3",
|
||||
|
|
|
@ -5,6 +5,7 @@ specifiers:
|
|||
'@codemirror/basic-setup': ^0.19.0
|
||||
'@codemirror/lang-html': ^0.19.3
|
||||
'@codemirror/lang-java': ^0.19.1
|
||||
'@halo-dev/admin-api': ^1.0.0-alpha.44
|
||||
'@vue/cli-plugin-babel': ^3.12.1
|
||||
'@vue/cli-plugin-eslint': ^4.5.15
|
||||
'@vue/cli-plugin-unit-jest': ^4.5.15
|
||||
|
@ -12,11 +13,11 @@ specifiers:
|
|||
'@vue/eslint-config-prettier': ^6.0.0
|
||||
'@vue/test-utils': ^1.2.2
|
||||
ant-design-vue: ^1.7.8
|
||||
axios: ^0.21.4
|
||||
babel-core: 7.0.0-bridge.0
|
||||
babel-eslint: ^10.1.0
|
||||
babel-jest: ^26.6.3
|
||||
babel-plugin-import: ^1.13.3
|
||||
crypto-js: ^4.1.1
|
||||
dayjs: ^1.10.7
|
||||
enquire.js: ^2.1.6
|
||||
eslint: ^6.8.0
|
||||
|
@ -53,8 +54,9 @@ dependencies:
|
|||
'@codemirror/basic-setup': 0.19.0
|
||||
'@codemirror/lang-html': 0.19.3
|
||||
'@codemirror/lang-java': 0.19.1
|
||||
'@halo-dev/admin-api': 1.0.0-alpha.44
|
||||
ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c
|
||||
axios: 0.21.4
|
||||
crypto-js: 4.1.1
|
||||
dayjs: 1.10.7
|
||||
enquire.js: 2.1.6
|
||||
filepond: 4.30.3
|
||||
|
@ -1346,6 +1348,36 @@ packages:
|
|||
purgecss: 2.3.0
|
||||
dev: true
|
||||
|
||||
/@halo-dev/admin-api/1.0.0-alpha.44:
|
||||
resolution: {integrity: sha512-nCJsx4gDxCjkoGJKsBpcgLhAUc8RYrqKMGM1VSi9t64xTFdXDjIJgtcIQuBgJ0b4R58JIp6A6ti5S764vz4BDA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@halo-dev/rest-api-client': 1.0.0-alpha.44
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/@halo-dev/logger/1.0.0-alpha.44:
|
||||
resolution: {integrity: sha512-ORHP6pj8wLb+mwsk+pYqvH9tqNWTr+96AiZgtSMcdwojA1KupFSVzHN0aqpk8HeGfSrBNbMz4VT6ey+OVnWrcQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
|
||||
/@halo-dev/rest-api-client/1.0.0-alpha.44:
|
||||
resolution: {integrity: sha512-fCzh7ihLpI7hrq0S2M9YjROTvoTT5JeEOqv5O/hon4bTfpBqEb7REjjYMONR8lJNgmEI9wzviEhn7Xxg7nX/vA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@halo-dev/logger': 1.0.0-alpha.44
|
||||
axios: 0.24.0
|
||||
form-data: 4.0.0
|
||||
js-base64: 3.7.2
|
||||
qs: 6.10.1
|
||||
store: 2.0.12
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/@hapi/address/2.1.4:
|
||||
resolution: {integrity: sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==}
|
||||
deprecated: Moved to 'npm install @sideway/address'
|
||||
|
@ -2787,7 +2819,6 @@ packages:
|
|||
|
||||
/asynckit/0.4.0:
|
||||
resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=}
|
||||
dev: true
|
||||
|
||||
/atob/2.1.2:
|
||||
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
|
||||
|
@ -2816,8 +2847,8 @@ packages:
|
|||
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
|
||||
dev: true
|
||||
|
||||
/axios/0.21.4:
|
||||
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||
/axios/0.24.0:
|
||||
resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
|
||||
dependencies:
|
||||
follow-redirects: 1.14.4
|
||||
transitivePeerDependencies:
|
||||
|
@ -3453,7 +3484,6 @@ packages:
|
|||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
get-intrinsic: 1.1.1
|
||||
dev: true
|
||||
|
||||
/call-me-maybe/1.0.1:
|
||||
resolution: {integrity: sha1-JtII6onje1y95gJQoV8DHBak1ms=}
|
||||
|
@ -3824,7 +3854,6 @@ packages:
|
|||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
dev: true
|
||||
|
||||
/commander/2.17.1:
|
||||
resolution: {integrity: sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==}
|
||||
|
@ -4128,6 +4157,10 @@ packages:
|
|||
randomfill: 1.0.4
|
||||
dev: true
|
||||
|
||||
/crypto-js/4.1.1:
|
||||
resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==}
|
||||
dev: false
|
||||
|
||||
/css-color-names/0.0.4:
|
||||
resolution: {integrity: sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=}
|
||||
dev: true
|
||||
|
@ -4508,7 +4541,6 @@ packages:
|
|||
/delayed-stream/1.0.0:
|
||||
resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: true
|
||||
|
||||
/delegate/3.2.0:
|
||||
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
|
||||
|
@ -5537,6 +5569,15 @@ packages:
|
|||
mime-types: 2.1.32
|
||||
dev: true
|
||||
|
||||
/form-data/4.0.0:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.32
|
||||
dev: false
|
||||
|
||||
/forwarded/0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -5614,7 +5655,6 @@ packages:
|
|||
|
||||
/function-bind/1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
dev: true
|
||||
|
||||
/functional-red-black-tree/1.0.1:
|
||||
resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=}
|
||||
|
@ -5636,7 +5676,6 @@ packages:
|
|||
function-bind: 1.1.1
|
||||
has: 1.0.3
|
||||
has-symbols: 1.0.2
|
||||
dev: true
|
||||
|
||||
/get-own-enumerable-property-symbols/3.0.2:
|
||||
resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
|
||||
|
@ -5844,7 +5883,6 @@ packages:
|
|||
/has-symbols/1.0.2:
|
||||
resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/has-tostringtag/1.0.0:
|
||||
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
|
||||
|
@ -5889,7 +5927,6 @@ packages:
|
|||
engines: {node: '>= 0.4.0'}
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
dev: true
|
||||
|
||||
/hash-base/3.1.0:
|
||||
resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==}
|
||||
|
@ -7248,6 +7285,10 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/js-base64/3.7.2:
|
||||
resolution: {integrity: sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==}
|
||||
dev: false
|
||||
|
||||
/js-beautify/1.14.0:
|
||||
resolution: {integrity: sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -7886,7 +7927,6 @@ packages:
|
|||
/mime-db/1.49.0:
|
||||
resolution: {integrity: sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/mime-db/1.50.0:
|
||||
resolution: {integrity: sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==}
|
||||
|
@ -7898,7 +7938,6 @@ packages:
|
|||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.49.0
|
||||
dev: true
|
||||
|
||||
/mime/1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
|
@ -8320,7 +8359,6 @@ packages:
|
|||
|
||||
/object-inspect/1.11.0:
|
||||
resolution: {integrity: sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==}
|
||||
dev: true
|
||||
|
||||
/object-is/1.1.5:
|
||||
resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==}
|
||||
|
@ -9365,6 +9403,13 @@ packages:
|
|||
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
|
||||
dev: true
|
||||
|
||||
/qs/6.10.1:
|
||||
resolution: {integrity: sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==}
|
||||
engines: {node: '>=0.6'}
|
||||
dependencies:
|
||||
side-channel: 1.0.4
|
||||
dev: false
|
||||
|
||||
/qs/6.5.2:
|
||||
resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
@ -10024,7 +10069,6 @@ packages:
|
|||
call-bind: 1.0.2
|
||||
get-intrinsic: 1.1.1
|
||||
object-inspect: 1.11.0
|
||||
dev: true
|
||||
|
||||
/sigmund/1.0.1:
|
||||
resolution: {integrity: sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=}
|
||||
|
@ -10307,6 +10351,10 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/store/2.0.12:
|
||||
resolution: {integrity: sha1-jFNOKguDH3K3X8XxEZhXxE711ZM=}
|
||||
dev: false
|
||||
|
||||
/stream-browserify/2.0.2:
|
||||
resolution: {integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==}
|
||||
dependencies:
|
||||
|
@ -10859,6 +10907,10 @@ packages:
|
|||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
|
||||
/tslib/2.3.1:
|
||||
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
|
||||
dev: false
|
||||
|
||||
/tty-browserify/0.0.0:
|
||||
resolution: {integrity: sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=}
|
||||
dev: true
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="generator" content="Halo 1.4.13" />
|
||||
<meta name="generator" content="Halo <%= htmlWebpackPlugin.options.version %>" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>Halo Dashboard</title>
|
||||
<style>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<script>
|
||||
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
|
||||
import { deviceEnquire, DEVICE_TYPE } from '@/utils/device'
|
||||
import { DEVICE_TYPE, deviceEnquire } from '@/utils/device'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/actuator'
|
||||
|
||||
const actuatorApi = {}
|
||||
|
||||
actuatorApi.logfile = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/logfile`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.env = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/env`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getSystemCpuCount = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/system.cpu.count`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getSystemCpuUsage = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/system.cpu.usage`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getProcessUptime = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/process.uptime`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getProcessStartTime = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/process.start.time`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getProcessCpuUsage = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/process.cpu.usage`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getJvmMemoryMax = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/jvm.memory.max`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getJvmMemoryCommitted = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/jvm.memory.committed`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getJvmMemoryUsed = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/jvm.memory.used`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
actuatorApi.getJvmGcPause = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/metrics/jvm.gc.pause`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default actuatorApi
|
117
src/api/admin.js
117
src/api/admin.js
|
@ -1,117 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin'
|
||||
|
||||
const adminApi = {}
|
||||
|
||||
adminApi.counts = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/counts`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.isInstalled = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/is_installed`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.environments = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/environments`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.install = data => {
|
||||
return service({
|
||||
url: `${baseUrl}/installations`,
|
||||
data: data,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.loginPreCheck = (username, password) => {
|
||||
return service({
|
||||
url: `${baseUrl}/login/precheck`,
|
||||
data: {
|
||||
username: username,
|
||||
password: password
|
||||
},
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.login = (username, password, authcode) => {
|
||||
return service({
|
||||
url: `${baseUrl}/login`,
|
||||
data: {
|
||||
username: username,
|
||||
password: password,
|
||||
authcode: authcode
|
||||
},
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.logout = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/logout`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.refreshToken = refreshToken => {
|
||||
return service({
|
||||
url: `${baseUrl}/refresh/${refreshToken}`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.sendResetCode = param => {
|
||||
return service({
|
||||
url: `${baseUrl}/password/code`,
|
||||
data: param,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.resetPassword = param => {
|
||||
return service({
|
||||
url: `${baseUrl}/password/reset`,
|
||||
data: param,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.updateAdminAssets = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo-admin`,
|
||||
method: 'put',
|
||||
timeout: 600 * 1000
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.getLogFiles = lines => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo/logfile`,
|
||||
params: {
|
||||
lines: lines
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.downloadLogFiles = lines => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo/logfile/download`,
|
||||
params: {
|
||||
lines: lines
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default adminApi
|
|
@ -1,127 +0,0 @@
|
|||
import axios from 'axios'
|
||||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/attachments'
|
||||
|
||||
const attachmentApi = {}
|
||||
|
||||
attachmentApi.query = params => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.get = attachmentId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${attachmentId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.delete = attachmentId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${attachmentId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.deleteInBatch = attachmentIds => {
|
||||
return service({
|
||||
url: `${baseUrl}`,
|
||||
method: 'delete',
|
||||
data: attachmentIds,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.update = (attachmentId, attachment) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${attachmentId}`,
|
||||
method: 'put',
|
||||
data: attachment
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.getMediaTypes = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/media_types`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.getTypes = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/types`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.CancelToken = axios.CancelToken
|
||||
attachmentApi.isCancel = axios.isCancel
|
||||
|
||||
attachmentApi.upload = (formData, uploadProgress, cancelToken) => {
|
||||
return service({
|
||||
url: `${baseUrl}/upload`,
|
||||
timeout: 8640000, // 24 hours
|
||||
data: formData, // form data
|
||||
onUploadProgress: uploadProgress,
|
||||
cancelToken: cancelToken,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.uploads = (formDatas, uploadProgress, cancelToken) => {
|
||||
return service({
|
||||
url: `${baseUrl}/uploads`,
|
||||
timeout: 8640000, // 24 hours
|
||||
data: formDatas, // form data
|
||||
onUploadProgress: uploadProgress,
|
||||
cancelToken: cancelToken,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.type = {
|
||||
LOCAL: {
|
||||
type: 'LOCAL',
|
||||
text: '本地'
|
||||
},
|
||||
SMMS: {
|
||||
type: 'SMMS',
|
||||
text: 'SM.MS'
|
||||
},
|
||||
UPOSS: {
|
||||
type: 'UPOSS',
|
||||
text: '又拍云'
|
||||
},
|
||||
QINIUOSS: {
|
||||
type: 'QINIUOSS',
|
||||
text: '七牛云'
|
||||
},
|
||||
ALIOSS: {
|
||||
type: 'ALIOSS',
|
||||
text: '阿里云'
|
||||
},
|
||||
BAIDUBOS: {
|
||||
type: 'BAIDUBOS',
|
||||
text: '百度云'
|
||||
},
|
||||
TENCENTCOS: {
|
||||
type: 'TENCENTCOS',
|
||||
text: '腾讯云'
|
||||
},
|
||||
HUAWEIOBS: {
|
||||
type: 'HUAWEIOBS',
|
||||
text: '华为云'
|
||||
},
|
||||
MINIO: {
|
||||
type: 'MINIO',
|
||||
text: 'MinIO'
|
||||
}
|
||||
}
|
||||
|
||||
export default attachmentApi
|
|
@ -1,125 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/backups'
|
||||
|
||||
const backupApi = {}
|
||||
|
||||
backupApi.importMarkdown = (formData, uploadProgress, cancelToken) => {
|
||||
return service({
|
||||
url: `${baseUrl}/markdown/import`,
|
||||
timeout: 8640000, // 24 hours
|
||||
data: formData, // form data
|
||||
onUploadProgress: uploadProgress,
|
||||
cancelToken: cancelToken,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.backupWorkDir = options => {
|
||||
return service({
|
||||
url: `${baseUrl}/work-dir`,
|
||||
method: 'post',
|
||||
data: options,
|
||||
timeout: 8640000 // 24 hours
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.listWorkDirOptions = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/work-dir/options`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.listWorkDirBackups = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/work-dir`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.fetchWorkDir = filename => {
|
||||
return service({
|
||||
url: `${baseUrl}/work-dir/fetch?filename=${filename}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.deleteWorkDirBackup = filename => {
|
||||
return service({
|
||||
url: `${baseUrl}/work-dir`,
|
||||
params: {
|
||||
filename: filename
|
||||
},
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.exportData = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/data`,
|
||||
method: 'post',
|
||||
timeout: 8640000 // 24 hours
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.listExportedData = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/data`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.fetchData = filename => {
|
||||
return service({
|
||||
url: `${baseUrl}/data/fetch?filename=${filename}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.deleteExportedData = filename => {
|
||||
return service({
|
||||
url: `${baseUrl}/data`,
|
||||
params: {
|
||||
filename: filename
|
||||
},
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.exportMarkdowns = needFrontMatter => {
|
||||
return service({
|
||||
url: `${baseUrl}/markdown/export`,
|
||||
method: 'post',
|
||||
data: {
|
||||
needFrontMatter: needFrontMatter
|
||||
},
|
||||
timeout: 8640000 // 24 hours
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.listExportedMarkdowns = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/markdown/export`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.fetchMarkdown = filename => {
|
||||
return service({
|
||||
url: `${baseUrl}/markdown/fetch?filename=${filename}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.deleteExportedMarkdown = filename => {
|
||||
return service({
|
||||
url: `${baseUrl}/markdown/export`,
|
||||
params: {
|
||||
filename: filename
|
||||
},
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export default backupApi
|
|
@ -1,85 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/categories'
|
||||
|
||||
const categoryApi = {}
|
||||
|
||||
categoryApi.listAll = (more = false) => {
|
||||
return service({
|
||||
url: `${baseUrl}`,
|
||||
params: {
|
||||
more: more
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
categoryApi.listTree = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/tree_view`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
categoryApi.create = category => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: category,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
categoryApi.delete = categoryId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${categoryId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
categoryApi.get = categoryId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${categoryId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
categoryApi.update = (categoryId, category) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${categoryId}`,
|
||||
data: category,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
function concreteTree(parentCategory, categories) {
|
||||
categories.forEach(category => {
|
||||
if (parentCategory.key === category.parentId) {
|
||||
if (!parentCategory.children) {
|
||||
parentCategory.children = []
|
||||
}
|
||||
parentCategory.children.push({
|
||||
key: category.id,
|
||||
title: category.name,
|
||||
isLeaf: false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (parentCategory.children) {
|
||||
parentCategory.children.forEach(category => concreteTree(category, categories))
|
||||
} else {
|
||||
parentCategory.isLeaf = true
|
||||
}
|
||||
}
|
||||
|
||||
categoryApi.concreteTree = categories => {
|
||||
const topCategoryNode = {
|
||||
key: 0,
|
||||
title: 'top',
|
||||
children: []
|
||||
}
|
||||
concreteTree(topCategoryNode, categories)
|
||||
return topCategoryNode.children
|
||||
}
|
||||
|
||||
export default categoryApi
|
|
@ -1,140 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin'
|
||||
|
||||
const commentApi = {}
|
||||
|
||||
commentApi.latestComment = (target, top, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments/latest`,
|
||||
params: {
|
||||
top: top,
|
||||
status: status
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.queryComment = (target, params) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments`,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.commentTree = (target, id, params) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments/${id}/tree_view`,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.updateStatus = (target, commentId, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments/${commentId}/status/${status}`,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.updateStatusInBatch = (target, ids, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments/status/${status}`,
|
||||
data: ids,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.delete = (target, commentId) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments/${commentId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.deleteInBatch = (target, ids) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments`,
|
||||
data: ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.create = (target, comment) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments`,
|
||||
data: comment,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.update = (target, commentId, comment) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments/${commentId}`,
|
||||
data: comment,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a comment.
|
||||
* @param {String} target
|
||||
* @param {Object} comment
|
||||
*/
|
||||
function createComment(target, comment) {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments`,
|
||||
method: 'post',
|
||||
data: comment
|
||||
})
|
||||
}
|
||||
|
||||
// Creation api
|
||||
|
||||
commentApi.createPostComment = comment => {
|
||||
return createComment('posts', comment)
|
||||
}
|
||||
|
||||
commentApi.createSheetComment = comment => {
|
||||
return createComment('sheets', comment)
|
||||
}
|
||||
|
||||
commentApi.createJournalComment = comment => {
|
||||
return createComment('journals', comment)
|
||||
}
|
||||
|
||||
commentApi.createComment = (comment, type) => {
|
||||
if (type === 'sheet') {
|
||||
return commentApi.createSheetComment(comment)
|
||||
}
|
||||
|
||||
if (type === 'journal') {
|
||||
return commentApi.createJournalComment(comment)
|
||||
}
|
||||
|
||||
return commentApi.createPostComment(comment)
|
||||
}
|
||||
|
||||
commentApi.commentStatus = {
|
||||
PUBLISHED: {
|
||||
value: 'PUBLISHED',
|
||||
color: 'green',
|
||||
status: 'success',
|
||||
text: '已发布'
|
||||
},
|
||||
AUDITING: {
|
||||
value: 'AUDITING',
|
||||
color: 'yellow',
|
||||
status: 'warning',
|
||||
text: '待审核'
|
||||
},
|
||||
RECYCLE: {
|
||||
value: 'RECYCLE',
|
||||
color: 'red',
|
||||
status: 'error',
|
||||
text: '回收站'
|
||||
}
|
||||
}
|
||||
|
||||
export default commentApi
|
|
@ -1,54 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/journals'
|
||||
|
||||
const journalApi = {}
|
||||
|
||||
journalApi.query = params => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
journalApi.create = journal => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: journal,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
journalApi.update = (journalId, journal) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${journalId}`,
|
||||
data: journal,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
journalApi.delete = journalId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${journalId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
journalApi.commentTree = journalId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${journalId}/comments/tree_view`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
journalApi.journalType = {
|
||||
PUBLIC: {
|
||||
text: '公开'
|
||||
},
|
||||
INTIMATE: {
|
||||
text: '私密'
|
||||
}
|
||||
}
|
||||
|
||||
export default journalApi
|
|
@ -1,22 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/journals/comments'
|
||||
|
||||
const journalCommentApi = {}
|
||||
|
||||
journalCommentApi.create = comment => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: comment,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
journalCommentApi.delete = commentId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${commentId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export default journalCommentApi
|
|
@ -1,61 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/links'
|
||||
|
||||
const linkApi = {}
|
||||
|
||||
linkApi.listAll = () => {
|
||||
return service({
|
||||
url: `${baseUrl}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
linkApi.create = link => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: link,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
linkApi.get = linkId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${linkId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
linkApi.getByParse = url => {
|
||||
return service({
|
||||
url: `${baseUrl}/parse`,
|
||||
params: {
|
||||
url: url
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
linkApi.update = (linkId, link) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${linkId}`,
|
||||
data: link,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
linkApi.delete = linkId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${linkId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
linkApi.listTeams = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/teams`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default linkApi
|
|
@ -1,91 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/logs'
|
||||
|
||||
const logApi = {}
|
||||
|
||||
logApi.listLatest = top => {
|
||||
return service({
|
||||
url: `${baseUrl}/latest`,
|
||||
params: {
|
||||
top: top
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
logApi.pageBy = logPagination => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: logPagination,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
logApi.clear = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/clear`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
logApi.logTypes = {
|
||||
BLOG_INITIALIZED: {
|
||||
value: 0,
|
||||
text: '博客初始化'
|
||||
},
|
||||
POST_PUBLISHED: {
|
||||
value: 5,
|
||||
text: '文章发布'
|
||||
},
|
||||
POST_EDITED: {
|
||||
value: 15,
|
||||
text: '文章修改'
|
||||
},
|
||||
POST_DELETED: {
|
||||
value: 20,
|
||||
text: '文章删除'
|
||||
},
|
||||
LOGGED_IN: {
|
||||
value: 25,
|
||||
text: '用户登录'
|
||||
},
|
||||
LOGGED_OUT: {
|
||||
value: 30,
|
||||
text: '注销登录'
|
||||
},
|
||||
LOGIN_FAILED: {
|
||||
value: 35,
|
||||
text: '登录失败'
|
||||
},
|
||||
PASSWORD_UPDATED: {
|
||||
value: 40,
|
||||
text: '修改密码'
|
||||
},
|
||||
PROFILE_UPDATED: {
|
||||
value: 45,
|
||||
text: '资料修改'
|
||||
},
|
||||
SHEET_PUBLISHED: {
|
||||
value: 50,
|
||||
text: '页面发布'
|
||||
},
|
||||
SHEET_EDITED: {
|
||||
value: 55,
|
||||
text: '页面修改'
|
||||
},
|
||||
SHEET_DELETED: {
|
||||
value: 60,
|
||||
text: '页面删除'
|
||||
},
|
||||
MFA_UPDATED: {
|
||||
value: 65,
|
||||
text: '两步验证'
|
||||
},
|
||||
LOGGED_PRE_CHECK: {
|
||||
value: 70,
|
||||
text: '登录验证'
|
||||
}
|
||||
}
|
||||
|
||||
export default logApi
|
|
@ -1,15 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/mails'
|
||||
|
||||
const mailApi = {}
|
||||
|
||||
mailApi.testMail = mailData => {
|
||||
return service({
|
||||
url: `${baseUrl}/test`,
|
||||
method: 'post',
|
||||
data: mailData
|
||||
})
|
||||
}
|
||||
|
||||
export default mailApi
|
|
@ -1,92 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/menus'
|
||||
|
||||
const menuApi = {}
|
||||
|
||||
menuApi.listAll = () => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.listTree = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/tree_view`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.listTreeByTeam = team => {
|
||||
return service({
|
||||
url: `${baseUrl}/team/tree_view`,
|
||||
params: {
|
||||
team: team
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.create = menu => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: menu,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.createBatch = menus => {
|
||||
return service({
|
||||
url: `${baseUrl}/batch`,
|
||||
data: menus,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.updateBatch = menus => {
|
||||
return service({
|
||||
url: `${baseUrl}/batch`,
|
||||
data: menus,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.delete = menuId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${menuId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.deleteBatch = menuIds => {
|
||||
return service({
|
||||
url: `${baseUrl}/batch`,
|
||||
data: menuIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.get = menuId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${menuId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.update = (menuId, menu) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${menuId}`,
|
||||
data: menu,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
menuApi.listTeams = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/teams`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default menuApi
|
|
@ -1,15 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/migrations'
|
||||
|
||||
const migrateApi = {}
|
||||
|
||||
migrateApi.migrate = formData => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo`,
|
||||
data: formData,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export default migrateApi
|
|
@ -1,79 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/options'
|
||||
|
||||
const optionApi = {}
|
||||
|
||||
optionApi.listAll = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/map_view`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.listAllByKeys = keys => {
|
||||
return service({
|
||||
url: `${baseUrl}/map_view/keys`,
|
||||
data: keys,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.query = params => {
|
||||
return service({
|
||||
url: `${baseUrl}/list_view`,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.save = options => {
|
||||
return service({
|
||||
url: `${baseUrl}/map_view/saving`,
|
||||
method: 'post',
|
||||
data: options
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.create = option => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: option,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.delete = optionId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${optionId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.get = optionId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${optionId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.update = (optionId, option) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${optionId}`,
|
||||
data: option,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.type = {
|
||||
INTERNAL: {
|
||||
value: 'INTERNAL',
|
||||
text: '系统'
|
||||
},
|
||||
CUSTOM: {
|
||||
value: 'CUSTOM',
|
||||
text: '自定义'
|
||||
}
|
||||
}
|
||||
|
||||
export default optionApi
|
|
@ -1,45 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/photos'
|
||||
|
||||
const photoApi = {}
|
||||
|
||||
photoApi.query = params => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
photoApi.create = photo => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: photo,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
photoApi.update = (photoId, photo) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${photoId}`,
|
||||
method: 'put',
|
||||
data: photo
|
||||
})
|
||||
}
|
||||
|
||||
photoApi.delete = photoId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${photoId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
photoApi.listTeams = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/teams`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default photoApi
|
154
src/api/post.js
154
src/api/post.js
|
@ -1,154 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/posts'
|
||||
|
||||
const postApi = {}
|
||||
|
||||
postApi.listLatest = top => {
|
||||
return service({
|
||||
url: `${baseUrl}/latest`,
|
||||
params: {
|
||||
top: top
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.query = params => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.get = postId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${postId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.create = (postToCreate, autoSave) => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
method: 'post',
|
||||
data: postToCreate,
|
||||
params: {
|
||||
autoSave: autoSave
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
postApi.update = (postId, postToUpdate, autoSave) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${postId}`,
|
||||
method: 'put',
|
||||
data: postToUpdate,
|
||||
params: {
|
||||
autoSave: autoSave
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
postApi.updateDraft = (postId, content) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${postId}/status/draft/content`,
|
||||
method: 'put',
|
||||
data: {
|
||||
content: content
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
postApi.updateStatus = (postId, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${postId}/status/${status}`,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.updateStatusInBatch = (ids, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/status/${status}`,
|
||||
data: ids,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.delete = postId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${postId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.deleteInBatch = ids => {
|
||||
return service({
|
||||
url: `${baseUrl}`,
|
||||
data: ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.preview = postId => {
|
||||
return service({
|
||||
url: `${baseUrl}/preview/${postId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.postStatus = {
|
||||
PUBLISHED: {
|
||||
value: 'PUBLISHED',
|
||||
color: 'green',
|
||||
status: 'success',
|
||||
text: '已发布'
|
||||
},
|
||||
DRAFT: {
|
||||
value: 'DRAFT',
|
||||
color: 'yellow',
|
||||
status: 'warning',
|
||||
text: '草稿'
|
||||
},
|
||||
RECYCLE: {
|
||||
value: 'RECYCLE',
|
||||
color: 'red',
|
||||
status: 'error',
|
||||
text: '回收站'
|
||||
},
|
||||
INTIMATE: {
|
||||
value: 'INTIMATE',
|
||||
color: 'blue',
|
||||
status: 'success',
|
||||
text: '私密'
|
||||
}
|
||||
}
|
||||
|
||||
postApi.permalinkType = {
|
||||
DEFAULT: {
|
||||
type: 'DEFAULT',
|
||||
text: '默认'
|
||||
},
|
||||
YEAR: {
|
||||
type: 'YEAR',
|
||||
text: '年份型'
|
||||
},
|
||||
DATE: {
|
||||
type: 'DATE',
|
||||
text: '年月型'
|
||||
},
|
||||
DAY: {
|
||||
type: 'DAY',
|
||||
text: '年月日型'
|
||||
},
|
||||
ID: {
|
||||
type: 'ID',
|
||||
text: 'ID 型'
|
||||
},
|
||||
ID_SLUG: {
|
||||
type: 'ID_SLUG',
|
||||
text: 'ID 别名型'
|
||||
}
|
||||
}
|
||||
export default postApi
|
|
@ -1,66 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/posts/comments'
|
||||
|
||||
const postCommentApi = {}
|
||||
|
||||
postCommentApi.listLatest = (top, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/latest`,
|
||||
params: {
|
||||
top: top,
|
||||
status: status
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
postCommentApi.query = params => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
postCommentApi.updateStatus = (commentId, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${commentId}/status/${status}`,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
postCommentApi.delete = commentId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${commentId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
postCommentApi.create = comment => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: comment,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
postCommentApi.commentStatus = {
|
||||
PUBLISHED: {
|
||||
color: 'green',
|
||||
status: 'success',
|
||||
text: '已发布'
|
||||
},
|
||||
AUDITING: {
|
||||
color: 'yellow',
|
||||
status: 'warning',
|
||||
text: '待审核'
|
||||
},
|
||||
RECYCLE: {
|
||||
color: 'red',
|
||||
status: 'error',
|
||||
text: '回收站'
|
||||
}
|
||||
}
|
||||
|
||||
export default postCommentApi
|
110
src/api/sheet.js
110
src/api/sheet.js
|
@ -1,110 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/sheets'
|
||||
|
||||
const sheetApi = {}
|
||||
|
||||
sheetApi.list = params => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.listIndependent = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/independent`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.get = sheetId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${sheetId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.create = (sheetToCreate, autoSave) => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
method: 'post',
|
||||
data: sheetToCreate,
|
||||
params: {
|
||||
autoSave: autoSave
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.update = (sheetId, sheetToUpdate, autoSave) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${sheetId}`,
|
||||
method: 'put',
|
||||
data: sheetToUpdate,
|
||||
params: {
|
||||
autoSave: autoSave
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.updateDraft = (sheetId, content) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${sheetId}/status/draft/content`,
|
||||
method: 'put',
|
||||
data: {
|
||||
content: content
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.updateStatus = (sheetId, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${sheetId}/${status}`,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.delete = sheetId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${sheetId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.preview = sheetId => {
|
||||
return service({
|
||||
url: `${baseUrl}/preview/${sheetId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.sheetStatus = {
|
||||
PUBLISHED: {
|
||||
color: 'green',
|
||||
status: 'success',
|
||||
text: '已发布'
|
||||
},
|
||||
DRAFT: {
|
||||
color: 'yellow',
|
||||
status: 'warning',
|
||||
text: '草稿'
|
||||
},
|
||||
RECYCLE: {
|
||||
color: 'red',
|
||||
status: 'error',
|
||||
text: '回收站'
|
||||
}
|
||||
}
|
||||
|
||||
sheetApi.permalinkType = {
|
||||
SECONDARY: {
|
||||
type: 'SECONDARY',
|
||||
text: '二级路径'
|
||||
},
|
||||
ROOT: {
|
||||
type: 'ROOT',
|
||||
text: '根路径'
|
||||
}
|
||||
}
|
||||
export default sheetApi
|
|
@ -1,78 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/statics'
|
||||
|
||||
const staticApi = {}
|
||||
|
||||
staticApi.list = () => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
staticApi.delete = path => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: {
|
||||
path: path
|
||||
},
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
staticApi.createFolder = (basePath, folderName) => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: {
|
||||
basePath: basePath,
|
||||
folderName: folderName
|
||||
},
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
staticApi.upload = (formData, uploadProgress, cancelToken, basePath) => {
|
||||
return service({
|
||||
url: `${baseUrl}/upload`,
|
||||
timeout: 8640000,
|
||||
data: formData,
|
||||
params: {
|
||||
basePath: basePath
|
||||
},
|
||||
onUploadProgress: uploadProgress,
|
||||
cancelToken: cancelToken,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
staticApi.rename = (basePath, newName) => {
|
||||
return service({
|
||||
url: `${baseUrl}/rename`,
|
||||
params: {
|
||||
basePath: basePath,
|
||||
newName: newName
|
||||
},
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
staticApi.getContent = url => {
|
||||
return service({
|
||||
url: `${url}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
staticApi.save = (path, content) => {
|
||||
return service({
|
||||
url: `${baseUrl}/files`,
|
||||
data: {
|
||||
path: path,
|
||||
content: content
|
||||
},
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
export default staticApi
|
|
@ -1,21 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/statistics'
|
||||
|
||||
const statisticsApi = {}
|
||||
|
||||
statisticsApi.statistics = () => {
|
||||
return service({
|
||||
url: `${baseUrl}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
statisticsApi.statisticsWithUser = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/user`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default statisticsApi
|
|
@ -1,50 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/tags'
|
||||
|
||||
const tagApi = {}
|
||||
|
||||
tagApi.listAll = (more = false) => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: {
|
||||
more: more
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
tagApi.createWithName = name => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: {
|
||||
name: name
|
||||
},
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
tagApi.create = tag => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
data: tag,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
tagApi.update = (tagId, tag) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${tagId}`,
|
||||
data: tag,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
tagApi.delete = tagId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${tagId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export default tagApi
|
195
src/api/theme.js
195
src/api/theme.js
|
@ -1,195 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/themes'
|
||||
|
||||
const themeApi = {}
|
||||
|
||||
themeApi.list = () => {
|
||||
return service({
|
||||
url: `${baseUrl}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.listFilesActivated = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/activation/files`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.listFiles = themeId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/files`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.customSheetTpls = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/activation/template/custom/sheet`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.customPostTpls = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/activation/template/custom/post`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.active = theme => {
|
||||
return service({
|
||||
url: `${baseUrl}/${theme}/activation`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.getActivatedTheme = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/activation`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.update = themeId => {
|
||||
return service({
|
||||
url: `${baseUrl}/fetching/${themeId}`,
|
||||
timeout: 60000,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.delete = (key, deleteSettings) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${key}`,
|
||||
params: {
|
||||
deleteSettings: deleteSettings
|
||||
},
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.fetchConfiguration = themeId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/configurations`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.fetchSettings = themeId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/settings`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.saveSettings = (themeId, settings) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/settings`,
|
||||
data: settings,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.getProperty = themeId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.upload = (formData, uploadProgress, cancelToken) => {
|
||||
return service({
|
||||
url: `${baseUrl}/upload`,
|
||||
timeout: 86400000, // 24 hours
|
||||
data: formData, // form data
|
||||
onUploadProgress: uploadProgress,
|
||||
cancelToken: cancelToken,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.updateByUpload = (formData, uploadProgress, cancelToken, themeId) => {
|
||||
return service({
|
||||
url: `${baseUrl}/upload/${themeId}`,
|
||||
timeout: 86400000, // 24 hours
|
||||
data: formData, // form data
|
||||
onUploadProgress: uploadProgress,
|
||||
cancelToken: cancelToken,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.fetching = url => {
|
||||
return service({
|
||||
url: `${baseUrl}/fetching`,
|
||||
timeout: 60000,
|
||||
params: {
|
||||
uri: url
|
||||
},
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.getContent = path => {
|
||||
return service({
|
||||
url: `${baseUrl}/files/content`,
|
||||
params: {
|
||||
path: path
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.getContent = (themeId, path) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/files/content`,
|
||||
params: {
|
||||
path: path
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.saveContent = (path, content) => {
|
||||
return service({
|
||||
url: `${baseUrl}/files/content`,
|
||||
data: {
|
||||
path: path,
|
||||
content: content
|
||||
},
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.saveContent = (themeId, path, content) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/files/content`,
|
||||
data: {
|
||||
path: path,
|
||||
content: content
|
||||
},
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.reload = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/reload`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.exists = template => {
|
||||
return service({
|
||||
url: `${baseUrl}/activation/template/exists`,
|
||||
method: 'get',
|
||||
params: {
|
||||
template: template
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default themeApi
|
|
@ -1,65 +0,0 @@
|
|||
import service from '@/utils/service'
|
||||
|
||||
const baseUrl = '/api/admin/users'
|
||||
|
||||
const userApi = {}
|
||||
|
||||
userApi.getProfile = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/profiles`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
userApi.updateProfile = profile => {
|
||||
return service({
|
||||
url: `${baseUrl}/profiles`,
|
||||
method: 'put',
|
||||
data: profile
|
||||
})
|
||||
}
|
||||
|
||||
userApi.updatePassword = (oldPassword, newPassword) => {
|
||||
return service({
|
||||
url: `${baseUrl}/profiles/password`,
|
||||
method: 'put',
|
||||
data: {
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPassword
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
userApi.mfaGenerate = mfaType => {
|
||||
return service({
|
||||
url: `${baseUrl}/mfa/generate`,
|
||||
method: 'put',
|
||||
data: {
|
||||
mfaType: mfaType
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
userApi.mfaUpdate = (mfaType, mfaKey, authcode) => {
|
||||
return service({
|
||||
url: `${baseUrl}/mfa/update`,
|
||||
method: 'put',
|
||||
data: {
|
||||
mfaType: mfaType,
|
||||
mfaKey: mfaKey,
|
||||
authcode: authcode
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
userApi.mfaCheck = authcode => {
|
||||
return service({
|
||||
url: `${baseUrl}/mfa/check`,
|
||||
method: 'put',
|
||||
data: {
|
||||
authcode: authcode
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default userApi
|
|
@ -1,34 +1,34 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-drawer
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
:title="title"
|
||||
:visible="visible"
|
||||
:width="isMobile() ? '100%' : drawerWidth"
|
||||
closable
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-input-search placeholder="搜索" v-model="queryParam.keyword" @search="handleQuery()" enterButton />
|
||||
<a-row align="middle" type="flex">
|
||||
<a-input-search v-model="queryParam.keyword" enterButton placeholder="搜索" @search="handleQuery()" />
|
||||
</a-row>
|
||||
<a-divider />
|
||||
<a-row type="flex" align="middle">
|
||||
<a-row align="middle" type="flex">
|
||||
<a-col :span="24">
|
||||
<a-spin :spinning="loading" class="attachments-group">
|
||||
<a-empty v-if="attachments.length === 0" />
|
||||
<div
|
||||
v-else
|
||||
class="attach-item attachments-group-item"
|
||||
v-for="(item, index) in attachments"
|
||||
v-else
|
||||
:key="index"
|
||||
class="attach-item attachments-group-item"
|
||||
@click="handleSelectAttachment(item)"
|
||||
>
|
||||
<span v-if="!handleJudgeMediaType(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
|
||||
<span
|
||||
v-else
|
||||
class="attachments-group-item-img"
|
||||
:style="`background-image:url(${item.thumbPath})`"
|
||||
class="attachments-group-item-img"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
@ -39,30 +39,28 @@
|
|||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
@change="handlePaginationChange"
|
||||
:total="pagination.total"
|
||||
showLessItems
|
||||
@change="handlePaginationChange"
|
||||
></a-pagination>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-space>
|
||||
<a-button type="dashed" v-if="isChooseAvatar" @click="handleSelectGravatar">使用 Gravatar</a-button>
|
||||
<a-button @click="handleShowUploadModal" type="primary">上传附件</a-button>
|
||||
<a-button v-if="isChooseAvatar" type="dashed" @click="handleSelectGravatar">使用 Gravatar</a-button>
|
||||
<a-button type="primary" @click="handleShowUploadModal">上传附件</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
||||
<a-modal title="上传附件" v-model="uploadVisible" :footer="null" :afterClose="onUploadClose" destroyOnClose>
|
||||
<FilePondUpload ref="upload" :uploadHandler="uploadHandler"></FilePondUpload>
|
||||
</a-modal>
|
||||
<AttachmentUploadModal :visible.sync="uploadVisible" @close="onUploadClose" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'AttachmentSelectDrawer',
|
||||
|
@ -109,8 +107,7 @@ export default {
|
|||
sort: null,
|
||||
keyword: null
|
||||
},
|
||||
attachments: [],
|
||||
uploadHandler: attachmentApi.upload
|
||||
attachments: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -122,16 +119,14 @@ export default {
|
|||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
attachmentApi
|
||||
.query(this.queryParam)
|
||||
apiClient.attachment
|
||||
.list(this.queryParam)
|
||||
.then(response => {
|
||||
this.attachments = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
this.attachments = response.data.content
|
||||
this.pagination.total = response.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 200)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
|
@ -149,7 +144,6 @@ export default {
|
|||
this.handleListAttachments()
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleAfterVisibleChanged(visible) {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<a-modal v-model="modalVisible" :afterClose="onClose" :footer="null" destroyOnClose title="上传附件">
|
||||
<FilePondUpload ref="upload" :uploadHandler="uploadHandler"></FilePondUpload>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'AttachmentUploadModal',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
uploadHandler: (file, options) => apiClient.attachment.upload(file, options)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
modalVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:visible', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<a-button
|
||||
:type="computedType"
|
||||
@click="handleClick"
|
||||
:block="block"
|
||||
:icon="computedIcon"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:block="block"
|
||||
>{{ computedText }}</a-button
|
||||
>
|
||||
:type="computedType"
|
||||
@click="handleClick"
|
||||
>{{ computedText }}
|
||||
</a-button>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
import { toolbars } from '@/core/const'
|
||||
import { haloEditor } from 'halo-editor'
|
||||
import 'halo-editor/dist/css/index.css'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'MarkdownEditor',
|
||||
|
@ -46,10 +46,10 @@ export default {
|
|||
handleAttachmentUpload(pos, $file) {
|
||||
const formdata = new FormData()
|
||||
formdata.append('file', $file)
|
||||
attachmentApi.upload(formdata).then(response => {
|
||||
apiClient.attachment.upload(formdata).then(response => {
|
||||
const responseObject = response.data
|
||||
const HaloEditor = this.$refs.md
|
||||
HaloEditor.$img2Url(pos, encodeURI(responseObject.data.path))
|
||||
HaloEditor.$img2Url(pos, encodeURI(responseObject.path))
|
||||
})
|
||||
},
|
||||
handleSaveDraft() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-input type="textarea" v-model="originalContent" :rows="16" />
|
||||
<a-input v-model="originalContent" :rows="16" type="textarea" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -1,6 +1,30 @@
|
|||
<script>
|
||||
import Tooltip from 'ant-design-vue/es/tooltip'
|
||||
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/util'
|
||||
|
||||
const getStrFullLength = (str = '') =>
|
||||
str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
return pre + 1
|
||||
}
|
||||
return pre + 2
|
||||
}, 0)
|
||||
|
||||
const cutStrByFullLength = (str = '', maxLength) => {
|
||||
let showLength = 0
|
||||
return str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
showLength += 1
|
||||
} else {
|
||||
showLength += 2
|
||||
}
|
||||
if (showLength <= maxLength) {
|
||||
return pre + cur
|
||||
}
|
||||
return pre
|
||||
}, '')
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Ellipsis',
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import GlobalFooter from './GlobalFooter'
|
||||
|
||||
export default GlobalFooter
|
||||
|
|
|
@ -12,19 +12,19 @@
|
|||
<div v-if="mode === 'sidemenu'" class="header">
|
||||
<a-icon
|
||||
v-if="device === 'mobile'"
|
||||
class="trigger"
|
||||
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
|
||||
class="trigger"
|
||||
@click="toggle"
|
||||
/>
|
||||
<a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle" />
|
||||
<a-icon v-else :type="collapsed ? 'menu-unfold' : 'menu-fold'" class="trigger" @click="toggle" />
|
||||
<user-menu></user-menu>
|
||||
</div>
|
||||
<div v-else :class="['top-nav-header-index', theme]">
|
||||
<div class="header-index-wide">
|
||||
<div class="header-index-left">
|
||||
<logo class="top-nav-header" v-if="device !== 'mobile'" />
|
||||
<s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" />
|
||||
<a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" />
|
||||
<logo v-if="device !== 'mobile'" class="top-nav-header" />
|
||||
<s-menu v-if="device !== 'mobile'" :menu="menus" :theme="theme" mode="horizontal" />
|
||||
<a-icon v-else :type="collapsed ? 'menu-fold' : 'menu-unfold'" class="trigger" @click="toggle" />
|
||||
</div>
|
||||
<user-menu class="header-index-right"></user-menu>
|
||||
</div>
|
||||
|
@ -120,12 +120,15 @@ export default {
|
|||
position: relative;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.showHeader-enter-active {
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.showHeader-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.showHeader-enter,
|
||||
.showHeader-leave-to {
|
||||
opacity: 0;
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import GlobalHeader from './GlobalHeader'
|
||||
|
||||
export default GlobalHeader
|
||||
|
|
|
@ -36,13 +36,13 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import adminApi from '@/api/admin'
|
||||
import { mapActions } from 'vuex'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'LoginForm',
|
||||
data() {
|
||||
const authcodeValidate = (rule, value, callback) => {
|
||||
const mfaValidate = (rule, value, callback) => {
|
||||
if (!value && this.form.needAuthCode) {
|
||||
callback(new Error('* 请输入两步验证码'))
|
||||
} else {
|
||||
|
@ -59,7 +59,7 @@ export default {
|
|||
rules: {
|
||||
username: [{ required: true, message: '* 用户名/邮箱不能为空', trigger: ['change'] }],
|
||||
password: [{ required: true, message: '* 密码不能为空', trigger: ['change'] }],
|
||||
authcode: [{ validator: authcodeValidate, trigger: ['change'] }]
|
||||
authcode: [{ validator: mfaValidate, trigger: ['change'] }]
|
||||
},
|
||||
needAuthCode: false,
|
||||
logging: false
|
||||
|
@ -78,10 +78,13 @@ export default {
|
|||
_this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
_this.form.logging = true
|
||||
adminApi
|
||||
.loginPreCheck(_this.form.model.username, _this.form.model.password)
|
||||
apiClient
|
||||
.needMFACode({
|
||||
username: _this.form.model.username,
|
||||
password: _this.form.model.password
|
||||
})
|
||||
.then(response => {
|
||||
const data = response.data.data
|
||||
const data = response.data
|
||||
if (data && data.needMFACode) {
|
||||
_this.form.needAuthCode = true
|
||||
_this.form.model.authcode = null
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<div>
|
||||
<a-modal
|
||||
v-model="loginModal"
|
||||
title="重新登录"
|
||||
:footer="null"
|
||||
:width="320"
|
||||
:maskClosable="false"
|
||||
:width="320"
|
||||
title="重新登录"
|
||||
@cancel="handleCancelLogin"
|
||||
>
|
||||
<LoginForm @success="onLoginSucceed" />
|
||||
|
@ -14,7 +14,8 @@
|
|||
</template>
|
||||
<script>
|
||||
import LoginForm from './LoginForm'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'LoginModal',
|
||||
components: {
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<a-layout-sider
|
||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
||||
width="256px"
|
||||
:collapsible="collapsible"
|
||||
v-model="collapsed"
|
||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixedSidebar ? 'ant-fixed-sidemenu' : null]"
|
||||
:collapsible="collapsible"
|
||||
:trigger="null"
|
||||
width="256px"
|
||||
>
|
||||
<logo />
|
||||
<s-menu
|
||||
:collapsed="collapsed"
|
||||
:menu="menus"
|
||||
:theme="theme"
|
||||
:mode="mode"
|
||||
@select="onSelect"
|
||||
:theme="theme"
|
||||
style="padding: 16px 0px;"
|
||||
@select="onSelect"
|
||||
></s-menu>
|
||||
</a-layout-sider>
|
||||
</template>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import SMenu from './menu'
|
||||
|
||||
export default SMenu
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import themeApi from '@/api/theme'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'MetaEditor',
|
||||
|
@ -93,8 +93,8 @@ export default {
|
|||
*/
|
||||
async handleListPresetMetasField() {
|
||||
try {
|
||||
const response = await themeApi.getActivatedTheme()
|
||||
this.presetFields = response.data.data[`${this.target}MetaField`] || []
|
||||
const response = await apiClient.theme.getActivatedTheme()
|
||||
this.presetFields = response.data[`${this.target}MetaField`] || []
|
||||
|
||||
this.handleGenerateMetas()
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="setting-drawer" ref="settingDrawer">
|
||||
<a-drawer width="300" closable @close="onClose" :visible="layoutSetting">
|
||||
<div ref="settingDrawer" class="setting-drawer">
|
||||
<a-drawer :visible="layoutSetting" closable width="300" @close="onClose">
|
||||
<div class="setting-drawer-index-content">
|
||||
<div class="mb-6">
|
||||
<h3 class="setting-drawer-index-title">整体风格设置</h3>
|
||||
|
@ -8,8 +8,8 @@
|
|||
<a-tooltip>
|
||||
<template slot="title">暗色菜单风格</template>
|
||||
<div class="setting-drawer-index-item" @click="handleMenuTheme('dark')">
|
||||
<img src="/images/dark.svg" alt="dark" />
|
||||
<div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'">
|
||||
<img alt="dark" src="/images/dark.svg" />
|
||||
<div v-if="navTheme === 'dark'" class="setting-drawer-index-selectIcon">
|
||||
<a-icon type="check" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,8 +18,8 @@
|
|||
<a-tooltip>
|
||||
<template slot="title">亮色菜单风格</template>
|
||||
<div class="setting-drawer-index-item" @click="handleMenuTheme('light')">
|
||||
<img src="/images/dark.svg" alt="light" />
|
||||
<div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'">
|
||||
<img alt="light" src="/images/dark.svg" />
|
||||
<div v-if="navTheme !== 'dark'" class="setting-drawer-index-selectIcon">
|
||||
<a-icon type="check" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,10 +30,10 @@
|
|||
<div class="mb-6">
|
||||
<h3 class="setting-drawer-index-title">主题色</h3>
|
||||
<div class="h-5">
|
||||
<a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index">
|
||||
<a-tooltip v-for="(item, index) in colorList" :key="index" class="setting-drawer-theme-color-colorBlock">
|
||||
<template slot="title">{{ item.key }}</template>
|
||||
<a-tag :color="item.color" @click="changeColor(item.color)">
|
||||
<a-icon type="check" v-if="item.color === primaryColor"></a-icon>
|
||||
<a-icon v-if="item.color === primaryColor" type="check"></a-icon>
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
@ -44,15 +44,15 @@
|
|||
|
||||
<div class="setting-drawer-index-blockChecbox">
|
||||
<div class="setting-drawer-index-item" @click="handleLayout('sidemenu')">
|
||||
<img src="/images/sidemenu.svg" alt="sidemenu" />
|
||||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'">
|
||||
<img alt="sidemenu" src="/images/sidemenu.svg" />
|
||||
<div v-if="layoutMode === 'sidemenu'" class="setting-drawer-index-selectIcon">
|
||||
<a-icon type="check" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-drawer-index-item" @click="handleLayout('topmenu')">
|
||||
<img src="/images/topmenu.svg" alt="topmenu" />
|
||||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'">
|
||||
<img alt="topmenu" src="/images/topmenu.svg" />
|
||||
<div v-if="layoutMode !== 'sidemenu'" class="setting-drawer-index-selectIcon">
|
||||
<a-icon type="check" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -67,13 +67,13 @@
|
|||
该设定仅 [顶部栏导航] 时有效
|
||||
</template>
|
||||
<a-select
|
||||
:defaultValue="contentWidth"
|
||||
size="small"
|
||||
style="width: 80px;"
|
||||
:defaultValue="contentWidth"
|
||||
@change="handleContentWidthChange"
|
||||
>
|
||||
<a-select-option value="Fixed">固定</a-select-option>
|
||||
<a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option>
|
||||
<a-select-option v-if="layoutMode !== 'sidemenu'" value="Fluid">流式</a-select-option>
|
||||
</a-select>
|
||||
</a-tooltip>
|
||||
<a-list-item-meta>
|
||||
|
@ -81,7 +81,7 @@
|
|||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
|
||||
<a-switch slot="actions" :defaultChecked="fixedHeader" size="small" @change="handleFixedHeader" />
|
||||
<a-list-item-meta>
|
||||
<div slot="title">固定 Header</div>
|
||||
</a-list-item-meta>
|
||||
|
@ -89,9 +89,9 @@
|
|||
<a-list-item>
|
||||
<a-switch
|
||||
slot="actions"
|
||||
size="small"
|
||||
:disabled="!fixedHeader"
|
||||
:defaultChecked="autoHideHeader"
|
||||
:disabled="!fixedHeader"
|
||||
size="small"
|
||||
@change="handleFixedHeaderHidden"
|
||||
/>
|
||||
<a-list-item-meta>
|
||||
|
@ -104,10 +104,10 @@
|
|||
<a-list-item>
|
||||
<a-switch
|
||||
slot="actions"
|
||||
size="small"
|
||||
:defaultChecked="fixedSidebar"
|
||||
:disabled="layoutMode === 'topmenu'"
|
||||
:defaultChecked="fixSiderbar"
|
||||
@change="handleFixSiderbar"
|
||||
size="small"
|
||||
@change="handleFixedSidebar"
|
||||
/>
|
||||
<a-list-item-meta>
|
||||
<div slot="title" :style="{ opacity: layoutMode === 'topmenu' ? '0.5' : '1' }">固定侧边菜单</div>
|
||||
|
@ -123,7 +123,7 @@
|
|||
|
||||
<script>
|
||||
import config from '@/config/defaultSettings'
|
||||
import { updateTheme, colorList } from './setting'
|
||||
import { colorList, updateTheme } from './setting'
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
|
@ -157,7 +157,7 @@ export default {
|
|||
handleLayout(mode) {
|
||||
this.baseConfig.layout = mode
|
||||
this.$store.dispatch('ToggleLayoutMode', mode)
|
||||
this.handleFixSiderbar(false)
|
||||
this.handleFixedSidebar(false)
|
||||
if (mode === 'sidemenu') {
|
||||
this.handleContentWidthChange('Fixed')
|
||||
}
|
||||
|
@ -181,14 +181,14 @@ export default {
|
|||
this.baseConfig.autoHideHeader = autoHidden
|
||||
this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden)
|
||||
},
|
||||
handleFixSiderbar(fixed) {
|
||||
handleFixedSidebar(fixed) {
|
||||
if (this.layoutMode === 'topmenu') {
|
||||
this.baseConfig.fixSiderbar = false
|
||||
this.$store.dispatch('ToggleFixSiderbar', false)
|
||||
this.baseConfig.fixedSidebar = false
|
||||
this.$store.dispatch('ToggleFixedSidebar', false)
|
||||
return
|
||||
}
|
||||
this.baseConfig.fixSiderbar = fixed
|
||||
this.$store.dispatch('ToggleFixSiderbar', fixed)
|
||||
this.baseConfig.fixedSidebar = fixed
|
||||
this.$store.dispatch('ToggleFixedSidebar', fixed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -223,6 +223,7 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-drawer-theme-color-colorBlock {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import SettingDrawer from './SettingDrawer'
|
||||
|
||||
export default SettingDrawer
|
||||
|
|
|
@ -48,6 +48,7 @@ const updateTheme = primaryColor => {
|
|||
return
|
||||
}
|
||||
const hideMessage = message.loading('正在编译主题!', 0)
|
||||
|
||||
function buildIt() {
|
||||
if (!window.less) {
|
||||
return
|
||||
|
@ -66,6 +67,7 @@ const updateTheme = primaryColor => {
|
|||
})
|
||||
}, 200)
|
||||
}
|
||||
|
||||
if (!lessNodesAppended) {
|
||||
// insert less.js and color.less
|
||||
const lessStyleNode = document.createElement('link')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="head-info" :class="center && 'center'">
|
||||
<div :class="center && 'center'" class="head-info">
|
||||
<span>{{ title }}</span>
|
||||
<p>{{ content }}</p>
|
||||
<em v-if="bordered" />
|
||||
|
@ -49,12 +49,14 @@ export default {
|
|||
line-height: 22px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
em {
|
||||
background-color: #e8e8e8;
|
||||
position: absolute;
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<template>
|
||||
<a-popover
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
:autoAdjustOverflow="true"
|
||||
:arrowPointAtCenter="true"
|
||||
:autoAdjustOverflow="true"
|
||||
:overlayStyle="{ width: '300px', top: '50px' }"
|
||||
placement="bottomRight"
|
||||
title="待审核评论"
|
||||
trigger="click"
|
||||
>
|
||||
<template slot="content">
|
||||
<div class="custom-tab-wrapper">
|
||||
<a-tabs v-model="activeKey" @change="handleTabsChanged" :animated="{ inkBar: true, tabPane: false }">
|
||||
<a-tab-pane tab="文章" key="post">
|
||||
<a-list :loading="postCommentsLoading" :dataSource="converttedPostComments">
|
||||
<a-tabs v-model="activeKey" :animated="{ inkBar: true, tabPane: false }" @change="handleTabsChanged">
|
||||
<a-tab-pane key="post" tab="文章">
|
||||
<a-list :dataSource="converttedPostComments" :loading="postCommentsLoading">
|
||||
<a-list-item slot="renderItem" slot-scope="item">
|
||||
<a-list-item-meta>
|
||||
<a-avatar class="bg-white" slot="avatar" :src="item.avatar" size="large" />
|
||||
<a-avatar slot="avatar" :src="item.avatar" class="bg-white" size="large" />
|
||||
<template slot="title">
|
||||
<a :href="item.authorUrl" target="_blank">{{ item.author }}</a
|
||||
>:<span v-html="item.content"></span>
|
||||
|
@ -27,11 +27,11 @@
|
|||
</a-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="页面" key="sheet">
|
||||
<a-list :loading="sheetCommentsLoading" :dataSource="converttedSheetComments">
|
||||
<a-tab-pane key="sheet" tab="页面">
|
||||
<a-list :dataSource="converttedSheetComments" :loading="sheetCommentsLoading">
|
||||
<a-list-item slot="renderItem" slot-scope="item">
|
||||
<a-list-item-meta>
|
||||
<a-avatar class="bg-white" slot="avatar" :src="item.avatar" size="large" />
|
||||
<a-avatar slot="avatar" :src="item.avatar" class="bg-white" size="large" />
|
||||
<template slot="title">
|
||||
<a :href="item.authorUrl" target="_blank">{{ item.author }}</a
|
||||
>:<span v-html="item.content"></span>
|
||||
|
@ -47,7 +47,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<span class="header-comment">
|
||||
<a-badge dot v-if="postComments.length > 0 || sheetComments.length > 0">
|
||||
<a-badge v-if="postComments.length > 0 || sheetComments.length > 0" dot>
|
||||
<a-icon type="bell" />
|
||||
</a-badge>
|
||||
<a-badge v-else>
|
||||
|
@ -58,7 +58,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import commentApi from '@/api/comment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
import marked from 'marked'
|
||||
|
||||
export default {
|
||||
|
@ -107,30 +107,26 @@ export default {
|
|||
if (enableLoading) {
|
||||
this.postCommentsLoading = true
|
||||
}
|
||||
commentApi
|
||||
.latestComment('posts', 5, 'AUDITING')
|
||||
apiClient.comment
|
||||
.latest('posts', 5, 'AUDITING')
|
||||
.then(response => {
|
||||
this.postComments = response.data.data
|
||||
this.postComments = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.postCommentsLoading = false
|
||||
}, 200)
|
||||
this.postCommentsLoading = false
|
||||
})
|
||||
},
|
||||
handleListSheetAuditingComments(enableLoading = true) {
|
||||
if (enableLoading) {
|
||||
this.sheetCommentsLoading = true
|
||||
}
|
||||
commentApi
|
||||
.latestComment('sheets', 5, 'AUDITING')
|
||||
apiClient.comment
|
||||
.latest('sheets', 5, 'AUDITING')
|
||||
.then(response => {
|
||||
this.sheetComments = response.data.data
|
||||
this.sheetComments = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.sheetCommentsLoading = false
|
||||
}, 200)
|
||||
this.sheetCommentsLoading = false
|
||||
})
|
||||
},
|
||||
handleTabsChanged(activeKey) {
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
<template>
|
||||
<div class="logo">
|
||||
<a href="javascript:void(0);" @click="onLogoClick()">
|
||||
<img src="/images/logo.svg" alt="Halo Logo" />
|
||||
<img alt="Halo Logo" src="/images/logo.svg" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import optionApi from '@/api/option'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'Logo',
|
||||
data() {
|
||||
return {
|
||||
clickCount: 0,
|
||||
optionsToCreate: {
|
||||
developer_mode: true
|
||||
}
|
||||
clickCount: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -24,16 +22,22 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['refreshOptionsCache']),
|
||||
onLogoClick() {
|
||||
async onLogoClick() {
|
||||
this.clickCount++
|
||||
if (this.clickCount === 10) {
|
||||
optionApi.save(this.optionsToCreate).then(() => {
|
||||
this.refreshOptionsCache()
|
||||
try {
|
||||
await apiClient.option.saveMapView({ developer_mode: true })
|
||||
|
||||
await this.refreshOptionsCache()
|
||||
this.$message.success(`开发者选项已启用!`)
|
||||
this.clickCount = 0
|
||||
this.$router.push({ name: 'ToolList' })
|
||||
})
|
||||
} else if (this.clickCount >= 5) {
|
||||
this.$router.push({ name: 'ToolList' }).catch(() => {})
|
||||
} catch (e) {
|
||||
this.$log.error(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (this.clickCount >= 5) {
|
||||
if (this.options.developer_mode) {
|
||||
this.$message.info(`当前已启用开发者选项!`)
|
||||
this.clickCount = 0
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
</a>
|
||||
<header-comment class="action" />
|
||||
<a-dropdown>
|
||||
<span class="action ant-dropdown-link user-dropdown-menu" v-if="user">
|
||||
<a-avatar class="avatar" size="small" :src="user.avatar || '//cn.gravatar.com/avatar/?s=256&d=mm'" />
|
||||
<span v-if="user" class="action ant-dropdown-link user-dropdown-menu">
|
||||
<a-avatar :src="user.avatar || '//cn.gravatar.com/avatar/?s=256&d=mm'" class="avatar" size="small" />
|
||||
</span>
|
||||
<a-menu slot="overlay" class="user-dropdown-menu-wrapper">
|
||||
<a-menu-item key="0">
|
||||
|
@ -53,25 +53,22 @@ export default {
|
|||
methods: {
|
||||
...mapActions(['logout', 'ToggleLayoutSetting']),
|
||||
handleLogout() {
|
||||
const that = this
|
||||
const _this = this
|
||||
|
||||
this.$confirm({
|
||||
title: '提示',
|
||||
content: '确定要注销登录吗 ?',
|
||||
onOk() {
|
||||
return that
|
||||
.logout({})
|
||||
.then(() => {
|
||||
window.location.reload()
|
||||
onOk: async () => {
|
||||
try {
|
||||
await _this.logout()
|
||||
window.location.reload()
|
||||
} catch (e) {
|
||||
_this.$message.error({
|
||||
title: '错误',
|
||||
description: e.message
|
||||
})
|
||||
.catch(err => {
|
||||
that.$message.error({
|
||||
title: '错误',
|
||||
description: err.message
|
||||
})
|
||||
})
|
||||
},
|
||||
onCancel() {}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleShowLayoutSetting() {
|
||||
|
|
|
@ -2,24 +2,24 @@
|
|||
<div>
|
||||
<file-pond
|
||||
ref="pond"
|
||||
:label-idle="label"
|
||||
:name="name"
|
||||
:allow-multiple="multiple"
|
||||
:allowRevert="false"
|
||||
:accepted-file-types="accepts"
|
||||
:maxParallelUploads="maxParallelUploads"
|
||||
:allow-multiple="multiple"
|
||||
:allowImagePreview="allowImagePreview"
|
||||
:allowRevert="false"
|
||||
:files="fileList"
|
||||
:label-idle="label"
|
||||
:maxFiles="maxFiles"
|
||||
:maxParallelUploads="maxParallelUploads"
|
||||
:name="name"
|
||||
:server="server"
|
||||
fileValidateTypeLabelExpectedTypes="请选择 {lastType} 格式的文件"
|
||||
labelFileProcessing="上传中"
|
||||
labelFileProcessingComplete="上传完成"
|
||||
labelFileProcessingAborted="取消上传"
|
||||
labelFileProcessingComplete="上传完成"
|
||||
labelFileProcessingError="上传错误"
|
||||
labelFileTypeNotAllowed="不支持当前文件格式"
|
||||
labelTapToCancel="点击取消"
|
||||
labelTapToRetry="点击重试"
|
||||
labelFileTypeNotAllowed="不支持当前文件格式"
|
||||
fileValidateTypeLabelExpectedTypes="请选择 {lastType} 格式的文件"
|
||||
:files="fileList"
|
||||
:server="server"
|
||||
@init="handleFilePondInit"
|
||||
>
|
||||
</file-pond>
|
||||
|
@ -27,7 +27,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
import { Axios } from '@halo-dev/admin-api'
|
||||
|
||||
import vueFilePond from 'vue-filepond'
|
||||
import 'filepond/dist/filepond.min.css'
|
||||
|
@ -50,7 +50,7 @@ export default {
|
|||
required: false,
|
||||
default: 'file'
|
||||
},
|
||||
filed: {
|
||||
field: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
|
@ -102,22 +102,19 @@ export default {
|
|||
return {
|
||||
server: {
|
||||
process: (fieldName, file, metadata, load, error, progress, abort) => {
|
||||
const formData = new FormData()
|
||||
formData.append(fieldName, file, file.name)
|
||||
|
||||
const CancelToken = axios.CancelToken
|
||||
const CancelToken = Axios.CancelToken
|
||||
const source = CancelToken.source()
|
||||
|
||||
this.uploadHandler(
|
||||
formData,
|
||||
progressEvent => {
|
||||
if (progressEvent.total > 0) {
|
||||
progress(progressEvent.lengthComputable, progressEvent.loaded, progressEvent.total)
|
||||
}
|
||||
file,
|
||||
{
|
||||
onUploadProgress: progressEvent => {
|
||||
if (progressEvent.total > 0) {
|
||||
progress(progressEvent.lengthComputable, progressEvent.loaded, progressEvent.total)
|
||||
}
|
||||
},
|
||||
cancelToken: source.token
|
||||
},
|
||||
source.token,
|
||||
this.filed,
|
||||
file
|
||||
this.field
|
||||
)
|
||||
.then(response => {
|
||||
load(response)
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* 获取字符串长度,英文字符 长度1,中文字符长度2
|
||||
* @param {*} str
|
||||
*/
|
||||
export const getStrFullLength = (str = '') =>
|
||||
str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
return pre + 1
|
||||
}
|
||||
return pre + 2
|
||||
}, 0)
|
||||
|
||||
/**
|
||||
* 截取字符串,根据 maxLength 截取后返回
|
||||
* @param {*} str
|
||||
* @param {*} maxLength
|
||||
*/
|
||||
export const cutStrByFullLength = (str = '', maxLength) => {
|
||||
let showLength = 0
|
||||
return str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
showLength += 1
|
||||
} else {
|
||||
showLength += 2
|
||||
}
|
||||
if (showLength <= maxLength) {
|
||||
return pre + cur
|
||||
}
|
||||
return pre
|
||||
}, '')
|
||||
}
|
|
@ -4,6 +4,7 @@ import Ellipsis from '@/components/Ellipsis'
|
|||
import FooterToolbar from '@/components/FooterToolbar'
|
||||
import FilePondUpload from '@/components/Upload/FilePondUpload'
|
||||
import AttachmentSelectDrawer from './Attachment/AttachmentSelectDrawer'
|
||||
import AttachmentUploadModal from './Attachment/AttachmentUploadModal'
|
||||
import ReactiveButton from './Button/ReactiveButton'
|
||||
|
||||
const _components = {
|
||||
|
@ -11,6 +12,7 @@ const _components = {
|
|||
FooterToolbar,
|
||||
FilePondUpload,
|
||||
AttachmentSelectDrawer,
|
||||
AttachmentUploadModal,
|
||||
ReactiveButton
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ export default {
|
|||
layout: 'topmenu',
|
||||
contentWidth: 'Fixed',
|
||||
fixedHeader: false,
|
||||
fixSiderbar: false,
|
||||
fixedSidebar: false,
|
||||
autoHideHeader: false,
|
||||
storageOptions: {
|
||||
namespace: 'halo__',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// eslint-disable-next-line
|
||||
import { BasicLayout, PageView, BlankLayout } from '@/layouts'
|
||||
import { BasicLayout, BlankLayout, PageView } from '@/layouts'
|
||||
|
||||
export const asyncRouterMap = [
|
||||
{
|
||||
|
|
|
@ -3,16 +3,15 @@ import store from '@/store/'
|
|||
import {
|
||||
ACCESS_TOKEN,
|
||||
DEFAULT_COLOR,
|
||||
DEFAULT_THEME,
|
||||
DEFAULT_LAYOUT_MODE,
|
||||
SIDEBAR_TYPE,
|
||||
DEFAULT_CONTENT_WIDTH_TYPE,
|
||||
DEFAULT_FIXED_HEADER,
|
||||
DEFAULT_FIXED_HEADER_HIDDEN,
|
||||
DEFAULT_FIXED_SIDEMENU,
|
||||
DEFAULT_CONTENT_WIDTH_TYPE,
|
||||
USER,
|
||||
API_URL,
|
||||
OPTIONS
|
||||
DEFAULT_FIXED_SIDEBAR,
|
||||
DEFAULT_LAYOUT_MODE,
|
||||
DEFAULT_THEME,
|
||||
OPTIONS,
|
||||
SIDEBAR_TYPE,
|
||||
USER
|
||||
} from '@/store/mutation-types'
|
||||
import config from '@/config/defaultSettings'
|
||||
|
||||
|
@ -21,13 +20,12 @@ export default function Initializer() {
|
|||
store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))
|
||||
store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))
|
||||
store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))
|
||||
store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))
|
||||
store.commit('TOGGLE_FIXED_SIDEBAR', Vue.ls.get(DEFAULT_FIXED_SIDEBAR, config.fixedSidebar))
|
||||
store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))
|
||||
store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))
|
||||
store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))
|
||||
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
|
||||
store.commit('SET_USER', Vue.ls.get(USER))
|
||||
store.commit('SET_API_URL', Vue.ls.get(API_URL))
|
||||
store.commit('SET_OPTIONS', Vue.ls.get(OPTIONS))
|
||||
// last step
|
||||
}
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
import Vue from 'vue'
|
||||
import {
|
||||
Affix,
|
||||
Alert,
|
||||
Anchor,
|
||||
AutoComplete,
|
||||
Alert,
|
||||
Avatar,
|
||||
Badge,
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Card,
|
||||
Collapse,
|
||||
Checkbox,
|
||||
Col,
|
||||
Collapse,
|
||||
Comment,
|
||||
ConfigProvider,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Drawer,
|
||||
Dropdown,
|
||||
Empty,
|
||||
Form,
|
||||
FormModel,
|
||||
Icon,
|
||||
|
@ -23,8 +27,8 @@ import {
|
|||
Layout,
|
||||
List,
|
||||
LocaleProvider,
|
||||
message,
|
||||
Menu,
|
||||
message,
|
||||
Modal,
|
||||
notification,
|
||||
PageHeader,
|
||||
|
@ -33,26 +37,22 @@ import {
|
|||
Popover,
|
||||
Progress,
|
||||
Radio,
|
||||
Result,
|
||||
Row,
|
||||
Select,
|
||||
Skeleton,
|
||||
Space,
|
||||
Spin,
|
||||
Steps,
|
||||
Switch,
|
||||
Table,
|
||||
Tree,
|
||||
TreeSelect,
|
||||
Tabs,
|
||||
Tag,
|
||||
Timeline,
|
||||
TimePicker,
|
||||
Tooltip,
|
||||
Drawer,
|
||||
Skeleton,
|
||||
Comment,
|
||||
ConfigProvider,
|
||||
Timeline,
|
||||
Steps,
|
||||
Empty,
|
||||
Result,
|
||||
Space
|
||||
Tree,
|
||||
TreeSelect
|
||||
} from 'ant-design-vue'
|
||||
|
||||
Vue.use(Affix)
|
||||
|
|
|
@ -3,29 +3,29 @@
|
|||
<!-- SideMenu -->
|
||||
<a-drawer
|
||||
v-if="isMobile()"
|
||||
placement="left"
|
||||
:wrapClassName="`drawer-sider ${navTheme}`"
|
||||
:closable="false"
|
||||
:visible="collapsed"
|
||||
:wrapClassName="`drawer-sider ${navTheme}`"
|
||||
placement="left"
|
||||
@close="drawerClose"
|
||||
>
|
||||
<side-menu
|
||||
mode="inline"
|
||||
:menus="menus"
|
||||
:theme="navTheme"
|
||||
:collapsed="false"
|
||||
:collapsible="true"
|
||||
:menus="menus"
|
||||
:theme="navTheme"
|
||||
mode="inline"
|
||||
@menuSelect="menuSelect"
|
||||
></side-menu>
|
||||
</a-drawer>
|
||||
|
||||
<side-menu
|
||||
v-else-if="isSideMenu()"
|
||||
mode="inline"
|
||||
:menus="menus"
|
||||
:theme="navTheme"
|
||||
:collapsed="collapsed"
|
||||
:collapsible="true"
|
||||
:menus="menus"
|
||||
:theme="navTheme"
|
||||
mode="inline"
|
||||
></side-menu>
|
||||
|
||||
<a-layout
|
||||
|
@ -34,11 +34,11 @@
|
|||
>
|
||||
<!-- layout header -->
|
||||
<global-header
|
||||
:mode="layoutMode"
|
||||
:menus="menus"
|
||||
:theme="navTheme"
|
||||
:collapsed="collapsed"
|
||||
:device="device"
|
||||
:menus="menus"
|
||||
:mode="layoutMode"
|
||||
:theme="navTheme"
|
||||
@toggle="toggle"
|
||||
/>
|
||||
|
||||
|
@ -63,7 +63,7 @@
|
|||
|
||||
<script>
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import { mapActions } from 'vuex'
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin'
|
||||
import config from '@/config/defaultSettings'
|
||||
import { asyncRouterMap } from '@/config/router.config.js'
|
||||
|
@ -94,12 +94,8 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
// 动态主路由
|
||||
mainMenu: state => state.permission.addRouters
|
||||
}),
|
||||
contentPaddingLeft() {
|
||||
if (!this.fixSidebar || this.isMobile()) {
|
||||
if (!this.fixedSidebar || this.isMobile()) {
|
||||
return '0'
|
||||
}
|
||||
if (this.sidebarOpened) {
|
||||
|
@ -115,7 +111,6 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.menus = asyncRouterMap.find(item => item.path === '/').children
|
||||
// this.menus = this.mainMenu.find((item) => item.path === '/').children
|
||||
this.collapsed = !this.sidebarOpened
|
||||
},
|
||||
mounted() {
|
||||
|
@ -141,7 +136,7 @@ export default {
|
|||
if (this.sidebarOpened) {
|
||||
left = this.isDesktop() ? '256px' : '80px'
|
||||
} else {
|
||||
left = (this.isMobile() && '0') || (this.fixSidebar && '80px') || '0'
|
||||
left = (this.isMobile() && '0') || (this.fixedSidebar && '80px') || '0'
|
||||
}
|
||||
return left
|
||||
},
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<template>
|
||||
<div :style="!$route.meta.hiddenHeaderContent ? 'margin: -24px -24px 0px;' : null">
|
||||
<a-affix v-if="affix">
|
||||
<div class="page-header" v-if="!$route.meta.hiddenHeaderContent">
|
||||
<div v-if="!$route.meta.hiddenHeaderContent" class="page-header">
|
||||
<div class="page-header-index-wide">
|
||||
<a-page-header :title="title" :sub-title="subTitle" :breadcrumb="{ props: { routes: breadList } }">
|
||||
<slot name="extra" slot="extra"> </slot>
|
||||
<slot name="footer" slot="footer"> </slot>
|
||||
<a-page-header :breadcrumb="{ props: { routes: breadList } }" :sub-title="subTitle" :title="title">
|
||||
<slot slot="extra" name="extra"></slot>
|
||||
<slot slot="footer" name="footer"></slot>
|
||||
</a-page-header>
|
||||
</div>
|
||||
</div>
|
||||
</a-affix>
|
||||
<div class="page-header" v-if="!$route.meta.hiddenHeaderContent && !affix">
|
||||
<div v-if="!$route.meta.hiddenHeaderContent && !affix" class="page-header">
|
||||
<div class="page-header-index-wide">
|
||||
<a-page-header :title="title" :sub-title="subTitle" :breadcrumb="{ props: { routes: breadList } }">
|
||||
<slot name="extra" slot="extra"> </slot>
|
||||
<slot name="footer" slot="footer"> </slot>
|
||||
<a-page-header :breadcrumb="{ props: { routes: breadList } }" :sub-title="subTitle" :title="title">
|
||||
<slot slot="extra" name="extra"></slot>
|
||||
<slot slot="footer" name="footer"></slot>
|
||||
</a-page-header>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,6 +75,7 @@ export default {
|
|||
background: #fff;
|
||||
padding: 0 24px 0;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
|
||||
.ant-page-header {
|
||||
padding: 16px 0px;
|
||||
}
|
||||
|
@ -83,6 +84,7 @@ export default {
|
|||
.mobile .page-header,
|
||||
.tablet .page-header {
|
||||
padding: 0 !important;
|
||||
|
||||
.ant-page-header {
|
||||
padding: 16px;
|
||||
}
|
||||
|
@ -90,21 +92,26 @@ export default {
|
|||
|
||||
.content {
|
||||
margin: 24px 24px 0;
|
||||
|
||||
.link {
|
||||
margin-top: 16px;
|
||||
|
||||
&:not(:empty) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
margin-right: 32px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
display: inline-block;
|
||||
|
||||
i {
|
||||
font-size: 24px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
span {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
|
|
|
@ -12,8 +12,7 @@ const mixin = {
|
|||
navTheme: state => state.app.theme,
|
||||
primaryColor: state => state.app.color,
|
||||
fixedHeader: state => state.app.fixedHeader,
|
||||
fixSiderbar: state => state.app.fixSiderbar,
|
||||
fixSidebar: state => state.app.fixSiderbar,
|
||||
fixedSidebar: state => state.app.fixedSidebar,
|
||||
contentWidth: state => state.app.contentWidth,
|
||||
autoHideHeader: state => state.app.autoHideHeader,
|
||||
sidebarOpened: state => state.app.sidebar
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import Vue from 'vue'
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
import NProgress from 'nprogress'
|
||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||
import adminApi from '@/api/admin'
|
||||
import { domTitle, setDocumentTitle } from '@/utils/domUtil'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
NProgress.configure({ showSpinner: false, speed: 500 })
|
||||
|
||||
|
@ -16,14 +15,13 @@ router.beforeEach(async (to, from, next) => {
|
|||
NProgress.start()
|
||||
}, 250)
|
||||
to.meta && typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`)
|
||||
Vue.$log.debug('Token', store.getters.token)
|
||||
if (store.getters.token) {
|
||||
if (to.name === 'Install') {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const response = await adminApi.isInstalled()
|
||||
if (!response.data.data) {
|
||||
const response = await apiClient.isInstalled()
|
||||
if (!response.data) {
|
||||
next({
|
||||
name: 'Install'
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import { constantRouterMap, asyncRouterMap } from '@/config/router.config'
|
||||
import { asyncRouterMap, constantRouterMap } from '@/config/router.config'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
|
|
|
@ -6,13 +6,6 @@ const getters = {
|
|||
loginModal: state => state.app.loginModal,
|
||||
token: state => state.user.token,
|
||||
user: state => state.user.user,
|
||||
addRouters: state => state.permission.addRouters,
|
||||
apiUrl: state => {
|
||||
if (state.app.apiUrl) {
|
||||
return state.app.apiUrl
|
||||
}
|
||||
return `${window.location.protocol}//${window.location.host}`
|
||||
},
|
||||
options: state => state.option.options
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import Vuex from 'vuex'
|
|||
|
||||
import app from './modules/app'
|
||||
import user from './modules/user'
|
||||
import permission from './modules/permission'
|
||||
import option from './modules/option'
|
||||
import getters from './getters'
|
||||
|
||||
|
@ -13,7 +12,6 @@ export default new Vuex.Store({
|
|||
modules: {
|
||||
app,
|
||||
user,
|
||||
permission,
|
||||
option
|
||||
},
|
||||
state: {},
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import Vue from 'vue'
|
||||
import {
|
||||
SIDEBAR_TYPE,
|
||||
DEFAULT_THEME,
|
||||
DEFAULT_LAYOUT_MODE,
|
||||
DEFAULT_COLOR,
|
||||
DEFAULT_FIXED_HEADER,
|
||||
DEFAULT_FIXED_SIDEMENU,
|
||||
DEFAULT_FIXED_HEADER_HIDDEN,
|
||||
DEFAULT_CONTENT_WIDTH_TYPE,
|
||||
API_URL,
|
||||
LAYOUT_SETTING
|
||||
DEFAULT_FIXED_HEADER,
|
||||
DEFAULT_FIXED_HEADER_HIDDEN,
|
||||
DEFAULT_FIXED_SIDEBAR,
|
||||
DEFAULT_LAYOUT_MODE,
|
||||
DEFAULT_THEME,
|
||||
LAYOUT_SETTING,
|
||||
SIDEBAR_TYPE
|
||||
} from '@/store/mutation-types'
|
||||
|
||||
const app = {
|
||||
|
@ -20,22 +19,13 @@ const app = {
|
|||
layout: '',
|
||||
contentWidth: '',
|
||||
fixedHeader: false,
|
||||
fixSiderbar: false,
|
||||
fixedSidebar: false,
|
||||
autoHideHeader: false,
|
||||
color: null,
|
||||
apiUrl: null,
|
||||
layoutSetting: false,
|
||||
loginModal: false
|
||||
},
|
||||
mutations: {
|
||||
SET_API_URL: (state, apiUrl) => {
|
||||
state.apiUrl = apiUrl
|
||||
Vue.ls.set(API_URL, apiUrl)
|
||||
},
|
||||
RESTORE_API_URL: state => {
|
||||
state.apiUrl = null
|
||||
Vue.ls.set(API_URL, null)
|
||||
},
|
||||
SET_SIDEBAR_TYPE: (state, type) => {
|
||||
state.sidebar = type
|
||||
Vue.ls.set(SIDEBAR_TYPE, type)
|
||||
|
@ -59,9 +49,9 @@ const app = {
|
|||
Vue.ls.set(DEFAULT_FIXED_HEADER, fixed)
|
||||
state.fixedHeader = fixed
|
||||
},
|
||||
TOGGLE_FIXED_SIDERBAR: (state, fixed) => {
|
||||
Vue.ls.set(DEFAULT_FIXED_SIDEMENU, fixed)
|
||||
state.fixSiderbar = fixed
|
||||
TOGGLE_FIXED_SIDEBAR: (state, fixed) => {
|
||||
Vue.ls.set(DEFAULT_FIXED_SIDEBAR, fixed)
|
||||
state.fixedSidebar = fixed
|
||||
},
|
||||
TOGGLE_FIXED_HEADER_HIDDEN: (state, show) => {
|
||||
Vue.ls.set(DEFAULT_FIXED_HEADER_HIDDEN, show)
|
||||
|
@ -87,12 +77,6 @@ const app = {
|
|||
setSidebar({ commit }, type) {
|
||||
commit('SET_SIDEBAR_TYPE', type)
|
||||
},
|
||||
CloseSidebar({ commit }) {
|
||||
commit('CLOSE_SIDEBAR')
|
||||
},
|
||||
ToggleDevice({ commit }, device) {
|
||||
commit('TOGGLE_DEVICE', device)
|
||||
},
|
||||
ToggleTheme({ commit }, theme) {
|
||||
commit('TOGGLE_THEME', theme)
|
||||
},
|
||||
|
@ -105,8 +89,8 @@ const app = {
|
|||
}
|
||||
commit('TOGGLE_FIXED_HEADER', fixedHeader)
|
||||
},
|
||||
ToggleFixSiderbar({ commit }, fixSiderbar) {
|
||||
commit('TOGGLE_FIXED_SIDERBAR', fixSiderbar)
|
||||
ToggleFixedSidebar({ commit }, fixedSidebar) {
|
||||
commit('TOGGLE_FIXED_SIDEBAR', fixedSidebar)
|
||||
},
|
||||
ToggleFixedHeaderHidden({ commit }, show) {
|
||||
commit('TOGGLE_FIXED_HEADER_HIDDEN', show)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import { OPTIONS } from '@/store/mutation-types'
|
||||
import optionApi from '@/api/option'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
const keys = [
|
||||
'blog_url',
|
||||
'developer_mode',
|
||||
|
@ -28,10 +29,10 @@ const option = {
|
|||
actions: {
|
||||
refreshOptionsCache({ commit }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
optionApi
|
||||
.listAllByKeys(keys)
|
||||
apiClient.option
|
||||
.listAsMapViewByKeys(keys)
|
||||
.then(response => {
|
||||
commit('SET_OPTIONS', response.data.data)
|
||||
commit('SET_OPTIONS', response.data)
|
||||
resolve(response)
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import { asyncRouterMap, constantRouterMap } from '@/config/router.config'
|
||||
|
||||
/**
|
||||
* 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
|
||||
*
|
||||
* @param permission
|
||||
* @param route
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasPermission(permission, route) {
|
||||
if (route.meta && route.meta.permission) {
|
||||
let flag = false
|
||||
for (let i = 0, len = permission.length; i < len; i++) {
|
||||
flag = route.meta.permission.includes(permission[i])
|
||||
if (flag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function filterAsyncRouter(routerMap, roles) {
|
||||
const accessedRouters = routerMap.filter(route => {
|
||||
if (hasPermission(roles.permissionList, route)) {
|
||||
if (route.children && route.children.length) {
|
||||
route.children = filterAsyncRouter(route.children, roles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
return accessedRouters
|
||||
}
|
||||
|
||||
const permission = {
|
||||
state: {
|
||||
routers: constantRouterMap,
|
||||
addRouters: []
|
||||
},
|
||||
mutations: {
|
||||
SET_ROUTERS: (state, routers) => {
|
||||
state.addRouters = routers
|
||||
state.routers = constantRouterMap.concat(routers)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
GenerateRoutes({ commit }, data) {
|
||||
return new Promise(resolve => {
|
||||
const { roles } = data
|
||||
const accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
|
||||
commit('SET_ROUTERS', accessedRouters)
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default permission
|
|
@ -1,7 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN, USER } from '@/store/mutation-types'
|
||||
import adminApi from '@/api/admin'
|
||||
import userApi from '@/api/user'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
|
@ -25,7 +24,7 @@ const user = {
|
|||
actions: {
|
||||
installCleanToken({ commit }, installData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
adminApi
|
||||
apiClient.installation
|
||||
.install(installData)
|
||||
.then(response => {
|
||||
commit('CLEAR_TOKEN')
|
||||
|
@ -38,10 +37,10 @@ const user = {
|
|||
},
|
||||
refreshUserCache({ commit }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
userApi
|
||||
apiClient.user
|
||||
.getProfile()
|
||||
.then(response => {
|
||||
commit('SET_USER', response.data.data)
|
||||
commit('SET_USER', response.data)
|
||||
resolve(response)
|
||||
})
|
||||
.catch(error => {
|
||||
|
@ -51,10 +50,10 @@ const user = {
|
|||
},
|
||||
login({ commit }, { username, password, authcode }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
adminApi
|
||||
.login(username, password, authcode)
|
||||
apiClient
|
||||
.login({ username, password, authcode })
|
||||
.then(response => {
|
||||
const token = response.data.data
|
||||
const token = response.data
|
||||
Vue.$log.debug('Got token', token)
|
||||
commit('SET_TOKEN', token)
|
||||
|
||||
|
@ -67,10 +66,11 @@ const user = {
|
|||
},
|
||||
logout({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
adminApi
|
||||
apiClient
|
||||
.logout()
|
||||
.then(() => {
|
||||
commit('CLEAR_TOKEN')
|
||||
commit('SET_USER', {})
|
||||
resolve()
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -80,17 +80,17 @@ const user = {
|
|||
},
|
||||
refreshToken({ commit }, refreshToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
adminApi
|
||||
apiClient
|
||||
.refreshToken(refreshToken)
|
||||
.then(response => {
|
||||
const token = response.data.data
|
||||
const token = response.data
|
||||
Vue.$log.debug('Got token', token)
|
||||
commit('SET_TOKEN', token)
|
||||
|
||||
resolve(response)
|
||||
})
|
||||
.catch(error => {
|
||||
const data = error.response.data
|
||||
const data = error.data
|
||||
Vue.$log.debug('Refresh error data', data)
|
||||
if (data && data.status === 400 && data.data === refreshToken) {
|
||||
// The refresh token expired
|
||||
|
|
|
@ -4,11 +4,10 @@ export const DEFAULT_THEME = 'DEFAULT_THEME'
|
|||
export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE'
|
||||
export const DEFAULT_COLOR = 'DEFAULT_COLOR'
|
||||
export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER'
|
||||
export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU'
|
||||
export const DEFAULT_FIXED_SIDEBAR = 'DEFAULT_FIXED_SIDEBAR'
|
||||
export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
|
||||
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
|
||||
export const USER = 'USER'
|
||||
export const API_URL = 'API_URL'
|
||||
export const OPTIONS = 'OPTIONS'
|
||||
export const LAYOUT_SETTING = 'LAYOUT_SETTING'
|
||||
|
||||
|
|
|
@ -819,6 +819,7 @@ body {
|
|||
&-item {
|
||||
padding: 0;
|
||||
height: 140px;
|
||||
|
||||
&-img {
|
||||
display: block;
|
||||
height: 100%;
|
||||
|
@ -826,6 +827,7 @@ body {
|
|||
background-size: 100%;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.attachments-group &-type {
|
||||
font-size: 38px;
|
||||
text-transform: capitalize;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import './animate.less';
|
||||
|
||||
.container-wrapper {
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
import { AdminApiClient, Axios, HaloRestAPIClient } from '@halo-dev/admin-api'
|
||||
import store from '@/store'
|
||||
import { message, notification } from 'ant-design-vue'
|
||||
import { isObject } from './util'
|
||||
|
||||
const apiUrl = process.env.VUE_APP_API_URL ? process.env.VUE_APP_API_URL : 'http://localhost:8080'
|
||||
|
||||
const haloRestApiClient = new HaloRestAPIClient({
|
||||
baseUrl: apiUrl
|
||||
})
|
||||
|
||||
const apiClient = new AdminApiClient(haloRestApiClient)
|
||||
|
||||
haloRestApiClient.interceptors.request.use(
|
||||
config => {
|
||||
const token = store.getters.token
|
||||
if (token && token.access_token) {
|
||||
config.headers['Admin-Authorization'] = token.access_token
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
console.log('request error', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
let isRefreshingToken = false
|
||||
let pendingRequests = []
|
||||
|
||||
haloRestApiClient.interceptors.response.use(
|
||||
response => {
|
||||
return response
|
||||
},
|
||||
async error => {
|
||||
if (Axios.isCancel(error)) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (/Network Error/.test(error.message)) {
|
||||
message.error('网络错误,请检查网络连接')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
const token = store.getters.token
|
||||
const originalRequest = error.config
|
||||
|
||||
const response = error.response
|
||||
|
||||
const data = response ? response.data : null
|
||||
|
||||
if (data) {
|
||||
if (data.status === 400) {
|
||||
const params = data.data
|
||||
|
||||
if (isObject(params)) {
|
||||
const paramMessages = Object.keys(params || {}).map(key => params[key])
|
||||
notification.error({
|
||||
message: data.message,
|
||||
description: h => {
|
||||
const errorNodes = paramMessages.map(errorDetail => {
|
||||
return h('a-alert', {
|
||||
props: {
|
||||
message: errorDetail,
|
||||
banner: true,
|
||||
showIcon: false,
|
||||
type: 'error'
|
||||
}
|
||||
})
|
||||
})
|
||||
return h('div', errorNodes)
|
||||
},
|
||||
duration: 10
|
||||
})
|
||||
} else {
|
||||
message.error(data.message)
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
if (data.status === 401) {
|
||||
if (!isRefreshingToken) {
|
||||
isRefreshingToken = true
|
||||
try {
|
||||
await store.dispatch('refreshToken', token.refresh_token)
|
||||
|
||||
pendingRequests.forEach(callback => callback())
|
||||
pendingRequests = []
|
||||
|
||||
return Axios(originalRequest)
|
||||
} catch (e) {
|
||||
message.warning('当前登录状态已失效,请重新登录')
|
||||
await store.dispatch('ToggleLoginModal', true)
|
||||
return Promise.reject(e)
|
||||
} finally {
|
||||
isRefreshingToken = false
|
||||
}
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
pendingRequests.push(() => {
|
||||
resolve(Axios(originalRequest))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
message.error(data.message || '服务器错误')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
message.error('网络异常')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default apiClient
|
||||
|
||||
export { haloRestApiClient }
|
|
@ -1,5 +1,6 @@
|
|||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
function datetimeFormat(datetime = new Date(), pattern = 'YYYY-MM-DD HH:mm') {
|
||||
|
|
|
@ -2,7 +2,7 @@ export const setDocumentTitle = function(title) {
|
|||
document.title = title
|
||||
const ua = navigator.userAgent
|
||||
// eslint-disable-next-line
|
||||
const regex = /\bMicroMessenger\/([\d\.]+)/
|
||||
const regex = /\bMicroMessenger\/([\d\.]+)/
|
||||
if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) {
|
||||
const i = document.createElement('iframe')
|
||||
i.src = '/favicon.ico'
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import CryptoJS from 'crypto-js'
|
||||
|
||||
const CRYPTO_KEY = 'halo-crypt'
|
||||
|
||||
export default {
|
||||
encrypt(plaintObject) {
|
||||
if (!plaintObject) {
|
||||
return undefined
|
||||
}
|
||||
return CryptoJS.AES.encrypt(JSON.stringify(plaintObject), CRYPTO_KEY).toString()
|
||||
},
|
||||
|
||||
decrypt(ciphertext) {
|
||||
if (!ciphertext) {
|
||||
return undefined
|
||||
}
|
||||
const bytes = CryptoJS.AES.decrypt(ciphertext, CRYPTO_KEY)
|
||||
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
import axios from 'axios'
|
||||
import Vue from 'vue'
|
||||
import { message, notification } from 'ant-design-vue'
|
||||
import store from '@/store'
|
||||
import { isObject } from './util'
|
||||
|
||||
const service = axios.create({
|
||||
timeout: 10000,
|
||||
withCredentials: true
|
||||
})
|
||||
|
||||
function setTokenToHeader(config) {
|
||||
// set token
|
||||
const token = store.getters.token
|
||||
Vue.$log.debug('Got token from store', token)
|
||||
if (token && token.access_token) {
|
||||
config.headers['Admin-Authorization'] = token.access_token
|
||||
}
|
||||
}
|
||||
|
||||
async function reRequest(error) {
|
||||
const config = error.response.config
|
||||
setTokenToHeader(config)
|
||||
return await axios.request(config)
|
||||
}
|
||||
|
||||
let refreshTask = null
|
||||
|
||||
async function refreshToken(error) {
|
||||
const refreshToken = store.getters.token.refresh_token
|
||||
try {
|
||||
if (refreshTask === null) {
|
||||
refreshTask = store.dispatch('refreshToken', refreshToken)
|
||||
}
|
||||
|
||||
await refreshTask
|
||||
} catch (err) {
|
||||
if (err.response && err.response.data && err.response.data.data === refreshToken) {
|
||||
message.warning('当前登录状态已失效,请重新登录')
|
||||
store.dispatch('ToggleLoginModal', true)
|
||||
}
|
||||
Vue.$log.error('Failed to refresh token', err)
|
||||
} finally {
|
||||
refreshTask = null
|
||||
}
|
||||
// Rerequest the request
|
||||
return reRequest(error)
|
||||
}
|
||||
|
||||
function getFieldValidationError(data) {
|
||||
if (!isObject(data) || !isObject(data.data)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const errorDetail = data.data
|
||||
|
||||
return Object.keys(errorDetail).map(key => errorDetail[key])
|
||||
}
|
||||
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
config.baseURL = store.getters.apiUrl
|
||||
setTokenToHeader(config)
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
return response
|
||||
},
|
||||
error => {
|
||||
if (axios.isCancel(error)) {
|
||||
Vue.$log.debug('Cancelled uploading by user.')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
Vue.$log.error('Response failed', error)
|
||||
|
||||
const response = error.response
|
||||
const status = response ? response.status : -1
|
||||
Vue.$log.error('Server response status', status)
|
||||
|
||||
const data = response ? response.data : null
|
||||
if (data) {
|
||||
let handled = false
|
||||
// Business response
|
||||
Vue.$log.error('Business response status', data.status)
|
||||
if (data.status === 400) {
|
||||
const errorDetails = getFieldValidationError(data)
|
||||
if (errorDetails) {
|
||||
handled = true
|
||||
|
||||
notification.error({
|
||||
message: data.message,
|
||||
description: h => {
|
||||
const errorNodes = errorDetails.map(errorDetail => {
|
||||
return h('a-alert', {
|
||||
props: {
|
||||
message: errorDetail,
|
||||
banner: true,
|
||||
showIcon: false,
|
||||
type: 'error'
|
||||
}
|
||||
})
|
||||
})
|
||||
return h('div', errorNodes)
|
||||
},
|
||||
duration: 10
|
||||
})
|
||||
}
|
||||
} else if (data.status === 401) {
|
||||
if (store.getters.token && store.getters.token.access_token === data.data) {
|
||||
const res = refreshToken(error)
|
||||
if (res !== error) {
|
||||
return res
|
||||
}
|
||||
} else {
|
||||
// Login
|
||||
message.warning('当前登录状态已失效,请重新登录')
|
||||
store.dispatch('ToggleLoginModal', true)
|
||||
}
|
||||
} else if (data.status === 403) {
|
||||
// TODO handle 403 status error
|
||||
} else if (data.status === 404) {
|
||||
// TODO handle 404 status error
|
||||
} else if (data.status === 500) {
|
||||
// TODO handle 500 status error
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
message.error(data.message)
|
||||
}
|
||||
} else {
|
||||
message.error('网络异常')
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
|
@ -11,7 +11,7 @@ export function isObject(value) {
|
|||
|
||||
export function deepClone(source) {
|
||||
if (!source && typeof source !== 'object') {
|
||||
throw new Error('error arguments', 'deepClone')
|
||||
throw new Error('error arguments')
|
||||
}
|
||||
const targetObj = source.constructor === Array ? [] : {}
|
||||
Object.keys(source).forEach(keys => {
|
||||
|
|
|
@ -120,9 +120,9 @@
|
|||
@showSizeChange="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
<a-modal v-model="upload.visible" :afterClose="onUploadClose" :footer="null" destroyOnClose title="上传附件">
|
||||
<FilePondUpload ref="upload" :uploadHandler="upload.handler"></FilePondUpload>
|
||||
</a-modal>
|
||||
|
||||
<AttachmentUploadModal :visible.sync="upload.visible" @close="onUploadClose" />
|
||||
|
||||
<AttachmentDetailModal
|
||||
:addToPhoto="true"
|
||||
:attachment="list.selected"
|
||||
|
@ -141,9 +141,48 @@
|
|||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import { PageView } from '@/layouts'
|
||||
import AttachmentDetailModal from './components/AttachmentDetailModal.vue'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
const attachmentType = {
|
||||
LOCAL: {
|
||||
type: 'LOCAL',
|
||||
text: '本地'
|
||||
},
|
||||
SMMS: {
|
||||
type: 'SMMS',
|
||||
text: 'SM.MS'
|
||||
},
|
||||
UPOSS: {
|
||||
type: 'UPOSS',
|
||||
text: '又拍云'
|
||||
},
|
||||
QINIUOSS: {
|
||||
type: 'QINIUOSS',
|
||||
text: '七牛云'
|
||||
},
|
||||
ALIOSS: {
|
||||
type: 'ALIOSS',
|
||||
text: '阿里云'
|
||||
},
|
||||
BAIDUBOS: {
|
||||
type: 'BAIDUBOS',
|
||||
text: '百度云'
|
||||
},
|
||||
TENCENTCOS: {
|
||||
type: 'TENCENTCOS',
|
||||
text: '腾讯云'
|
||||
},
|
||||
HUAWEIOBS: {
|
||||
type: 'HUAWEIOBS',
|
||||
text: '华为云'
|
||||
},
|
||||
MINIO: {
|
||||
type: 'MINIO',
|
||||
text: 'MinIO'
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageView,
|
||||
|
@ -152,7 +191,7 @@ export default {
|
|||
mixins: [mixin, mixinDevice],
|
||||
filters: {
|
||||
typeText(type) {
|
||||
return attachmentApi.type[type].text
|
||||
return attachmentType[type].text
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -167,9 +206,9 @@ export default {
|
|||
params: {
|
||||
page: 0,
|
||||
size: 18,
|
||||
keyword: null,
|
||||
mediaType: null,
|
||||
attachmentType: null
|
||||
keyword: undefined,
|
||||
mediaType: undefined,
|
||||
attachmentType: undefined
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -184,7 +223,6 @@ export default {
|
|||
},
|
||||
|
||||
upload: {
|
||||
handler: attachmentApi.upload,
|
||||
visible: false
|
||||
},
|
||||
|
||||
|
@ -240,12 +278,12 @@ export default {
|
|||
try {
|
||||
this.list.loading = true
|
||||
|
||||
const response = await attachmentApi.query(this.list.params)
|
||||
const response = await apiClient.attachment.list(this.list.params)
|
||||
|
||||
this.list.data = response.data.data.content
|
||||
this.list.total = response.data.data.total
|
||||
this.list.hasNext = response.data.data.hasNext
|
||||
this.list.hasPrevious = response.data.data.hasPrevious
|
||||
this.list.data = response.data.content
|
||||
this.list.total = response.data.total
|
||||
this.list.hasNext = response.data.hasNext
|
||||
this.list.hasPrevious = response.data.hasPrevious
|
||||
} catch (error) {
|
||||
this.$log.error(error)
|
||||
} finally {
|
||||
|
@ -260,9 +298,9 @@ export default {
|
|||
try {
|
||||
this.mediaTypes.loading = true
|
||||
|
||||
const response = await attachmentApi.getMediaTypes()
|
||||
const response = await apiClient.attachment.listMediaTypes()
|
||||
|
||||
this.mediaTypes.data = response.data.data
|
||||
this.mediaTypes.data = response.data
|
||||
} catch (error) {
|
||||
this.$log.error(error)
|
||||
} finally {
|
||||
|
@ -277,9 +315,9 @@ export default {
|
|||
try {
|
||||
this.types.loading = true
|
||||
|
||||
const response = await attachmentApi.getTypes()
|
||||
const response = await apiClient.attachment.listTypes()
|
||||
|
||||
this.types.data = response.data.data
|
||||
this.types.data = response.data
|
||||
} catch (error) {
|
||||
this.$log.error(error)
|
||||
} finally {
|
||||
|
@ -343,8 +381,8 @@ export default {
|
|||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await attachmentApi.delete(item.id)
|
||||
this.handleListAttachments()
|
||||
await apiClient.attachment.delete(item.id)
|
||||
await this.handleListAttachments()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -378,9 +416,9 @@ export default {
|
|||
* Reset query params
|
||||
*/
|
||||
handleResetParam() {
|
||||
this.list.params.keyword = null
|
||||
this.list.params.mediaType = null
|
||||
this.list.params.attachmentType = null
|
||||
this.list.params.keyword = undefined
|
||||
this.list.params.mediaType = undefined
|
||||
this.list.params.attachmentType = undefined
|
||||
this.handlePageChange()
|
||||
this.handleListMediaTypes()
|
||||
this.handleListTypes()
|
||||
|
@ -393,7 +431,6 @@ export default {
|
|||
this.handlePageChange()
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.handlePageChange()
|
||||
this.handleListMediaTypes()
|
||||
this.handleListTypes()
|
||||
|
@ -443,7 +480,7 @@ export default {
|
|||
title: '确定要批量删除选中的附件吗?',
|
||||
content: '一旦删除不可恢复,请谨慎操作',
|
||||
onOk() {
|
||||
attachmentApi
|
||||
apiClient.attachment
|
||||
.deleteInBatch(that.batchSelectedAttachments)
|
||||
.then(() => {
|
||||
that.handleCancelMultipleSelection()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<a-modal title="附件详情" :width="isMobile() ? '100%' : '50%'" v-model="modalVisible">
|
||||
<a-row type="flex" :gutter="24">
|
||||
<a-col :xl="9" :lg="9" :md="24" :sm="24" :xs="24">
|
||||
<a-modal v-model="modalVisible" :width="isMobile() ? '100%' : '50%'" title="附件详情">
|
||||
<a-row :gutter="24" type="flex">
|
||||
<a-col :lg="9" :md="24" :sm="24" :xl="9" :xs="24">
|
||||
<div class="attach-detail-img pb-3">
|
||||
<a v-if="isImage" :href="attachment.path" target="_blank">
|
||||
<img :src="attachment.path" class="w-full" loading="lazy" />
|
||||
|
@ -9,14 +9,14 @@
|
|||
<div v-else>此文件不支持预览</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xl="15" :lg="15" :md="24" :sm="24" :xs="24">
|
||||
<a-col :lg="15" :md="24" :sm="24" :xl="15" :xs="24">
|
||||
<a-list itemLayout="horizontal">
|
||||
<a-list-item style="padding-top: 0;">
|
||||
<a-list-item-meta>
|
||||
<template slot="description" v-if="editable">
|
||||
<template v-if="editable" slot="description">
|
||||
<a-input ref="nameInput" v-model="attachment.name" @blur="handleUpdateName" />
|
||||
</template>
|
||||
<template slot="description" v-else>{{ attachment.name }}</template>
|
||||
<template v-else slot="description">{{ attachment.name }}</template>
|
||||
<span slot="title">
|
||||
附件名:
|
||||
<a href="javascript:void(0);">
|
||||
|
@ -89,16 +89,16 @@
|
|||
|
||||
<template #footer>
|
||||
<slot name="extraFooter" />
|
||||
<a-popconfirm title="你确定要删除该附件?" @confirm="handleDelete" okText="确定" cancelText="取消">
|
||||
<a-popconfirm cancelText="取消" okText="确定" title="你确定要删除该附件?" @confirm="handleDelete">
|
||||
<ReactiveButton
|
||||
type="danger"
|
||||
@callback="handleDeletedCallback"
|
||||
:loading="deleting"
|
||||
:errored="deleteErrored"
|
||||
text="删除"
|
||||
:loading="deleting"
|
||||
erroredText="删除失败"
|
||||
icon="delete"
|
||||
loadedText="删除成功"
|
||||
erroredText="删除失败"
|
||||
text="删除"
|
||||
type="danger"
|
||||
@callback="handleDeletedCallback"
|
||||
></ReactiveButton>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
|
@ -107,14 +107,53 @@
|
|||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
const attachmentType = {
|
||||
LOCAL: {
|
||||
type: 'LOCAL',
|
||||
text: '本地'
|
||||
},
|
||||
SMMS: {
|
||||
type: 'SMMS',
|
||||
text: 'SM.MS'
|
||||
},
|
||||
UPOSS: {
|
||||
type: 'UPOSS',
|
||||
text: '又拍云'
|
||||
},
|
||||
QINIUOSS: {
|
||||
type: 'QINIUOSS',
|
||||
text: '七牛云'
|
||||
},
|
||||
ALIOSS: {
|
||||
type: 'ALIOSS',
|
||||
text: '阿里云'
|
||||
},
|
||||
BAIDUBOS: {
|
||||
type: 'BAIDUBOS',
|
||||
text: '百度云'
|
||||
},
|
||||
TENCENTCOS: {
|
||||
type: 'TENCENTCOS',
|
||||
text: '腾讯云'
|
||||
},
|
||||
HUAWEIOBS: {
|
||||
type: 'HUAWEIOBS',
|
||||
text: '华为云'
|
||||
},
|
||||
MINIO: {
|
||||
type: 'MINIO',
|
||||
text: 'MinIO'
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AttachmentDetailModal',
|
||||
mixins: [mixin, mixinDevice],
|
||||
filters: {
|
||||
typeText(type) {
|
||||
return type ? attachmentApi.type[type].text : ''
|
||||
return type ? attachmentType[type].text : ''
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
@ -158,7 +197,7 @@ export default {
|
|||
try {
|
||||
this.deleting = true
|
||||
|
||||
await attachmentApi.delete(this.attachment.id)
|
||||
await apiClient.attachment.delete(this.attachment.id)
|
||||
} catch (error) {
|
||||
this.$log.error(error)
|
||||
this.deleteErrored = true
|
||||
|
@ -202,7 +241,7 @@ export default {
|
|||
return
|
||||
}
|
||||
try {
|
||||
await attachmentApi.update(this.attachment.id, this.attachment)
|
||||
await apiClient.attachment.update(this.attachment.id, this.attachment)
|
||||
} catch (error) {
|
||||
this.$log.error(error)
|
||||
} finally {
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-drawer
|
||||
title="附件库"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
:visible="visible"
|
||||
:width="isMobile() ? '100%' : '480'"
|
||||
closable
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
title="附件库"
|
||||
@close="onClose"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-input-search placeholder="搜索附件" v-model="queryParam.keyword" @search="handleQuery()" enterButton />
|
||||
<a-row align="middle" type="flex">
|
||||
<a-input-search v-model="queryParam.keyword" enterButton placeholder="搜索附件" @search="handleQuery()" />
|
||||
</a-row>
|
||||
<a-divider />
|
||||
<a-row type="flex" align="middle">
|
||||
<a-row align="middle" type="flex">
|
||||
<a-col :span="24">
|
||||
<a-spin :spinning="loading" class="attachments-group">
|
||||
<a-empty v-if="formattedDatas.length === 0" />
|
||||
<div
|
||||
v-else
|
||||
class="attach-item attachments-group-item"
|
||||
v-for="(item, index) in formattedDatas"
|
||||
v-else
|
||||
:key="index"
|
||||
class="attach-item attachments-group-item"
|
||||
@click="handleShowDetailDrawer(item)"
|
||||
@contextmenu.prevent="handleContextMenu($event, item)"
|
||||
>
|
||||
<span v-if="!handleJudgeMediaType(item)" class="attachments-group-item-type">{{ item.suffix }}</span>
|
||||
<span
|
||||
v-else
|
||||
class="attachments-group-item-img"
|
||||
:style="`background-image:url(${item.thumbPath})`"
|
||||
class="attachments-group-item-img"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
@ -40,10 +40,10 @@
|
|||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
@change="handlePaginationChange"
|
||||
:total="pagination.total"
|
||||
showLessItems
|
||||
@change="handlePaginationChange"
|
||||
></a-pagination>
|
||||
</div>
|
||||
|
||||
|
@ -55,20 +55,18 @@
|
|||
/> -->
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-button @click="uploadVisible = true" type="primary">上传附件</a-button>
|
||||
<a-button type="primary" @click="uploadVisible = true">上传附件</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
||||
<a-modal title="上传附件" v-model="uploadVisible" :footer="null" :afterClose="onUploadClose" destroyOnClose>
|
||||
<FilePondUpload ref="upload" :uploadHandler="uploadHandler"></FilePondUpload>
|
||||
</a-modal>
|
||||
<AttachmentUploadModal :visible.sync="uploadVisible" @close="onUploadClose" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
// import AttachmentDetailDrawer from './AttachmentDetailDrawer'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'AttachmentDrawer',
|
||||
|
@ -89,7 +87,44 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
attachmentType: attachmentApi.type,
|
||||
attachmentType: {
|
||||
LOCAL: {
|
||||
type: 'LOCAL',
|
||||
text: '本地'
|
||||
},
|
||||
SMMS: {
|
||||
type: 'SMMS',
|
||||
text: 'SM.MS'
|
||||
},
|
||||
UPOSS: {
|
||||
type: 'UPOSS',
|
||||
text: '又拍云'
|
||||
},
|
||||
QINIUOSS: {
|
||||
type: 'QINIUOSS',
|
||||
text: '七牛云'
|
||||
},
|
||||
ALIOSS: {
|
||||
type: 'ALIOSS',
|
||||
text: '阿里云'
|
||||
},
|
||||
BAIDUBOS: {
|
||||
type: 'BAIDUBOS',
|
||||
text: '百度云'
|
||||
},
|
||||
TENCENTCOS: {
|
||||
type: 'TENCENTCOS',
|
||||
text: '腾讯云'
|
||||
},
|
||||
HUAWEIOBS: {
|
||||
type: 'HUAWEIOBS',
|
||||
text: '华为云'
|
||||
},
|
||||
MINIO: {
|
||||
type: 'MINIO',
|
||||
text: 'MinIO'
|
||||
}
|
||||
},
|
||||
detailVisible: false,
|
||||
attachmentDrawerVisible: false,
|
||||
uploadVisible: false,
|
||||
|
@ -107,8 +142,7 @@ export default {
|
|||
keyword: null
|
||||
},
|
||||
attachments: [],
|
||||
selectedAttachment: {},
|
||||
uploadHandler: attachmentApi.upload
|
||||
selectedAttachment: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -171,16 +205,14 @@ export default {
|
|||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
attachmentApi
|
||||
.query(this.queryParam)
|
||||
apiClient.attachment
|
||||
.list(this.queryParam)
|
||||
.then(response => {
|
||||
this.attachments = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
this.attachments = response.data.content
|
||||
this.pagination.total = response.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 200)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
|
@ -192,7 +224,6 @@ export default {
|
|||
this.handleListAttachments()
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleAfterVisibleChanged(visible) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<script>
|
||||
import { PageView } from '@/layouts'
|
||||
import CommentTab from './components/CommentTab'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageView,
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="评论详情"
|
||||
:width="isMobile() ? '100%' : '480'"
|
||||
closable
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-col :span="24">
|
||||
<a-list itemLayout="horizontal">
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="comment.author">
|
||||
<span slot="title">评论者昵称:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="comment.email">
|
||||
<span slot="title">评论者邮箱:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="comment.ipAddress">
|
||||
<span slot="title">评论者 IP:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<a slot="description" target="_blank" :href="comment.authorUrl">{{ comment.authorUrl }}</a>
|
||||
<span slot="title">评论者网址:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<span slot="description">
|
||||
<a-badge :status="comment.statusProperty.status" :text="comment.statusProperty.text" />
|
||||
</span>
|
||||
<span slot="title">评论状态:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<a slot="description" target="_blank" :href="comment.post.fullPath" v-if="this.type == 'posts'">{{
|
||||
comment.post.title
|
||||
}}</a>
|
||||
<a slot="description" target="_blank" :href="comment.sheet.fullPath" v-else-if="this.type == 'sheets'">{{
|
||||
comment.sheet.title
|
||||
}}</a>
|
||||
<span slot="title" v-if="this.type == 'posts'">评论文章:</span>
|
||||
<span slot="title" v-else-if="this.type == 'sheets'">评论页面:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template slot="description" v-if="editable">
|
||||
<a-input type="textarea" :autoSize="{ minRows: 5 }" v-model="comment.content" />
|
||||
</template>
|
||||
<span slot="description" v-html="comment.content" v-else></span>
|
||||
<span slot="title">评论内容:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-space>
|
||||
<a-button type="dashed" @click="handleEditComment" v-if="!editable">编辑</a-button>
|
||||
<a-button type="primary" @click="handleUpdateComment" v-if="editable">保存</a-button>
|
||||
<a-popconfirm title="你确定要将此评论者加入黑名单?" okText="确定" cancelText="取消">
|
||||
<a-button type="danger">加入黑名单</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import commentApi from '@/api/comment'
|
||||
export default {
|
||||
name: 'CommentDetail',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
editable: false,
|
||||
commentStatus: commentApi.commentStatus,
|
||||
keys: ['blog_url']
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'close'
|
||||
},
|
||||
props: {
|
||||
comment: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'posts',
|
||||
validator: function(value) {
|
||||
return ['posts', 'sheets', 'journals'].indexOf(value) !== -1
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEditComment() {
|
||||
this.editable = true
|
||||
},
|
||||
handleUpdateComment() {
|
||||
commentApi.update(this.type, this.comment.id, this.comment).then(response => {
|
||||
this.$log.debug('Updated comment', response.data.data)
|
||||
this.$message.success('评论修改成功!')
|
||||
})
|
||||
this.editable = false
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,20 +1,20 @@
|
|||
<template>
|
||||
<div class="comment-tab-wrapper">
|
||||
<a-card :bordered="false" :bodyStyle="{ padding: 0 }">
|
||||
<a-card :bodyStyle="{ padding: 0 }" :bordered="false">
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline">
|
||||
<a-row :gutter="48">
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="关键词:">
|
||||
<a-input v-model="queryParam.keyword" @keyup.enter="handleQuery()" />
|
||||
<a-input v-model="list.params.keyword" @keyup.enter="handleQuery()" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="评论状态:">
|
||||
<a-select v-model="queryParam.status" placeholder="请选择评论状态" @change="handleQuery()" allowClear>
|
||||
<a-select-option v-for="status in Object.keys(commentStatus)" :key="status" :value="status">{{
|
||||
commentStatus[status].text
|
||||
}}</a-select-option>
|
||||
<a-select v-model="list.params.status" allowClear placeholder="请选择评论状态" @change="handleQuery()">
|
||||
<a-select-option v-for="status in Object.keys(commentStatus)" :key="status" :value="status">
|
||||
{{ commentStatus[status].text }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -32,19 +32,19 @@
|
|||
</div>
|
||||
|
||||
<div class="table-operator">
|
||||
<a-dropdown v-show="queryParam.status != null && queryParam.status !== '' && !isMobile()">
|
||||
<a-dropdown v-show="list.params.status != null && list.params.status !== '' && !isMobile()">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1" v-if="queryParam.status === 'AUDITING'">
|
||||
<a-menu-item v-if="list.params.status === 'AUDITING'" key="1">
|
||||
<a href="javascript:void(0);" @click="handleEditStatusMore(commentStatus.PUBLISHED.value)">
|
||||
通过
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="2" v-if="queryParam.status === 'PUBLISHED' || queryParam.status === 'AUDITING'">
|
||||
<a-menu-item v-if="list.params.status === 'PUBLISHED' || list.params.status === 'AUDITING'" key="2">
|
||||
<a href="javascript:void(0);" @click="handleEditStatusMore(commentStatus.RECYCLE.value)">
|
||||
移到回收站
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="3" v-if="queryParam.status === 'RECYCLE'">
|
||||
<a-menu-item v-if="list.params.status === 'RECYCLE'" key="3">
|
||||
<a href="javascript:void(0);" @click="handleDeleteMore">
|
||||
永久删除
|
||||
</a>
|
||||
|
@ -60,15 +60,15 @@
|
|||
<!-- Mobile -->
|
||||
<a-list
|
||||
v-if="isMobile()"
|
||||
:dataSource="formattedComments"
|
||||
:loading="list.loading"
|
||||
:pagination="false"
|
||||
itemLayout="vertical"
|
||||
size="large"
|
||||
:pagination="false"
|
||||
:dataSource="formattedComments"
|
||||
:loading="loading"
|
||||
>
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" :key="index">
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<template slot="actions">
|
||||
<a-dropdown placement="topLeft" :trigger="['click']">
|
||||
<a-dropdown :trigger="['click']" placement="topLeft">
|
||||
<span>
|
||||
<a-icon type="bars" />
|
||||
</span>
|
||||
|
@ -85,9 +85,9 @@
|
|||
<a-menu-item v-else-if="item.status === 'RECYCLE'">
|
||||
<a-popconfirm
|
||||
:title="'你确定要还原该评论?'"
|
||||
@confirm="handleEditStatusClick(item.id, 'PUBLISHED')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleEditStatusClick(item.id, 'PUBLISHED')"
|
||||
>
|
||||
<a href="javascript:void(0);">还原</a>
|
||||
</a-popconfirm>
|
||||
|
@ -95,9 +95,9 @@
|
|||
<a-menu-item v-if="item.status === 'PUBLISHED' || item.status === 'AUDITING'">
|
||||
<a-popconfirm
|
||||
:title="'你确定要将该评论移到回收站?'"
|
||||
@confirm="handleEditStatusClick(item.id, 'RECYCLE')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleEditStatusClick(item.id, 'RECYCLE')"
|
||||
>
|
||||
<a href="javascript:void(0);">回收站</a>
|
||||
</a-popconfirm>
|
||||
|
@ -105,9 +105,9 @@
|
|||
<a-menu-item v-else-if="item.status === 'RECYCLE'">
|
||||
<a-popconfirm
|
||||
:title="'你确定要永久删除该评论?'"
|
||||
@confirm="handleDeleteClick(item.id)"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleDeleteClick(item.id)"
|
||||
>
|
||||
<a href="javascript:void(0);">删除</a>
|
||||
</a-popconfirm>
|
||||
|
@ -126,25 +126,26 @@
|
|||
<a v-if="type === 'posts'" :href="item.post.fullPath" target="_blank">《{{ item.post.title }}》</a>
|
||||
<a v-if="type === 'sheets'" :href="item.sheet.fullPath" target="_blank">《{{ item.sheet.title }}》</a>
|
||||
</template>
|
||||
<a-avatar slot="avatar" size="large" :src="item.avatar" />
|
||||
<a-avatar slot="avatar" :src="item.avatar" size="large" />
|
||||
<span
|
||||
v-if="item.authorUrl"
|
||||
slot="title"
|
||||
style="max-width: 300px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
|
||||
v-if="item.authorUrl"
|
||||
>
|
||||
<a-icon type="user" v-if="item.isAdmin" style="margin-right: 3px;" />
|
||||
<a-icon v-if="item.isAdmin" style="margin-right: 3px;" type="user" />
|
||||
<a :href="item.authorUrl" target="_blank">{{ item.author }}</a>
|
||||
<small style="color:rgba(0, 0, 0, 0.45)">{{ item.createTime | timeAgo }}</small>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
slot="title"
|
||||
style="max-width: 300px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
|
||||
v-else
|
||||
>
|
||||
<a-icon type="user" v-if="item.isAdmin" style="margin-right: 3px;" /> {{ item.author }} <small
|
||||
<a-icon v-if="item.isAdmin" style="margin-right: 3px;" type="user" /> {{ item.author }} <small
|
||||
style="color:rgba(0, 0, 0, 0.45)"
|
||||
>{{ item.createTime | timeAgo }}</small
|
||||
>
|
||||
{{ item.createTime | timeAgo }}
|
||||
</small>
|
||||
</span>
|
||||
</a-list-item-meta>
|
||||
<p v-html="item.content"></p>
|
||||
|
@ -153,33 +154,33 @@
|
|||
<!-- Desktop -->
|
||||
<a-table
|
||||
v-else
|
||||
:columns="columns"
|
||||
:dataSource="formattedComments"
|
||||
:loading="list.loading"
|
||||
:pagination="false"
|
||||
:rowKey="comment => comment.id"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: onSelectionChange,
|
||||
getCheckboxProps: getCheckboxProps
|
||||
}"
|
||||
:columns="columns"
|
||||
:dataSource="formattedComments"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
scrollToFirstRowOnChange
|
||||
>
|
||||
<template slot="author" slot-scope="text, record">
|
||||
<a-icon type="user" v-if="record.isAdmin" style="margin-right: 3px;" />
|
||||
<a :href="record.authorUrl" target="_blank" v-if="record.authorUrl">{{ text }}</a>
|
||||
<a-icon v-if="record.isAdmin" style="margin-right: 3px;" type="user" />
|
||||
<a v-if="record.authorUrl" :href="record.authorUrl" target="_blank">{{ text }}</a>
|
||||
<span v-else>{{ text }}</span>
|
||||
</template>
|
||||
<p class="comment-content-wrapper" slot="content" slot-scope="content" v-html="content"></p>
|
||||
<p slot="content" slot-scope="content" class="comment-content-wrapper" v-html="content"></p>
|
||||
<span slot="status" slot-scope="statusProperty">
|
||||
<a-badge :status="statusProperty.status" :text="statusProperty.text" />
|
||||
</span>
|
||||
<a v-if="type === 'posts'" slot="post" slot-scope="post" :href="post.fullPath" target="_blank">{{
|
||||
post.title
|
||||
}}</a>
|
||||
<a v-if="type === 'sheets'" slot="sheet" slot-scope="sheet" :href="sheet.fullPath" target="_blank">{{
|
||||
sheet.title
|
||||
}}</a>
|
||||
<a v-if="type === 'posts'" slot="post" slot-scope="post" :href="post.fullPath" target="_blank">
|
||||
{{ post.title }}
|
||||
</a>
|
||||
<a v-if="type === 'sheets'" slot="sheet" slot-scope="sheet" :href="sheet.fullPath" target="_blank">
|
||||
{{ sheet.title }}
|
||||
</a>
|
||||
<span slot="createTime" slot-scope="createTime">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
|
@ -189,8 +190,8 @@
|
|||
</a-tooltip>
|
||||
</span>
|
||||
<span slot="action" slot-scope="text, record">
|
||||
<a-dropdown :trigger="['click']" v-if="record.status === 'AUDITING'">
|
||||
<a href="javascript:void(0);" class="ant-dropdown-link">通过</a>
|
||||
<a-dropdown v-if="record.status === 'AUDITING'" :trigger="['click']">
|
||||
<a class="ant-dropdown-link" href="javascript:void(0);">通过</a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1">
|
||||
<a href="javascript:void(0);" @click="handleEditStatusClick(record.id, 'PUBLISHED')">通过</a>
|
||||
|
@ -201,16 +202,16 @@
|
|||
</a-menu>
|
||||
</a-dropdown>
|
||||
|
||||
<a href="javascript:void(0);" v-else-if="record.status === 'PUBLISHED'" @click="handleReplyClick(record)"
|
||||
>回复</a
|
||||
>
|
||||
<a v-else-if="record.status === 'PUBLISHED'" href="javascript:void(0);" @click="handleReplyClick(record)">
|
||||
回复
|
||||
</a>
|
||||
|
||||
<a-popconfirm
|
||||
:title="'你确定要还原该评论?'"
|
||||
@confirm="handleEditStatusClick(record.id, 'PUBLISHED')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
v-else-if="record.status === 'RECYCLE'"
|
||||
:title="'你确定要还原该评论?'"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleEditStatusClick(record.id, 'PUBLISHED')"
|
||||
>
|
||||
<a href="javascript:void(0);">还原</a>
|
||||
</a-popconfirm>
|
||||
|
@ -218,21 +219,21 @@
|
|||
<a-divider type="vertical" />
|
||||
|
||||
<a-popconfirm
|
||||
:title="'你确定要将该评论移到回收站?'"
|
||||
@confirm="handleEditStatusClick(record.id, 'RECYCLE')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
v-if="record.status === 'PUBLISHED' || record.status === 'AUDITING'"
|
||||
:title="'你确定要将该评论移到回收站?'"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleEditStatusClick(record.id, 'RECYCLE')"
|
||||
>
|
||||
<a href="javascript:void(0);">回收站</a>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-popconfirm
|
||||
:title="'你确定要永久删除该评论?'"
|
||||
@confirm="handleDeleteClick(record.id)"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
v-else-if="record.status === 'RECYCLE'"
|
||||
:title="'你确定要永久删除该评论?'"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleDeleteClick(record.id)"
|
||||
>
|
||||
<a href="javascript:void(0);">删除</a>
|
||||
</a-popconfirm>
|
||||
|
@ -240,15 +241,15 @@
|
|||
</a-table>
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
class="pagination"
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
:pageSizeOptions="['1', '2', '5', '10', '20', '50', '100']"
|
||||
showSizeChanger
|
||||
@showSizeChange="handlePaginationChange"
|
||||
@change="handlePaginationChange"
|
||||
:pageSizeOptions="['10', '20', '50', '100']"
|
||||
:total="pagination.total"
|
||||
class="pagination"
|
||||
showLessItems
|
||||
showSizeChanger
|
||||
@change="handlePageChange"
|
||||
@showSizeChange="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -256,26 +257,26 @@
|
|||
|
||||
<a-modal
|
||||
v-if="selectedComment"
|
||||
:title="'回复给:' + selectedComment.author"
|
||||
v-model="replyCommentVisible"
|
||||
@close="onReplyClose"
|
||||
:title="'回复给:' + selectedComment.author"
|
||||
destroyOnClose
|
||||
@close="onReplyClose"
|
||||
>
|
||||
<template slot="footer">
|
||||
<ReactiveButton
|
||||
type="primary"
|
||||
@click="handleCreateClick"
|
||||
@callback="handleRepliedCallback"
|
||||
:loading="replying"
|
||||
:errored="replyErrored"
|
||||
text="回复"
|
||||
loadedText="回复成功"
|
||||
:loading="replying"
|
||||
erroredText="回复失败"
|
||||
loadedText="回复成功"
|
||||
text="回复"
|
||||
type="primary"
|
||||
@callback="handleRepliedCallback"
|
||||
@click="handleCreateClick"
|
||||
></ReactiveButton>
|
||||
</template>
|
||||
<a-form-model ref="replyCommentForm" :model="replyComment" :rules="replyCommentRules" layout="vertical">
|
||||
<a-form-model-item prop="content">
|
||||
<a-input ref="contentInput" type="textarea" :autoSize="{ minRows: 8 }" v-model="replyComment.content" />
|
||||
<a-input ref="contentInput" v-model="replyComment.content" :autoSize="{ minRows: 8 }" type="textarea" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
|
@ -284,7 +285,7 @@
|
|||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import marked from 'marked'
|
||||
import commentApi from '@/api/comment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
const postColumns = [
|
||||
{
|
||||
|
@ -366,6 +367,27 @@ const sheetColumns = [
|
|||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
|
||||
const commentStatus = {
|
||||
PUBLISHED: {
|
||||
value: 'PUBLISHED',
|
||||
color: 'green',
|
||||
status: 'success',
|
||||
text: '已发布'
|
||||
},
|
||||
AUDITING: {
|
||||
value: 'AUDITING',
|
||||
color: 'yellow',
|
||||
status: 'warning',
|
||||
text: '待审核'
|
||||
},
|
||||
RECYCLE: {
|
||||
value: 'RECYCLE',
|
||||
color: 'red',
|
||||
status: 'error',
|
||||
text: '回收站'
|
||||
}
|
||||
}
|
||||
export default {
|
||||
name: 'CommentTab',
|
||||
mixins: [mixin, mixinDevice],
|
||||
|
@ -381,31 +403,29 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
columns: this.type === 'posts' ? postColumns : sheetColumns,
|
||||
commentStatus,
|
||||
replyCommentVisible: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: null,
|
||||
total: 1
|
||||
},
|
||||
queryParam: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
sort: null,
|
||||
keyword: null,
|
||||
status: null
|
||||
|
||||
list: {
|
||||
data: [],
|
||||
loading: false,
|
||||
total: 0,
|
||||
hasPrevious: false,
|
||||
hasNext: false,
|
||||
params: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
keyword: null,
|
||||
status: null
|
||||
}
|
||||
},
|
||||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
comments: [],
|
||||
selectedComment: {},
|
||||
replyComment: {},
|
||||
replyCommentRules: {
|
||||
content: [{ required: true, message: '* 内容不能为空', trigger: ['change'] }]
|
||||
},
|
||||
loading: false,
|
||||
commentStatus: commentApi.commentStatus,
|
||||
replying: false,
|
||||
replyErrored: false
|
||||
}
|
||||
|
@ -415,38 +435,47 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
formattedComments() {
|
||||
return this.comments.map(comment => {
|
||||
return this.list.data.map(comment => {
|
||||
comment.statusProperty = this.commentStatus[comment.status]
|
||||
comment.content = marked(comment.content)
|
||||
return comment
|
||||
})
|
||||
},
|
||||
pagination() {
|
||||
return {
|
||||
page: this.list.params.page + 1,
|
||||
size: this.list.params.size,
|
||||
total: this.list.total
|
||||
}
|
||||
},
|
||||
columns() {
|
||||
return this.type === 'posts' ? postColumns : sheetColumns
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleListComments() {
|
||||
this.loading = true
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
commentApi
|
||||
.queryComment(this.type, this.queryParam)
|
||||
.then(response => {
|
||||
this.comments = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 200)
|
||||
})
|
||||
async handleListComments() {
|
||||
try {
|
||||
this.list.loading = true
|
||||
|
||||
const response = await apiClient.comment.list(this.type, this.list.params)
|
||||
|
||||
this.list.data = response.data.content
|
||||
this.list.total = response.data.total
|
||||
this.list.hasPrevious = response.data.hasPrevious
|
||||
this.list.hasNext = response.data.hasNext
|
||||
} catch (e) {
|
||||
this.$log.error(e)
|
||||
} finally {
|
||||
this.list.loading = false
|
||||
}
|
||||
},
|
||||
handleQuery() {
|
||||
this.handleClearRowKeys()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
this.handlePageChange(1)
|
||||
},
|
||||
handleEditStatusClick(commentId, status) {
|
||||
commentApi
|
||||
.updateStatus(this.type, commentId, status)
|
||||
apiClient.comment
|
||||
.updateStatusById(this.type, commentId, status)
|
||||
.then(() => {
|
||||
this.$message.success('操作成功!')
|
||||
})
|
||||
|
@ -455,7 +484,7 @@ export default {
|
|||
})
|
||||
},
|
||||
handleDeleteClick(commentId) {
|
||||
commentApi
|
||||
apiClient.comment
|
||||
.delete(this.type, commentId)
|
||||
.then(() => {
|
||||
this.$message.success('删除成功!')
|
||||
|
@ -486,7 +515,7 @@ export default {
|
|||
_this.$refs.replyCommentForm.validate(valid => {
|
||||
if (valid) {
|
||||
_this.replying = true
|
||||
commentApi
|
||||
apiClient.comment
|
||||
.create(_this.type, _this.replyComment)
|
||||
.catch(() => {
|
||||
_this.replyErrored = true
|
||||
|
@ -509,24 +538,37 @@ export default {
|
|||
this.handleListComments()
|
||||
}
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
|
||||
/**
|
||||
* Handle page change
|
||||
*/
|
||||
handlePageChange(page = 1) {
|
||||
this.list.params.page = page - 1
|
||||
this.handleListComments()
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle page size change
|
||||
*/
|
||||
handlePageSizeChange(current, size) {
|
||||
this.$log.debug(`Current: ${current}, PageSize: ${size}`)
|
||||
this.list.params.page = 0
|
||||
this.list.params.size = size
|
||||
this.handleListComments()
|
||||
},
|
||||
|
||||
handleResetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.status = null
|
||||
this.list.params.keyword = null
|
||||
this.list.params.status = null
|
||||
this.handleClearRowKeys()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
this.handlePageChange(1)
|
||||
},
|
||||
handleEditStatusMore(status) {
|
||||
if (this.selectedRowKeys.length <= 0) {
|
||||
this.$message.info('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
commentApi
|
||||
apiClient.comment
|
||||
.updateStatusInBatch(this.type, this.selectedRowKeys, status)
|
||||
.then(() => {
|
||||
this.$log.debug(`commentIds: ${this.selectedRowKeys}, status: ${status}`)
|
||||
|
@ -541,7 +583,7 @@ export default {
|
|||
this.$message.info('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
commentApi
|
||||
apiClient.comment
|
||||
.deleteInBatch(this.type, this.selectedRowKeys)
|
||||
.then(() => {
|
||||
this.$log.debug(`delete: ${this.selectedRowKeys}`)
|
||||
|
@ -566,7 +608,7 @@ export default {
|
|||
getCheckboxProps(comment) {
|
||||
return {
|
||||
props: {
|
||||
disabled: this.queryParam.status == null || this.queryParam.status === '',
|
||||
disabled: this.list.params.status == null || this.list.params.status === '',
|
||||
name: comment.author
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="评论列表"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
:visible="visible"
|
||||
:width="isMobile() ? '100%' : '480'"
|
||||
closable
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
title="评论列表"
|
||||
@close="onClose"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-row align="middle" type="flex">
|
||||
<a-col :span="24">
|
||||
<a-list itemLayout="horizontal">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template slot="description">
|
||||
<p v-html="description" class="comment-drawer-content"></p>
|
||||
<p class="comment-drawer-content" v-html="description"></p>
|
||||
</template>
|
||||
<h3 slot="title">{{ title }}</h3>
|
||||
</a-list-item-meta>
|
||||
|
@ -26,13 +26,13 @@
|
|||
<a-spin :spinning="list.loading">
|
||||
<a-empty v-if="list.data.length === 0" />
|
||||
<TargetCommentTree
|
||||
v-else
|
||||
v-for="(comment, index) in list.data"
|
||||
v-else
|
||||
:key="index"
|
||||
:comment="comment"
|
||||
@reply="handleCommentReply"
|
||||
@delete="handleCommentDelete"
|
||||
@editStatus="handleEditStatusClick"
|
||||
@reply="handleCommentReply"
|
||||
/>
|
||||
</a-spin>
|
||||
</a-col>
|
||||
|
@ -41,32 +41,32 @@
|
|||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:current="list.pagination.page"
|
||||
:total="list.pagination.total"
|
||||
:defaultPageSize="list.pagination.size"
|
||||
@change="handlePaginationChange"
|
||||
:total="list.pagination.total"
|
||||
showLessItems
|
||||
@change="handlePaginationChange"
|
||||
></a-pagination>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-button type="primary" @click="handleCommentReply({})">评论</a-button>
|
||||
</div>
|
||||
<a-modal :title="replyModalTitle" v-model="replyModal.visible" @close="onReplyModalClose" destroyOnClose>
|
||||
<a-modal v-model="replyModal.visible" :title="replyModalTitle" destroyOnClose @close="onReplyModalClose">
|
||||
<template slot="footer">
|
||||
<ReactiveButton
|
||||
type="primary"
|
||||
@click="handleReplyClick"
|
||||
@callback="handleReplyCallback"
|
||||
:loading="replyModal.saving"
|
||||
:errored="replyModal.saveErrored"
|
||||
text="回复"
|
||||
loadedText="回复成功"
|
||||
:loading="replyModal.saving"
|
||||
erroredText="回复失败"
|
||||
loadedText="回复成功"
|
||||
text="回复"
|
||||
type="primary"
|
||||
@callback="handleReplyCallback"
|
||||
@click="handleReplyClick"
|
||||
></ReactiveButton>
|
||||
</template>
|
||||
<a-form-model ref="replyCommentForm" :model="replyModal.model" :rules="replyModal.rules" layout="vertical">
|
||||
<a-form-model-item prop="content">
|
||||
<a-input ref="contentInput" type="textarea" :autoSize="{ minRows: 8 }" v-model="replyModal.model.content" />
|
||||
<a-input ref="contentInput" v-model="replyModal.model.content" :autoSize="{ minRows: 8 }" type="textarea" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
|
@ -75,7 +75,8 @@
|
|||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import TargetCommentTree from './TargetCommentTree'
|
||||
import commentApi from '@/api/comment'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'TargetCommentDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
|
@ -148,16 +149,14 @@ export default {
|
|||
this.list.queryParam.page = this.list.pagination.page - 1
|
||||
this.list.queryParam.size = this.list.pagination.size
|
||||
this.list.queryParam.sort = this.list.pagination.sort
|
||||
commentApi
|
||||
.commentTree(this.target, this.id, this.list.queryParam)
|
||||
apiClient.comment
|
||||
.listAsTreeView(this.target, this.id, this.list.queryParam)
|
||||
.then(response => {
|
||||
this.list.data = response.data.data.content
|
||||
this.list.pagination.total = response.data.data.total
|
||||
this.list.data = response.data.content
|
||||
this.list.pagination.total = response.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.list.loading = false
|
||||
}, 200)
|
||||
this.list.loading = false
|
||||
})
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
|
@ -179,7 +178,7 @@ export default {
|
|||
_this.$refs.replyCommentForm.validate(valid => {
|
||||
if (valid) {
|
||||
_this.replyModal.saving = true
|
||||
commentApi
|
||||
apiClient.comment
|
||||
.create(_this.target, _this.replyModal.model)
|
||||
.catch(() => {
|
||||
_this.replyModal.saveErrored = true
|
||||
|
@ -203,8 +202,8 @@ export default {
|
|||
}
|
||||
},
|
||||
handleEditStatusClick(comment, status) {
|
||||
commentApi
|
||||
.updateStatus(this.target, comment.id, status)
|
||||
apiClient.comment
|
||||
.updateStatusById(this.target, comment.id, status)
|
||||
.then(() => {
|
||||
this.$message.success('操作成功!')
|
||||
})
|
||||
|
@ -213,7 +212,7 @@ export default {
|
|||
})
|
||||
},
|
||||
handleCommentDelete(comment) {
|
||||
commentApi
|
||||
apiClient.comment
|
||||
.delete(this.target, comment.id)
|
||||
.then(() => {
|
||||
this.$message.success('删除成功!')
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<a-comment>
|
||||
<template slot="actions">
|
||||
<a-dropdown :trigger="['click']" v-if="comment.status === 'AUDITING'">
|
||||
<a-dropdown v-if="comment.status === 'AUDITING'" :trigger="['click']">
|
||||
<span href="javascript:void(0);">通过</span>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1">
|
||||
|
@ -19,9 +19,9 @@
|
|||
<a-popconfirm
|
||||
v-else-if="comment.status === 'RECYCLE'"
|
||||
:title="'你确定要还原该评论?'"
|
||||
@confirm="handleEditStatusClick('PUBLISHED')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleEditStatusClick('PUBLISHED')"
|
||||
>
|
||||
<span>还原</span>
|
||||
</a-popconfirm>
|
||||
|
@ -29,22 +29,22 @@
|
|||
<a-popconfirm
|
||||
v-if="comment.status === 'PUBLISHED' || comment.status === 'AUDITING'"
|
||||
:title="'你确定要将该评论移到回收站?'"
|
||||
@confirm="handleEditStatusClick('RECYCLE')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
@confirm="handleEditStatusClick('RECYCLE')"
|
||||
>
|
||||
<span>回收站</span>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-popconfirm :title="'你确定要永久删除该评论?'" @confirm="handleDeleteClick" okText="确定" cancelText="取消">
|
||||
<a-popconfirm :title="'你确定要永久删除该评论?'" cancelText="取消" okText="确定" @confirm="handleDeleteClick">
|
||||
<span>删除</span>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<a slot="author" :href="comment.authorUrl" target="_blank">
|
||||
<a-icon type="user" v-if="comment.isAdmin" style="margin-right: 3px;" />
|
||||
<a-icon v-if="comment.isAdmin" style="margin-right: 3px;" type="user" />
|
||||
{{ comment.author }}
|
||||
</a>
|
||||
<a-avatar size="large" slot="avatar" :src="comment.avatar" :alt="comment.author" />
|
||||
<a-avatar slot="avatar" :alt="comment.author" :src="comment.avatar" size="large" />
|
||||
<p slot="content" v-html="content"></p>
|
||||
<a-tooltip slot="datetime">
|
||||
<span slot="title">{{ comment.createTime | moment }}</span>
|
||||
|
@ -55,11 +55,11 @@
|
|||
v-for="(child, index) in comment.children"
|
||||
:key="index"
|
||||
:comment="child"
|
||||
v-on="$listeners"
|
||||
v-bind="$attrs"
|
||||
@reply="handleReplyClick"
|
||||
@delete="handleDeleteClick"
|
||||
@editStatus="handleEditStatusClick"
|
||||
@reply="handleReplyClick"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</template>
|
||||
</a-comment>
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
<template>
|
||||
<page-view>
|
||||
<a-row :gutter="12">
|
||||
<a-col :xl="6" :lg="6" :md="12" :sm="12" :xs="12" class="mb-3">
|
||||
<analysis-card title="文章" :number="statisticsData.postCount">
|
||||
<router-link :to="{ name: 'PostWrite' }" slot="action">
|
||||
<a-col :lg="6" :md="12" :sm="12" :xl="6" :xs="12" class="mb-3">
|
||||
<analysis-card :number="statisticsData.postCount" title="文章">
|
||||
<router-link slot="action" :to="{ name: 'PostWrite' }">
|
||||
<a-icon v-if="statisticsLoading" type="loading" />
|
||||
<a-icon v-else type="plus" />
|
||||
</router-link>
|
||||
</analysis-card>
|
||||
</a-col>
|
||||
<a-col :xl="6" :lg="6" :md="12" :sm="12" :xs="12" class="mb-3">
|
||||
<analysis-card title="评论" :number="statisticsData.commentCount">
|
||||
<router-link :to="{ name: 'Comments' }" slot="action">
|
||||
<a-col :lg="6" :md="12" :sm="12" :xl="6" :xs="12" class="mb-3">
|
||||
<analysis-card :number="statisticsData.commentCount" title="评论">
|
||||
<router-link slot="action" :to="{ name: 'Comments' }">
|
||||
<a-icon v-if="statisticsLoading" type="loading" />
|
||||
<a-icon v-else type="unordered-list" />
|
||||
</router-link>
|
||||
</analysis-card>
|
||||
</a-col>
|
||||
<a-col :xl="6" :lg="6" :md="12" :sm="12" :xs="12" class="mb-3">
|
||||
<analysis-card title="阅读量" :number="statisticsData.visitCount">
|
||||
<a-col :lg="6" :md="12" :sm="12" :xl="6" :xs="12" class="mb-3">
|
||||
<analysis-card :number="statisticsData.visitCount" title="阅读量">
|
||||
<a-tooltip slot="action">
|
||||
<template slot="title"> 文章阅读共 {{ statisticsData.visitCount }} 次 </template>
|
||||
<template slot="title"> 文章阅读共 {{ statisticsData.visitCount }} 次</template>
|
||||
<a href="javascript:void(0);">
|
||||
<a-icon v-if="statisticsLoading" type="loading" />
|
||||
<a-icon v-else type="info-circle-o" />
|
||||
|
@ -28,8 +28,8 @@
|
|||
</a-tooltip>
|
||||
</analysis-card>
|
||||
</a-col>
|
||||
<a-col :xl="6" :lg="6" :md="12" :sm="12" :xs="12" class="mb-3">
|
||||
<analysis-card title="建立天数" :number="statisticsData.establishDays">
|
||||
<a-col :lg="6" :md="12" :sm="12" :xl="6" :xs="12" class="mb-3">
|
||||
<analysis-card :number="statisticsData.establishDays" title="建立天数">
|
||||
<a-tooltip slot="action">
|
||||
<template slot="title">博客建立于 {{ statisticsData.birthday | moment }}</template>
|
||||
<a href="javascript:void(0);">
|
||||
|
@ -41,29 +41,31 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="12">
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="24" :xs="24" class="mb-3">
|
||||
<a-card :bordered="false" title="新动态" :bodyStyle="{ padding: 0 }">
|
||||
<a-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24" class="mb-3">
|
||||
<a-card :bodyStyle="{ padding: 0 }" :bordered="false" title="新动态">
|
||||
<div class="card-container">
|
||||
<a-tabs type="card">
|
||||
<a-tab-pane key="1" tab="最近文章">
|
||||
<a-list :loading="activityLoading" :dataSource="latestPosts">
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" :key="index">
|
||||
<a-list :dataSource="latestPosts" :loading="activityLoading">
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<a-list-item-meta>
|
||||
<a
|
||||
v-if="['PUBLISHED', 'INTIMATE'].includes(item.status)"
|
||||
slot="title"
|
||||
:href="item.fullPath"
|
||||
target="_blank"
|
||||
>{{ item.title }}</a
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
<a
|
||||
v-else-if="item.status === 'DRAFT'"
|
||||
slot="title"
|
||||
href="javascript:void(0)"
|
||||
@click="handlePostPreview(item.id)"
|
||||
>{{ item.title }}</a
|
||||
>
|
||||
<a v-else-if="item.status === 'RECYCLE'" slot="title" href="javascript:void(0);" disabled>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
<a v-else-if="item.status === 'RECYCLE'" slot="title" disabled href="javascript:void(0);">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</a-list-item-meta>
|
||||
|
@ -74,18 +76,12 @@
|
|||
<a-tab-pane key="2" tab="最近评论">
|
||||
<div class="custom-tab-wrapper">
|
||||
<a-tabs :animated="{ inkBar: true, tabPane: false }">
|
||||
<a-tab-pane tab="文章" key="1">
|
||||
<a-tab-pane key="1" tab="文章">
|
||||
<recent-comment-tab type="posts"></recent-comment-tab>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="页面" key="2">
|
||||
<a-tab-pane key="2" tab="页面">
|
||||
<recent-comment-tab type="sheets"></recent-comment-tab>
|
||||
</a-tab-pane>
|
||||
<!-- <a-tab-pane
|
||||
tab="日志"
|
||||
key="3"
|
||||
>
|
||||
<recent-comment-tab type="journals"></recent-comment-tab>
|
||||
</a-tab-pane>-->
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
@ -93,11 +89,11 @@
|
|||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="24" :xs="24" class="mb-3">
|
||||
<a-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24" class="mb-3">
|
||||
<JournalPublishCard />
|
||||
</a-col>
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="24" :xs="24" class="mb-3">
|
||||
<a-card :bordered="false" :bodyStyle="{ padding: '16px' }">
|
||||
<a-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24" class="mb-3">
|
||||
<a-card :bodyStyle="{ padding: '16px' }" :bordered="false">
|
||||
<template slot="title">
|
||||
操作记录
|
||||
<a-tooltip slot="action" title="更多">
|
||||
|
@ -107,7 +103,7 @@
|
|||
</a-tooltip>
|
||||
</template>
|
||||
<a-list :dataSource="formattedLogDatas" :loading="logLoading">
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" :key="index">
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<a-list-item-meta :description="item.createTime | timeAgo">
|
||||
<span slot="title">{{ item.type }}</span>
|
||||
</a-list-item-meta>
|
||||
|
@ -129,9 +125,8 @@ import JournalPublishCard from './components/JournalPublishCard'
|
|||
import RecentCommentTab from './components/RecentCommentTab'
|
||||
import LogListDrawer from './components/LogListDrawer'
|
||||
|
||||
import postApi from '@/api/post'
|
||||
import logApi from '@/api/log'
|
||||
import statisticsApi from '@/api/statistics'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
|
@ -143,7 +138,64 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
logTypes: logApi.logTypes,
|
||||
logTypes: {
|
||||
BLOG_INITIALIZED: {
|
||||
value: 0,
|
||||
text: '博客初始化'
|
||||
},
|
||||
POST_PUBLISHED: {
|
||||
value: 5,
|
||||
text: '文章发布'
|
||||
},
|
||||
POST_EDITED: {
|
||||
value: 15,
|
||||
text: '文章修改'
|
||||
},
|
||||
POST_DELETED: {
|
||||
value: 20,
|
||||
text: '文章删除'
|
||||
},
|
||||
LOGGED_IN: {
|
||||
value: 25,
|
||||
text: '用户登录'
|
||||
},
|
||||
LOGGED_OUT: {
|
||||
value: 30,
|
||||
text: '注销登录'
|
||||
},
|
||||
LOGIN_FAILED: {
|
||||
value: 35,
|
||||
text: '登录失败'
|
||||
},
|
||||
PASSWORD_UPDATED: {
|
||||
value: 40,
|
||||
text: '修改密码'
|
||||
},
|
||||
PROFILE_UPDATED: {
|
||||
value: 45,
|
||||
text: '资料修改'
|
||||
},
|
||||
SHEET_PUBLISHED: {
|
||||
value: 50,
|
||||
text: '页面发布'
|
||||
},
|
||||
SHEET_EDITED: {
|
||||
value: 55,
|
||||
text: '页面修改'
|
||||
},
|
||||
SHEET_DELETED: {
|
||||
value: 60,
|
||||
text: '页面删除'
|
||||
},
|
||||
MFA_UPDATED: {
|
||||
value: 65,
|
||||
text: '两步验证'
|
||||
},
|
||||
LOGGED_PRE_CHECK: {
|
||||
value: 70,
|
||||
text: '登录验证'
|
||||
}
|
||||
},
|
||||
activityLoading: false,
|
||||
logLoading: false,
|
||||
statisticsLoading: true,
|
||||
|
@ -196,47 +248,41 @@ export default {
|
|||
methods: {
|
||||
handleListLatestPosts() {
|
||||
this.activityLoading = true
|
||||
postApi
|
||||
.listLatest(5)
|
||||
apiClient.post
|
||||
.latest(5)
|
||||
.then(response => {
|
||||
this.latestPosts = response.data.data
|
||||
this.latestPosts = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.activityLoading = false
|
||||
}, 200)
|
||||
this.activityLoading = false
|
||||
})
|
||||
},
|
||||
handleListLatestLogs() {
|
||||
this.logLoading = true
|
||||
logApi
|
||||
.listLatest(5)
|
||||
apiClient.log
|
||||
.latest(5)
|
||||
.then(response => {
|
||||
this.latestLogs = response.data.data
|
||||
this.latestLogs = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.logLoading = false
|
||||
}, 200)
|
||||
this.logLoading = false
|
||||
})
|
||||
},
|
||||
handleLoadStatistics() {
|
||||
statisticsApi
|
||||
apiClient.statistic
|
||||
.statistics()
|
||||
.then(response => {
|
||||
this.statisticsData = response.data.data
|
||||
this.statisticsData = response.data
|
||||
})
|
||||
.catch(() => {
|
||||
clearInterval(this.interval)
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.statisticsLoading = false
|
||||
}, 200)
|
||||
this.statisticsLoading = false
|
||||
})
|
||||
},
|
||||
handlePostPreview(postId) {
|
||||
postApi.preview(postId).then(response => {
|
||||
apiClient.post.getPreviewLinkById(postId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
<div class="number">
|
||||
<slot name="number">
|
||||
<countTo
|
||||
:startVal="startNumber"
|
||||
:endVal="(typeof number === 'function' && number()) || number"
|
||||
:duration="3000"
|
||||
:autoplay="true"
|
||||
:duration="3000"
|
||||
:endVal="(typeof number === 'function' && number()) || number"
|
||||
:startVal="startNumber"
|
||||
></countTo>
|
||||
</slot>
|
||||
</div>
|
||||
|
@ -25,6 +25,7 @@
|
|||
|
||||
<script>
|
||||
import countTo from 'vue-count-to'
|
||||
|
||||
export default {
|
||||
name: 'AnalysisCard',
|
||||
components: {
|
||||
|
|
|
@ -1,41 +1,42 @@
|
|||
<template>
|
||||
<a-card :bordered="false" :bodyStyle="{ padding: '16px' }">
|
||||
<a-card :bodyStyle="{ padding: '16px' }" :bordered="false">
|
||||
<template slot="title">
|
||||
速记
|
||||
<a-tooltip slot="action" title="内容将保存到页面/所有页面/日志页面">
|
||||
<a-icon type="info-circle-o" class="cursor-pointer" />
|
||||
<a-icon class="cursor-pointer" type="info-circle-o" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-form-model ref="journalForm" :model="form.model" :rules="form.rules" layout="vertical">
|
||||
<a-form-model-item prop="sourceContent">
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autoSize="{ minRows: 8 }"
|
||||
v-model="form.model.sourceContent"
|
||||
:autoSize="{ minRows: 8 }"
|
||||
placeholder="写点什么吧..."
|
||||
type="textarea"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item>
|
||||
<ReactiveButton
|
||||
@click="handleCreateJournalClick"
|
||||
:errored="form.errored"
|
||||
:loading="form.saving"
|
||||
erroredText="发布失败"
|
||||
loadedText="发布成功"
|
||||
text="发布"
|
||||
@callback="
|
||||
() => {
|
||||
if (!form.errored) form.model = {}
|
||||
form.errored = false
|
||||
}
|
||||
"
|
||||
:loading="form.saving"
|
||||
:errored="form.errored"
|
||||
text="发布"
|
||||
loadedText="发布成功"
|
||||
erroredText="发布失败"
|
||||
@click="handleCreateJournalClick"
|
||||
></ReactiveButton>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-card>
|
||||
</template>
|
||||
<script>
|
||||
import journalApi from '@/api/journal'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'JournalPublishCard',
|
||||
data() {
|
||||
|
@ -56,7 +57,7 @@ export default {
|
|||
_this.$refs.journalForm.validate(valid => {
|
||||
if (valid) {
|
||||
_this.form.saving = true
|
||||
journalApi
|
||||
apiClient.journal
|
||||
.create(_this.form.model)
|
||||
.catch(() => {
|
||||
this.form.errored = true
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-drawer
|
||||
title="操作日志"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
:visible="visible"
|
||||
:width="isMobile() ? '100%' : '480'"
|
||||
closable
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
title="操作日志"
|
||||
@close="onClose"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-row align="middle" type="flex">
|
||||
<a-col :span="24">
|
||||
<a-list :loading="loading" :dataSource="formattedLogsDatas">
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" :key="index">
|
||||
<a-list :dataSource="formattedLogsDatas" :loading="loading">
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<a-list-item-meta :description="item.createTime | timeAgo">
|
||||
<span slot="title">{{ item.type }}</span>
|
||||
</a-list-item-meta>
|
||||
|
@ -22,22 +22,22 @@
|
|||
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
class="pagination"
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
:pageSizeOptions="['50', '100', '150', '200']"
|
||||
showSizeChanger
|
||||
@showSizeChange="handlePaginationChange"
|
||||
@change="handlePaginationChange"
|
||||
:total="pagination.total"
|
||||
class="pagination"
|
||||
showLessItems
|
||||
showSizeChanger
|
||||
@change="handlePaginationChange"
|
||||
@showSizeChange="handlePaginationChange"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-popconfirm title="你确定要清空所有操作日志?" okText="确定" @confirm="handleClearLogs" cancelText="取消">
|
||||
<a-popconfirm cancelText="取消" okText="确定" title="你确定要清空所有操作日志?" @confirm="handleClearLogs">
|
||||
<a-button type="danger">清空操作日志</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
|
@ -47,13 +47,71 @@
|
|||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import logApi from '@/api/log'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'LogListDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
return {
|
||||
logTypes: logApi.logTypes,
|
||||
logTypes: {
|
||||
BLOG_INITIALIZED: {
|
||||
value: 0,
|
||||
text: '博客初始化'
|
||||
},
|
||||
POST_PUBLISHED: {
|
||||
value: 5,
|
||||
text: '文章发布'
|
||||
},
|
||||
POST_EDITED: {
|
||||
value: 15,
|
||||
text: '文章修改'
|
||||
},
|
||||
POST_DELETED: {
|
||||
value: 20,
|
||||
text: '文章删除'
|
||||
},
|
||||
LOGGED_IN: {
|
||||
value: 25,
|
||||
text: '用户登录'
|
||||
},
|
||||
LOGGED_OUT: {
|
||||
value: 30,
|
||||
text: '注销登录'
|
||||
},
|
||||
LOGIN_FAILED: {
|
||||
value: 35,
|
||||
text: '登录失败'
|
||||
},
|
||||
PASSWORD_UPDATED: {
|
||||
value: 40,
|
||||
text: '修改密码'
|
||||
},
|
||||
PROFILE_UPDATED: {
|
||||
value: 45,
|
||||
text: '资料修改'
|
||||
},
|
||||
SHEET_PUBLISHED: {
|
||||
value: 50,
|
||||
text: '页面发布'
|
||||
},
|
||||
SHEET_EDITED: {
|
||||
value: 55,
|
||||
text: '页面修改'
|
||||
},
|
||||
SHEET_DELETED: {
|
||||
value: 60,
|
||||
text: '页面删除'
|
||||
},
|
||||
MFA_UPDATED: {
|
||||
value: 65,
|
||||
text: '两步验证'
|
||||
},
|
||||
LOGGED_PRE_CHECK: {
|
||||
value: 70,
|
||||
text: '登录验证'
|
||||
}
|
||||
},
|
||||
loading: true,
|
||||
logs: [],
|
||||
pagination: {
|
||||
|
@ -90,20 +148,18 @@ export default {
|
|||
this.logQueryParam.page = this.pagination.page - 1
|
||||
this.logQueryParam.size = this.pagination.size
|
||||
this.logQueryParam.sort = this.pagination.sort
|
||||
logApi
|
||||
.pageBy(this.logQueryParam)
|
||||
apiClient.log
|
||||
.list(this.logQueryParam)
|
||||
.then(response => {
|
||||
this.logs = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
this.logs = response.data.content
|
||||
this.pagination.total = response.data.total
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 200)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleClearLogs() {
|
||||
logApi
|
||||
apiClient.log
|
||||
.clear()
|
||||
.then(() => {
|
||||
this.$message.success('清除成功!')
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<a-list itemLayout="horizontal" :dataSource="formmatedCommentData" :loading="loading">
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" :key="index">
|
||||
<a-list :dataSource="formmatedCommentData" :loading="loading" itemLayout="horizontal">
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<a-comment :avatar="item.avatar">
|
||||
<template slot="author" v-if="type === 'posts'">
|
||||
<template v-if="type === 'posts'" slot="author">
|
||||
<a :href="item.authorUrl" target="_blank">{{ item.author }}</a> 发表在 《<a
|
||||
v-if="['PUBLISHED', 'INTIMATE'].includes(item.post.status)"
|
||||
:href="item.post.fullPath"
|
||||
|
@ -16,7 +16,7 @@
|
|||
><a v-else href="javascript:void(0)">{{ item.post.title }}</a>
|
||||
》
|
||||
</template>
|
||||
<template slot="author" v-else-if="type === 'sheets'">
|
||||
<template v-else-if="type === 'sheets'" slot="author">
|
||||
<a :href="item.authorUrl" target="_blank">{{ item.author }}</a> 发表在 《<a
|
||||
v-if="item.sheet.status === 'PUBLISHED'"
|
||||
:href="item.sheet.fullPath"
|
||||
|
@ -33,7 +33,7 @@
|
|||
<!-- <template slot="actions">
|
||||
<span>回复</span>
|
||||
</template> -->
|
||||
<p class="comment-content-wrapper" slot="content" v-html="item.content"></p>
|
||||
<p slot="content" class="comment-content-wrapper" v-html="item.content"></p>
|
||||
<a-tooltip slot="datetime" :title="item.createTime | moment">
|
||||
<span>{{ item.createTime | timeAgo }}</span>
|
||||
</a-tooltip>
|
||||
|
@ -43,11 +43,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import commentApi from '@/api/comment'
|
||||
import postApi from '@/api/post'
|
||||
import sheetApi from '@/api/sheet'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
import marked from 'marked'
|
||||
|
||||
export default {
|
||||
name: 'RecentCommentTab',
|
||||
props: {
|
||||
|
@ -80,24 +79,22 @@ export default {
|
|||
methods: {
|
||||
handleListTargetComments() {
|
||||
this.loading = true
|
||||
commentApi
|
||||
.latestComment(this.type, 5, 'PUBLISHED')
|
||||
apiClient.comment
|
||||
.latest(this.type, 5, 'PUBLISHED')
|
||||
.then(response => {
|
||||
this.comments = response.data.data
|
||||
this.comments = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 200)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handlePostPreview(postId) {
|
||||
postApi.preview(postId).then(response => {
|
||||
apiClient.post.getPreviewLinkById(postId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
handleSheetPreview(sheetId) {
|
||||
sheetApi.preview(sheetId).then(response => {
|
||||
apiClient.post.getPreviewLinkById(sheetId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="exception">
|
||||
<a-result :status="type" :title="type" :subTitle="config[type].desc">
|
||||
<a-result :status="type" :subTitle="config[type].desc" :title="type">
|
||||
<template v-slot:extra>
|
||||
<a-button type="primary" @click="handleToHome">返回仪表盘</a-button>
|
||||
</template>
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
<template>
|
||||
<page-view>
|
||||
<a-row :gutter="12">
|
||||
<a-col :xl="6" :lg="6" :md="6" :sm="24" :xs="24" class="mb-3">
|
||||
<a-col :lg="6" :md="6" :sm="24" :xl="6" :xs="24" class="mb-3">
|
||||
<a-card :bodyStyle="{ padding: '16px' }" title="分组">
|
||||
<template slot="extra">
|
||||
<ReactiveButton
|
||||
type="default"
|
||||
@click="handleSetDefaultTeam"
|
||||
@callback="handleSetDefaultTeamCallback"
|
||||
:loading="teams.default.saving"
|
||||
:errored="teams.default.errored"
|
||||
text="设为默认"
|
||||
loadedText="设置成功"
|
||||
:loading="teams.default.saving"
|
||||
erroredText="设置失败"
|
||||
loadedText="设置成功"
|
||||
text="设为默认"
|
||||
type="default"
|
||||
@callback="handleSetDefaultTeamCallback"
|
||||
@click="handleSetDefaultTeam"
|
||||
></ReactiveButton>
|
||||
</template>
|
||||
<div class="menu-teams">
|
||||
<a-spin :spinning="teams.loading">
|
||||
<a-empty v-if="teams.data.length === 0 && !teams.loading" />
|
||||
<a-menu
|
||||
v-if="teams.data.length > 0"
|
||||
v-model="selectedTeam"
|
||||
class="w-full"
|
||||
mode="inline"
|
||||
v-model="selectedTeam"
|
||||
v-if="teams.data.length > 0"
|
||||
@select="handleSelectedTeam"
|
||||
>
|
||||
<a-menu-item v-for="team in teams.data" :key="team">
|
||||
|
@ -33,11 +33,11 @@
|
|||
</div>
|
||||
<a-popover
|
||||
v-model="teams.form.visible"
|
||||
destroyTooltipOnHide
|
||||
placement="bottom"
|
||||
title="新增分组"
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
@visibleChange="handleTeamFormVisibleChange"
|
||||
destroyTooltipOnHide
|
||||
>
|
||||
<template slot="content">
|
||||
<a-form-model
|
||||
|
@ -56,42 +56,42 @@
|
|||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
<a-button type="primary" block class="mt-3">
|
||||
<a-button block class="mt-3" type="primary">
|
||||
新增分组
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xl="18" :lg="18" :md="18" :sm="24" :xs="24" class="pb-3">
|
||||
<a-col :lg="18" :md="18" :sm="24" :xl="18" :xs="24" class="pb-3">
|
||||
<a-card :bodyStyle="{ padding: '16px' }">
|
||||
<template slot="title">
|
||||
<span>
|
||||
{{ menuListTitle }}
|
||||
</span>
|
||||
<a-tooltip
|
||||
v-if="list.data.length <= 0 && !list.loading"
|
||||
slot="action"
|
||||
title="分组下的菜单为空时,该分组也不会保存"
|
||||
v-if="list.data.length <= 0 && !list.loading"
|
||||
>
|
||||
<a-icon type="info-circle-o" class="cursor-pointer" />
|
||||
<a-icon class="cursor-pointer" type="info-circle-o" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="extra">
|
||||
<a-space>
|
||||
<ReactiveButton
|
||||
@click="handleUpdateBatch"
|
||||
@callback="formBatch.errored = false"
|
||||
:loading="formBatch.saving"
|
||||
:errored="formBatch.errored"
|
||||
text="保存"
|
||||
loadedText="保存成功"
|
||||
erroredText="保存失败"
|
||||
:disabled="list.data.length <= 0"
|
||||
:errored="formBatch.errored"
|
||||
:loading="formBatch.saving"
|
||||
erroredText="保存失败"
|
||||
loadedText="保存成功"
|
||||
text="保存"
|
||||
@callback="formBatch.errored = false"
|
||||
@click="handleUpdateBatch"
|
||||
></ReactiveButton>
|
||||
<a-button v-if="!form.visible" @click="handleOpenCreateMenuForm()" type="primary" ghost>
|
||||
<a-button v-if="!form.visible" ghost type="primary" @click="handleOpenCreateMenuForm()">
|
||||
新增
|
||||
</a-button>
|
||||
<a-button v-else @click="handleCloseCreateMenuForm()" type="default">
|
||||
<a-button v-else type="default" @click="handleCloseCreateMenuForm()">
|
||||
取消新增
|
||||
</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
|
@ -114,8 +114,8 @@
|
|||
<MenuForm
|
||||
v-if="form.visible"
|
||||
:menu="form.model"
|
||||
@succeed="handleCreateMenuSucceed()"
|
||||
@cancel="handleCloseCreateMenuForm()"
|
||||
@succeed="handleCreateMenuSucceed()"
|
||||
/>
|
||||
<a-empty v-if="list.data.length === 0 && !list.loading && !form.visible" />
|
||||
<MenuTreeNode v-model="list.data" :excludedTeams="excludedTeams" @reload="handleListMenus" />
|
||||
|
@ -142,8 +142,8 @@ import { deepClone } from '@/utils/util'
|
|||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
// apis
|
||||
import menuApi from '@/api/menu'
|
||||
import optionApi from '@/api/option'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
components: { PageView, MenuTreeNode, MenuForm, MenuInternalLinkSelector },
|
||||
data() {
|
||||
|
@ -224,33 +224,29 @@ export default {
|
|||
...mapActions(['refreshOptionsCache']),
|
||||
handleListTeams(autoSelectTeam = false) {
|
||||
this.teams.loading = true
|
||||
menuApi
|
||||
apiClient.menu
|
||||
.listTeams()
|
||||
.then(response => {
|
||||
this.teams.data = response.data.data
|
||||
this.teams.data = response.data
|
||||
if (!this.teams.selected || autoSelectTeam) {
|
||||
this.teams.selected = this.teams.data[0]
|
||||
}
|
||||
this.handleListMenus()
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.teams.loading = false
|
||||
}, 200)
|
||||
this.teams.loading = false
|
||||
})
|
||||
},
|
||||
handleListMenus() {
|
||||
this.list.data = []
|
||||
this.list.loading = true
|
||||
menuApi
|
||||
.listTreeByTeam(this.teams.selected)
|
||||
apiClient.menu
|
||||
.listTreeViewByTeam(this.teams.selected)
|
||||
.then(response => {
|
||||
this.list.data = response.data.data
|
||||
this.list.data = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.list.loading = false
|
||||
}, 200)
|
||||
this.list.loading = false
|
||||
})
|
||||
},
|
||||
handleMenuMoved(pid, menus) {
|
||||
|
@ -281,8 +277,8 @@ export default {
|
|||
},
|
||||
handleUpdateBatch() {
|
||||
this.formBatch.saving = true
|
||||
menuApi
|
||||
.updateBatch(this.computedMenusWithoutLevel)
|
||||
apiClient.menu
|
||||
.updateInBatch(this.computedMenusWithoutLevel)
|
||||
.catch(() => {
|
||||
this.formBatch.errored = true
|
||||
})
|
||||
|
@ -299,7 +295,7 @@ export default {
|
|||
title: '提示',
|
||||
content: '确定要删除当前分组以及所有菜单?',
|
||||
onOk() {
|
||||
menuApi.deleteBatch(_this.computedMenuIds).finally(() => {
|
||||
apiClient.menu.deleteInBatch(_this.computedMenuIds).finally(() => {
|
||||
_this.handleListTeams(true)
|
||||
})
|
||||
}
|
||||
|
@ -340,10 +336,12 @@ export default {
|
|||
},
|
||||
handleSetDefaultTeam() {
|
||||
this.teams.default.saving = true
|
||||
optionApi
|
||||
.save({
|
||||
default_menu_team: this.teams.selected
|
||||
})
|
||||
apiClient.option
|
||||
.save([
|
||||
{
|
||||
default_menu_team: this.teams.selected
|
||||
}
|
||||
])
|
||||
.catch(() => {
|
||||
this.teams.default.errored = true
|
||||
})
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import themeApi from '@/api/theme'
|
||||
import apiClient from '@/utils/api-client'
|
||||
import ThemeFile from './components/ThemeFile'
|
||||
import { PageView } from '@/layouts'
|
||||
import Codemirror from '@/components/Codemirror/Codemirror'
|
||||
|
@ -90,10 +90,10 @@ export default {
|
|||
methods: {
|
||||
handleListThemes() {
|
||||
this.themes.loading = true
|
||||
themeApi
|
||||
apiClient.theme
|
||||
.list()
|
||||
.then(response => {
|
||||
this.themes.data = response.data.data
|
||||
this.themes.data = response.data
|
||||
|
||||
const activatedTheme = this.themes.data.find(item => item.activated)
|
||||
|
||||
|
@ -109,10 +109,10 @@ export default {
|
|||
onSelectTheme(themeId) {
|
||||
this.files.data = []
|
||||
this.files.loading = true
|
||||
themeApi
|
||||
apiClient.theme
|
||||
.listFiles(themeId)
|
||||
.then(response => {
|
||||
this.files.data = response.data.data
|
||||
this.files.data = response.data
|
||||
this.files.content = ''
|
||||
this.files.selected = {}
|
||||
})
|
||||
|
@ -140,16 +140,16 @@ export default {
|
|||
}
|
||||
})
|
||||
}
|
||||
themeApi.getContent(this.themes.selectedId, file.path).then(response => {
|
||||
this.files.content = response.data.data
|
||||
apiClient.theme.getTemplateContent(this.themes.selectedId, file.path).then(response => {
|
||||
this.files.content = response.data
|
||||
this.files.selected = file
|
||||
this.handleInitEditor()
|
||||
})
|
||||
},
|
||||
handlerSaveContent() {
|
||||
this.files.saving = true
|
||||
themeApi
|
||||
.saveContent(this.themes.selectedId, this.files.selected.path, this.files.content)
|
||||
apiClient.theme
|
||||
.updateTemplateContent(this.themes.selectedId, { path: this.files.selected.path, content: this.files.content })
|
||||
.catch(() => {
|
||||
this.files.saveErrored = true
|
||||
})
|
||||
|
|
|
@ -1,42 +1,55 @@
|
|||
<template>
|
||||
<page-view affix :title="activatedTheme ? activatedTheme.name : '无'" subTitle="当前启用">
|
||||
<page-view :title="activatedTheme ? activatedTheme.name : '无'" affix subTitle="当前启用">
|
||||
<template slot="extra">
|
||||
<a-button icon="reload" :loading="list.loading" @click="handleRefreshThemesCache">
|
||||
<a-button :loading="list.loading" icon="reload" @click="handleRefreshThemesCache">
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" icon="plus" @click="installModal.visible = true">
|
||||
<a-button icon="plus" type="primary" @click="installModal.visible = true">
|
||||
安装
|
||||
</a-button>
|
||||
</template>
|
||||
<a-row :gutter="12" type="flex" align="middle">
|
||||
<a-row :gutter="12" align="middle" type="flex">
|
||||
<a-col :span="24">
|
||||
<a-list
|
||||
:grid="{ gutter: 12, xs: 1, sm: 1, md: 2, lg: 4, xl: 4, xxl: 4 }"
|
||||
:dataSource="sortedThemes"
|
||||
:grid="{ gutter: 12, xs: 1, sm: 1, md: 2, lg: 4, xl: 4, xxl: 4 }"
|
||||
:loading="list.loading"
|
||||
>
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" :key="index">
|
||||
<a-card hoverable :title="item.name" :bodyStyle="{ padding: 0 }">
|
||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
||||
<a-card :bodyStyle="{ padding: 0 }" :title="item.name" hoverable>
|
||||
<div class="theme-screenshot">
|
||||
<img :alt="item.name" :src="item.screenshots || '/images/placeholder.jpg'" loading="lazy" />
|
||||
</div>
|
||||
<template class="ant-card-actions" slot="actions">
|
||||
<div v-if="item.activated"><a-icon type="unlock" theme="twoTone" style="margin-right:3px" />已启用</div>
|
||||
<div v-else @click="handleActiveTheme(item)"><a-icon type="lock" style="margin-right:3px" />启用</div>
|
||||
<div @click="handleOpenThemeSettingDrawer(item)">
|
||||
<a-icon type="setting" style="margin-right:3px" />设置
|
||||
<template slot="actions" class="ant-card-actions">
|
||||
<div v-if="item.activated">
|
||||
<a-icon style="margin-right:3px" theme="twoTone" type="unlock" />
|
||||
已启用
|
||||
</div>
|
||||
<a-dropdown placement="topCenter" :trigger="['click']">
|
||||
<a class="ant-dropdown-link" href="#"> <a-icon type="ellipsis" style="margin-right:3px" />更多 </a>
|
||||
<div v-else @click="handleActiveTheme(item)">
|
||||
<a-icon style="margin-right:3px" type="lock" />
|
||||
启用
|
||||
</div>
|
||||
<div @click="handleOpenThemeSettingDrawer(item)">
|
||||
<a-icon style="margin-right:3px" type="setting" />
|
||||
设置
|
||||
</div>
|
||||
<a-dropdown :trigger="['click']" placement="topCenter">
|
||||
<a class="ant-dropdown-link" href="#">
|
||||
<a-icon style="margin-right:3px" type="ellipsis" />
|
||||
更多
|
||||
</a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item :key="1" :disabled="item.activated" @click="handleOpenThemeDeleteModal(item)">
|
||||
<a-icon type="delete" style="margin-right:3px" />删除
|
||||
<a-icon style="margin-right:3px" type="delete" />
|
||||
删除
|
||||
</a-menu-item>
|
||||
<a-menu-item :key="2" v-if="item.repo" @click="handleConfirmRemoteUpdate(item)">
|
||||
<a-icon type="cloud" style="margin-right:3px" />在线更新
|
||||
<a-menu-item v-if="item.repo" :key="2" @click="handleConfirmRemoteUpdate(item)">
|
||||
<a-icon style="margin-right:3px" type="cloud" />
|
||||
在线更新
|
||||
</a-menu-item>
|
||||
<a-menu-item :key="3" @click="handleOpenLocalUpdateModal(item)">
|
||||
<a-icon type="file" style="margin-right:3px" />从主题包更新
|
||||
<a-icon style="margin-right:3px" type="file" />
|
||||
从主题包更新
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
|
@ -48,64 +61,64 @@
|
|||
</a-row>
|
||||
|
||||
<ThemeSettingDrawer
|
||||
:theme="themeSettingDrawer.selected"
|
||||
v-model="themeSettingDrawer.visible"
|
||||
:theme="themeSettingDrawer.selected"
|
||||
@close="onThemeSettingsDrawerClose"
|
||||
/>
|
||||
|
||||
<a-modal
|
||||
title="安装主题"
|
||||
v-model="installModal.visible"
|
||||
destroyOnClose
|
||||
:footer="null"
|
||||
:bodyStyle="{ padding: '0 24px 24px' }"
|
||||
:afterClose="onThemeInstallModalClose"
|
||||
:bodyStyle="{ padding: '0 24px 24px' }"
|
||||
:footer="null"
|
||||
destroyOnClose
|
||||
title="安装主题"
|
||||
>
|
||||
<div class="custom-tab-wrapper">
|
||||
<a-tabs :animated="{ inkBar: true, tabPane: false }">
|
||||
<a-tab-pane tab="本地上传" key="1">
|
||||
<a-tab-pane key="1" tab="本地上传">
|
||||
<FilePondUpload
|
||||
ref="upload"
|
||||
name="file"
|
||||
:accepts="['application/x-zip', 'application/x-zip-compressed', 'application/zip']"
|
||||
label="点击选择主题包或将主题包拖拽到此处<br>仅支持 ZIP 格式的文件"
|
||||
:uploadHandler="installModal.local.uploadHandler"
|
||||
label="点击选择主题包或将主题包拖拽到此处<br>仅支持 ZIP 格式的文件"
|
||||
name="file"
|
||||
@success="handleUploadSucceed"
|
||||
></FilePondUpload>
|
||||
<a-alert type="info" closable>
|
||||
<a-alert closable type="info">
|
||||
<template slot="message">
|
||||
更多主题请访问:
|
||||
<a target="_blank" href="https://halo.run/themes.html">https://halo.run/themes</a>
|
||||
<a href="https://halo.run/themes.html" target="_blank">https://halo.run/themes</a>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="远程下载" key="2">
|
||||
<a-tab-pane key="2" tab="远程下载">
|
||||
<a-form-model
|
||||
ref="remoteInstallForm"
|
||||
:model="installModal.remote"
|
||||
:rules="installModal.remote.rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-model-item prop="url" label="远程地址:" help="* 支持 Git 仓库地址,ZIP 链接。">
|
||||
<a-form-model-item help="* 支持 Git 仓库地址,ZIP 链接。" label="远程地址:" prop="url">
|
||||
<a-input v-model="installModal.remote.url" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item>
|
||||
<ReactiveButton
|
||||
type="primary"
|
||||
@click="handleRemoteFetching"
|
||||
@callback="handleRemoteFetchCallback"
|
||||
:loading="installModal.remote.fetching"
|
||||
:errored="installModal.remote.fetchErrored"
|
||||
text="下载"
|
||||
loadedText="下载成功"
|
||||
:loading="installModal.remote.fetching"
|
||||
erroredText="下载失败"
|
||||
loadedText="下载成功"
|
||||
text="下载"
|
||||
type="primary"
|
||||
@callback="handleRemoteFetchCallback"
|
||||
@click="handleRemoteFetching"
|
||||
></ReactiveButton>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
<a-alert type="info" closable>
|
||||
<a-alert closable type="info">
|
||||
<template slot="message">
|
||||
目前仅支持远程 Git 仓库和 ZIP 下载链接。更多主题请访问:
|
||||
<a target="_blank" href="https://halo.run/themes.html">https://halo.run/themes</a>
|
||||
<a href="https://halo.run/themes.html" target="_blank">https://halo.run/themes</a>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-tab-pane>
|
||||
|
@ -113,43 +126,43 @@
|
|||
</div>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
title="更新主题"
|
||||
v-model="localUpdateModel.visible"
|
||||
:afterClose="onThemeInstallModalClose"
|
||||
:footer="null"
|
||||
destroyOnClose
|
||||
:afterClose="onThemeInstallModalClose"
|
||||
title="更新主题"
|
||||
>
|
||||
<FilePondUpload
|
||||
ref="updateByupload"
|
||||
name="file"
|
||||
ref="updateByFile"
|
||||
:accepts="['application/x-zip', 'application/x-zip-compressed', 'application/zip']"
|
||||
label="点击选择主题更新包或将主题更新包拖拽到此处<br>仅支持 ZIP 格式的文件"
|
||||
:uploadHandler="localUpdateModel.uploadHandler"
|
||||
:filed="localUpdateModel.selected.id"
|
||||
:field="localUpdateModel.selected.id"
|
||||
:multiple="false"
|
||||
:uploadHandler="localUpdateModel.uploadHandler"
|
||||
label="点击选择主题更新包或将主题更新包拖拽到此处<br>仅支持 ZIP 格式的文件"
|
||||
name="file"
|
||||
@success="handleUploadSucceed"
|
||||
></FilePondUpload>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
title="提示"
|
||||
v-model="themeDeleteModal.visible"
|
||||
:width="416"
|
||||
:closable="false"
|
||||
destroyOnClose
|
||||
:afterClose="onThemeDeleteModalClose"
|
||||
:closable="false"
|
||||
:width="416"
|
||||
destroyOnClose
|
||||
title="提示"
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button @click="themeDeleteModal.visible = false">
|
||||
取消
|
||||
</a-button>
|
||||
<ReactiveButton
|
||||
@click="handleDeleteTheme(themeDeleteModal.selected.id, themeDeleteModal.deleteSettings)"
|
||||
@callback="handleDeleteThemeCallback"
|
||||
:loading="themeDeleteModal.deleting"
|
||||
:errored="themeDeleteModal.deleteErrored"
|
||||
text="确定"
|
||||
loadedText="删除成功"
|
||||
:loading="themeDeleteModal.deleting"
|
||||
erroredText="删除失败"
|
||||
loadedText="删除成功"
|
||||
text="确定"
|
||||
@callback="handleDeleteThemeCallback"
|
||||
@click="handleDeleteTheme(themeDeleteModal.selected.id, themeDeleteModal.deleteSettings)"
|
||||
></ReactiveButton>
|
||||
</template>
|
||||
<p>确定删除【{{ themeDeleteModal.selected.name }}】主题?</p>
|
||||
|
@ -163,7 +176,7 @@
|
|||
<script>
|
||||
import ThemeSettingDrawer from './components/ThemeSettingDrawer'
|
||||
import { PageView } from '@/layouts'
|
||||
import themeApi from '@/api/theme'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -180,7 +193,7 @@ export default {
|
|||
installModal: {
|
||||
visible: false,
|
||||
local: {
|
||||
uploadHandler: themeApi.upload
|
||||
uploadHandler: (file, options) => apiClient.theme.upload(file, options)
|
||||
},
|
||||
|
||||
remote: {
|
||||
|
@ -197,7 +210,7 @@ export default {
|
|||
|
||||
localUpdateModel: {
|
||||
visible: false,
|
||||
uploadHandler: themeApi.updateByUpload,
|
||||
uploadHandler: (file, options, field) => apiClient.theme.updateByUpload(file, options, field),
|
||||
selected: {}
|
||||
},
|
||||
|
||||
|
@ -249,30 +262,28 @@ export default {
|
|||
methods: {
|
||||
handleListThemes() {
|
||||
this.list.loading = true
|
||||
themeApi
|
||||
apiClient.theme
|
||||
.list()
|
||||
.then(response => {
|
||||
this.list.data = response.data.data
|
||||
this.list.data = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.list.loading = false
|
||||
}, 200)
|
||||
this.list.loading = false
|
||||
})
|
||||
},
|
||||
handleRefreshThemesCache() {
|
||||
themeApi.reload().finally(() => {
|
||||
apiClient.theme.reload().finally(() => {
|
||||
this.handleListThemes()
|
||||
})
|
||||
},
|
||||
handleActiveTheme(theme) {
|
||||
themeApi.active(theme.id).finally(() => {
|
||||
apiClient.theme.active(theme.id).finally(() => {
|
||||
this.handleListThemes()
|
||||
})
|
||||
},
|
||||
handleDeleteTheme(themeId, deleteSettings) {
|
||||
this.themeDeleteModal.deleting = true
|
||||
themeApi
|
||||
apiClient.theme
|
||||
.delete(themeId, deleteSettings)
|
||||
.catch(() => {
|
||||
this.themeDeleteModal.deleteErrored = false
|
||||
|
@ -300,8 +311,8 @@ export default {
|
|||
this.$refs.remoteInstallForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.installModal.remote.fetching = true
|
||||
themeApi
|
||||
.fetching(this.installModal.remote.url)
|
||||
apiClient.theme
|
||||
.fetchTheme(this.installModal.remote.url)
|
||||
.catch(() => {
|
||||
this.installModal.remote.fetchErrored = true
|
||||
})
|
||||
|
@ -341,8 +352,8 @@ export default {
|
|||
content: '确定更新【' + item.name + '】主题?',
|
||||
onOk() {
|
||||
const hide = _this.$message.loading('更新中...', 0)
|
||||
themeApi
|
||||
.update(item.id)
|
||||
apiClient.theme
|
||||
.updateThemeByFetching(item.id)
|
||||
.then(() => {
|
||||
_this.$message.success('更新成功!')
|
||||
})
|
||||
|
@ -350,16 +361,15 @@ export default {
|
|||
hide()
|
||||
_this.handleListThemes()
|
||||
})
|
||||
},
|
||||
onCancel() {}
|
||||
}
|
||||
})
|
||||
},
|
||||
onThemeInstallModalClose() {
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
}
|
||||
if (this.$refs.updateByupload) {
|
||||
this.$refs.updateByupload.handleClearFileList()
|
||||
if (this.$refs.updateByFile) {
|
||||
this.$refs.updateByFile.handleClearFileList()
|
||||
}
|
||||
this.installModal.remote.url = null
|
||||
this.handleListThemes()
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form-model
|
||||
labelAlign="left"
|
||||
ref="menuForm"
|
||||
:model="menuModel"
|
||||
:rules="form.rules"
|
||||
labelAlign="left"
|
||||
@keyup.enter.native="handleCreateOrUpdateMenu"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="12" :xs="12">
|
||||
<a-form-model-item label="名称" prop="name" help="* 页面上所显示的名称">
|
||||
<a-col :lg="8" :md="12" :sm="12" :xl="8" :xs="12">
|
||||
<a-form-model-item help="* 页面上所显示的名称" label="名称" prop="name">
|
||||
<a-input v-model="menuModel.name" autoFocus />
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="12" :xs="12">
|
||||
<a-form-model-item label="地址" prop="url" help="* 菜单的地址">
|
||||
<a-col :lg="8" :md="12" :sm="12" :xl="8" :xs="12">
|
||||
<a-form-model-item help="* 菜单的地址" label="地址" prop="url">
|
||||
<a-input v-model="menuModel.url" />
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="12" :xs="12">
|
||||
<a-form-model-item label="图标" prop="icon" help="* 请根据主题的支持情况选填">
|
||||
<a-col :lg="8" :md="12" :sm="12" :xl="8" :xs="12">
|
||||
<a-form-model-item help="* 请根据主题的支持情况选填" label="图标" prop="icon">
|
||||
<a-input v-model="menuModel.icon" />
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="12" :xs="12">
|
||||
<a-col :lg="8" :md="12" :sm="12" :xl="8" :xs="12">
|
||||
<a-form-model-item label="打开方式" prop="target">
|
||||
<a-radio-group v-model="menuModel.target" :options="targets" />
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :xl="8" :lg="8" :md="12" :sm="12" :xs="12">
|
||||
<a-form-model-item label=" " :colon="false">
|
||||
<a-col :lg="8" :md="12" :sm="12" :xl="8" :xs="12">
|
||||
<a-form-model-item :colon="false" label=" ">
|
||||
<a-space>
|
||||
<ReactiveButton
|
||||
type="primary"
|
||||
@click="handleCreateOrUpdateMenu"
|
||||
@callback="handleSavedCallback"
|
||||
:loading="form.saving"
|
||||
:errored="form.errored"
|
||||
text="保存"
|
||||
loadedText="保存成功"
|
||||
:loading="form.saving"
|
||||
erroredText="保存失败"
|
||||
loadedText="保存成功"
|
||||
text="保存"
|
||||
type="primary"
|
||||
@callback="handleSavedCallback"
|
||||
@click="handleCreateOrUpdateMenu"
|
||||
></ReactiveButton>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
</a-space>
|
||||
|
@ -50,7 +50,8 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import menuApi from '@/api/menu'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
const targets = [
|
||||
{
|
||||
value: '_self',
|
||||
|
@ -116,7 +117,7 @@ export default {
|
|||
if (valid) {
|
||||
_this.form.saving = true
|
||||
if (_this.isUpdateMode) {
|
||||
menuApi
|
||||
apiClient.menu
|
||||
.update(_this.menuModel.id, _this.menuModel)
|
||||
.catch(() => {
|
||||
_this.form.errored = true
|
||||
|
@ -127,7 +128,7 @@ export default {
|
|||
}, 400)
|
||||
})
|
||||
} else {
|
||||
menuApi
|
||||
apiClient.menu
|
||||
.create(_this.menuModel)
|
||||
.catch(() => {
|
||||
_this.form.errored = true
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
<template>
|
||||
<a-modal v-model="visible" title="从系统预设链接添加菜单" :width="1024" :bodyStyle="{ padding: '0 24px 24px' }">
|
||||
<a-modal v-model="visible" :bodyStyle="{ padding: '0 24px 24px' }" :width="1024" title="从系统预设链接添加菜单">
|
||||
<template slot="footer">
|
||||
<a-button @click="handleCancel">
|
||||
取消
|
||||
</a-button>
|
||||
<ReactiveButton
|
||||
@click="handleCreateBatch"
|
||||
@callback="handleCreateBatchCallback"
|
||||
:loading="saving"
|
||||
:errored="saveErrored"
|
||||
text="添加"
|
||||
loadedText="添加成功"
|
||||
erroredText="添加失败"
|
||||
:disabled="menus && menus.length <= 0"
|
||||
:errored="saveErrored"
|
||||
:loading="saving"
|
||||
erroredText="添加失败"
|
||||
loadedText="添加成功"
|
||||
text="添加"
|
||||
@callback="handleCreateBatchCallback"
|
||||
@click="handleCreateBatch"
|
||||
></ReactiveButton>
|
||||
</template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-spin :spinning="loading">
|
||||
<div class="custom-tab-wrapper">
|
||||
<a-tabs default-active-key="1" :animated="{ inkBar: true, tabPane: false }">
|
||||
<a-tab-pane key="1" tab="分类目录" force-render>
|
||||
<a-tabs :animated="{ inkBar: true, tabPane: false }" default-active-key="1">
|
||||
<a-tab-pane key="1" force-render tab="分类目录">
|
||||
<a-list item-layout="horizontal">
|
||||
<a-list-item v-for="(category, index) in categories" :key="index">
|
||||
<a-list-item-meta>
|
||||
|
@ -28,7 +28,7 @@
|
|||
<span slot="description">{{ category.fullPath }}</span>
|
||||
</a-list-item-meta>
|
||||
<template slot="actions">
|
||||
<a href="javascript:void(0);" class="text-base">
|
||||
<a class="text-base" href="javascript:void(0);">
|
||||
<a-icon type="plus-circle" @click="handleInsertPre(category.name, category.fullPath)" />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -43,7 +43,7 @@
|
|||
<span slot="description">{{ tag.fullPath }}</span>
|
||||
</a-list-item-meta>
|
||||
<template slot="actions">
|
||||
<a href="javascript:void(0);" class="text-base">
|
||||
<a class="text-base" href="javascript:void(0);">
|
||||
<a-icon type="plus-circle" @click="handleInsertPre(tag.name, tag.fullPath)" />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -58,7 +58,7 @@
|
|||
<span slot="description">{{ item.fullPath }}</span>
|
||||
</a-list-item-meta>
|
||||
<template slot="actions">
|
||||
<a href="javascript:void(0);" class="text-base">
|
||||
<a class="text-base" href="javascript:void(0);">
|
||||
<a-icon type="plus-circle" @click="handleInsertPre(item.title, item.fullPath)" />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -73,7 +73,7 @@
|
|||
<span slot="description">{{ item.fullPath }}</span>
|
||||
</a-list-item-meta>
|
||||
<template slot="actions">
|
||||
<a href="javascript:void(0);" class="text-base">
|
||||
<a class="text-base" href="javascript:void(0);">
|
||||
<a-icon type="plus-circle" @click="handleInsertPre(item.title, item.fullPath)" />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -81,15 +81,15 @@
|
|||
</a-list>
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
class="pagination"
|
||||
:current="sheet.customs.pagination.page"
|
||||
:total="sheet.customs.pagination.total"
|
||||
:defaultPageSize="sheet.customs.pagination.size"
|
||||
:pageSizeOptions="['1', '2', '5', '10', '20', '50', '100']"
|
||||
showSizeChanger
|
||||
@showSizeChange="handleSheetPaginationChange"
|
||||
@change="handleSheetPaginationChange"
|
||||
:pageSizeOptions="['10', '20', '50', '100']"
|
||||
:total="sheet.customs.pagination.total"
|
||||
class="pagination"
|
||||
showLessItems
|
||||
showSizeChanger
|
||||
@change="handleSheetPaginationChange"
|
||||
@showSizeChange="handleSheetPaginationChange"
|
||||
/>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
@ -101,7 +101,7 @@
|
|||
<span slot="description">{{ item.url }}</span>
|
||||
</a-list-item-meta>
|
||||
<template slot="actions">
|
||||
<a href="javascript:void(0);" class="text-base">
|
||||
<a class="text-base" href="javascript:void(0);">
|
||||
<a-icon type="plus-circle" @click="handleInsertPre(item.name, item.url)" />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -115,7 +115,7 @@
|
|||
<a-col :span="12">
|
||||
<div class="custom-tab-wrapper">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="备选" force-render>
|
||||
<a-tab-pane key="1" force-render tab="备选">
|
||||
<a-list item-layout="horizontal">
|
||||
<a-list-item v-for="(menu, index) in menus" :key="index">
|
||||
<a-list-item-meta>
|
||||
|
@ -123,7 +123,7 @@
|
|||
<span slot="description">{{ menu.url }}</span>
|
||||
</a-list-item-meta>
|
||||
<template slot="actions">
|
||||
<a href="javascript:void(0);" class="text-base" @click="handleRemovePre(index)">
|
||||
<a class="text-base" href="javascript:void(0);" @click="handleRemovePre(index)">
|
||||
<a-icon type="close-circle" />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -137,11 +137,7 @@
|
|||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import categoryApi from '@/api/category'
|
||||
import tagApi from '@/api/tag'
|
||||
import menuApi from '@/api/menu'
|
||||
import sheetApi from '@/api/sheet'
|
||||
import optionApi from '@/api/option'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'MenuInternalLinkSelector',
|
||||
|
@ -234,26 +230,29 @@ export default {
|
|||
methods: {
|
||||
handleFetchAll() {
|
||||
this.loading = true
|
||||
Promise.all([optionApi.listAll(), categoryApi.listAll(true), tagApi.listAll(true), sheetApi.listIndependent()])
|
||||
Promise.all([
|
||||
apiClient.option.listAsMapView(),
|
||||
apiClient.category.list({ sort: [], more: false }),
|
||||
apiClient.tag.list({ more: false }),
|
||||
apiClient.sheet.listIndependents()
|
||||
])
|
||||
.then(response => {
|
||||
this.options = response[0].data.data
|
||||
this.categories = response[1].data.data
|
||||
this.tags = response[2].data.data
|
||||
this.sheet.independents = response[3].data.data
|
||||
this.options = response[0].data
|
||||
this.categories = response[1].data
|
||||
this.tags = response[2].data
|
||||
this.sheet.independents = response[3].data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 200)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleListSheets() {
|
||||
this.sheet.customs.queryParam.page = this.sheet.customs.pagination.page - 1
|
||||
this.sheet.customs.queryParam.size = this.sheet.customs.pagination.size
|
||||
this.sheet.customs.queryParam.sort = this.sheet.customs.pagination.sort
|
||||
sheetApi.list(this.sheet.customs.queryParam).then(response => {
|
||||
this.sheet.customs.data = response.data.data.content
|
||||
this.sheet.customs.pagination.total = response.data.data.total
|
||||
apiClient.sheet.list(this.sheet.customs.queryParam).then(response => {
|
||||
this.sheet.customs.data = response.data.content
|
||||
this.sheet.customs.pagination.total = response.data.total
|
||||
})
|
||||
},
|
||||
handleSheetPaginationChange(page, pageSize) {
|
||||
|
@ -278,8 +277,8 @@ export default {
|
|||
},
|
||||
handleCreateBatch() {
|
||||
this.saving = true
|
||||
menuApi
|
||||
.createBatch(this.menus)
|
||||
apiClient.menu
|
||||
.createInBatch(this.menus)
|
||||
.catch(() => {
|
||||
this.saveErrored = false
|
||||
})
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
<template>
|
||||
<a-list item-layout="horizontal">
|
||||
<draggable
|
||||
v-bind="dragOptions"
|
||||
tag="div"
|
||||
class="item-container"
|
||||
:list="list"
|
||||
:value="value"
|
||||
class="item-container"
|
||||
handle=".mover"
|
||||
tag="div"
|
||||
v-bind="dragOptions"
|
||||
@end="isDragging = false"
|
||||
@input="emitter"
|
||||
@start="isDragging = true"
|
||||
@end="isDragging = false"
|
||||
handle=".mover"
|
||||
>
|
||||
<transition-group>
|
||||
<div :key="item.id" v-for="item in realValue">
|
||||
<div v-for="item in realValue" :key="item.id">
|
||||
<a-list-item class="menu-item">
|
||||
<a-list-item-meta>
|
||||
<span slot="title" class="inline-block font-bold title">
|
||||
<a-icon class="cursor-pointer mover" type="bars" />
|
||||
{{ item.name }}
|
||||
<a-tooltip title="外部链接" v-if="item.target === '_blank'">
|
||||
<a-tooltip v-if="item.target === '_blank'" title="外部链接">
|
||||
<a-icon type="link" />
|
||||
</a-tooltip>
|
||||
{{ item.formVisible ? '(正在编辑)' : '' }}
|
||||
</span>
|
||||
<span slot="description" class="inline-block">
|
||||
<a :href="item.url" target="_blank" class="ant-anchor-link-title"> {{ item.url }} </a>
|
||||
<a :href="item.url" class="ant-anchor-link-title" target="_blank"> {{ item.url }} </a>
|
||||
</span>
|
||||
</a-list-item-meta>
|
||||
<template slot="actions">
|
||||
|
@ -38,7 +38,7 @@
|
|||
<template slot="actions">
|
||||
<a href="javascript:void(0);" @click="handleDelete(item.id)">删除</a>
|
||||
</template>
|
||||
<template slot="actions" v-if="excludedTeams && excludedTeams.length > 0">
|
||||
<template v-if="excludedTeams && excludedTeams.length > 0" slot="actions">
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a class="ant-dropdown-link" @click="e => e.preventDefault()">
|
||||
更多
|
||||
|
@ -46,20 +46,14 @@
|
|||
</a>
|
||||
<a-menu slot="overlay">
|
||||
<a-sub-menu title="移动到分组">
|
||||
<a-menu-item
|
||||
v-for="(team, index) in excludedTeams"
|
||||
:key="index"
|
||||
@click="handleMoveMenu(item, team)"
|
||||
>{{ team === '' ? '未分组' : team }}</a-menu-item
|
||||
>
|
||||
<a-menu-item v-for="(team, index) in excludedTeams" :key="index" @click="handleMoveMenu(item, team)"
|
||||
>{{ team === '' ? '未分组' : team }}
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu title="复制到分组">
|
||||
<a-menu-item
|
||||
v-for="(team, index) in excludedTeams"
|
||||
:key="index"
|
||||
@click="handleCopyMenu(item, team)"
|
||||
>{{ team === '' ? '未分组' : team }}</a-menu-item
|
||||
>
|
||||
<a-menu-item v-for="(team, index) in excludedTeams" :key="index" @click="handleCopyMenu(item, team)"
|
||||
>{{ team === '' ? '未分组' : team }}
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
|
@ -68,11 +62,11 @@
|
|||
<MenuForm
|
||||
v-if="item.formVisible"
|
||||
:menu="item"
|
||||
@succeed="handleUpdateMenuSucceed(item)"
|
||||
@cancel="handleCloseCreateMenuForm(item)"
|
||||
@succeed="handleUpdateMenuSucceed(item)"
|
||||
/>
|
||||
<div class="a-list-nested" style="margin-left: 44px;">
|
||||
<MenuTreeNode :list="item.children" :excludedTeams="excludedTeams" @reload="onReloadEmit" />
|
||||
<MenuTreeNode :excludedTeams="excludedTeams" :list="item.children" @reload="onReloadEmit" />
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
|
@ -85,8 +79,9 @@ import draggable from 'vuedraggable'
|
|||
import MenuForm from './MenuForm'
|
||||
|
||||
// apis
|
||||
import menuApi from '@/api/menu'
|
||||
import apiClient from '@/utils/api-client'
|
||||
import { deepClone } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'MenuTreeNode',
|
||||
components: {
|
||||
|
@ -140,7 +135,7 @@ export default {
|
|||
title: '提示',
|
||||
content: '确定要删除当前菜单?',
|
||||
onOk() {
|
||||
menuApi.delete(id).finally(() => {
|
||||
apiClient.menu.delete(id).finally(() => {
|
||||
_this.onReloadEmit()
|
||||
})
|
||||
}
|
||||
|
@ -162,7 +157,7 @@ export default {
|
|||
menu.parentId = 0
|
||||
menu.priority = 0
|
||||
menu.id = null
|
||||
menuApi.create(menu).then(() => {
|
||||
apiClient.menu.create(menu).then(() => {
|
||||
this.$emit('reload')
|
||||
})
|
||||
},
|
||||
|
@ -171,7 +166,7 @@ export default {
|
|||
menu.team = team
|
||||
menu.parentId = 0
|
||||
menu.priority = 0
|
||||
menuApi.update(menu.id, menu).then(() => {
|
||||
apiClient.menu.update(menu.id, menu).then(() => {
|
||||
this.$emit('reload')
|
||||
})
|
||||
},
|
||||
|
@ -186,18 +181,22 @@ export default {
|
|||
opacity: 0.8;
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
|
||||
.chosen {
|
||||
opacity: 0.8;
|
||||
@apply bg-gray-200;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.drag {
|
||||
@apply bg-white;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
::v-deep .ant-list-item-action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep .menu-item:hover .ant-list-item-action {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -1,100 +1,100 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
:title="`${theme.name} 主题设置`"
|
||||
width="100%"
|
||||
placement="right"
|
||||
:visible="visible"
|
||||
closable
|
||||
destroyOnClose
|
||||
placement="right"
|
||||
width="100%"
|
||||
@close="onClose"
|
||||
:visible="visible"
|
||||
:afterVisibleChange="handleAfterVisibleChanged"
|
||||
>
|
||||
<a-row :gutter="12" type="flex">
|
||||
<a-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24" v-if="!viewMode">
|
||||
<a-col v-if="!viewMode" :lg="12" :md="12" :sm="24" :xl="12" :xs="24">
|
||||
<a-card :bordered="false">
|
||||
<img :alt="theme.name" :src="theme.screenshots" slot="cover" />
|
||||
<img slot="cover" :alt="theme.name" :src="theme.screenshots" />
|
||||
<a-card-meta :description="theme.description">
|
||||
<template slot="title">
|
||||
<a :href="author.website" target="_blank">{{ author.name }}</a>
|
||||
</template>
|
||||
<a-avatar v-if="theme.logo" :src="theme.logo" size="large" slot="avatar" />
|
||||
<a-avatar v-else size="large" slot="avatar">{{ author.name }}</a-avatar>
|
||||
<a-avatar v-if="theme.logo" slot="avatar" :src="theme.logo" size="large" />
|
||||
<a-avatar v-else slot="avatar" size="large">{{ author.name }}</a-avatar>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xl="formColValue" :lg="formColValue" :md="formColValue" :sm="24" :xs="24" style="padding-bottom: 50px;">
|
||||
<a-col :lg="formColValue" :md="formColValue" :sm="24" :xl="formColValue" :xs="24" style="padding-bottom: 50px;">
|
||||
<a-spin :spinning="settingLoading">
|
||||
<div class="card-container" v-if="themeConfigurations.length > 0">
|
||||
<a-tabs type="card" defaultActiveKey="0">
|
||||
<div v-if="themeConfigurations.length > 0" class="card-container">
|
||||
<a-tabs defaultActiveKey="0" type="card">
|
||||
<a-tab-pane v-for="(group, index) in themeConfigurations" :key="index.toString()" :tab="group.label">
|
||||
<a-form layout="vertical" :wrapperCol="wrapperCol">
|
||||
<a-form-item v-for="(item, index1) in group.items" :label="item.label + ':'" :key="index1">
|
||||
<a-form :wrapperCol="wrapperCol" layout="vertical">
|
||||
<a-form-item v-for="(item, index1) in group.items" :key="index1" :label="item.label + ':'">
|
||||
<p v-if="item.description && item.description !== ''" slot="help" v-html="item.description"></p>
|
||||
<a-input
|
||||
v-if="item.type === 'TEXT'"
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
:placeholder="item.placeholder"
|
||||
v-if="item.type === 'TEXT'"
|
||||
/>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autoSize="{ minRows: 5 }"
|
||||
v-model="themeSettings[item.name]"
|
||||
:placeholder="item.placeholder"
|
||||
v-else-if="item.type === 'TEXTAREA'"
|
||||
v-model="themeSettings[item.name]"
|
||||
:autoSize="{ minRows: 5 }"
|
||||
:placeholder="item.placeholder"
|
||||
type="textarea"
|
||||
/>
|
||||
<a-radio-group
|
||||
v-else-if="item.type === 'RADIO'"
|
||||
v-model="themeSettings[item.name]"
|
||||
v-decorator="['radio-group']"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-model="themeSettings[item.name]"
|
||||
v-else-if="item.type === 'RADIO'"
|
||||
>
|
||||
<a-radio v-for="(option, index2) in item.options" :key="index2" :value="option.value">{{
|
||||
option.label
|
||||
}}</a-radio>
|
||||
<a-radio v-for="(option, index2) in item.options" :key="index2" :value="option.value"
|
||||
>{{ option.label }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<a-select
|
||||
v-else-if="item.type === 'SELECT'"
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type === 'SELECT'"
|
||||
>
|
||||
<a-select-option v-for="option in item.options" :key="option.value" :value="option.value">{{
|
||||
option.label
|
||||
}}</a-select-option>
|
||||
<a-select-option v-for="option in item.options" :key="option.value" :value="option.value"
|
||||
>{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<verte
|
||||
picker="square"
|
||||
model="hex"
|
||||
v-else-if="item.type === 'COLOR'"
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type === 'COLOR'"
|
||||
model="hex"
|
||||
picker="square"
|
||||
style="display: inline-block;height: 24px;"
|
||||
></verte>
|
||||
<a-input
|
||||
v-else-if="item.type === 'ATTACHMENT'"
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type === 'ATTACHMENT'"
|
||||
>
|
||||
<a href="javascript:void(0);" slot="addonAfter" @click="handleShowSelectAttachment(item.name)">
|
||||
<a slot="addonAfter" href="javascript:void(0);" @click="handleShowSelectAttachment(item.name)">
|
||||
<a-icon type="picture" />
|
||||
</a>
|
||||
</a-input>
|
||||
<a-input-number
|
||||
v-else-if="item.type === 'NUMBER'"
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type === 'NUMBER'"
|
||||
style="width:100%"
|
||||
/>
|
||||
<a-switch
|
||||
v-else-if="item.type === 'SWITCH'"
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultChecked="item.defaultValue"
|
||||
v-else-if="item.type === 'SWITCH'"
|
||||
/>
|
||||
<a-input
|
||||
v-else
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
:placeholder="item.placeholder"
|
||||
v-else
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
@ -105,17 +105,17 @@
|
|||
</a-spin>
|
||||
</a-col>
|
||||
|
||||
<a-col :xl="20" :lg="20" :md="20" :sm="24" :xs="24" v-if="viewMode" style="padding-bottom: 50px;">
|
||||
<a-card :bordered="true" :bodyStyle="{ padding: 0 }">
|
||||
<a-col v-if="viewMode" :lg="20" :md="20" :sm="24" :xl="20" :xs="24" style="padding-bottom: 50px;">
|
||||
<a-card :bodyStyle="{ padding: 0 }" :bordered="true">
|
||||
<iframe
|
||||
id="themeViewIframe"
|
||||
title="主题预览"
|
||||
:height="clientHeight - 165"
|
||||
:src="options.blog_url"
|
||||
border="0"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
border="0"
|
||||
:src="options.blog_url"
|
||||
title="主题预览"
|
||||
width="100%"
|
||||
:height="clientHeight - 165"
|
||||
>
|
||||
</iframe>
|
||||
</a-card>
|
||||
|
@ -124,27 +124,27 @@
|
|||
|
||||
<AttachmentSelectDrawer
|
||||
v-model="attachmentDrawerVisible"
|
||||
@listenToSelect="handleSelectAttachment"
|
||||
title="选择附件"
|
||||
@listenToSelect="handleSelectAttachment"
|
||||
/>
|
||||
|
||||
<footer-tool-bar v-if="themeConfigurations.length > 0" class="w-full">
|
||||
<a-space>
|
||||
<a-button v-if="!this.isMobile() && theme.activated && viewMode" type="primary" @click="toggleViewMode" ghost
|
||||
>普通模式</a-button
|
||||
>
|
||||
<a-button v-if="!this.isMobile() && theme.activated && viewMode" ghost type="primary" @click="toggleViewMode"
|
||||
>普通模式
|
||||
</a-button>
|
||||
<a-button v-else-if="!this.isMobile() && theme.activated && !viewMode" type="dashed" @click="toggleViewMode"
|
||||
>预览模式</a-button
|
||||
>
|
||||
>预览模式
|
||||
</a-button>
|
||||
<ReactiveButton
|
||||
type="primary"
|
||||
@click="handleSaveSettings"
|
||||
@callback="saveErrored = false"
|
||||
:loading="saving"
|
||||
:errored="saveErrored"
|
||||
text="保存"
|
||||
loadedText="保存成功"
|
||||
:loading="saving"
|
||||
erroredText="保存失败"
|
||||
loadedText="保存成功"
|
||||
text="保存"
|
||||
type="primary"
|
||||
@callback="saveErrored = false"
|
||||
@click="handleSaveSettings"
|
||||
></ReactiveButton>
|
||||
</a-space>
|
||||
</footer-tool-bar>
|
||||
|
@ -156,7 +156,8 @@ import { mapGetters } from 'vuex'
|
|||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import Verte from 'verte'
|
||||
import 'verte/dist/verte.css'
|
||||
import themeApi from '@/api/theme'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
export default {
|
||||
name: 'ThemeSetting',
|
||||
mixins: [mixin, mixinDevice],
|
||||
|
@ -212,26 +213,24 @@ export default {
|
|||
methods: {
|
||||
async handleFetchConfiguration() {
|
||||
this.settingLoading = true
|
||||
await themeApi.fetchConfiguration(this.theme.id).then(response => {
|
||||
this.themeConfigurations = response.data.data
|
||||
await apiClient.theme.listConfigurations(this.theme.id).then(response => {
|
||||
this.themeConfigurations = response.data
|
||||
})
|
||||
this.handleFetchSettings()
|
||||
},
|
||||
handleFetchSettings() {
|
||||
themeApi
|
||||
.fetchSettings(this.theme.id)
|
||||
apiClient.theme
|
||||
.listSettings(this.theme.id)
|
||||
.then(response => {
|
||||
this.themeSettings = response.data.data
|
||||
this.themeSettings = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
this.settingLoading = false
|
||||
}, 200)
|
||||
this.settingLoading = false
|
||||
})
|
||||
},
|
||||
handleSaveSettings() {
|
||||
this.saving = true
|
||||
themeApi
|
||||
apiClient.theme
|
||||
.saveSettings(this.theme.id, this.themeSettings)
|
||||
.then(() => {
|
||||
if (this.viewMode) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue