mirror of https://github.com/halo-dev/halo-admin
release: 1.2.0. (#59)
release: 1.2.0. Co-authored-by: Ryan Wang <i@ryanc.cc> Co-authored-by: John Niang <johnniang@foxmail.com>pull/64/head v1.2.0
commit
92e965aed1
|
@ -0,0 +1,4 @@
|
|||
/node_modules/*
|
||||
/.idea/*
|
||||
/.git/*
|
||||
/.github/*
|
|
@ -22,3 +22,6 @@ branches:
|
|||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
|
||||
|
||||
notifications:
|
||||
webhooks: https://fathomless-fjord-24024.herokuapp.com/notify
|
2
LICENSE
2
LICENSE
|
@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
|
|||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"name": "halo-admin",
|
||||
"version": "1.1.2",
|
||||
"private": true,
|
||||
"version": "1.2.0",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
|
@ -9,25 +8,23 @@
|
|||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"animate.css": "^3.7.0",
|
||||
"ant-design-vue": "^1.4.1",
|
||||
"axios": "^0.18.0",
|
||||
"ant-design-vue": "^1.4.10",
|
||||
"axios": "^0.19.0",
|
||||
"enquire.js": "^2.1.6",
|
||||
"filepond": "^4.7.2",
|
||||
"filepond-plugin-image-preview": "^4.5.0",
|
||||
"halo-editor": "^2.7.6",
|
||||
"marked": "^0.6.3",
|
||||
"filepond": "^4.9.2",
|
||||
"filepond-plugin-image-preview": "^4.6.0",
|
||||
"halo-editor": "^2.8.2",
|
||||
"marked": "^0.8.0",
|
||||
"moment": "^2.24.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"verte": "^0.0.12",
|
||||
"vue": "^2.6.10",
|
||||
"vue": "^2.6.11",
|
||||
"vue-clipboard2": "^0.3.0",
|
||||
"vue-codemirror-lite": "^1.0.4",
|
||||
"vue-count-to": "^1.0.13",
|
||||
"vue-filepond": "^5.1.3",
|
||||
"vue-dplayer": "0.0.10",
|
||||
"vue-filepond": "^6.0.0",
|
||||
"vue-ls": "^3.2.1",
|
||||
"vue-router": "^3.1.3",
|
||||
"vue-video-player": "^5.0.2",
|
||||
"vuejs-logger": "^1.5.3",
|
||||
"vuex": "^3.1.1"
|
||||
},
|
||||
|
@ -35,7 +32,7 @@
|
|||
"@babel/polyfill": "^7.4.4",
|
||||
"@vue/cli-plugin-babel": "^3.8.0",
|
||||
"@vue/cli-plugin-eslint": "^3.8.0",
|
||||
"@vue/cli-plugin-unit-jest": "^3.8.0",
|
||||
"@vue/cli-plugin-unit-jest": "^4.1.1",
|
||||
"@vue/cli-service": "^3.8.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.20",
|
||||
|
@ -46,11 +43,13 @@
|
|||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-html": "^5.0.5",
|
||||
"eslint-plugin-vue": "^5.2.3",
|
||||
"flv.js": "^1.5.0",
|
||||
"generate-asset-webpack-plugin": "^0.3.0",
|
||||
"less": "^3.10.0",
|
||||
"less-loader": "^5.0.0",
|
||||
"vue-svg-component-runtime": "^1.0.1",
|
||||
"vue-svg-icon-loader": "^2.1.1",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
@ -126,5 +125,16 @@
|
|||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10"
|
||||
]
|
||||
],
|
||||
"description": "Halo admin client.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/halo-dev/halo-admin.git"
|
||||
},
|
||||
"author": "halo-dev",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/halo-dev/halo-admin/issues"
|
||||
},
|
||||
"homepage": "https://github.com/halo-dev/halo-admin#readme"
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
|
@ -6,12 +6,16 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<meta name="robots" content="noindex,nofllow" />
|
||||
<meta name="generator" content="Halo" />
|
||||
<meta name="generator" content="Halo 1.2.0" />
|
||||
<link rel="icon" href="<%= BASE_URL %>logo.png" />
|
||||
<title>Halo Dashboard</title>
|
||||
<style>
|
||||
#loader{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto;border:solid 3px #e5e5e5;border-top-color:#333;border-radius:50%;width:30px;height:30px;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
|
||||
body {height: 100%;background-color: #f5f5f5;}#loader{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto;border:solid 3px #e5e5e5;border-top-color:#333;border-radius:50%;width:30px;height:30px;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
<!-- require cdn assets css -->
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
||||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
|
||||
<% } %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -22,6 +26,10 @@
|
|||
<div id="app">
|
||||
<div id="loader"></div>
|
||||
</div>
|
||||
<!-- require cdn assets js -->
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
||||
<% } %>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
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
|
|
@ -7,8 +7,7 @@ const adminApi = {}
|
|||
adminApi.counts = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/counts`,
|
||||
method: 'get',
|
||||
mute: true
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -82,4 +81,49 @@ adminApi.updateAdminAssets = () => {
|
|||
timeout: 600 * 1000
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.getApplicationConfig = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/spring/application.yaml`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.updateApplicationConfig = content => {
|
||||
return service({
|
||||
url: `${baseUrl}/spring/application.yaml`,
|
||||
params: {
|
||||
content: content
|
||||
},
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.restartApplication = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/spring/restart`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -27,6 +27,17 @@ attachmentApi.delete = attachmentId => {
|
|||
})
|
||||
}
|
||||
|
||||
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}`,
|
||||
|
@ -42,6 +53,13 @@ attachmentApi.getMediaTypes = () => {
|
|||
})
|
||||
}
|
||||
|
||||
attachmentApi.getTypes = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/types`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
attachmentApi.CancelToken = axios.CancelToken
|
||||
attachmentApi.isCancel = axios.isCancel
|
||||
|
||||
|
@ -69,31 +87,31 @@ attachmentApi.uploads = (formDatas, uploadProgress, cancelToken) => {
|
|||
|
||||
attachmentApi.type = {
|
||||
LOCAL: {
|
||||
type: 'local',
|
||||
type: 'LOCAL',
|
||||
text: '本地'
|
||||
},
|
||||
SMMS: {
|
||||
type: 'smms',
|
||||
type: 'SMMS',
|
||||
text: 'SM.MS'
|
||||
},
|
||||
UPYUN: {
|
||||
type: 'upyun',
|
||||
UPOSS: {
|
||||
type: 'UPOSS',
|
||||
text: '又拍云'
|
||||
},
|
||||
QNYUN: {
|
||||
type: 'qnyun',
|
||||
QINIUOSS: {
|
||||
type: 'QINIUOSS',
|
||||
text: '七牛云'
|
||||
},
|
||||
ALIYUN: {
|
||||
type: 'aliyun',
|
||||
ALIOSS: {
|
||||
type: 'ALIOSS',
|
||||
text: '阿里云'
|
||||
},
|
||||
BAIDUYUN: {
|
||||
type: 'baiduyun',
|
||||
BAIDUBOS: {
|
||||
type: 'BAIDUBOS',
|
||||
text: '百度云'
|
||||
},
|
||||
TENCENTYUN: {
|
||||
type: 'tencentyun',
|
||||
TENCENTCOS: {
|
||||
type: 'TENCENTCOS',
|
||||
text: '腾讯云'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,4 +15,29 @@ backupApi.importMarkdown = (formData, uploadProgress, cancelToken) => {
|
|||
})
|
||||
}
|
||||
|
||||
backupApi.backupHalo = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo`,
|
||||
method: 'post',
|
||||
timeout: 8640000 // 24 hours
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.listHaloBackups = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
backupApi.deleteHaloBackup = filename => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo`,
|
||||
params: {
|
||||
filename: filename
|
||||
},
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export default backupApi
|
||||
|
|
|
@ -23,6 +23,14 @@ commentApi.queryComment = (target, params) => {
|
|||
})
|
||||
}
|
||||
|
||||
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}`,
|
||||
|
@ -30,6 +38,14 @@ commentApi.updateStatus = (target, commentId, status) => {
|
|||
})
|
||||
}
|
||||
|
||||
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}`,
|
||||
|
@ -37,6 +53,14 @@ commentApi.delete = (target, commentId) => {
|
|||
})
|
||||
}
|
||||
|
||||
commentApi.deleteInBatch = (target, ids) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments`,
|
||||
data: ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
commentApi.create = (target, comment) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments`,
|
||||
|
@ -94,16 +118,19 @@ commentApi.createComment = (comment, type) => {
|
|||
|
||||
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: '回收站'
|
||||
|
|
|
@ -26,6 +26,16 @@ linkApi.get = linkId => {
|
|||
})
|
||||
}
|
||||
|
||||
linkApi.getByParse = url => {
|
||||
return service({
|
||||
url: `${baseUrl}/parse`,
|
||||
params: {
|
||||
url: url
|
||||
},
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
linkApi.update = (linkId, link) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${linkId}`,
|
||||
|
@ -41,4 +51,11 @@ linkApi.delete = linkId => {
|
|||
})
|
||||
}
|
||||
|
||||
linkApi.listTeams = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/teams`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default linkApi
|
||||
|
|
|
@ -48,4 +48,11 @@ menuApi.update = (menuId, menu) => {
|
|||
})
|
||||
}
|
||||
|
||||
menuApi.listTeams = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/teams`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default menuApi
|
||||
|
|
|
@ -14,6 +14,14 @@ optionApi.listAll = keys => {
|
|||
})
|
||||
}
|
||||
|
||||
optionApi.query = params => {
|
||||
return service({
|
||||
url: `${baseUrl}/list_view`,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
optionApi.save = options => {
|
||||
return service({
|
||||
url: `${baseUrl}/map_view/saving`,
|
||||
|
@ -22,4 +30,45 @@ optionApi.save = 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
|
||||
|
|
|
@ -34,7 +34,6 @@ postApi.create = (postToCreate, autoSave) => {
|
|||
url: baseUrl,
|
||||
method: 'post',
|
||||
data: postToCreate,
|
||||
mute: autoSave,
|
||||
params: {
|
||||
autoSave: autoSave
|
||||
}
|
||||
|
@ -52,6 +51,16 @@ postApi.update = (postId, postToUpdate, 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}`,
|
||||
|
@ -59,6 +68,14 @@ postApi.updateStatus = (postId, status) => {
|
|||
})
|
||||
}
|
||||
|
||||
postApi.updateStatusInBatch = (ids, status) => {
|
||||
return service({
|
||||
url: `${baseUrl}/status/${status}`,
|
||||
data: ids,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.delete = postId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${postId}`,
|
||||
|
@ -66,6 +83,14 @@ postApi.delete = postId => {
|
|||
})
|
||||
}
|
||||
|
||||
postApi.deleteInBatch = ids => {
|
||||
return service({
|
||||
url: `${baseUrl}`,
|
||||
data: ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.preview = postId => {
|
||||
return service({
|
||||
url: `${baseUrl}/preview/${postId}`,
|
||||
|
@ -75,21 +100,25 @@ postApi.preview = postId => {
|
|||
|
||||
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: '私密'
|
||||
|
|
|
@ -4,9 +4,10 @@ const baseUrl = '/api/admin/sheets'
|
|||
|
||||
const sheetApi = {}
|
||||
|
||||
sheetApi.list = () => {
|
||||
sheetApi.list = params => {
|
||||
return service({
|
||||
url: baseUrl,
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
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'
|
||||
})
|
||||
}
|
||||
|
||||
export default staticApi
|
|
@ -0,0 +1,21 @@
|
|||
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
|
|
@ -25,9 +25,16 @@ themeApi.listFiles = themeId => {
|
|||
})
|
||||
}
|
||||
|
||||
themeApi.customTpls = () => {
|
||||
themeApi.customSheetTpls = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/files/custom`,
|
||||
url: `${baseUrl}/activation/template/custom/sheet`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.customPostTpls = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/activation/template/custom/post`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
|
@ -24,7 +24,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
title="待审核评论"
|
||||
>
|
||||
<template slot="content">
|
||||
<a-spin :spinning="loadding">
|
||||
<a-spin :spinning="loading">
|
||||
<div class="custom-tab-wrapper">
|
||||
<a-tabs>
|
||||
<a-tab-pane
|
||||
|
@ -99,7 +99,7 @@ export default {
|
|||
name: 'HeaderComment',
|
||||
data() {
|
||||
return {
|
||||
loadding: false,
|
||||
loading: false,
|
||||
visible: false,
|
||||
postComments: [],
|
||||
sheetComments: []
|
||||
|
@ -111,13 +111,13 @@ export default {
|
|||
computed: {
|
||||
converttedPostComments() {
|
||||
return this.postComments.map(comment => {
|
||||
comment.content = marked(comment.content, { sanitize: true })
|
||||
comment.content = marked(comment.content)
|
||||
return comment
|
||||
})
|
||||
},
|
||||
converttedSheetComments() {
|
||||
return this.sheetComments.map(comment => {
|
||||
comment.content = marked(comment.content, { sanitize: true })
|
||||
comment.content = marked(comment.content)
|
||||
return comment
|
||||
})
|
||||
}
|
||||
|
@ -125,21 +125,21 @@ export default {
|
|||
methods: {
|
||||
fetchComment() {
|
||||
if (!this.visible) {
|
||||
this.loadding = true
|
||||
this.loading = true
|
||||
this.getComment()
|
||||
} else {
|
||||
this.loadding = false
|
||||
this.loading = false
|
||||
}
|
||||
this.visible = !this.visible
|
||||
},
|
||||
getComment() {
|
||||
commentApi.latestComment('posts', 5, 'AUDITING').then(response => {
|
||||
this.postComments = response.data.data
|
||||
this.loadding = false
|
||||
this.loading = false
|
||||
})
|
||||
commentApi.latestComment('sheets', 5, 'AUDITING').then(response => {
|
||||
this.sheetComments = response.data.data
|
||||
this.loadding = false
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,54 @@
|
|||
<template>
|
||||
<div class="logo">
|
||||
<router-link :to="{ name:'Dashboard' }">
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="onLogoClick()"
|
||||
>
|
||||
<h1 class="logo-title">Halo</h1>
|
||||
<h1 class="logo-sub-title">Dashboard</h1>
|
||||
</router-link>
|
||||
<h1
|
||||
class="logo-sub-title"
|
||||
style="padding-left: 10px;"
|
||||
>Dashboard</h1>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import optionApi from '@/api/option'
|
||||
export default {
|
||||
name: 'Logo'
|
||||
name: 'Logo',
|
||||
data() {
|
||||
return {
|
||||
clickCount: 0,
|
||||
optionsToCreate: {
|
||||
developer_mode: true
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['loadOptions']),
|
||||
onLogoClick() {
|
||||
this.clickCount++
|
||||
if (this.clickCount === 10) {
|
||||
optionApi.save(this.optionsToCreate).then(response => {
|
||||
this.loadOptions()
|
||||
this.$message.success(`开发者选项已启用!`)
|
||||
this.clickCount = 0
|
||||
this.$router.push({ name: 'ToolList' })
|
||||
})
|
||||
} else if (this.clickCount >= 5) {
|
||||
if (this.options.developer_mode) {
|
||||
this.$message.info(`当前已启用开发者选项!`)
|
||||
this.clickCount = 0
|
||||
} else {
|
||||
this.$message.info(`再点击 ${10 - this.clickCount} 次即可启用开发者选项!`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scope>
|
||||
.logo-sub-title{
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
<a-avatar
|
||||
class="avatar"
|
||||
size="small"
|
||||
style="margin-right: 0.3rem;"
|
||||
:src="user.avatar || '//cn.gravatar.com/avatar/?s=256&d=mm'"
|
||||
/>
|
||||
</span>
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
<template>
|
||||
<div class="clearfix">
|
||||
<a-upload
|
||||
:name="name"
|
||||
:customRequest="handleUpload"
|
||||
listType="picture-card"
|
||||
:fileList="fileList"
|
||||
@preview="handlePreview"
|
||||
@change="handleChange"
|
||||
>
|
||||
<div v-if="fileList.length < 9 && plusPhotoVisible" id="plus-photo-uploadbox">
|
||||
<a-icon type="plus"/>
|
||||
<div class="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
|
||||
<img alt="example" style="width: 100%" :src="previewImage">
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
export default {
|
||||
props: {
|
||||
photoList: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
plusPhotoVisible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: 'file',
|
||||
previewVisible: false,
|
||||
previewImage: '',
|
||||
fileList: [],
|
||||
uploadHandler: attachmentApi.upload
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 在生命周期开始时调用一次赋值解决watch没有监控到数据的问题
|
||||
this.handlerEditPreviewPhoto(this.photoList)
|
||||
},
|
||||
watch: {
|
||||
photoList(newValue, oldValue) {
|
||||
this.handlerEditPreviewPhoto(newValue)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlerEditPreviewPhoto(data) {
|
||||
// 先清空
|
||||
this.fileList = []
|
||||
// 编辑日志时回显图片
|
||||
if (data !== null && data !== undefined) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
// 构造合适的对象
|
||||
this.fileList.push({
|
||||
uid: data[i].id,
|
||||
name: data[i].name,
|
||||
status: 'done',
|
||||
url: data[i].thumbnail
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.previewVisible = false
|
||||
},
|
||||
handlePreview(file) {
|
||||
this.previewImage = file.url || file.thumbUrl
|
||||
this.previewVisible = true
|
||||
},
|
||||
handleChange({ fileList }) {
|
||||
this.fileList = fileList
|
||||
},
|
||||
handleUpload(option) {
|
||||
this.$log.debug('Uploading option', option)
|
||||
const CancelToken = axios.CancelToken
|
||||
const source = CancelToken.source()
|
||||
|
||||
const data = new FormData()
|
||||
data.append(this.name, option.file)
|
||||
|
||||
this.uploadHandler(
|
||||
data,
|
||||
progressEvent => {
|
||||
if (progressEvent.total > 0) {
|
||||
progressEvent.percent = (progressEvent.loaded / progressEvent.total) * 100
|
||||
}
|
||||
this.$log.debug('Uploading percent: ', progressEvent.percent)
|
||||
option.onProgress(progressEvent)
|
||||
},
|
||||
source.token,
|
||||
option.file
|
||||
)
|
||||
.then(response => {
|
||||
this.$log.debug('Uploaded successfully', response)
|
||||
option.onSuccess(response, option.file)
|
||||
this.$emit('success', response, option.file)
|
||||
})
|
||||
.catch(error => {
|
||||
this.$log.debug('Failed to upload file', error)
|
||||
option.onError(error, error.response)
|
||||
this.$emit('failure', error, option.file)
|
||||
})
|
||||
return {
|
||||
abort: () => {
|
||||
this.$log.debug('Upload operation aborted by the user')
|
||||
source.cancel('Upload operation canceled by the user.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.ant-upload-select-picture-card i {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.ant-upload-select-picture-card .ant-upload-text {
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
}
|
||||
.ant-upload-list-picture-card {
|
||||
/* 将浮动恢复为默认值,避免出现纵向换行情况 */
|
||||
float: initial;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,143 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
/*!
|
||||
* animate.css -https://daneden.github.io/animate.css/
|
||||
* Version - 3.7.2
|
||||
* Licensed under the MIT license - https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Copyright (c) 2019 Daniel Eden
|
||||
*/
|
||||
|
||||
@-webkit-keyframes fadeInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeInRight {
|
||||
-webkit-animation-name: fadeInRight;
|
||||
animation-name: fadeInRight;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeInUp {
|
||||
-webkit-animation-name: fadeInUp;
|
||||
animation-name: fadeInUp;
|
||||
}
|
||||
|
||||
.animated {
|
||||
-webkit-animation-duration: 1s;
|
||||
animation-duration: 1s;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.animated.infinite {
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.animated.delay-1s {
|
||||
-webkit-animation-delay: 1s;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.animated.delay-2s {
|
||||
-webkit-animation-delay: 2s;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.animated.delay-3s {
|
||||
-webkit-animation-delay: 3s;
|
||||
animation-delay: 3s;
|
||||
}
|
||||
|
||||
.animated.delay-4s {
|
||||
-webkit-animation-delay: 4s;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
.animated.delay-5s {
|
||||
-webkit-animation-delay: 5s;
|
||||
animation-delay: 5s;
|
||||
}
|
||||
|
||||
.animated.fast {
|
||||
-webkit-animation-duration: 800ms;
|
||||
animation-duration: 800ms;
|
||||
}
|
||||
|
||||
.animated.faster {
|
||||
-webkit-animation-duration: 500ms;
|
||||
animation-duration: 500ms;
|
||||
}
|
||||
|
||||
.animated.slow {
|
||||
-webkit-animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
}
|
||||
|
||||
.animated.slower {
|
||||
-webkit-animation-duration: 3s;
|
||||
animation-duration: 3s;
|
||||
}
|
||||
|
||||
@media (print), (prefers-reduced-motion: reduce) {
|
||||
.animated {
|
||||
-webkit-animation-duration: 1ms !important;
|
||||
animation-duration: 1ms !important;
|
||||
-webkit-transition-duration: 1ms !important;
|
||||
transition-duration: 1ms !important;
|
||||
-webkit-animation-iteration-count: 1 !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
@import './index.less';
|
||||
@import './style.less';
|
||||
|
||||
* {
|
||||
&::-webkit-scrollbar {
|
||||
|
@ -112,7 +113,23 @@ body {
|
|||
&.content-width-Fluid {
|
||||
.header-index-wide {
|
||||
max-width: unset;
|
||||
margin-left: 24px;
|
||||
|
||||
.header-index-left {
|
||||
flex: 1 1 1000px;
|
||||
|
||||
.logo {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.ant-menu.ant-menu-horizontal {
|
||||
max-width: calc(100vw - 190px - 238px - 25px);
|
||||
flex: 1 1 calc(100vw - 190px - 238px - 25px);
|
||||
}
|
||||
}
|
||||
|
||||
.header-index-right {
|
||||
margin-right: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header-index-wide {
|
||||
|
@ -143,7 +160,7 @@ body {
|
|||
|
||||
.header {
|
||||
height: 64px;
|
||||
padding: 0 12px 0 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
|
||||
position: relative;
|
||||
|
@ -158,7 +175,7 @@ body {
|
|||
|
||||
.action {
|
||||
cursor: pointer;
|
||||
padding: 0 12px;
|
||||
padding: 0 18px;
|
||||
display: inline-block;
|
||||
transition: all .3s;
|
||||
height: 100%;
|
||||
|
@ -255,7 +272,7 @@ body {
|
|||
}
|
||||
|
||||
.ant-menu.ant-menu-horizontal {
|
||||
flex: 1 1;
|
||||
flex: 1 1 auto;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
@ -276,16 +293,19 @@ body {
|
|||
height: 64px;
|
||||
|
||||
.ant-menu.ant-menu-horizontal {
|
||||
max-width: 835px;
|
||||
flex: 0 1 835px;
|
||||
border: none;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
}
|
||||
|
||||
.header-index-left {
|
||||
flex: 1 1;
|
||||
flex: 0 1 1000px;
|
||||
display: flex;
|
||||
|
||||
.logo.top-nav-header {
|
||||
flex: 0 0 165px;
|
||||
width: 165px;
|
||||
height: 64px;
|
||||
position: relative;
|
||||
|
@ -314,8 +334,20 @@ body {
|
|||
|
||||
.header-index-right {
|
||||
flex: 0 0 auto;
|
||||
align-self: flex-end;
|
||||
height: 64px;
|
||||
overflow: hidden;
|
||||
|
||||
.content-box {
|
||||
float: right;
|
||||
|
||||
.action {
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,11 +412,10 @@ body {
|
|||
.sider {
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
height: auto;
|
||||
z-index: @ant-global-sider-zindex;
|
||||
min-height: 100vh;
|
||||
|
||||
.ant-layout-sider-children {
|
||||
padding-top: 64px;
|
||||
overflow-y: hidden;
|
||||
|
||||
&:hover {
|
||||
|
@ -398,18 +429,13 @@ body {
|
|||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
-webkit-transition: all .3s;
|
||||
transition: all .3s;
|
||||
background: #002140;
|
||||
overflow: hidden;
|
||||
z-index: 9;
|
||||
line-height: 64px;
|
||||
background: #002140;
|
||||
transition: all .3s;
|
||||
|
||||
img,
|
||||
svg,
|
||||
|
@ -477,11 +503,6 @@ body {
|
|||
|
||||
}
|
||||
|
||||
// 数据列表 样式
|
||||
.table-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-page-search-wrapper {
|
||||
|
||||
.ant-form-inline {
|
||||
|
@ -518,6 +539,10 @@ body {
|
|||
|
||||
}
|
||||
|
||||
.ant-table-thead>tr>th {
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
.table-operator {
|
||||
|
@ -592,18 +617,27 @@ body {
|
|||
}
|
||||
|
||||
.ant-comment-inner {
|
||||
padding: 0 !important;
|
||||
|
||||
.ant-comment-content {
|
||||
.ant-comment-content-detail {
|
||||
p {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-comment-avatar {
|
||||
img {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-control {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
|
@ -719,13 +753,15 @@ body {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.attach-thumb {
|
||||
.attach-thumb,
|
||||
.photo-thumb {
|
||||
width: 100%;
|
||||
padding-bottom: 56%;
|
||||
}
|
||||
|
||||
.attach-item,
|
||||
.attach-thumb {
|
||||
.attach-thumb,
|
||||
.photo-thumb {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
@ -788,4 +824,91 @@ body {
|
|||
li {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.exception {
|
||||
min-height: 500px;
|
||||
height: 80%;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin-top: 150px;
|
||||
|
||||
.img {
|
||||
display: inline-block;
|
||||
padding-right: 52px;
|
||||
zoom: 1;
|
||||
|
||||
img {
|
||||
height: 360px;
|
||||
max-width: 430px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: inline-block;
|
||||
flex: auto;
|
||||
|
||||
h1 {
|
||||
color: #434e59;
|
||||
font-size: 72px;
|
||||
font-weight: 600;
|
||||
line-height: 72px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.exception {
|
||||
margin-top: 30px;
|
||||
|
||||
.img {
|
||||
padding-right: unset;
|
||||
|
||||
img {
|
||||
height: 40%;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vue-codemirror-wrap {
|
||||
.CodeMirror {
|
||||
height: 560px;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #fff3f3;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.select-attachment-checkbox {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
|
||||
.ant-checkbox {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.journal-list-content,
|
||||
.comment-drawer-content {
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -7,3 +7,5 @@ html [type='button'] {
|
|||
|
||||
// The prefix to use on all css classes from ant-pro.
|
||||
@ant-pro-prefix : ant-pro;
|
||||
@ant-global-sider-zindex : 106;
|
||||
@ant-global-header-zindex : 105;
|
|
@ -0,0 +1,40 @@
|
|||
@import './animate.less';
|
||||
.container-wrapper {
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
border-radius: 5px;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
margin: -160px 0 0 -160px;
|
||||
width: 320px;
|
||||
padding: 18px 28px 28px 28px;
|
||||
box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.halo-logo {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
vertical-align: text-bottom;
|
||||
font-size: 38px;
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
color: #1790fe;
|
||||
background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
|
||||
small {
|
||||
margin-left: 5px;
|
||||
font-size: 35%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
float: right;
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ export const asyncRouterMap = [
|
|||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: () => import('@/views/dashboard/Dashboard'),
|
||||
meta: { title: '仪表盘', icon: 'dashboard', hiddenHeaderContent: false }
|
||||
meta: { title: '仪表盘', icon: 'dashboard', hiddenHeaderContent: false, keepAlive: false }
|
||||
},
|
||||
|
||||
// posts
|
||||
|
@ -35,7 +35,7 @@ export const asyncRouterMap = [
|
|||
path: '/posts/write',
|
||||
name: 'PostEdit',
|
||||
component: () => import('@/views/post/PostEdit'),
|
||||
meta: { title: '写文章', hiddenHeaderContent: false }
|
||||
meta: { title: '写文章', hiddenHeaderContent: false, keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/categories',
|
||||
|
@ -70,7 +70,7 @@ export const asyncRouterMap = [
|
|||
path: '/sheets/write',
|
||||
name: 'SheetEdit',
|
||||
component: () => import('@/views/sheet/SheetEdit'),
|
||||
meta: { title: '新建页面', hiddenHeaderContent: false }
|
||||
meta: { title: '新建页面', hiddenHeaderContent: false, keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/sheets/links',
|
||||
|
@ -166,18 +166,19 @@ export const asyncRouterMap = [
|
|||
redirect: '/system/options',
|
||||
meta: { title: '系统', icon: 'setting' },
|
||||
children: [
|
||||
{
|
||||
path: '/system/developer/options',
|
||||
name: 'DeveloperOptions',
|
||||
hidden: true,
|
||||
component: () => import('@/views/system/developer/DeveloperOptions'),
|
||||
meta: { title: '开发者选项', hiddenHeaderContent: false }
|
||||
},
|
||||
{
|
||||
path: '/system/options',
|
||||
name: 'OptionForm',
|
||||
component: () => import('@/views/system/OptionForm'),
|
||||
meta: { title: '博客设置', hiddenHeaderContent: false }
|
||||
},
|
||||
// {
|
||||
// path: '/system/backup',
|
||||
// name: 'BackupList',
|
||||
// component: () => import('@/views/system/BackupList'),
|
||||
// meta: { title: '博客备份', hiddenHeaderContent: false }
|
||||
// },
|
||||
{
|
||||
path: '/system/tools',
|
||||
name: 'ToolList',
|
||||
|
@ -201,10 +202,6 @@ export const asyncRouterMap = [
|
|||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 基础路由
|
||||
* @type { *[] }
|
||||
*/
|
||||
export const constantRouterMap = [
|
||||
{
|
||||
path: '/login',
|
||||
|
@ -227,6 +224,6 @@ export const constantRouterMap = [
|
|||
{
|
||||
path: '/404',
|
||||
name: 'NotFound',
|
||||
component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404')
|
||||
component: () => import('@/views/exception/404')
|
||||
}
|
||||
]
|
||||
|
|
|
@ -9,14 +9,12 @@ import './core/lazy_use'
|
|||
import './permission'
|
||||
import '@/utils/filter' // global filter
|
||||
import './components'
|
||||
import animated from 'animate.css'
|
||||
import { version } from '../package.json'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.VERSION = version
|
||||
|
||||
Vue.use(router)
|
||||
Vue.use(animated)
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
|
|
|
@ -6,17 +6,9 @@ import {
|
|||
domTitle
|
||||
} from '@/utils/domUtil'
|
||||
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import 'nprogress/nprogress.css' // progress bar style
|
||||
|
||||
NProgress.configure({
|
||||
showSpinner: false
|
||||
}) // NProgress Configuration
|
||||
|
||||
const whiteList = ['Login', 'Install', 'NotFound', 'ResetPassword'] // no redirect whitelist
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
|
||||
Vue.$log.debug('Token', store.getters.token)
|
||||
if (store.getters.token) {
|
||||
|
@ -24,7 +16,6 @@ router.beforeEach((to, from, next) => {
|
|||
next({
|
||||
name: 'Dashboard'
|
||||
})
|
||||
NProgress.done()
|
||||
return
|
||||
}
|
||||
// TODO Get installation status
|
||||
|
@ -34,7 +25,6 @@ router.beforeEach((to, from, next) => {
|
|||
}
|
||||
|
||||
next()
|
||||
NProgress.done()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -42,7 +32,6 @@ router.beforeEach((to, from, next) => {
|
|||
// Check whitelist
|
||||
if (whiteList.includes(to.name)) {
|
||||
next()
|
||||
NProgress.done()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -52,5 +41,4 @@ router.beforeEach((to, from, next) => {
|
|||
redirect: to.fullPath
|
||||
}
|
||||
})
|
||||
NProgress.done()
|
||||
})
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
import optionApi from '@/api/option'
|
||||
const keys = [
|
||||
'blog_url',
|
||||
'developer_mode',
|
||||
'attachment_upload_image_preview_enable',
|
||||
'attachment_upload_max_parallel_uploads',
|
||||
'attachment_upload_max_files'
|
||||
|
|
|
@ -36,3 +36,12 @@ Vue.filter('fileSizeFormat', function(value) {
|
|||
size = size.toFixed(2)
|
||||
return size + ' ' + unitArr[index]
|
||||
})
|
||||
|
||||
Vue.filter('dayTime', function(value) {
|
||||
var days = Math.floor(value / 86400)
|
||||
var hours = Math.floor((value % 86400) / 3600)
|
||||
var minutes = Math.floor(((value % 86400) % 3600) / 60)
|
||||
var seconds = Math.floor(((value % 86400) % 3600) % 60)
|
||||
var duration = days + 'd ' + hours + 'h ' + minutes + 'm ' + seconds + 's'
|
||||
return duration
|
||||
})
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import axios from 'axios'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import Vue from 'vue'
|
||||
import { message, notification } from 'ant-design-vue'
|
||||
import store from '@/store'
|
||||
|
@ -8,7 +6,7 @@ import router from '@/router'
|
|||
import { isObject } from './util'
|
||||
|
||||
const service = axios.create({
|
||||
timeout: 8000,
|
||||
timeout: 10000,
|
||||
withCredentials: true
|
||||
})
|
||||
|
||||
|
@ -63,27 +61,20 @@ function getFieldValidationError(data) {
|
|||
service.interceptors.request.use(
|
||||
config => {
|
||||
config.baseURL = store.getters.apiUrl
|
||||
if (!config.mute) {
|
||||
NProgress.start()
|
||||
}
|
||||
// TODO set token
|
||||
setTokenToHeader(config)
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
NProgress.remove()
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
NProgress.done()
|
||||
return response
|
||||
},
|
||||
error => {
|
||||
NProgress.done()
|
||||
|
||||
if (axios.isCancel(error)) {
|
||||
Vue.$log.debug('Cancelled uploading by user.')
|
||||
return Promise.reject(error)
|
||||
|
|
|
@ -10,20 +10,6 @@ export function triggerWindowResizeEvent() {
|
|||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove loading animate
|
||||
* @param id parent element id or class
|
||||
* @param timeout
|
||||
*/
|
||||
export function removeLoadingAnimate(id = '', timeout = 1500) {
|
||||
if (id === '') {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(document.getElementById(id))
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export function timeAgo(time) {
|
||||
var currentTime = new Date().getTime()
|
||||
var between = currentTime - time
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
>
|
||||
<a-col
|
||||
:span="24"
|
||||
class="search-box"
|
||||
style="padding-bottom: 12px;"
|
||||
>
|
||||
<a-card
|
||||
|
@ -22,7 +21,10 @@
|
|||
:sm="24"
|
||||
>
|
||||
<a-form-item label="关键词">
|
||||
<a-input v-model="queryParam.keyword" />
|
||||
<a-input
|
||||
v-model="queryParam.keyword"
|
||||
@keyup.enter="handleQuery()"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
|
@ -32,13 +34,15 @@
|
|||
<a-form-item label="存储位置">
|
||||
<a-select
|
||||
v-model="queryParam.attachmentType"
|
||||
@change="handleQuery"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in Object.keys(attachmentType)"
|
||||
v-for="item in types"
|
||||
:key="item"
|
||||
:value="item"
|
||||
>{{ attachmentType[item].text }}</a-select-option>
|
||||
>{{
|
||||
attachmentType[item].text
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -49,13 +53,15 @@
|
|||
<a-form-item label="文件类型">
|
||||
<a-select
|
||||
v-model="queryParam.mediaType"
|
||||
@change="handleQuery"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item,index) in mediaTypes"
|
||||
v-for="(item, index) in mediaTypes"
|
||||
:key="index"
|
||||
:value="item"
|
||||
>{{ item }}</a-select-option>
|
||||
>{{
|
||||
item
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -66,23 +72,48 @@
|
|||
<span class="table-page-search-submitButtons">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleQuery"
|
||||
@click="handleQuery()"
|
||||
>查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px;"
|
||||
@click="handleResetParam"
|
||||
@click="handleResetParam()"
|
||||
>重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="table-operator" style="margin-bottom: 0;">
|
||||
<div
|
||||
class="table-operator"
|
||||
style="margin-bottom: 0;"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="()=>this.uploadVisible = true"
|
||||
icon="cloud-upload"
|
||||
@click="() => (uploadVisible = true)"
|
||||
>上传</a-button>
|
||||
<a-button
|
||||
icon="select"
|
||||
v-show="!supportMultipleSelection"
|
||||
@click="handleMultipleSelection"
|
||||
>
|
||||
批量操作
|
||||
</a-button>
|
||||
<a-button
|
||||
type="danger"
|
||||
icon="delete"
|
||||
v-show="supportMultipleSelection"
|
||||
@click="handleDeleteAttachmentInBatch"
|
||||
>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
icon="close"
|
||||
v-show="supportMultipleSelection"
|
||||
@click="handleCancelMultipleSelection"
|
||||
>
|
||||
取消
|
||||
</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
@ -107,15 +138,23 @@
|
|||
<img
|
||||
:src="item.thumbPath"
|
||||
v-show="handleJudgeMediaType(item)"
|
||||
>
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<a-card-meta style="padding: 0.8rem;">
|
||||
<ellipsis
|
||||
:length="isMobile()?12:16"
|
||||
:length="isMobile() ? 12 : 16"
|
||||
tooltip
|
||||
slot="description"
|
||||
>{{ item.name }}</ellipsis>
|
||||
</a-card-meta>
|
||||
<a-checkbox
|
||||
class="select-attachment-checkbox"
|
||||
:style="getCheckStatus(item.id) ? selectedAttachmentStyle : ''"
|
||||
:checked="getCheckStatus(item.id)"
|
||||
@click="handleAttachmentSelectionChanged($event, item)"
|
||||
v-show="supportMultipleSelection"
|
||||
></a-checkbox>
|
||||
</a-card>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
|
@ -124,9 +163,10 @@
|
|||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
class="pagination"
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
:pageSizeOptions="['18', '36', '54','72','90','108']"
|
||||
:pageSizeOptions="['18', '36', '54', '72', '90', '108']"
|
||||
showSizeChanger
|
||||
@change="handlePaginationChange"
|
||||
@showSizeChange="handlePaginationChange"
|
||||
|
@ -149,7 +189,7 @@
|
|||
v-if="selectAttachment"
|
||||
:attachment="selectAttachment"
|
||||
:addToPhoto="true"
|
||||
@delete="()=>this.loadAttachments()"
|
||||
@delete="() => this.loadAttachments()"
|
||||
/>
|
||||
</page-view>
|
||||
</template>
|
||||
|
@ -159,6 +199,7 @@ import { mixin, mixinDevice } from '@/utils/mixin.js'
|
|||
import { PageView } from '@/layouts'
|
||||
import AttachmentDetailDrawer from './components/AttachmentDetailDrawer'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -171,9 +212,13 @@ export default {
|
|||
attachmentType: attachmentApi.type,
|
||||
listLoading: true,
|
||||
uploadVisible: false,
|
||||
supportMultipleSelection: false,
|
||||
selectedAttachmentCheckbox: {},
|
||||
batchSelectedAttachments: [],
|
||||
selectAttachment: {},
|
||||
attachments: [],
|
||||
mediaTypes: [],
|
||||
types: [],
|
||||
editable: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
@ -198,11 +243,17 @@ export default {
|
|||
attachment.typeProperty = this.attachmentType[attachment.type]
|
||||
return attachment
|
||||
})
|
||||
},
|
||||
selectedAttachmentStyle() {
|
||||
return {
|
||||
border: `2px solid ${this.color()}`
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadAttachments()
|
||||
this.loadMediaTypes()
|
||||
this.loadTypes()
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.drawerVisible) {
|
||||
|
@ -216,6 +267,7 @@ export default {
|
|||
next()
|
||||
},
|
||||
methods: {
|
||||
...mapGetters(['color']),
|
||||
loadAttachments() {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
|
@ -232,9 +284,18 @@ export default {
|
|||
this.mediaTypes = response.data.data
|
||||
})
|
||||
},
|
||||
loadTypes() {
|
||||
attachmentApi.getTypes().then(response => {
|
||||
this.types = response.data.data
|
||||
})
|
||||
},
|
||||
handleShowDetailDrawer(attachment) {
|
||||
this.selectAttachment = attachment
|
||||
this.drawerVisible = true
|
||||
if (this.supportMultipleSelection) {
|
||||
this.drawerVisible = false
|
||||
} else {
|
||||
this.drawerVisible = true
|
||||
}
|
||||
},
|
||||
handlePaginationChange(page, size) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${size}`)
|
||||
|
@ -246,18 +307,18 @@ export default {
|
|||
this.queryParam.keyword = null
|
||||
this.queryParam.mediaType = null
|
||||
this.queryParam.attachmentType = null
|
||||
this.loadAttachments()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
this.loadMediaTypes()
|
||||
this.loadTypes()
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParam.page = 0
|
||||
this.pagination.page = 1
|
||||
this.loadAttachments()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.loadAttachments()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
this.loadMediaTypes()
|
||||
this.loadTypes()
|
||||
},
|
||||
handleJudgeMediaType(attachment) {
|
||||
var mediaType = attachment.mediaType
|
||||
|
@ -275,6 +336,56 @@ export default {
|
|||
}
|
||||
// 没有获取到文件返回false
|
||||
return false
|
||||
},
|
||||
getCheckStatus(key) {
|
||||
return this.selectedAttachmentCheckbox[key] || false
|
||||
},
|
||||
handleMultipleSelection() {
|
||||
this.supportMultipleSelection = true
|
||||
// 不允许附件详情抽屉显示
|
||||
this.drawerVisible = false
|
||||
this.attachments.forEach(item => {
|
||||
this.$set(this.selectedAttachmentCheckbox, item.id, false)
|
||||
})
|
||||
},
|
||||
handleCancelMultipleSelection() {
|
||||
this.supportMultipleSelection = false
|
||||
this.drawerVisible = false
|
||||
this.batchSelectedAttachments = []
|
||||
for (var key in this.selectedCheckbox) {
|
||||
this.$set(this.selectedAttachmentCheckbox, key, false)
|
||||
}
|
||||
},
|
||||
handleAttachmentSelectionChanged(e, item) {
|
||||
var isChecked = e.target.checked || false
|
||||
if (isChecked) {
|
||||
this.$set(this.selectedAttachmentCheckbox, item.id, true)
|
||||
this.batchSelectedAttachments.push(item.id)
|
||||
} else {
|
||||
this.$set(this.selectedAttachmentCheckbox, item.id, false)
|
||||
// 从选中id集合中删除id
|
||||
var index = this.batchSelectedAttachments.indexOf(item.id)
|
||||
this.batchSelectedAttachments.splice(index, 1)
|
||||
}
|
||||
},
|
||||
handleDeleteAttachmentInBatch() {
|
||||
var that = this
|
||||
if (this.batchSelectedAttachments.length <= 0) {
|
||||
this.$message.success('你还未选择任何附件,请至少选择一个!')
|
||||
return
|
||||
}
|
||||
this.$confirm({
|
||||
title: '确定要批量删除选中的附件吗?',
|
||||
content: '一旦删除不可恢复,请谨慎操作',
|
||||
onOk() {
|
||||
attachmentApi.deleteInBatch(that.batchSelectedAttachments).then(res => {
|
||||
that.handleCancelMultipleSelection()
|
||||
that.loadAttachments()
|
||||
that.$message.success('删除成功')
|
||||
})
|
||||
},
|
||||
onCancel() {}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,22 +19,25 @@
|
|||
>
|
||||
<div class="attach-detail-img">
|
||||
<div v-show="nonsupportPreviewVisible">此文件不支持预览</div>
|
||||
<a :href="attachment.path" target="_blank">
|
||||
<a
|
||||
:href="attachment.path"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
:src="attachment.path"
|
||||
v-show="photoPreviewVisible"
|
||||
style="width: 100%;"
|
||||
loading="lazy"
|
||||
>
|
||||
</a>
|
||||
<video-player
|
||||
class="video-player-box"
|
||||
<d-player
|
||||
ref="player"
|
||||
:options="videoOptions"
|
||||
v-show="videoPreviewVisible"
|
||||
ref="videoPlayer"
|
||||
:options="playerOptions"
|
||||
:playsinline="true"
|
||||
class="video-player-box"
|
||||
style="width: 100%;"
|
||||
>
|
||||
</video-player>
|
||||
</d-player>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
</a-col>
|
||||
|
@ -162,16 +165,18 @@
|
|||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { videoPlayer } from 'vue-video-player'
|
||||
import 'video.js/dist/video-js.css'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import photoApi from '@/api/photo'
|
||||
import 'vue-dplayer/dist/vue-dplayer.css'
|
||||
import VueDPlayer from 'vue-dplayer'
|
||||
import flvjs from 'flv.js'
|
||||
window.flvjs = flvjs
|
||||
|
||||
export default {
|
||||
name: 'AttachmentDetailDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
videoPlayer
|
||||
'd-player': VueDPlayer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -181,24 +186,13 @@ export default {
|
|||
photoPreviewVisible: false,
|
||||
videoPreviewVisible: false,
|
||||
nonsupportPreviewVisible: false,
|
||||
playerOptions: {
|
||||
// videojs options
|
||||
muted: true,
|
||||
language: 'zh-CN',
|
||||
aspectRatio: '16:9',
|
||||
fluid: true,
|
||||
controls: true,
|
||||
loop: false,
|
||||
playbackRates: [0.7, 1.0, 1.5, 2.0],
|
||||
sources: [
|
||||
{
|
||||
type: 'video/mp4',
|
||||
src: 'https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm'
|
||||
}
|
||||
],
|
||||
poster: '/static/images/author.jpg',
|
||||
width: document.documentElement.clientWidth,
|
||||
notSupportedMessage: '此视频暂无法播放,请稍后再试'
|
||||
player: {},
|
||||
videoOptions: {
|
||||
lang: 'zh-cn',
|
||||
video: {
|
||||
url: '',
|
||||
type: 'auto'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -222,13 +216,8 @@ export default {
|
|||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
},
|
||||
computed: {
|
||||
player() {
|
||||
return this.$refs.videoPlayer.player
|
||||
}
|
||||
mounted() {
|
||||
this.player = this.$refs.player
|
||||
},
|
||||
watch: {
|
||||
visible: function(newValue, oldValue) {
|
||||
|
@ -302,8 +291,8 @@ export default {
|
|||
},
|
||||
handleAddToPhoto() {
|
||||
this.photo['name'] = this.attachment.name
|
||||
this.photo['thumbnail'] = this.attachment.thumbPath
|
||||
this.photo['url'] = this.attachment.path
|
||||
this.photo['thumbnail'] = encodeURI(this.attachment.thumbPath)
|
||||
this.photo['url'] = encodeURI(this.attachment.path)
|
||||
this.photo['takeTime'] = new Date().getTime()
|
||||
photoApi.create(this.photo).then(response => {
|
||||
this.$message.success('添加成功!')
|
||||
|
@ -320,39 +309,29 @@ export default {
|
|||
var prefix = mediaType.split('/')[0]
|
||||
|
||||
if (prefix === 'video' || prefix === 'flv') {
|
||||
this.videoPreviewVisible = true
|
||||
this.photoPreviewVisible = false
|
||||
this.nonsupportPreviewVisible = false
|
||||
// 控制各个组件的显示
|
||||
this.handlePreviewVisible(false, true, false)
|
||||
|
||||
// 去除视频地址后面的参数
|
||||
var lastIndex = attachment.path.lastIndexOf('?')
|
||||
var path = attachment.path.substring(0, lastIndex)
|
||||
|
||||
// 设置视频地址
|
||||
this.$set(this.playerOptions.sources, 0, {
|
||||
type: mediaType,
|
||||
src: attachment.path
|
||||
})
|
||||
console.log(this.playerOptions.sources)
|
||||
this.$set(this.videoOptions.video, 'url', path)
|
||||
this.$log.debug('video url', path)
|
||||
} else if (prefix === 'image') {
|
||||
this.photoPreviewVisible = true
|
||||
this.videoPreviewVisible = false
|
||||
this.nonsupportPreviewVisible = false
|
||||
this.handlePreviewVisible(true, false, false)
|
||||
} else {
|
||||
this.nonsupportPreviewVisible = true
|
||||
this.videoPreviewVisible = false
|
||||
this.photoPreviewVisible = false
|
||||
this.handlePreviewVisible(false, false, true)
|
||||
}
|
||||
}
|
||||
},
|
||||
handlePreviewVisible(photo, video, nonsupport) {
|
||||
// 为了更好的使vue监听到组件变化及时刷新,方式修改组件后需要刷新才能显示一下部分
|
||||
this.$set(this, 'photoPreviewVisible', photo)
|
||||
this.$set(this, 'videoPreviewVisible', video)
|
||||
this.$set(this, 'nonsupportPreviewVisible', nonsupport)
|
||||
}
|
||||
// handleDownLoadPhoto(attachment) {
|
||||
// var path = attachment.path
|
||||
|
||||
// var index = path.lastIndexOf('/')
|
||||
// var filename = path.substr(index+1, path.length)
|
||||
// // chrome/firefox
|
||||
// var aTag = document.createElement('a')
|
||||
// aTag.download = filename
|
||||
// aTag.href = path//URL.createObjectURL(blob)
|
||||
// aTag.target = '_blank'
|
||||
// aTag.click()
|
||||
// URL.revokeObjectURL(aTag.href)
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<a-input-search
|
||||
placeholder="搜索附件"
|
||||
v-model="queryParam.keyword"
|
||||
@search="loadAttachments(true)"
|
||||
@search="handleQuery()"
|
||||
enterButton
|
||||
/>
|
||||
</a-row>
|
||||
|
@ -30,7 +30,7 @@
|
|||
:paragraph="{ rows: 18 }"
|
||||
>
|
||||
<a-col :span="24">
|
||||
<a-empty v-if="formattedDatas.length==0"/>
|
||||
<a-empty v-if="formattedDatas.length==0" />
|
||||
<div
|
||||
v-else
|
||||
class="attach-item"
|
||||
|
@ -42,6 +42,7 @@
|
|||
<img
|
||||
:src="item.thumbPath"
|
||||
v-show="handleJudgeMediaType(item)"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
|
@ -50,8 +51,9 @@
|
|||
<a-divider />
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:defaultPageSize="pagination.size"
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
@change="handlePaginationChange"
|
||||
></a-pagination>
|
||||
</div>
|
||||
|
@ -122,7 +124,7 @@ export default {
|
|||
},
|
||||
queryParam: {
|
||||
page: 0,
|
||||
size: 18,
|
||||
size: 12,
|
||||
sort: null,
|
||||
keyword: null
|
||||
},
|
||||
|
@ -139,14 +141,11 @@ export default {
|
|||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
},
|
||||
watch: {
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -165,18 +164,18 @@ export default {
|
|||
this.$log.debug('Show detail of', attachment)
|
||||
this.detailVisible = true
|
||||
},
|
||||
loadAttachments(isSearch) {
|
||||
loadAttachments() {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
if (isSearch) {
|
||||
this.queryParam.page = 0
|
||||
}
|
||||
attachmentApi.query(this.queryParam).then(response => {
|
||||
this.attachments = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
|
@ -185,7 +184,7 @@ export default {
|
|||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleDelete() {
|
||||
this.loadAttachments()
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
>
|
||||
<a-input-search
|
||||
placeholder="搜索附件"
|
||||
v-model="queryParam.keyword"
|
||||
@search="handleQuery()"
|
||||
enterButton
|
||||
/>
|
||||
</a-row>
|
||||
|
@ -28,7 +30,7 @@
|
|||
:paragraph="{ rows: 18 }"
|
||||
>
|
||||
<a-col :span="24">
|
||||
<a-empty v-if="attachments.length==0"/>
|
||||
<a-empty v-if="attachments.length==0" />
|
||||
<div
|
||||
v-else
|
||||
class="attach-item"
|
||||
|
@ -40,6 +42,7 @@
|
|||
<img
|
||||
:src="item.thumbPath"
|
||||
v-show="handleJudgeMediaType(item)"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
|
@ -48,8 +51,9 @@
|
|||
<a-divider />
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:defaultPageSize="pagination.size"
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
@change="handlePaginationChange"
|
||||
></a-pagination>
|
||||
</div>
|
||||
|
@ -125,18 +129,21 @@ export default {
|
|||
size: 12,
|
||||
sort: ''
|
||||
},
|
||||
queryParam: {
|
||||
page: 0,
|
||||
size: 12,
|
||||
sort: null,
|
||||
keyword: null
|
||||
},
|
||||
attachments: [],
|
||||
uploadHandler: attachmentApi.upload
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
},
|
||||
watch: {
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -151,13 +158,17 @@ export default {
|
|||
this.uploadVisible = true
|
||||
},
|
||||
loadAttachments() {
|
||||
const pagination = Object.assign({}, this.pagination)
|
||||
pagination.page--
|
||||
attachmentApi.query(pagination).then(response => {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
attachmentApi.query(this.queryParam).then(response => {
|
||||
this.attachments = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleSelectAttachment(item) {
|
||||
this.$emit('listenToSelect', item)
|
||||
},
|
||||
|
@ -169,14 +180,10 @@ export default {
|
|||
this.pagination.size = pageSize
|
||||
this.loadAttachments()
|
||||
},
|
||||
handleAttachmentUploadSuccess() {
|
||||
this.$message.success('上传成功!')
|
||||
this.loadAttachments()
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleJudgeMediaType(attachment) {
|
||||
var mediaType = attachment.mediaType
|
||||
|
|
|
@ -163,9 +163,6 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
:sm="24"
|
||||
>
|
||||
<a-form-item label="关键词">
|
||||
<a-input v-model="queryParam.keyword" />
|
||||
<a-input
|
||||
v-model="queryParam.keyword"
|
||||
@keyup.enter="handleQuery()"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
|
@ -23,7 +26,7 @@
|
|||
<a-select
|
||||
v-model="queryParam.status"
|
||||
placeholder="请选择评论状态"
|
||||
@change="handleQuery"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="status in Object.keys(commentStatus)"
|
||||
|
@ -41,11 +44,11 @@
|
|||
<span class="table-page-search-submitButtons">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleQuery"
|
||||
@click="handleQuery()"
|
||||
>查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px;"
|
||||
@click="handleResetParam"
|
||||
@click="handleResetParam()"
|
||||
>重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
|
@ -62,7 +65,7 @@
|
|||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handlePublishMore"
|
||||
@click="handleEditStatusMore(commentStatus.PUBLISHED.value)"
|
||||
>
|
||||
通过
|
||||
</a>
|
||||
|
@ -73,7 +76,7 @@
|
|||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleRecycleMore"
|
||||
@click="handleEditStatusMore(commentStatus.RECYCLE.value)"
|
||||
>
|
||||
移到回收站
|
||||
</a>
|
||||
|
@ -234,6 +237,7 @@
|
|||
v-else
|
||||
:rowKey="comment => comment.id"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: onSelectionChange,
|
||||
getCheckboxProps: getCheckboxProps
|
||||
}"
|
||||
|
@ -377,7 +381,9 @@
|
|||
<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"
|
||||
|
@ -388,8 +394,8 @@
|
|||
</a-card>
|
||||
|
||||
<a-modal
|
||||
v-if="selectComment"
|
||||
:title="'回复给:'+selectComment.author"
|
||||
v-if="selectedComment"
|
||||
:title="'回复给:'+selectedComment.author"
|
||||
v-model="replyCommentVisible"
|
||||
@close="onReplyClose"
|
||||
destroyOnClose
|
||||
|
@ -415,8 +421,8 @@
|
|||
</a-modal>
|
||||
<!-- <CommentDetail
|
||||
v-model="commentDetailVisible"
|
||||
v-if="selectComment"
|
||||
:comment="selectComment"
|
||||
v-if="selectedComment"
|
||||
:comment="selectedComment"
|
||||
:type="this.type"
|
||||
/> -->
|
||||
</div>
|
||||
|
@ -431,6 +437,7 @@ const postColumns = [
|
|||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'author',
|
||||
width: '150px',
|
||||
scopedSlots: { customRender: 'author' }
|
||||
},
|
||||
{
|
||||
|
@ -468,6 +475,7 @@ const sheetColumns = [
|
|||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'author',
|
||||
width: '150px',
|
||||
scopedSlots: { customRender: 'author' }
|
||||
},
|
||||
{
|
||||
|
@ -522,8 +530,8 @@ export default {
|
|||
columns: this.type === 'posts' ? postColumns : sheetColumns,
|
||||
replyCommentVisible: false,
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: null
|
||||
},
|
||||
queryParam: {
|
||||
|
@ -536,7 +544,7 @@ export default {
|
|||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
comments: [],
|
||||
selectComment: {},
|
||||
selectedComment: {},
|
||||
replyComment: {},
|
||||
loading: false,
|
||||
commentStatus: commentApi.commentStatus,
|
||||
|
@ -550,7 +558,7 @@ export default {
|
|||
formattedComments() {
|
||||
return this.comments.map(comment => {
|
||||
comment.statusProperty = this.commentStatus[comment.status]
|
||||
comment.content = marked(comment.content, { sanitize: true })
|
||||
comment.content = marked(comment.content)
|
||||
return comment
|
||||
})
|
||||
},
|
||||
|
@ -559,8 +567,8 @@ export default {
|
|||
methods: {
|
||||
loadComments() {
|
||||
this.loading = true
|
||||
this.queryParam.page = this.pagination.current - 1
|
||||
this.queryParam.size = this.pagination.pageSize
|
||||
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
|
||||
|
@ -569,9 +577,8 @@ export default {
|
|||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParam.page = 0
|
||||
this.pagination.current = 1
|
||||
this.loadComments()
|
||||
this.handleClearRowKeys()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleEditStatusClick(commentId, status) {
|
||||
commentApi.updateStatus(this.type, commentId, status).then(response => {
|
||||
|
@ -590,7 +597,7 @@ export default {
|
|||
this.handleEditStatusClick(comment.id, 'PUBLISHED')
|
||||
},
|
||||
handleReplyClick(comment) {
|
||||
this.selectComment = comment
|
||||
this.selectedComment = comment
|
||||
this.replyCommentVisible = true
|
||||
this.replyComment.parentId = comment.id
|
||||
if (this.type === 'posts') {
|
||||
|
@ -610,67 +617,51 @@ export default {
|
|||
commentApi.create(this.type, this.replyComment).then(response => {
|
||||
this.$message.success('回复成功!')
|
||||
this.replyComment = {}
|
||||
this.selectComment = {}
|
||||
this.selectedComment = {}
|
||||
this.replyCommentVisible = false
|
||||
this.loadComments()
|
||||
})
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.pagination.current = page
|
||||
this.pagination.pageSize = pageSize
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
this.loadComments()
|
||||
},
|
||||
handleResetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.status = null
|
||||
this.loadComments()
|
||||
this.handleClearRowKeys()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handlePublishMore() {
|
||||
handleEditStatusMore(status) {
|
||||
if (this.selectedRowKeys.length <= 0) {
|
||||
this.$message.success('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
||||
const element = this.selectedRowKeys[index]
|
||||
commentApi.updateStatus(this.type, element, 'PUBLISHED').then(response => {
|
||||
this.$log.debug(`commentId: ${element}, status: PUBLISHED`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadComments()
|
||||
})
|
||||
}
|
||||
},
|
||||
handleRecycleMore() {
|
||||
if (this.selectedRowKeys.length <= 0) {
|
||||
this.$message.success('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
||||
const element = this.selectedRowKeys[index]
|
||||
commentApi.updateStatus(this.type, element, 'RECYCLE').then(response => {
|
||||
this.$log.debug(`commentId: ${element}, status: RECYCLE`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadComments()
|
||||
})
|
||||
}
|
||||
commentApi.updateStatusInBatch(this.type, this.selectedRowKeys, status).then(response => {
|
||||
this.$log.debug(`commentIds: ${this.selectedRowKeys}, status: ${status}`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadComments()
|
||||
})
|
||||
},
|
||||
handleDeleteMore() {
|
||||
if (this.selectedRowKeys.length <= 0) {
|
||||
this.$message.success('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
||||
const element = this.selectedRowKeys[index]
|
||||
commentApi.delete(this.type, element).then(response => {
|
||||
this.$log.debug(`delete: ${element}`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadComments()
|
||||
})
|
||||
}
|
||||
commentApi.deleteInBatch(this.type, this.selectedRowKeys).then(response => {
|
||||
this.$log.debug(`delete: ${this.selectedRowKeys}`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadComments()
|
||||
})
|
||||
},
|
||||
handleClearRowKeys() {
|
||||
this.selectedRowKeys = []
|
||||
},
|
||||
onReplyClose() {
|
||||
this.replyComment = {}
|
||||
this.selectComment = {}
|
||||
this.selectedComment = {}
|
||||
this.replyCommentVisible = false
|
||||
},
|
||||
onSelectionChange(selectedRowKeys) {
|
||||
|
@ -680,13 +671,13 @@ export default {
|
|||
getCheckboxProps(comment) {
|
||||
return {
|
||||
props: {
|
||||
disabled: comment.status === 'RECYCLE',
|
||||
disabled: this.queryParam.status == null || this.queryParam.status === '',
|
||||
name: comment.author
|
||||
}
|
||||
}
|
||||
},
|
||||
handleShowDetailDrawer(comment) {
|
||||
this.selectComment = comment
|
||||
this.selectedComment = comment
|
||||
this.commentDetailVisible = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="评论列表"
|
||||
:width="isMobile() ? '100%' : '460'"
|
||||
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>
|
||||
<template slot="description">
|
||||
<p
|
||||
v-html="description"
|
||||
class="comment-drawer-content"
|
||||
></p>
|
||||
</template>
|
||||
<h3 slot="title">{{ title }}</h3>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-col>
|
||||
<a-divider />
|
||||
<a-col :span="24">
|
||||
<a-empty v-if="comments.length == 0" />
|
||||
<TargetCommentTree
|
||||
v-else
|
||||
v-for="(comment, index) in comments"
|
||||
:key="index"
|
||||
:comment="comment"
|
||||
@reply="handleCommentReply"
|
||||
@delete="handleCommentDelete"
|
||||
@editStatus="handleEditStatusClick"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider />
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
@change="handlePaginationChange"
|
||||
></a-pagination>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleComment"
|
||||
>评论</a-button>
|
||||
</div>
|
||||
<a-modal
|
||||
v-if="selectedComment"
|
||||
:title="'回复给:' + selectedComment.author"
|
||||
v-model="replyCommentVisible"
|
||||
@close="onReplyClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
@click="handleCreateClick"
|
||||
>
|
||||
回复
|
||||
</a-button>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 8 }"
|
||||
v-model="replyComment.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
title="评论"
|
||||
v-model="commentVisible"
|
||||
@close="onCommentClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
@click="handleCreateClick"
|
||||
>
|
||||
回复
|
||||
</a-button>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 8 }"
|
||||
v-model="replyComment.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import TargetCommentTree from './TargetCommentTree'
|
||||
import commentApi from '@/api/comment'
|
||||
export default {
|
||||
name: 'TargetCommentDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: { TargetCommentTree },
|
||||
data() {
|
||||
return {
|
||||
comments: [],
|
||||
selectedComment: {},
|
||||
replyComment: {},
|
||||
replyCommentVisible: false,
|
||||
commentVisible: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: ''
|
||||
},
|
||||
queryParam: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
sort: null,
|
||||
keyword: null
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(newValue, oldValue) {
|
||||
this.$log.debug('old value', oldValue)
|
||||
this.$log.debug('new value', newValue)
|
||||
if (newValue) {
|
||||
this.loadComments()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadComments() {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
commentApi.commentTree(this.target, this.id, this.queryParam).then(response => {
|
||||
this.comments = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
})
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
this.loadComments()
|
||||
},
|
||||
handleCommentReply(comment) {
|
||||
this.selectedComment = comment
|
||||
this.replyCommentVisible = true
|
||||
this.replyComment.parentId = comment.id
|
||||
this.replyComment.postId = this.id
|
||||
},
|
||||
handleComment() {
|
||||
this.replyComment.postId = this.id
|
||||
this.commentVisible = true
|
||||
},
|
||||
handleCreateClick() {
|
||||
if (!this.replyComment.content) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '评论内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
commentApi.create(this.target, this.replyComment).then(response => {
|
||||
this.$message.success('回复成功!')
|
||||
this.replyComment = {}
|
||||
this.selectedComment = {}
|
||||
this.replyCommentVisible = false
|
||||
this.commentVisible = false
|
||||
this.loadComments()
|
||||
})
|
||||
},
|
||||
handleEditStatusClick(comment, status) {
|
||||
commentApi.updateStatus(this.target, comment.id, status).then(response => {
|
||||
this.$message.success('操作成功!')
|
||||
this.loadComments()
|
||||
})
|
||||
},
|
||||
handleCommentDelete(comment) {
|
||||
commentApi.delete(this.target, comment.id).then(response => {
|
||||
this.$message.success('删除成功!')
|
||||
this.loadComments()
|
||||
})
|
||||
},
|
||||
onReplyClose() {
|
||||
this.replyComment = {}
|
||||
this.selectedComment = {}
|
||||
this.replyCommentVisible = false
|
||||
},
|
||||
onCommentClose() {
|
||||
this.replyComment = {}
|
||||
this.commentVisible = false
|
||||
},
|
||||
onClose() {
|
||||
this.comments = []
|
||||
this.pagination = {
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: ''
|
||||
}
|
||||
this.$emit('close', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-comment>
|
||||
<template slot="actions">
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
v-if="comment.status === 'AUDITING'"
|
||||
>
|
||||
<span href="javascript:void(0);">通过</span>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1">
|
||||
<span
|
||||
href="javascript:void(0);"
|
||||
@click="handleEditStatusClick('PUBLISHED')"
|
||||
>通过</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="2">
|
||||
<span href="javascript:void(0);">通过并回复</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
|
||||
<span
|
||||
v-else-if="comment.status === 'PUBLISHED'"
|
||||
@click="handleReplyClick"
|
||||
>回复</span>
|
||||
|
||||
<a-popconfirm
|
||||
v-else-if="comment.status === 'RECYCLE'"
|
||||
:title="'你确定要还原该评论?'"
|
||||
@confirm="handleEditStatusClick('PUBLISHED')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<span>还原</span>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-popconfirm
|
||||
v-if="comment.status === 'PUBLISHED' || comment.status === 'AUDITING'"
|
||||
:title="'你确定要将该评论移到回收站?'"
|
||||
@confirm="handleEditStatusClick('RECYCLE')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<span>回收站</span>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-popconfirm
|
||||
:title="'你确定要永久删除该评论?'"
|
||||
@confirm="handleDeleteClick"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<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;"
|
||||
/>
|
||||
{{ comment.author }}
|
||||
</a>
|
||||
<a-avatar
|
||||
size="large"
|
||||
slot="avatar"
|
||||
:src="avatar"
|
||||
:alt="comment.author"
|
||||
/>
|
||||
<p
|
||||
slot="content"
|
||||
v-html="content"
|
||||
></p>
|
||||
<a-tooltip slot="datetime">
|
||||
<span slot="title">{{ comment.createTime | moment }}</span>
|
||||
<span>{{ comment.createTime | timeAgo }}</span>
|
||||
</a-tooltip>
|
||||
<template v-if="comment.children">
|
||||
<TargetCommentTree
|
||||
v-for="(child, index) in comment.children"
|
||||
:key="index"
|
||||
:comment="child"
|
||||
v-on="$listeners"
|
||||
v-bind="$attrs"
|
||||
@reply="handleReplyClick"
|
||||
@delete="handleDeleteClick"
|
||||
@editStatus="handleEditStatusClick"
|
||||
/>
|
||||
</template>
|
||||
</a-comment>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import marked from 'marked'
|
||||
export default {
|
||||
name: 'TargetCommentTree',
|
||||
props: {
|
||||
comment: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
avatar() {
|
||||
return `//cn.gravatar.com/avatar/${this.comment.gravatarMd5}/?s=256&d=mp`
|
||||
},
|
||||
content() {
|
||||
return marked(this.comment.content)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleReplyClick() {
|
||||
this.$emit('reply', this.comment)
|
||||
},
|
||||
handleEditStatusClick(status) {
|
||||
this.$emit('editStatus', this.comment, status)
|
||||
},
|
||||
handleDeleteClick() {
|
||||
this.$emit('delete', this.comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -12,7 +12,7 @@
|
|||
<analysis-card
|
||||
:loading="countsLoading"
|
||||
title="文章"
|
||||
:number="countsData.postCount"
|
||||
:number="statisticsData.postCount"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name:'PostList' }"
|
||||
|
@ -33,7 +33,7 @@
|
|||
<analysis-card
|
||||
:loading="countsLoading"
|
||||
title="评论"
|
||||
:number="countsData.commentCount"
|
||||
:number="statisticsData.commentCount"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name:'Comments' }"
|
||||
|
@ -51,27 +51,20 @@
|
|||
:xs="12"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<!-- <analysis-card :loading="countsLoading" title="总访问" :number="countsData.visitCount">
|
||||
<a-tooltip slot="action">
|
||||
<template slot="title">文章总访问共 {{ countsData.visitCount }} 次</template>-->
|
||||
<analysis-card
|
||||
:loading="countsLoading"
|
||||
title="总访问"
|
||||
:number="countsData.visitCount"
|
||||
:number="statisticsData.visitCount"
|
||||
>
|
||||
<a-tooltip slot="action">
|
||||
<template slot="title">
|
||||
文章总访问共
|
||||
<countTo
|
||||
:startVal="0"
|
||||
:endVal="countsData.visitCount"
|
||||
:endVal="statisticsData.visitCount"
|
||||
:duration="3000"
|
||||
></countTo>次
|
||||
</template>
|
||||
<!-- <countTo :startVal="0" :endVal="countsData.visitCount" :duration="3000"></countTo> -->
|
||||
<!-- <template>
|
||||
<countTo :startVal="0" :endVal="countsData.visitCount" :duration="3000"></countTo>
|
||||
</template>-->
|
||||
<a href="javascript:void(0);">
|
||||
<a-icon type="info-circle-o" />
|
||||
</a>
|
||||
|
@ -89,10 +82,10 @@
|
|||
<analysis-card
|
||||
:loading="countsLoading"
|
||||
title="建立天数"
|
||||
:number="countsData.establishDays"
|
||||
:number="statisticsData.establishDays"
|
||||
>
|
||||
<a-tooltip slot="action">
|
||||
<template slot="title">博客建立于 {{ countsData.birthday | moment }}</template>
|
||||
<template slot="title">博客建立于 {{ statisticsData.birthday | moment }}</template>
|
||||
<a href="javascript:void(0);">
|
||||
<a-icon type="info-circle-o" />
|
||||
</a>
|
||||
|
@ -129,17 +122,11 @@
|
|||
>
|
||||
<a-list-item-meta>
|
||||
<a
|
||||
v-if="item.status=='PUBLISHED'"
|
||||
v-if="item.status=='PUBLISHED' || item.status == 'INTIMATE'"
|
||||
slot="title"
|
||||
:href="options.blog_url+'/archives/'+item.url"
|
||||
target="_blank"
|
||||
>{{ item.title }}</a>
|
||||
<a
|
||||
v-else-if="item.status == 'INTIMATE'"
|
||||
slot="title"
|
||||
:href="options.blog_url+'/archives/'+item.url+'/password'"
|
||||
target="_blank"
|
||||
>{{ item.title }}</a>
|
||||
<a
|
||||
v-else-if="item.status=='DRAFT'"
|
||||
slot="title"
|
||||
|
@ -216,30 +203,15 @@
|
|||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 8 }"
|
||||
v-model="journal.content"
|
||||
v-model="journal.sourceContent"
|
||||
placeholder="写点什么吧..."
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 日志图片上传 -->
|
||||
<!-- <a-form-item v-show="showMoreOptions">
|
||||
<UploadPhoto
|
||||
@success="handlerPhotoUploadSuccess"
|
||||
:photoList="photoList"
|
||||
></UploadPhoto>
|
||||
</a-form-item> -->
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleCreateJournalClick"
|
||||
>保存</a-button>
|
||||
<!-- <a
|
||||
href="javascript:;"
|
||||
class="more-options-btn"
|
||||
type="default"
|
||||
@click="handleUploadPhotoWallClick"
|
||||
>更多选项<a-icon type="down" /></a> -->
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
@ -295,16 +267,16 @@
|
|||
destroyOnClose
|
||||
@close="()=>this.logDrawerVisible = false"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="logsLoading"
|
||||
:paragraph="{rows: 18}"
|
||||
<a-row
|
||||
type="flex"
|
||||
align="middle"
|
||||
>
|
||||
<a-row
|
||||
type="flex"
|
||||
align="middle"
|
||||
>
|
||||
<a-col :span="24">
|
||||
<a-col :span="24">
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="logsLoading"
|
||||
:paragraph="{rows: 18}"
|
||||
>
|
||||
<a-list :dataSource="formattedLogsDatas">
|
||||
<a-list-item
|
||||
slot="renderItem"
|
||||
|
@ -316,22 +288,23 @@
|
|||
</a-list-item-meta>
|
||||
<div>{{ item.content }}</div>
|
||||
</a-list-item>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
class="pagination"
|
||||
:total="logPagination.total"
|
||||
:defaultPageSize="logPagination.size"
|
||||
:pageSizeOptions="['50', '100','150','200']"
|
||||
showSizeChanger
|
||||
@showSizeChange="onPaginationChange"
|
||||
@change="onPaginationChange"
|
||||
/>
|
||||
</div>
|
||||
</a-list>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-skeleton>
|
||||
</a-skeleton>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
class="pagination"
|
||||
:current="logPagination.page"
|
||||
:total="logPagination.total"
|
||||
:defaultPageSize="logPagination.size"
|
||||
:pageSizeOptions="['50', '100','150','200']"
|
||||
showSizeChanger
|
||||
@showSizeChange="handlePaginationChange"
|
||||
@change="handlePaginationChange"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-popconfirm
|
||||
|
@ -354,11 +327,10 @@ import { PageView } from '@/layouts'
|
|||
import AnalysisCard from './components/AnalysisCard'
|
||||
import RecentCommentTab from './components/RecentCommentTab'
|
||||
import countTo from 'vue-count-to'
|
||||
import UploadPhoto from '../../components/Upload/UploadPhoto.vue'
|
||||
|
||||
import postApi from '@/api/post'
|
||||
import logApi from '@/api/log'
|
||||
import adminApi from '@/api/admin'
|
||||
import statisticsApi from '@/api/statistics'
|
||||
import journalApi from '@/api/journal'
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
|
@ -367,13 +339,10 @@ export default {
|
|||
PageView,
|
||||
AnalysisCard,
|
||||
RecentCommentTab,
|
||||
countTo,
|
||||
UploadPhoto
|
||||
countTo
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
photoList: [],
|
||||
// showMoreOptions: false,
|
||||
startVal: 0,
|
||||
logType: logApi.logType,
|
||||
activityLoading: true,
|
||||
|
@ -384,23 +353,27 @@ export default {
|
|||
logDrawerVisible: false,
|
||||
postData: [],
|
||||
logData: [],
|
||||
countsData: {},
|
||||
statisticsData: {},
|
||||
journal: {
|
||||
content: '',
|
||||
photos: []
|
||||
},
|
||||
journalPhotos: [], // 日志图片集合最多九张
|
||||
logs: [],
|
||||
logPagination: {
|
||||
page: 1,
|
||||
size: 50,
|
||||
sort: null
|
||||
},
|
||||
logQueryParam: {
|
||||
page: 0,
|
||||
size: 50,
|
||||
sort: null
|
||||
},
|
||||
interval: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCounts()
|
||||
this.getStatistics()
|
||||
this.listLatestPosts()
|
||||
this.listLatestLogs()
|
||||
},
|
||||
|
@ -434,7 +407,7 @@ export default {
|
|||
beforeRouteEnter(to, from, next) {
|
||||
next(vm => {
|
||||
vm.interval = setInterval(() => {
|
||||
vm.getCounts()
|
||||
vm.getStatistics()
|
||||
}, 5000)
|
||||
})
|
||||
},
|
||||
|
@ -450,18 +423,6 @@ export default {
|
|||
next()
|
||||
},
|
||||
methods: {
|
||||
// handlerPhotoUploadSuccess(response, file) {
|
||||
// var callData = response.data.data
|
||||
// var photo = {
|
||||
// name: callData.name,
|
||||
// url: callData.path,
|
||||
// thumbnail: callData.thumbPath,
|
||||
// suffix: callData.suffix,
|
||||
// width: callData.width,
|
||||
// height: callData.height
|
||||
// }
|
||||
// this.journalPhotos.push(photo)
|
||||
// },
|
||||
listLatestPosts() {
|
||||
postApi.listLatest(5).then(response => {
|
||||
this.postData = response.data.data
|
||||
|
@ -475,9 +436,9 @@ export default {
|
|||
this.writeLoading = false
|
||||
})
|
||||
},
|
||||
getCounts() {
|
||||
adminApi.counts().then(response => {
|
||||
this.countsData = response.data.data
|
||||
getStatistics() {
|
||||
statisticsApi.statistics().then(response => {
|
||||
this.statisticsData = response.data.data
|
||||
this.countsLoading = false
|
||||
})
|
||||
},
|
||||
|
@ -485,9 +446,7 @@ export default {
|
|||
this.$router.push({ name: 'PostEdit', query: { postId: post.id } })
|
||||
},
|
||||
handleCreateJournalClick() {
|
||||
// 给属性填充数据
|
||||
// this.journal.photos = this.journalPhotos
|
||||
if (!this.journal.content) {
|
||||
if (!this.journal.sourceContent) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '内容不能为空!'
|
||||
|
@ -497,14 +456,8 @@ export default {
|
|||
journalApi.create(this.journal).then(response => {
|
||||
this.$message.success('发表成功!')
|
||||
this.journal = {}
|
||||
// this.photoList = []
|
||||
// this.showMoreOptions = false
|
||||
})
|
||||
},
|
||||
// handleUploadPhotoWallClick() {
|
||||
// // 是否显示上传照片墙组件
|
||||
// this.showMoreOptions = !this.showMoreOptions
|
||||
// },
|
||||
handleShowLogDrawer() {
|
||||
this.logDrawerVisible = true
|
||||
this.loadLogs()
|
||||
|
@ -514,8 +467,10 @@ export default {
|
|||
setTimeout(() => {
|
||||
this.logsLoading = false
|
||||
}, 500)
|
||||
this.logPagination.page = this.logPagination.page - 1
|
||||
logApi.pageBy(this.logPagination).then(response => {
|
||||
this.logQueryParam.page = this.logPagination.page - 1
|
||||
this.logQueryParam.size = this.logPagination.size
|
||||
this.logQueryParam.sort = this.logPagination.sort
|
||||
logApi.pageBy(this.logQueryParam).then(response => {
|
||||
this.logs = response.data.data.content
|
||||
this.logPagination.total = response.data.data.total
|
||||
})
|
||||
|
@ -532,7 +487,7 @@ export default {
|
|||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
onPaginationChange(page, pageSize) {
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.logPagination.page = page
|
||||
this.logPagination.size = pageSize
|
||||
|
@ -541,13 +496,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* .more-options-btn {
|
||||
margin-left: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
} */
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<a-card
|
||||
:loading="loading"
|
||||
:body-style="{ padding: '18px 24px 18px' }"
|
||||
:body-style="{ padding: '24px' }"
|
||||
:bordered="false"
|
||||
>
|
||||
<div class="analysis-card-container">
|
||||
|
|
|
@ -18,13 +18,9 @@
|
|||
:href="item.authorUrl"
|
||||
target="_blank"
|
||||
>{{ item.author }}</a> 发表在 《<a
|
||||
v-if="item.post.status=='PUBLISHED'"
|
||||
v-if="item.post.status=='PUBLISHED' || item.post.status=='INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+item.post.url"
|
||||
target="_blank"
|
||||
>{{ item.post.title }}</a><a
|
||||
v-else-if="item.post.status=='INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+item.post.url+'/password'"
|
||||
target="_blank"
|
||||
>{{ item.post.title }}</a><a
|
||||
v-else-if="item.post.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
|
@ -102,7 +98,7 @@ export default {
|
|||
computed: {
|
||||
formmatedCommentData() {
|
||||
return this.comments.map(comment => {
|
||||
comment.content = marked(comment.content, { sanitize: true })
|
||||
comment.content = marked(comment.content)
|
||||
return comment
|
||||
})
|
||||
},
|
||||
|
|
|
@ -39,53 +39,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.exception {
|
||||
min-height: 500px;
|
||||
height: 80%;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin-top: 150px;
|
||||
.img {
|
||||
display: inline-block;
|
||||
padding-right: 52px;
|
||||
zoom: 1;
|
||||
img {
|
||||
height: 360px;
|
||||
max-width: 430px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
display: inline-block;
|
||||
flex: auto;
|
||||
h1 {
|
||||
color: #434e59;
|
||||
font-size: 72px;
|
||||
font-weight: 600;
|
||||
line-height: 72px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.desc {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.exception {
|
||||
margin-top: 30px;
|
||||
.img {
|
||||
padding-right: unset;
|
||||
|
||||
img {
|
||||
height: 40%;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:xl="10"
|
||||
|
@ -49,7 +49,11 @@
|
|||
label="分组:"
|
||||
:style="{ display: fieldExpand ? 'block' : 'none' }"
|
||||
>
|
||||
<a-input v-model="menuToCreate.team" />
|
||||
<a-auto-complete
|
||||
:dataSource="teams"
|
||||
v-model="menuToCreate.team"
|
||||
allowClear
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="打开方式:"
|
||||
|
@ -243,7 +247,8 @@ export default {
|
|||
menuToCreate: {
|
||||
target: '_self'
|
||||
},
|
||||
fieldExpand: false
|
||||
fieldExpand: false,
|
||||
teams: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -256,6 +261,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.loadMenus()
|
||||
this.loadTeams()
|
||||
},
|
||||
methods: {
|
||||
loadMenus() {
|
||||
|
@ -265,6 +271,11 @@ export default {
|
|||
this.loading = false
|
||||
})
|
||||
},
|
||||
loadTeams() {
|
||||
menuApi.listTeams().then(response => {
|
||||
this.teams = response.data.data
|
||||
})
|
||||
},
|
||||
handleSaveClick() {
|
||||
this.createOrUpdateMenu()
|
||||
},
|
||||
|
@ -280,18 +291,35 @@ export default {
|
|||
menuApi.delete(id).then(response => {
|
||||
this.$message.success('删除成功!')
|
||||
this.loadMenus()
|
||||
this.loadTeams()
|
||||
})
|
||||
},
|
||||
createOrUpdateMenu() {
|
||||
if (!this.menuToCreate.name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '菜单名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.menuToCreate.url) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '菜单地址不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.menuToCreate.id) {
|
||||
menuApi.update(this.menuToCreate.id, this.menuToCreate).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
this.loadMenus()
|
||||
this.loadTeams()
|
||||
})
|
||||
} else {
|
||||
menuApi.create(this.menuToCreate).then(response => {
|
||||
this.$message.success('保存成功!')
|
||||
this.loadMenus()
|
||||
this.loadTeams()
|
||||
})
|
||||
}
|
||||
this.handleAddMenu()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:xl="18"
|
||||
|
@ -14,7 +14,7 @@
|
|||
<a-form-item>
|
||||
<codemirror
|
||||
v-model="content"
|
||||
:options="options"
|
||||
:options="codemirrorOptions"
|
||||
></codemirror>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
|
@ -78,7 +78,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
buttonDisabled: true,
|
||||
options: {
|
||||
codemirrorOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'text/html',
|
||||
lineNumbers: true,
|
||||
|
@ -157,13 +157,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.CodeMirror {
|
||||
height: 560px;
|
||||
}
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #fff3f3;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row
|
||||
:gutter="12"
|
||||
type="flex"
|
||||
|
@ -24,7 +24,8 @@
|
|||
<div class="theme-thumb">
|
||||
<img
|
||||
:alt="item.name"
|
||||
:src="item.screenshots"
|
||||
:src="item.screenshots || '/images/placeholder.jpg'"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<template
|
||||
|
@ -298,10 +299,16 @@ export default {
|
|||
})
|
||||
},
|
||||
handleUpdateTheme(themeId) {
|
||||
themeApi.update(themeId).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
this.loadThemes()
|
||||
})
|
||||
const hide = this.$message.loading('更新中...', 0)
|
||||
themeApi
|
||||
.update(themeId)
|
||||
.then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
this.loadThemes()
|
||||
})
|
||||
.finally(() => {
|
||||
hide()
|
||||
})
|
||||
},
|
||||
handleDeleteTheme(key) {
|
||||
themeApi.delete(key).then(response => {
|
||||
|
|
|
@ -77,13 +77,20 @@
|
|||
:key="index.toString()"
|
||||
:tab="group.label"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form
|
||||
layout="vertical"
|
||||
:wrapperCol="wrapperCol"
|
||||
>
|
||||
<a-form-item
|
||||
v-for="(item, index1) in group.items"
|
||||
:label="item.label + ':'"
|
||||
:key="index1"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<p
|
||||
v-if="item.description && item.description!=''"
|
||||
slot="help"
|
||||
v-html="item.description"
|
||||
></p>
|
||||
<a-input
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:xl="10"
|
||||
|
@ -300,13 +300,11 @@ export default {
|
|||
categoryApi.update(this.categoryToCreate.id, this.categoryToCreate).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
this.loadCategories()
|
||||
this.categoryToCreate = {}
|
||||
})
|
||||
} else {
|
||||
categoryApi.create(this.categoryToCreate).then(response => {
|
||||
this.$message.success('保存成功!')
|
||||
this.loadCategories()
|
||||
this.categoryToCreate = {}
|
||||
})
|
||||
}
|
||||
this.handleAddCategory()
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col :span="24">
|
||||
<div style="margin-bottom: 16px">
|
||||
<a-input
|
||||
v-model="postToStage.title"
|
||||
v-decorator="['title', { rules: [{ required: true, message: '请输入文章标题' }] }]"
|
||||
size="large"
|
||||
placeholder="请输入文章标题"
|
||||
/>
|
||||
|
@ -20,22 +19,24 @@
|
|||
:ishljs="true"
|
||||
:autofocus="false"
|
||||
@imgAdd="handleAttachmentUpload"
|
||||
@keydown.ctrl.83.native="handleSaveDraft"
|
||||
@keydown.meta.83.native="handleSaveDraft"
|
||||
@save="handleSaveDraft(true)"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<PostSetting
|
||||
<PostSettingDrawer
|
||||
:post="postToStage"
|
||||
:tagIds="selectedTagIds"
|
||||
:categoryIds="selectedCategoryIds"
|
||||
:postMetas="selectedPostMetas"
|
||||
:visible="postSettingVisible"
|
||||
@close="onPostSettingsClose"
|
||||
@onRefreshPost="onRefreshPostFromSetting"
|
||||
@onRefreshTagIds="onRefreshTagIdsFromSetting"
|
||||
@onRefreshCategoryIds="onRefreshCategoryIdsFromSetting"
|
||||
@onRefreshPostMetas="onRefreshPostMetasFromSetting"
|
||||
@onSaved="onSaved"
|
||||
/>
|
||||
|
||||
<AttachmentDrawer v-model="attachmentDrawerVisible" />
|
||||
|
@ -43,11 +44,13 @@
|
|||
<footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}">
|
||||
<a-button
|
||||
type="danger"
|
||||
@click="handleSaveDraft"
|
||||
@click="handleSaveDraft(false)"
|
||||
:disabled="saving"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
@click="handlePreview"
|
||||
style="margin-left: 8px;"
|
||||
:disabled="saving"
|
||||
>预览</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
|
@ -67,7 +70,7 @@
|
|||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import PostSetting from './components/PostSetting'
|
||||
import PostSettingDrawer from './components/PostSettingDrawer'
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import { toolbars } from '@/core/const'
|
||||
|
@ -79,7 +82,7 @@ import attachmentApi from '@/api/attachment'
|
|||
export default {
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
PostSetting,
|
||||
PostSettingDrawer,
|
||||
haloEditor,
|
||||
FooterToolBar,
|
||||
AttachmentDrawer
|
||||
|
@ -87,16 +90,15 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
toolbars,
|
||||
wrapperCol: {
|
||||
xl: { span: 24 },
|
||||
sm: { span: 24 },
|
||||
xs: { span: 24 }
|
||||
},
|
||||
attachmentDrawerVisible: false,
|
||||
postSettingVisible: false,
|
||||
postToStage: {},
|
||||
selectedTagIds: [],
|
||||
selectedCategoryIds: []
|
||||
selectedCategoryIds: [],
|
||||
selectedPostMetas: [],
|
||||
isSaved: false,
|
||||
contentChanges: 0,
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
|
@ -109,6 +111,7 @@ export default {
|
|||
vm.postToStage = post
|
||||
vm.selectedTagIds = post.tagIds
|
||||
vm.selectedCategoryIds = post.categoryIds
|
||||
vm.selectedPostMetas = post.postMetas
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -120,6 +123,9 @@ export default {
|
|||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
if (window.onbeforeunload) {
|
||||
window.onbeforeunload = null
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.postSettingVisible) {
|
||||
|
@ -128,32 +134,75 @@ export default {
|
|||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
next()
|
||||
|
||||
if (this.contentChanges <= 1) {
|
||||
next()
|
||||
} else if (this.isSaved) {
|
||||
next()
|
||||
} else {
|
||||
this.$confirm({
|
||||
title: '当前页面数据未保存,确定要离开吗?',
|
||||
content: h => <div style="color:red;">如果离开当面页面,你的数据很可能会丢失!</div>,
|
||||
onOk() {
|
||||
next()
|
||||
},
|
||||
onCancel() {
|
||||
next(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.onbeforeunload = function(e) {
|
||||
e = e || window.event
|
||||
if (e) {
|
||||
e.returnValue = '当前页面数据未保存,确定要离开吗?'
|
||||
}
|
||||
return '当前页面数据未保存,确定要离开吗?'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
temporaryContent: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.contentChanges++
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
temporaryContent() {
|
||||
return this.postToStage.originalContent
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
handleSaveDraft() {
|
||||
handleSaveDraft(draftOnly = false) {
|
||||
this.$log.debug('Draft only: ' + draftOnly)
|
||||
this.postToStage.status = 'DRAFT'
|
||||
if (!this.postToStage.title) {
|
||||
this.postToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.postToStage.originalContent) {
|
||||
this.postToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
this.saving = true
|
||||
if (this.postToStage.id) {
|
||||
// Update the post
|
||||
postApi.update(this.postToStage.id, this.postToStage, false).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
this.$message.success('保存草稿成功!')
|
||||
})
|
||||
if (draftOnly) {
|
||||
postApi.updateDraft(this.postToStage.id, this.postToStage.originalContent).then(response => {
|
||||
this.$message.success('保存草稿成功!')
|
||||
this.saving = false
|
||||
})
|
||||
} else {
|
||||
postApi.update(this.postToStage.id, this.postToStage, false).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
this.$message.success('保存草稿成功!')
|
||||
this.saving = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Create the post
|
||||
postApi.create(this.postToStage, false).then(response => {
|
||||
this.$log.debug('Created post', response.data.data)
|
||||
this.$message.success('保存草稿成功!')
|
||||
this.postToStage = response.data.data
|
||||
this.saving = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -180,15 +229,14 @@ export default {
|
|||
if (!this.postToStage.title) {
|
||||
this.postToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.postToStage.originalContent) {
|
||||
this.postToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
this.saving = true
|
||||
if (this.postToStage.id) {
|
||||
// Update the post
|
||||
postApi.update(this.postToStage.id, this.postToStage, false).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
postApi.preview(this.postToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
} else {
|
||||
|
@ -198,6 +246,7 @@ export default {
|
|||
this.postToStage = response.data.data
|
||||
postApi.preview(this.postToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -214,6 +263,12 @@ export default {
|
|||
},
|
||||
onRefreshCategoryIdsFromSetting(categoryIds) {
|
||||
this.selectedCategoryIds = categoryIds
|
||||
},
|
||||
onRefreshPostMetasFromSetting(postMetas) {
|
||||
this.selectedPostMetas = postMetas
|
||||
},
|
||||
onSaved(isSaved) {
|
||||
this.isSaved = isSaved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
|
@ -12,7 +12,10 @@
|
|||
:sm="24"
|
||||
>
|
||||
<a-form-item label="关键词">
|
||||
<a-input v-model="queryParam.keyword" />
|
||||
<a-input
|
||||
v-model="queryParam.keyword"
|
||||
@keyup.enter="handleQuery()"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
|
@ -23,7 +26,7 @@
|
|||
<a-select
|
||||
v-model="queryParam.status"
|
||||
placeholder="请选择文章状态"
|
||||
@change="handleQuery"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="status in Object.keys(postStatus)"
|
||||
|
@ -41,7 +44,7 @@
|
|||
<a-select
|
||||
v-model="queryParam.categoryId"
|
||||
placeholder="请选择分类"
|
||||
@change="handleQuery"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="category in categories"
|
||||
|
@ -58,11 +61,11 @@
|
|||
<span class="table-page-search-submitButtons">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleQuery"
|
||||
@click="handleQuery()"
|
||||
>查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px;"
|
||||
@click="handleResetParam"
|
||||
@click="handleResetParam()"
|
||||
>重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
|
@ -81,11 +84,11 @@
|
|||
<a-menu slot="overlay">
|
||||
<a-menu-item
|
||||
key="1"
|
||||
v-if="queryParam.status === 'DRAFT'"
|
||||
v-if="queryParam.status === 'DRAFT' || queryParam.status === 'RECYCLE'"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handlePublishMore"
|
||||
@click="handleEditStatusMore(postStatus.PUBLISHED.value)"
|
||||
>
|
||||
<span>发布</span>
|
||||
</a>
|
||||
|
@ -96,14 +99,25 @@
|
|||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleRecycleMore"
|
||||
@click="handleEditStatusMore(postStatus.RECYCLE.value)"
|
||||
>
|
||||
<span>移到回收站</span>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="3"
|
||||
v-if="queryParam.status === 'RECYCLE'"
|
||||
v-if="queryParam.status === 'RECYCLE' || queryParam.status === 'PUBLISHED' || queryParam.status === 'INTIMATE'"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleEditStatusMore(postStatus.DRAFT.value)"
|
||||
>
|
||||
<span>草稿</span>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="4"
|
||||
v-if="queryParam.status === 'RECYCLE' || queryParam.status === 'DRAFT'"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
|
@ -140,7 +154,7 @@
|
|||
<a-icon type="eye" />
|
||||
{{ item.visits }}
|
||||
</span>
|
||||
<span>
|
||||
<span @click="handleShowPostComments(item)">
|
||||
<a-icon type="message" />
|
||||
{{ item.commentCount }}
|
||||
</span>
|
||||
|
@ -222,7 +236,7 @@
|
|||
style="margin-right: 3px;"
|
||||
/>
|
||||
<a
|
||||
v-if="item.status=='PUBLISHED'"
|
||||
v-if="item.status=='PUBLISHED' || item.status == 'INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+item.url"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
|
@ -232,17 +246,6 @@
|
|||
:title="'点击访问【'+item.title+'】'"
|
||||
>{{ item.title }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="item.status == 'INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+item.url+'/password'"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:title="'点击访问【'+item.title+'】'"
|
||||
>{{ item.title }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="item.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
|
@ -292,6 +295,7 @@
|
|||
v-else
|
||||
:rowKey="post => post.id"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: onSelectionChange,
|
||||
getCheckboxProps: getCheckboxProps
|
||||
}"
|
||||
|
@ -313,7 +317,7 @@
|
|||
style="margin-right: 3px;"
|
||||
/>
|
||||
<a
|
||||
v-if="record.status=='PUBLISHED'"
|
||||
v-if="record.status=='PUBLISHED' || record.status == 'INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+record.url"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
|
@ -323,17 +327,6 @@
|
|||
:title="'点击访问【'+text+'】'"
|
||||
>{{ text }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="record.status == 'INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+record.url+'/password'"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:title="'点击访问【'+text+'】'"
|
||||
>{{ text }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="record.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
|
@ -390,10 +383,12 @@
|
|||
|
||||
<span
|
||||
slot="commentCount"
|
||||
slot-scope="commentCount"
|
||||
slot-scope="text,record"
|
||||
@click="handleShowPostComments(record)"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<a-badge
|
||||
:count="commentCount"
|
||||
:count="record.commentCount"
|
||||
:numberStyle="{backgroundColor: '#f38181'} "
|
||||
:showZero="true"
|
||||
:overflowCount="999"
|
||||
|
@ -476,7 +471,9 @@
|
|||
<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"
|
||||
|
@ -486,10 +483,11 @@
|
|||
</div>
|
||||
</a-card>
|
||||
|
||||
<PostSetting
|
||||
<PostSettingDrawer
|
||||
:post="selectedPost"
|
||||
:tagIds="selectedTagIds"
|
||||
:categoryIds="selectedCategoryIds"
|
||||
:postMetas="selectedPostMetas"
|
||||
:needTitle="true"
|
||||
:saveDraftButton="false"
|
||||
:savePublishButton="false"
|
||||
|
@ -499,13 +497,24 @@
|
|||
@onRefreshPost="onRefreshPostFromSetting"
|
||||
@onRefreshTagIds="onRefreshTagIdsFromSetting"
|
||||
@onRefreshCategoryIds="onRefreshCategoryIdsFromSetting"
|
||||
@onRefreshPostMetas="onRefreshPostMetasFromSetting"
|
||||
/>
|
||||
|
||||
<TargetCommentDrawer
|
||||
:visible="postCommentVisible"
|
||||
:title="selectedPost.title"
|
||||
:description="selectedPost.summary"
|
||||
:target="`posts`"
|
||||
:id="selectedPost.id"
|
||||
@close="onPostCommentsClose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import PostSetting from './components/PostSetting'
|
||||
import PostSettingDrawer from './components/PostSettingDrawer'
|
||||
import TargetCommentDrawer from '../comment/components/TargetCommentDrawer'
|
||||
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
||||
import TagSelect from './components/TagSelect'
|
||||
import CategoryTree from './components/CategoryTree'
|
||||
|
@ -566,15 +575,16 @@ export default {
|
|||
AttachmentSelectDrawer,
|
||||
TagSelect,
|
||||
CategoryTree,
|
||||
PostSetting
|
||||
PostSettingDrawer,
|
||||
TargetCommentDrawer
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
return {
|
||||
postStatus: postApi.postStatus,
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: null
|
||||
},
|
||||
queryParam: {
|
||||
|
@ -588,11 +598,17 @@ export default {
|
|||
// 表头
|
||||
columns,
|
||||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
categories: [],
|
||||
selectedPostMetas: [
|
||||
{
|
||||
key: '',
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
posts: [],
|
||||
postsLoading: false,
|
||||
postSettingVisible: false,
|
||||
postCommentVisible: false,
|
||||
selectedPost: {},
|
||||
selectedTagIds: [],
|
||||
selectedCategoryIds: []
|
||||
|
@ -626,8 +642,8 @@ export default {
|
|||
loadPosts() {
|
||||
this.postsLoading = true
|
||||
// Set from pagination
|
||||
this.queryParam.page = this.pagination.current - 1
|
||||
this.queryParam.size = this.pagination.pageSize
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
postApi.query(this.queryParam).then(response => {
|
||||
this.posts = response.data.data.content
|
||||
|
@ -650,27 +666,27 @@ export default {
|
|||
getCheckboxProps(post) {
|
||||
return {
|
||||
props: {
|
||||
disabled: post.status === 'RECYCLE',
|
||||
disabled: this.queryParam.status == null || this.queryParam.status === '',
|
||||
name: post.title
|
||||
}
|
||||
}
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.pagination.current = page
|
||||
this.pagination.pageSize = pageSize
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
this.loadPosts()
|
||||
},
|
||||
handleResetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.categoryId = null
|
||||
this.queryParam.status = null
|
||||
this.loadPosts()
|
||||
this.handleClearRowKeys()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParam.page = 0
|
||||
this.pagination.current = 1
|
||||
this.loadPosts()
|
||||
this.handleClearRowKeys()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleEditStatusClick(postId, status) {
|
||||
postApi.updateStatus(postId, status).then(response => {
|
||||
|
@ -684,61 +700,51 @@ export default {
|
|||
this.loadPosts()
|
||||
})
|
||||
},
|
||||
handlePublishMore() {
|
||||
handleEditStatusMore(status) {
|
||||
if (this.selectedRowKeys.length <= 0) {
|
||||
this.$message.success('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
||||
const element = this.selectedRowKeys[index]
|
||||
postApi.updateStatus(element, 'PUBLISHED').then(response => {
|
||||
this.$log.debug(`postId: ${element}, status: PUBLISHED`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadPosts()
|
||||
})
|
||||
}
|
||||
},
|
||||
handleRecycleMore() {
|
||||
if (this.selectedRowKeys.length <= 0) {
|
||||
this.$message.success('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
||||
const element = this.selectedRowKeys[index]
|
||||
postApi.updateStatus(element, 'RECYCLE').then(response => {
|
||||
this.$log.debug(`postId: ${element}, status: RECYCLE`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadPosts()
|
||||
})
|
||||
}
|
||||
postApi.updateStatusInBatch(this.selectedRowKeys, status).then(response => {
|
||||
this.$log.debug(`postId: ${this.selectedRowKeys}, status: ${status}`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadPosts()
|
||||
})
|
||||
},
|
||||
handleDeleteMore() {
|
||||
if (this.selectedRowKeys.length <= 0) {
|
||||
this.$message.success('请至少选择一项!')
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
||||
const element = this.selectedRowKeys[index]
|
||||
postApi.delete(element).then(response => {
|
||||
this.$log.debug(`delete: ${element}`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadPosts()
|
||||
})
|
||||
}
|
||||
postApi.deleteInBatch(this.selectedRowKeys).then(response => {
|
||||
this.$log.debug(`delete: ${this.selectedRowKeys}`)
|
||||
this.selectedRowKeys = []
|
||||
this.loadPosts()
|
||||
})
|
||||
},
|
||||
handleShowPostSettings(post) {
|
||||
postApi.get(post.id).then(response => {
|
||||
this.selectedPost = response.data.data
|
||||
this.selectedTagIds = this.selectedPost.tagIds
|
||||
this.selectedCategoryIds = this.selectedPost.categoryIds
|
||||
this.selectedPostMetas = this.selectedPost.postMetas
|
||||
this.postSettingVisible = true
|
||||
})
|
||||
},
|
||||
handleShowPostComments(post) {
|
||||
postApi.get(post.id).then(response => {
|
||||
this.selectedPost = response.data.data
|
||||
this.postCommentVisible = true
|
||||
})
|
||||
},
|
||||
handlePreview(postId) {
|
||||
postApi.preview(postId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
handleClearRowKeys() {
|
||||
this.selectedRowKeys = []
|
||||
},
|
||||
// 关闭文章设置抽屉
|
||||
onPostSettingsClose() {
|
||||
this.postSettingVisible = false
|
||||
|
@ -747,6 +753,13 @@ export default {
|
|||
this.loadPosts()
|
||||
}, 500)
|
||||
},
|
||||
onPostCommentsClose() {
|
||||
this.postCommentVisible = false
|
||||
this.selectedPost = {}
|
||||
setTimeout(() => {
|
||||
this.loadPosts()
|
||||
}, 500)
|
||||
},
|
||||
onRefreshPostFromSetting(post) {
|
||||
this.selectedPost = post
|
||||
},
|
||||
|
@ -755,6 +768,9 @@ export default {
|
|||
},
|
||||
onRefreshCategoryIdsFromSetting(categoryIds) {
|
||||
this.selectedCategoryIds = categoryIds
|
||||
},
|
||||
onRefreshPostMetasFromSetting(postMetas) {
|
||||
this.selectedPostMetas = postMetas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:xl="10"
|
||||
|
|
|
@ -31,7 +31,10 @@
|
|||
<a-input v-model="selectedPost.url" />
|
||||
</a-form-item>
|
||||
<a-form-item label="访问密码:">
|
||||
<a-input-password v-model="selectedPost.password" />
|
||||
<a-input-password
|
||||
v-model="selectedPost.password"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="发表时间:">
|
||||
|
@ -62,6 +65,19 @@
|
|||
<a-radio :value="0">否</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义模板:">
|
||||
<a-select v-model="selectedPost.template">
|
||||
<a-select-option
|
||||
key=""
|
||||
value=""
|
||||
>无</a-select-option>
|
||||
<a-select-option
|
||||
v-for="tpl in customTpls"
|
||||
:key="tpl"
|
||||
:value="tpl"
|
||||
>{{ tpl }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -70,50 +86,50 @@
|
|||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">分类目录</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<category-tree
|
||||
v-model="selectedCategoryIds"
|
||||
:categories="categories"
|
||||
/>
|
||||
<div>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<category-select-tree
|
||||
:categories="categories"
|
||||
v-model="categoryToCreate.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<a-input
|
||||
placeholder="分类名称"
|
||||
v-model="categoryToCreate.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<a-input
|
||||
placeholder="分类路径"
|
||||
v-model="categoryToCreate.slugNames"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="marginRight: 8px"
|
||||
v-if="categoryFormVisible"
|
||||
@click="handlerCreateCategory"
|
||||
>保存</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="marginRight: 8px"
|
||||
v-if="!categoryFormVisible"
|
||||
@click="toggleCategoryForm"
|
||||
>新增</a-button>
|
||||
<a-button
|
||||
v-if="categoryFormVisible"
|
||||
@click="toggleCategoryForm"
|
||||
>取消</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<category-tree
|
||||
v-model="selectedCategoryIds"
|
||||
:categories="categories"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<category-select-tree
|
||||
:categories="categories"
|
||||
v-model="categoryToCreate.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<a-input
|
||||
placeholder="分类名称"
|
||||
v-model="categoryToCreate.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<a-input
|
||||
placeholder="分类路径"
|
||||
v-model="categoryToCreate.slugNames"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="marginRight: 8px"
|
||||
v-if="categoryFormVisible"
|
||||
@click="handlerCreateCategory"
|
||||
>保存</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="marginRight: 8px"
|
||||
v-if="!categoryFormVisible"
|
||||
@click="toggleCategoryForm"
|
||||
>新增</a-button>
|
||||
<a-button
|
||||
v-if="categoryFormVisible"
|
||||
@click="toggleCategoryForm"
|
||||
>取消</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
@ -153,9 +169,19 @@
|
|||
<div class="post-thumb">
|
||||
<img
|
||||
class="img"
|
||||
:src="selectedPost.thumbnail || '/images/placeholder.png'"
|
||||
:src="selectedPost.thumbnail || '/images/placeholder.jpg'"
|
||||
@click="()=>this.thumbDrawerVisible=true"
|
||||
>
|
||||
|
||||
<a-form layout="vertial">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
v-model="selectedPost.thumbnail"
|
||||
placeholder="点击缩略图选择图片,或者输入外部链接"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-button
|
||||
class="post-thumb-remove"
|
||||
type="dashed"
|
||||
|
@ -164,6 +190,41 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">元数据</h3>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
v-for="(postMeta, index) in selectedPostMetas"
|
||||
:key="index"
|
||||
:prop="'postMetas.' + index + '.value'"
|
||||
>
|
||||
<a-row :gutter="5">
|
||||
<a-col :span="12">
|
||||
<a-input v-model="postMeta.key"><i slot="addonBefore">K</i></a-input>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-input v-model="postMeta.value">
|
||||
<i slot="addonBefore">V</i>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
slot="addonAfter"
|
||||
@click.prevent="handleRemovePostMeta(postMeta)"
|
||||
>
|
||||
<a-icon type="close" />
|
||||
</a>
|
||||
</a-input>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click="handleInsertPostMeta"
|
||||
>新增</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
</div>
|
||||
</a-skeleton>
|
||||
|
@ -177,16 +238,19 @@
|
|||
style="marginRight: 8px"
|
||||
@click="handleDraftClick"
|
||||
v-if="saveDraftButton"
|
||||
:disabled="saving"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
@click="handlePublishClick"
|
||||
type="primary"
|
||||
v-if="savePublishButton"
|
||||
:disabled="saving"
|
||||
>发布</a-button>
|
||||
<a-button
|
||||
@click="handlePublishClick"
|
||||
type="primary"
|
||||
v-if="saveButton"
|
||||
:disabled="saving"
|
||||
>保存</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
@ -201,8 +265,9 @@ import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelect
|
|||
import { mapGetters } from 'vuex'
|
||||
import categoryApi from '@/api/category'
|
||||
import postApi from '@/api/post'
|
||||
import themeApi from '@/api/theme'
|
||||
export default {
|
||||
name: 'PostSetting',
|
||||
name: 'PostSettingDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
CategoryTree,
|
||||
|
@ -219,7 +284,9 @@ export default {
|
|||
selectedTagIds: this.tagIds,
|
||||
selectedCategoryIds: this.categoryIds,
|
||||
categories: [],
|
||||
categoryToCreate: {}
|
||||
categoryToCreate: {},
|
||||
customTpls: [],
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
@ -235,6 +302,10 @@ export default {
|
|||
type: Array,
|
||||
required: true
|
||||
},
|
||||
postMetas: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -261,10 +332,6 @@ export default {
|
|||
default: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
this.loadCategories()
|
||||
},
|
||||
watch: {
|
||||
post(val) {
|
||||
this.selectedPost = val
|
||||
|
@ -284,13 +351,24 @@ export default {
|
|||
selectedCategoryIds(val) {
|
||||
this.$emit('onRefreshCategoryIds', val)
|
||||
},
|
||||
selectedPostMetas(val) {
|
||||
this.$emit('onRefreshPostMetas', val)
|
||||
},
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
this.loadCategories()
|
||||
this.loadPresetMetasField()
|
||||
this.loadCustomTpls()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedPostMetas() {
|
||||
// 不能将selectedPostMetas直接定义在data里
|
||||
// 还没有获取到值就渲染视图,可以直接使用postMetas
|
||||
return this.postMetas
|
||||
},
|
||||
pickerDefaultValue() {
|
||||
if (this.selectedPost.createTime) {
|
||||
var date = new Date(this.selectedPost.createTime)
|
||||
|
@ -312,6 +390,26 @@ export default {
|
|||
this.categories = response.data.data
|
||||
})
|
||||
},
|
||||
loadPresetMetasField() {
|
||||
if (this.postMetas.length <= 0) {
|
||||
themeApi.getActivatedTheme().then(response => {
|
||||
const fields = response.data.data.postMetaField
|
||||
if (fields && fields.length > 0) {
|
||||
for (let i = 0, len = fields.length; i < len; i++) {
|
||||
this.selectedPostMetas.push({
|
||||
value: '',
|
||||
key: fields[i]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadCustomTpls() {
|
||||
themeApi.customPostTpls().then(response => {
|
||||
this.customTpls = response.data.data
|
||||
})
|
||||
},
|
||||
handleSelectPostThumb(data) {
|
||||
this.selectedPost.thumbnail = encodeURI(data.path)
|
||||
this.thumbDrawerVisible = false
|
||||
|
@ -359,24 +457,21 @@ export default {
|
|||
})
|
||||
return
|
||||
}
|
||||
if (!this.selectedPost.originalContent) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '文章内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
// Set category ids
|
||||
this.selectedPost.categoryIds = this.selectedCategoryIds
|
||||
// Set tag ids
|
||||
this.selectedPost.tagIds = this.selectedTagIds
|
||||
|
||||
// Set post metas
|
||||
this.selectedPost.postMetas = this.selectedPostMetas
|
||||
this.saving = true
|
||||
if (this.selectedPost.id) {
|
||||
// Update the post
|
||||
postApi.update(this.selectedPost.id, this.selectedPost, autoSave).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
if (updateSuccess) {
|
||||
updateSuccess()
|
||||
this.saving = false
|
||||
this.$emit('onSaved', true)
|
||||
this.$router.push({ name: 'PostList' })
|
||||
}
|
||||
})
|
||||
|
@ -386,6 +481,8 @@ export default {
|
|||
this.$log.debug('Created post', response.data.data)
|
||||
if (createSuccess) {
|
||||
createSuccess()
|
||||
this.saving = false
|
||||
this.$emit('onSaved', true)
|
||||
this.$router.push({ name: 'PostList' })
|
||||
}
|
||||
this.selectedPost = response.data.data
|
||||
|
@ -400,6 +497,18 @@ export default {
|
|||
},
|
||||
onPostDateOk(value) {
|
||||
this.selectedPost.createTime = value.valueOf()
|
||||
},
|
||||
handleRemovePostMeta(item) {
|
||||
var index = this.selectedPostMetas.indexOf(item)
|
||||
if (index !== -1) {
|
||||
this.selectedPostMetas.splice(index, 1)
|
||||
}
|
||||
},
|
||||
handleInsertPostMeta() {
|
||||
this.selectedPostMetas.push({
|
||||
value: '',
|
||||
key: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col :span="24">
|
||||
<div style="margin-bottom: 16px">
|
||||
<a-input
|
||||
v-model="sheetToStage.title"
|
||||
v-decorator="['title', { rules: [{ required: true, message: '请输入页面标题' }] }]"
|
||||
size="large"
|
||||
placeholder="请输入页面标题"
|
||||
/>
|
||||
|
@ -19,18 +18,20 @@
|
|||
:ishljs="true"
|
||||
:autofocus="false"
|
||||
@imgAdd="handleAttachmentUpload"
|
||||
@keydown.ctrl.83.native="handleSaveDraft"
|
||||
@keydown.meta.83.native="handleSaveDraft"
|
||||
@save="handleSaveDraft"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<SheetSetting
|
||||
<SheetSettingDrawer
|
||||
:sheet="sheetToStage"
|
||||
:sheetMetas="selectedSheetMetas"
|
||||
:visible="sheetSettingVisible"
|
||||
@close="onSheetSettingsClose"
|
||||
@onRefreshSheet="onRefreshSheetFromSetting"
|
||||
@onRefreshSheetMetas="onRefreshSheetMetasFromSetting"
|
||||
@onSaved="onSaved"
|
||||
/>
|
||||
|
||||
<AttachmentDrawer v-model="attachmentDrawerVisible" />
|
||||
|
@ -38,10 +39,12 @@
|
|||
<a-button
|
||||
type="danger"
|
||||
@click="handleSaveDraft"
|
||||
:disabled="saving"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
@click="handlePreview"
|
||||
style="margin-left: 8px;"
|
||||
:disabled="saving"
|
||||
>预览</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
|
@ -62,7 +65,7 @@ import { mixin, mixinDevice } from '@/utils/mixin.js'
|
|||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import { toolbars } from '@/core/const'
|
||||
import SheetSetting from './components/SheetSetting'
|
||||
import SheetSettingDrawer from './components/SheetSettingDrawer'
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import { haloEditor } from 'halo-editor'
|
||||
|
@ -74,20 +77,19 @@ export default {
|
|||
haloEditor,
|
||||
FooterToolBar,
|
||||
AttachmentDrawer,
|
||||
SheetSetting
|
||||
SheetSettingDrawer
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
return {
|
||||
toolbars,
|
||||
wrapperCol: {
|
||||
xl: { span: 24 },
|
||||
sm: { span: 24 },
|
||||
xs: { span: 24 }
|
||||
},
|
||||
attachmentDrawerVisible: false,
|
||||
sheetSettingVisible: false,
|
||||
sheetToStage: {}
|
||||
sheetToStage: {},
|
||||
selectedSheetMetas: [],
|
||||
isSaved: false,
|
||||
contentChanges: 0,
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
|
@ -99,6 +101,7 @@ export default {
|
|||
sheetApi.get(sheetId).then(response => {
|
||||
const sheet = response.data.data
|
||||
vm.sheetToStage = sheet
|
||||
vm.selectedSheetMetas = sheet.sheetMetas
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -110,6 +113,9 @@ export default {
|
|||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
if (window.onbeforeunload) {
|
||||
window.onbeforeunload = null
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.sheetSettingVisible) {
|
||||
|
@ -118,30 +124,64 @@ export default {
|
|||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
next()
|
||||
if (this.contentChanges <= 1) {
|
||||
next()
|
||||
} else if (this.isSaved) {
|
||||
next()
|
||||
} else {
|
||||
this.$confirm({
|
||||
title: '当前页面数据未保存,确定要离开吗?',
|
||||
content: h => <div style="color:red;">如果离开当面页面,你的数据很可能会丢失!</div>,
|
||||
onOk() {
|
||||
next()
|
||||
},
|
||||
onCancel() {
|
||||
next(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.onbeforeunload = function(e) {
|
||||
e = e || window.event
|
||||
if (e) {
|
||||
e.returnValue = '当前页面数据未保存,确定要离开吗?'
|
||||
}
|
||||
return '当前页面数据未保存,确定要离开吗?'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
temporaryContent: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.contentChanges++
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
temporaryContent() {
|
||||
return this.sheetToStage.originalContent
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
handleSaveDraft() {
|
||||
this.sheetToStage.status = 'DRAFT'
|
||||
this.saving = true
|
||||
if (!this.sheetToStage.title) {
|
||||
this.sheetToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.sheetToStage.originalContent) {
|
||||
this.sheetToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
if (this.sheetToStage.id) {
|
||||
sheetApi.update(this.sheetToStage.id, this.sheetToStage, false).then(response => {
|
||||
this.$log.debug('Updated sheet', response.data.data)
|
||||
this.$message.success('保存草稿成功!')
|
||||
this.saving = false
|
||||
})
|
||||
} else {
|
||||
sheetApi.create(this.sheetToStage, false).then(response => {
|
||||
this.$log.debug('Created sheet', response.data.data)
|
||||
this.$message.success('保存草稿成功!')
|
||||
this.sheetToStage = response.data.data
|
||||
this.saving = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -168,14 +208,13 @@ export default {
|
|||
if (!this.sheetToStage.title) {
|
||||
this.sheetToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.sheetToStage.originalContent) {
|
||||
this.sheetToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
this.saving = true
|
||||
if (this.sheetToStage.id) {
|
||||
sheetApi.update(this.sheetToStage.id, this.sheetToStage, false).then(response => {
|
||||
this.$log.debug('Updated sheet', response.data.data)
|
||||
sheetApi.preview(this.sheetToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
} else {
|
||||
|
@ -184,6 +223,7 @@ export default {
|
|||
this.sheetToStage = response.data.data
|
||||
sheetApi.preview(this.sheetToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
this.saving = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -193,6 +233,12 @@ export default {
|
|||
},
|
||||
onRefreshSheetFromSetting(sheet) {
|
||||
this.sheetToStage = sheet
|
||||
},
|
||||
onRefreshSheetMetasFromSetting(sheetMetas) {
|
||||
this.selectedSheetMetas = sheetMetas
|
||||
},
|
||||
onSaved(isSaved) {
|
||||
this.isSaved = isSaved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<div class="card-container">
|
||||
<a-tabs type="card">
|
||||
<a-tab-pane key="internal">
|
||||
<span slot="tab">
|
||||
<a-icon type="pushpin" />内置页面
|
||||
<a-icon type="paper-clip" />内置页面
|
||||
</span>
|
||||
|
||||
<!-- Mobile -->
|
||||
|
@ -161,7 +161,7 @@
|
|||
<a-icon type="eye" />
|
||||
{{ item.visits }}
|
||||
</span>
|
||||
<span>
|
||||
<span @click="handleShowSheetComments(item)">
|
||||
<a-icon type="message" />
|
||||
{{ item.commentCount }}
|
||||
</span>
|
||||
|
@ -181,7 +181,7 @@
|
|||
</a-menu-item>
|
||||
<a-menu-item v-else-if="item.status === 'RECYCLE'">
|
||||
<a-popconfirm
|
||||
:title="'你确定要发布【' + item.title + '】文章?'"
|
||||
:title="'你确定要发布【' + item.title + '】页面?'"
|
||||
@confirm="handleEditStatusClick(item.id,'PUBLISHED')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
|
@ -191,7 +191,7 @@
|
|||
</a-menu-item>
|
||||
<a-menu-item v-if="item.status === 'PUBLISHED' || item.status === 'DRAFT'">
|
||||
<a-popconfirm
|
||||
:title="'你确定要将【' + item.title + '】文章移到回收站?'"
|
||||
:title="'你确定要将【' + item.title + '】页面移到回收站?'"
|
||||
@confirm="handleEditStatusClick(item.id,'RECYCLE')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
|
@ -201,7 +201,7 @@
|
|||
</a-menu-item>
|
||||
<a-menu-item v-else-if="item.status === 'RECYCLE'">
|
||||
<a-popconfirm
|
||||
:title="'你确定要永久删除【' + item.title + '】文章?'"
|
||||
:title="'你确定要永久删除【' + item.title + '】页面?'"
|
||||
@confirm="handleDeleteClick(item.id)"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
|
@ -245,13 +245,6 @@
|
|||
slot="title"
|
||||
style="max-width: 300px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
|
||||
>
|
||||
<a-icon
|
||||
type="pushpin"
|
||||
v-if="item.topPriority!=0"
|
||||
theme="twoTone"
|
||||
twoToneColor="red"
|
||||
style="margin-right: 3px;"
|
||||
/>
|
||||
<a
|
||||
v-if="item.status=='PUBLISHED'"
|
||||
:href="options.blog_url+'/archives/'+item.url"
|
||||
|
@ -263,17 +256,6 @@
|
|||
:title="'点击访问【'+item.title+'】'"
|
||||
>{{ item.title }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="item.status == 'INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+item.url+'/password'"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:title="'点击访问【'+item.title+'】'"
|
||||
>{{ item.title }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="item.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
|
@ -358,10 +340,12 @@
|
|||
|
||||
<span
|
||||
slot="commentCount"
|
||||
slot-scope="commentCount"
|
||||
slot-scope="text,record"
|
||||
@click="handleShowSheetComments(record)"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<a-badge
|
||||
:count="commentCount"
|
||||
:count="record.commentCount"
|
||||
:numberStyle="{backgroundColor: '#f38181'} "
|
||||
:showZero="true"
|
||||
:overflowCount="999"
|
||||
|
@ -460,27 +444,50 @@
|
|||
</a-dropdown>
|
||||
</span>
|
||||
</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"
|
||||
/>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<SheetSetting
|
||||
<SheetSettingDrawer
|
||||
:sheet="selectedSheet"
|
||||
:sheetMetas="selectedSheetMetas"
|
||||
:visible="sheetSettingVisible"
|
||||
:needTitle="true"
|
||||
@close="onSheetSettingsClose"
|
||||
@onRefreshSheet="onRefreshSheetFromSetting"
|
||||
@onRefreshSheetMetas="onRefreshSheetMetasFromSetting"
|
||||
/>
|
||||
|
||||
<TargetCommentDrawer
|
||||
:visible="sheetCommentVisible"
|
||||
:title="selectedSheet.title"
|
||||
:description="selectedSheet.summary"
|
||||
:target="`sheets`"
|
||||
:id="selectedSheet.id"
|
||||
@close="onSheetCommentsClose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import SheetSetting from './components/SheetSetting'
|
||||
import SheetSettingDrawer from './components/SheetSettingDrawer'
|
||||
import TargetCommentDrawer from '../comment/components/TargetCommentDrawer'
|
||||
import sheetApi from '@/api/sheet'
|
||||
import menuApi from '@/api/menu'
|
||||
|
||||
|
@ -541,16 +548,32 @@ const customColumns = [
|
|||
export default {
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
SheetSetting
|
||||
SheetSettingDrawer,
|
||||
TargetCommentDrawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: null
|
||||
},
|
||||
queryParam: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
sort: null,
|
||||
keyword: null,
|
||||
categoryId: null,
|
||||
status: null
|
||||
},
|
||||
sheetsLoading: false,
|
||||
sheetStatus: sheetApi.sheetStatus,
|
||||
internalColumns,
|
||||
customColumns,
|
||||
selectedSheet: {},
|
||||
selectedSheetMetas: [],
|
||||
sheetSettingVisible: false,
|
||||
sheetCommentVisible: false,
|
||||
internalSheets: [],
|
||||
sheets: [],
|
||||
menu: {}
|
||||
|
@ -583,8 +606,12 @@ export default {
|
|||
methods: {
|
||||
loadSheets() {
|
||||
this.sheetsLoading = true
|
||||
sheetApi.list().then(response => {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
sheetApi.list(this.queryParam).then(response => {
|
||||
this.sheets = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
this.sheetsLoading = false
|
||||
})
|
||||
},
|
||||
|
@ -619,14 +646,27 @@ export default {
|
|||
handleShowSheetSettings(sheet) {
|
||||
sheetApi.get(sheet.id).then(response => {
|
||||
this.selectedSheet = response.data.data
|
||||
this.selectedSheetMetas = this.selectedSheet.sheetMetas
|
||||
this.sheetSettingVisible = true
|
||||
})
|
||||
},
|
||||
handleShowSheetComments(sheet) {
|
||||
sheetApi.get(sheet.id).then(response => {
|
||||
this.selectedSheet = response.data.data
|
||||
this.sheetCommentVisible = true
|
||||
})
|
||||
},
|
||||
handlePreview(sheetId) {
|
||||
sheetApi.preview(sheetId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
this.loadSheets()
|
||||
},
|
||||
onSheetSettingsClose() {
|
||||
this.sheetSettingVisible = false
|
||||
this.selectedSheet = {}
|
||||
|
@ -634,8 +674,18 @@ export default {
|
|||
this.loadSheets()
|
||||
}, 500)
|
||||
},
|
||||
onSheetCommentsClose() {
|
||||
this.sheetCommentVisible = false
|
||||
this.selectedSheet = {}
|
||||
setTimeout(() => {
|
||||
this.loadSheets()
|
||||
}, 500)
|
||||
},
|
||||
onRefreshSheetFromSetting(sheet) {
|
||||
this.selectedSheet = sheet
|
||||
},
|
||||
onRefreshSheetMetasFromSetting(sheetMetas) {
|
||||
this.selectedSheetMetas = sheetMetas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,15 +67,42 @@
|
|||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">摘要</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="selectedSheet.summary"
|
||||
placeholder="不填写则会自动生成"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">缩略图</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<div class="sheet-thumb">
|
||||
<img
|
||||
class="img"
|
||||
:src="selectedSheet.thumbnail || '/images/placeholder.png'"
|
||||
:src="selectedSheet.thumbnail || '/images/placeholder.jpg'"
|
||||
@click="()=>this.thumbDrawerVisible = true"
|
||||
>
|
||||
|
||||
<a-form layout="vertial">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
v-model="selectedSheet.thumbnail"
|
||||
placeholder="点击缩略图选择图片,或者输入外部链接"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-button
|
||||
class="sheet-thumb-remove"
|
||||
type="dashed"
|
||||
|
@ -84,6 +111,41 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">元数据</h3>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
v-for="(sheetMeta, index) in selectedSheetMetas"
|
||||
:key="index"
|
||||
:prop="'sheetMeta.' + index + '.value'"
|
||||
>
|
||||
<a-row :gutter="5">
|
||||
<a-col :span="12">
|
||||
<a-input v-model="sheetMeta.key"><i slot="addonBefore">K</i></a-input>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-input v-model="sheetMeta.value">
|
||||
<i slot="addonBefore">V</i>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
slot="addonAfter"
|
||||
@click.prevent="handleRemoveSheetMeta(sheetMeta)"
|
||||
>
|
||||
<a-icon type="close" />
|
||||
</a>
|
||||
</a-input>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click="handleInsertSheetMeta()"
|
||||
>新增</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
</div>
|
||||
</a-skeleton>
|
||||
|
@ -96,10 +158,12 @@
|
|||
<a-button
|
||||
style="marginRight: 8px"
|
||||
@click="handleDraftClick"
|
||||
:disabled="saving"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handlePublishClick"
|
||||
:disabled="saving"
|
||||
>发布</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
@ -112,7 +176,7 @@ import { mapGetters } from 'vuex'
|
|||
import themeApi from '@/api/theme'
|
||||
import sheetApi from '@/api/sheet'
|
||||
export default {
|
||||
name: 'SheetSetting',
|
||||
name: 'SheetSettingDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
AttachmentSelectDrawer
|
||||
|
@ -122,7 +186,8 @@ export default {
|
|||
thumbDrawerVisible: false,
|
||||
settingLoading: true,
|
||||
selectedSheet: this.sheet,
|
||||
customTpls: []
|
||||
customTpls: [],
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
@ -130,6 +195,10 @@ export default {
|
|||
type: Object,
|
||||
required: true
|
||||
},
|
||||
sheetMetas: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
needTitle: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -152,13 +221,20 @@ export default {
|
|||
selectedSheet(val) {
|
||||
this.$emit('onRefreshSheet', val)
|
||||
},
|
||||
selectedSheetMetas(val) {
|
||||
this.$emit('onRefreshSheetMetas', val)
|
||||
},
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
this.loadPresetMetasField()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedSheetMetas() {
|
||||
return this.sheetMetas
|
||||
},
|
||||
pickerDefaultValue() {
|
||||
if (this.selectedSheet.createTime) {
|
||||
var date = new Date(this.selectedSheet.createTime)
|
||||
|
@ -175,8 +251,23 @@ export default {
|
|||
this.settingLoading = false
|
||||
}, 500)
|
||||
},
|
||||
loadPresetMetasField() {
|
||||
if (this.sheetMetas.length <= 0) {
|
||||
themeApi.getActivatedTheme().then(response => {
|
||||
const fields = response.data.data.sheetMetaField
|
||||
if (fields && fields.length > 0) {
|
||||
for (let i = 0, len = fields.length; i < len; i++) {
|
||||
this.selectedSheetMetas.push({
|
||||
value: '',
|
||||
key: fields[i]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadCustomTpls() {
|
||||
themeApi.customTpls().then(response => {
|
||||
themeApi.customSheetTpls().then(response => {
|
||||
this.customTpls = response.data.data
|
||||
})
|
||||
},
|
||||
|
@ -210,18 +301,16 @@ export default {
|
|||
})
|
||||
return
|
||||
}
|
||||
if (!this.selectedSheet.originalContent) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '页面内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
this.selectedSheet.sheetMetas = this.selectedSheetMetas
|
||||
this.saving = true
|
||||
if (this.selectedSheet.id) {
|
||||
sheetApi.update(this.selectedSheet.id, this.selectedSheet, autoSave).then(response => {
|
||||
this.$log.debug('Updated sheet', response.data.data)
|
||||
if (updateSuccess) {
|
||||
updateSuccess()
|
||||
this.saving = false
|
||||
this.$emit('onSaved', true)
|
||||
this.$router.push({ name: 'SheetList' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
@ -229,6 +318,9 @@ export default {
|
|||
this.$log.debug('Created sheet', response.data.data)
|
||||
if (createSuccess) {
|
||||
createSuccess()
|
||||
this.saving = false
|
||||
this.$emit('onSaved', true)
|
||||
this.$router.push({ name: 'SheetList' })
|
||||
}
|
||||
this.selectedSheet = response.data.data
|
||||
})
|
||||
|
@ -242,6 +334,18 @@ export default {
|
|||
},
|
||||
onSheetDateOk(value) {
|
||||
this.selectedSheet.createTime = value.valueOf()
|
||||
},
|
||||
handleRemoveSheetMeta(item) {
|
||||
var index = this.selectedSheetMetas.indexOf(item)
|
||||
if (index !== -1) {
|
||||
this.selectedSheetMetas.splice(index, 1)
|
||||
}
|
||||
},
|
||||
handleInsertSheetMeta() {
|
||||
this.selectedSheetMetas.push({
|
||||
value: '',
|
||||
key: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-card
|
||||
|
@ -14,7 +14,10 @@
|
|||
:sm="24"
|
||||
>
|
||||
<a-form-item label="关键词">
|
||||
<a-input v-model="queryParam.keyword" />
|
||||
<a-input
|
||||
v-model="queryParam.keyword"
|
||||
@keyup.enter="handleQuery()"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
|
@ -25,7 +28,7 @@
|
|||
<a-select
|
||||
placeholder="请选择状态"
|
||||
v-model="queryParam.type"
|
||||
@change="loadJournals(true)"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="type in Object.keys(journalType)"
|
||||
|
@ -42,11 +45,11 @@
|
|||
<span class="table-page-search-submitButtons">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="loadJournals(true)"
|
||||
@click="handleQuery()"
|
||||
>查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px;"
|
||||
@click="resetParam"
|
||||
@click="resetParam()"
|
||||
>重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
|
@ -62,7 +65,9 @@
|
|||
</div>
|
||||
<a-divider />
|
||||
<div style="margin-top:15px">
|
||||
<a-empty v-if="journals.length==0" />
|
||||
<a-list
|
||||
v-else
|
||||
itemLayout="vertical"
|
||||
:pagination="false"
|
||||
:dataSource="journals"
|
||||
|
@ -73,46 +78,19 @@
|
|||
slot-scope="item, index"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 日志图片集合 -->
|
||||
<!-- <a-card
|
||||
hoverable
|
||||
v-for="(photo, photoIndex) in item.photos"
|
||||
:key="photoIndex"
|
||||
class="photo-card"
|
||||
@click="handlerPhotoPreview(photo)"
|
||||
>
|
||||
<img alt="example" :src="photo.thumbnail" slot="cover">
|
||||
</a-card> -->
|
||||
|
||||
<!-- <a-modal
|
||||
:visible="previewVisible"
|
||||
:footer="null"
|
||||
@cancel="handleCancelPreview"
|
||||
>
|
||||
<img
|
||||
:alt="previewPhoto.name + previewPhoto.description"
|
||||
style="width: 100%"
|
||||
:src="previewPhoto.url"
|
||||
>
|
||||
</a-modal> -->
|
||||
|
||||
<template slot="actions">
|
||||
<span>
|
||||
<a href="javascript:void(0);">
|
||||
<a-icon
|
||||
type="like-o"
|
||||
/>
|
||||
<a-icon type="like-o" />
|
||||
{{ item.likes }}
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleCommentShow(item)"
|
||||
@click="handleShowJournalComments(item)"
|
||||
>
|
||||
<a-icon
|
||||
type="message"
|
||||
/>
|
||||
<a-icon type="message" />
|
||||
{{ item.commentCount }}
|
||||
</a>
|
||||
</span>
|
||||
|
@ -129,9 +107,6 @@
|
|||
<a-icon type="unlock" />
|
||||
</a>
|
||||
</span>
|
||||
<!-- <span>
|
||||
From 微信
|
||||
</span>-->
|
||||
</template>
|
||||
<template slot="extra">
|
||||
<a
|
||||
|
@ -149,7 +124,10 @@
|
|||
</a-popconfirm>
|
||||
</template>
|
||||
|
||||
<a-list-item-meta :description="item.content">
|
||||
<a-list-item-meta>
|
||||
<template slot="description">
|
||||
<p v-html="item.content" class="journal-list-content"></p>
|
||||
</template>
|
||||
<span slot="title">{{ item.createTime | moment }}</span>
|
||||
<a-avatar
|
||||
slot="avatar"
|
||||
|
@ -161,12 +139,13 @@
|
|||
<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="onPaginationChange"
|
||||
@change="onPaginationChange"
|
||||
@showSizeChange="handlePaginationChange"
|
||||
@change="handlePaginationChange"
|
||||
/>
|
||||
</div>
|
||||
</a-list>
|
||||
|
@ -187,6 +166,10 @@
|
|||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="footer">
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click="()=>this.attachmentDrawerVisible = true"
|
||||
>附件库</a-button>
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
|
@ -198,7 +181,7 @@
|
|||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 8 }"
|
||||
v-model="journal.content"
|
||||
v-model="journal.sourceContent"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
|
@ -209,119 +192,39 @@
|
|||
defaultChecked
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item v-show="showMoreOptions">
|
||||
<UploadPhoto
|
||||
@success="handlerPhotoUploadSuccess"
|
||||
:photoList="photoList"
|
||||
:plusPhotoVisible="plusPhotoVisible"
|
||||
></UploadPhoto>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a
|
||||
href="javascript:;"
|
||||
class="more-options-btn"
|
||||
type="default"
|
||||
@click="handleUploadPhotoWallClick"
|
||||
>
|
||||
更多选项
|
||||
<a-icon type="down"/>
|
||||
</a>
|
||||
</a-form-item> -->
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 评论回复弹窗 -->
|
||||
<a-modal
|
||||
v-if="selectComment"
|
||||
:title="'回复给:'+selectComment.author"
|
||||
v-model="selectCommentVisible"
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
@click="handleReplyComment"
|
||||
>回复</a-button>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 8 }"
|
||||
v-model="replyComment.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<TargetCommentDrawer
|
||||
:visible="journalCommentVisible"
|
||||
:description="journal.content"
|
||||
:target="`journals`"
|
||||
:id="journal.id"
|
||||
@close="onJournalCommentsClose"
|
||||
/>
|
||||
|
||||
<!-- 评论列表抽屉 -->
|
||||
<a-drawer
|
||||
title="评论列表"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="commentVisible"
|
||||
destroyOnClose
|
||||
@close="()=>this.commentVisible = false"
|
||||
>
|
||||
<a-row
|
||||
type="flex"
|
||||
align="middle"
|
||||
>
|
||||
<a-col :span="24">
|
||||
<a-comment>
|
||||
<a-avatar
|
||||
:src="user.avatar"
|
||||
:alt="user.nickname"
|
||||
slot="avatar"
|
||||
/>
|
||||
<p slot="content">{{ journal.content }}</p>
|
||||
|
||||
<span slot="datetime">{{ journal.createTime | moment }}</span>
|
||||
</a-comment>
|
||||
</a-col>
|
||||
<a-divider />
|
||||
<a-col :span="24">
|
||||
<journal-comment-tree
|
||||
v-for="(comment,index) in comments"
|
||||
:key="index"
|
||||
:comment="comment"
|
||||
@reply="handleCommentReplyClick"
|
||||
@delete="handleCommentDelete"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-drawer>
|
||||
<AttachmentDrawer v-model="attachmentDrawerVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JournalCommentTree from './components/JournalCommentTree'
|
||||
import TargetCommentDrawer from '../../comment/components/TargetCommentDrawer'
|
||||
import AttachmentDrawer from '../../attachment/components/AttachmentDrawer'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import journalApi from '@/api/journal'
|
||||
import journalCommentApi from '@/api/journalComment'
|
||||
import UploadPhoto from '@/components/Upload/UploadPhoto.vue'
|
||||
export default {
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: { JournalCommentTree, UploadPhoto },
|
||||
components: { TargetCommentDrawer, AttachmentDrawer },
|
||||
data() {
|
||||
return {
|
||||
journalType: journalApi.journalType,
|
||||
// plusPhotoVisible: true,
|
||||
// photoList: [], // 编辑图片时回显所需对象
|
||||
// previewVisible: false,
|
||||
showMoreOptions: false,
|
||||
// previewPhoto: {
|
||||
// // 图片预览信息临时对象
|
||||
// name: '',
|
||||
// description: '',
|
||||
// url: ''
|
||||
// },
|
||||
title: '发表',
|
||||
listLoading: false,
|
||||
visible: false,
|
||||
commentVisible: false,
|
||||
selectCommentVisible: false,
|
||||
journalCommentVisible: false,
|
||||
attachmentDrawerVisible: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 10,
|
||||
|
@ -338,8 +241,6 @@ export default {
|
|||
comments: [],
|
||||
journal: {},
|
||||
isPublic: true,
|
||||
journalPhotos: [], // 日志图片集合最多九张
|
||||
selectComment: null,
|
||||
replyComment: {}
|
||||
}
|
||||
},
|
||||
|
@ -350,61 +251,30 @@ export default {
|
|||
...mapGetters(['user'])
|
||||
},
|
||||
methods: {
|
||||
// handleCancelPreview() {
|
||||
// this.previewVisible = false
|
||||
// },
|
||||
// handlerPhotoPreview(photo) {
|
||||
// // 日志图片预览
|
||||
// this.previewVisible = true
|
||||
// this.previewPhoto = photo
|
||||
// },
|
||||
// handlerPhotoUploadSuccess(response, file) {
|
||||
// var callData = response.data.data
|
||||
// var photo = {
|
||||
// name: callData.name,
|
||||
// url: callData.path,
|
||||
// thumbnail: callData.thumbPath,
|
||||
// suffix: callData.suffix,
|
||||
// width: callData.width,
|
||||
// height: callData.height
|
||||
// }
|
||||
// this.journalPhotos.push(photo)
|
||||
// },
|
||||
// handleUploadPhotoWallClick() {
|
||||
// // 是否显示上传照片墙组件
|
||||
// this.showMoreOptions = !this.showMoreOptions
|
||||
// },
|
||||
loadJournals(isSearch) {
|
||||
loadJournals() {
|
||||
this.listLoading = true
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
if (isSearch) {
|
||||
this.queryParam.page = 0
|
||||
}
|
||||
this.listLoading = true
|
||||
journalApi.query(this.queryParam).then(response => {
|
||||
this.journals = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleNew() {
|
||||
this.title = '新建'
|
||||
this.visible = true
|
||||
this.journal = {}
|
||||
|
||||
// 显示图片上传框
|
||||
// this.plusPhotoVisible = true
|
||||
// this.photoList = []
|
||||
},
|
||||
handleEdit(item) {
|
||||
this.title = '编辑'
|
||||
this.journal = item
|
||||
this.isPublic = item.type !== 'INTIMATE'
|
||||
this.visible = true
|
||||
// 为编辑时需要回显图片数组赋值,并隐藏图片上传框
|
||||
// this.plusPhotoVisible = false
|
||||
// this.photoList = item.photos
|
||||
},
|
||||
handleDelete(id) {
|
||||
journalApi.delete(id).then(response => {
|
||||
|
@ -412,27 +282,9 @@ export default {
|
|||
this.loadJournals()
|
||||
})
|
||||
},
|
||||
handleCommentShow(journal) {
|
||||
handleShowJournalComments(journal) {
|
||||
this.journal = journal
|
||||
journalApi.commentTree(this.journal.id).then(response => {
|
||||
this.comments = response.data.data.content
|
||||
this.commentVisible = true
|
||||
})
|
||||
},
|
||||
handleCommentReplyClick(comment) {
|
||||
this.selectComment = comment
|
||||
this.selectCommentVisible = true
|
||||
this.replyComment.parentId = comment.id
|
||||
this.replyComment.postId = this.journal.id
|
||||
},
|
||||
handleReplyComment() {
|
||||
journalCommentApi.create(this.replyComment).then(response => {
|
||||
this.$message.success('回复成功!')
|
||||
this.replyComment = {}
|
||||
this.selectComment = {}
|
||||
this.selectCommentVisible = false
|
||||
this.handleCommentShow(this.journal)
|
||||
})
|
||||
this.journalCommentVisible = true
|
||||
},
|
||||
handleCommentDelete(comment) {
|
||||
journalCommentApi.delete(comment.id).then(response => {
|
||||
|
@ -441,11 +293,9 @@ export default {
|
|||
})
|
||||
},
|
||||
createOrUpdateJournal() {
|
||||
// 给属性填充数据
|
||||
// this.journal.photos = this.journalPhotos
|
||||
this.journal.type = this.isPublic ? 'PUBLIC' : 'INTIMATE'
|
||||
|
||||
if (!this.journal.content) {
|
||||
if (!this.journal.sourceContent) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '发布内容不能为空!'
|
||||
|
@ -463,35 +313,26 @@ export default {
|
|||
journalApi.create(this.journal).then(response => {
|
||||
this.$message.success('发表成功!')
|
||||
this.loadJournals()
|
||||
// this.photoList = []
|
||||
this.isPublic = true
|
||||
})
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
onPaginationChange(page, pageSize) {
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
this.loadJournals()
|
||||
},
|
||||
onJournalCommentsClose() {
|
||||
this.journal = {}
|
||||
this.journalCommentVisible = false
|
||||
},
|
||||
resetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.type = null
|
||||
this.loadJournals()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped="scoped">
|
||||
/* .more-options-btn {
|
||||
margin-left: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.photo-card {
|
||||
width: 104px;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
} */
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:xl="10"
|
||||
|
@ -21,16 +21,25 @@
|
|||
label="网站地址:"
|
||||
help="* 需要加上 http://"
|
||||
>
|
||||
<a-input v-model="link.url" />
|
||||
<a-input v-model="link.url">
|
||||
<!-- <a
|
||||
href="javascript:void(0);"
|
||||
slot="addonAfter"
|
||||
@click="handleParseUrl"
|
||||
>
|
||||
<a-icon type="sync" />
|
||||
</a> -->
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Logo:">
|
||||
<a-input v-model="link.logo" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="分组:"
|
||||
help="* 非必填"
|
||||
>
|
||||
<a-input v-model="link.team" />
|
||||
<a-form-item label="分组:">
|
||||
<a-auto-complete
|
||||
:dataSource="teams"
|
||||
v-model="link.team"
|
||||
allowClear
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序编号:">
|
||||
<a-input
|
||||
|
@ -226,7 +235,8 @@ export default {
|
|||
loading: false,
|
||||
columns,
|
||||
links: [],
|
||||
link: {}
|
||||
link: {},
|
||||
teams: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -239,6 +249,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.loadLinks()
|
||||
this.loadTeams()
|
||||
},
|
||||
methods: {
|
||||
loadLinks() {
|
||||
|
@ -248,6 +259,11 @@ export default {
|
|||
this.loading = false
|
||||
})
|
||||
},
|
||||
loadTeams() {
|
||||
linkApi.listTeams().then(response => {
|
||||
this.teams = response.data.data
|
||||
})
|
||||
},
|
||||
handleSaveClick() {
|
||||
this.createOrUpdateLink()
|
||||
},
|
||||
|
@ -265,18 +281,40 @@ export default {
|
|||
linkApi.delete(id).then(response => {
|
||||
this.$message.success('删除成功!')
|
||||
this.loadLinks()
|
||||
this.loadTeams()
|
||||
})
|
||||
},
|
||||
handleParseUrl() {
|
||||
linkApi.getByParse(this.link.url).then(response => {
|
||||
this.link = response.data.data
|
||||
})
|
||||
},
|
||||
createOrUpdateLink() {
|
||||
if (!this.link.name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '网站名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.link.url) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '网站地址不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.link.id) {
|
||||
linkApi.update(this.link.id, this.link).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
this.loadLinks()
|
||||
this.loadTeams()
|
||||
})
|
||||
} else {
|
||||
linkApi.create(this.link).then(response => {
|
||||
this.$message.success('保存成功!')
|
||||
this.loadLinks()
|
||||
this.loadTeams()
|
||||
})
|
||||
}
|
||||
this.handleAddLink()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row
|
||||
:gutter="12"
|
||||
type="flex"
|
||||
|
@ -7,7 +7,7 @@
|
|||
>
|
||||
<a-col
|
||||
:span="24"
|
||||
class="search-box"
|
||||
style="padding-bottom: 12px;"
|
||||
>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
|
@ -31,7 +31,7 @@
|
|||
<a-form-item label="分组">
|
||||
<a-select
|
||||
v-model="queryParam.team"
|
||||
@change="loadPhotos(true)"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item,index) in teams"
|
||||
|
@ -48,18 +48,18 @@
|
|||
<span class="table-page-search-submitButtons">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="loadPhotos(true)"
|
||||
@click="handleQuery()"
|
||||
>查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px;"
|
||||
@click="resetParam"
|
||||
@click="resetParam()"
|
||||
>重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="table-operator">
|
||||
<div class="table-operator" style="margin-bottom: 0;">
|
||||
<a-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
|
@ -85,9 +85,9 @@
|
|||
@click="showDrawer(item)"
|
||||
>
|
||||
<div class="photo-thumb">
|
||||
<img :src="item.thumbnail">
|
||||
<img :src="item.thumbnail" loading="lazy">
|
||||
</div>
|
||||
<a-card-meta>
|
||||
<a-card-meta style="padding: 0.8rem;">
|
||||
<ellipsis
|
||||
:length="isMobile()?12:16"
|
||||
tooltip
|
||||
|
@ -101,6 +101,7 @@
|
|||
</a-row>
|
||||
<div class="page-wrapper">
|
||||
<a-pagination
|
||||
:current="pagination.page"
|
||||
:total="pagination.total"
|
||||
:defaultPageSize="pagination.size"
|
||||
:pageSizeOptions="['18', '36', '54','72','90','108']"
|
||||
|
@ -129,13 +130,15 @@
|
|||
>
|
||||
<div class="photo-detail-img">
|
||||
<img
|
||||
:src="photo.url || '/images/placeholder.png'"
|
||||
:src="photo.url || '/images/placeholder.jpg'"
|
||||
@click="showThumbDrawer"
|
||||
style="width: 100%;"
|
||||
>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
</a-col>
|
||||
<a-divider />
|
||||
<a-divider style="margin: 24px 0 12px 0;"/>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-skeleton
|
||||
active
|
||||
|
@ -216,7 +219,12 @@
|
|||
slot="description"
|
||||
v-if="editable"
|
||||
>
|
||||
<a-input v-model="photo.team" />
|
||||
<a-auto-complete
|
||||
:dataSource="teams"
|
||||
v-model="photo.team"
|
||||
allowClear
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
<span
|
||||
slot="description"
|
||||
|
@ -319,20 +327,20 @@ export default {
|
|||
this.loadTeams()
|
||||
},
|
||||
methods: {
|
||||
loadPhotos(isSearch) {
|
||||
loadPhotos() {
|
||||
this.listLoading = true
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
if (isSearch) {
|
||||
this.queryParam.page = 0
|
||||
}
|
||||
this.listLoading = true
|
||||
photoApi.query(this.queryParam).then(response => {
|
||||
this.photos = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
loadTeams() {
|
||||
photoApi.listTeams().then(response => {
|
||||
this.teams = response.data.data
|
||||
|
@ -343,11 +351,13 @@ export default {
|
|||
photoApi.update(this.photo.id, this.photo).then(response => {
|
||||
this.$message.success('照片更新成功!')
|
||||
this.loadPhotos()
|
||||
this.loadTeams()
|
||||
})
|
||||
} else {
|
||||
photoApi.create(this.photo).then(response => {
|
||||
this.$message.success('照片添加成功!')
|
||||
this.loadPhotos()
|
||||
this.loadTeams()
|
||||
this.photo = response.data.data
|
||||
})
|
||||
}
|
||||
|
@ -375,6 +385,7 @@ export default {
|
|||
this.$message.success('删除成功!')
|
||||
this.onDrawerClose()
|
||||
this.loadPhotos()
|
||||
this.loadTeams()
|
||||
})
|
||||
},
|
||||
showThumbDrawer() {
|
||||
|
@ -388,7 +399,7 @@ export default {
|
|||
resetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.team = null
|
||||
this.loadPhotos()
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
this.loadTeams()
|
||||
},
|
||||
onDrawerClose() {
|
||||
|
@ -399,40 +410,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-divider-horizontal {
|
||||
margin: 24px 0 12px 0;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.photo-thumb {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
padding-bottom: 56%;
|
||||
overflow: hidden;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-card-meta {
|
||||
padding: 0.8rem;
|
||||
}
|
||||
|
||||
.photo-detail-img img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-operator {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-comment>
|
||||
<span
|
||||
slot="actions"
|
||||
@click="handleReplyClick"
|
||||
>回复</span>
|
||||
<a-popconfirm
|
||||
:title="'你确定要永久删除该评论?'"
|
||||
@confirm="handleDeleteClick"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
slot="actions"
|
||||
>
|
||||
<span>删除</span>
|
||||
</a-popconfirm>
|
||||
<a slot="author"> {{ comment.author }} </a>
|
||||
<a-avatar
|
||||
slot="avatar"
|
||||
:src="avatar"
|
||||
:alt="comment.author"
|
||||
/>
|
||||
<p slot="content">{{ comment.content }}</p>
|
||||
|
||||
<template v-if="comment.children">
|
||||
<journal-comment-tree
|
||||
v-for="(child, index) in comment.children"
|
||||
:key="index"
|
||||
:comment="child"
|
||||
@reply="handleSubReply"
|
||||
@delete="handleSubDelete"
|
||||
/>
|
||||
</template>
|
||||
|
||||
</a-comment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JournalCommentTree',
|
||||
props: {
|
||||
comment: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
avatar() {
|
||||
return `//cn.gravatar.com/avatar/${this.comment.gravatarMd5}/?s=256&d=mp`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleReplyClick() {
|
||||
this.$emit('reply', this.comment)
|
||||
},
|
||||
handleSubReply(comment) {
|
||||
this.$emit('reply', comment)
|
||||
},
|
||||
handleDeleteClick() {
|
||||
this.$emit('delete', this.comment)
|
||||
},
|
||||
handleSubDelete(comment) {
|
||||
this.$emit('delete', comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-card
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div class="card-container">
|
||||
<a-tabs type="card">
|
||||
<a-tab-pane key="1">
|
||||
<span slot="tab">
|
||||
<a-icon type="folder" />资源文件备份
|
||||
</span>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:dataSource="ResourcesData"
|
||||
>
|
||||
<span
|
||||
slot="action"
|
||||
slot-scope="text, record"
|
||||
>
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="downResources('ResourcesData',record.id)"
|
||||
>下载</a>
|
||||
<a-divider type="vertical" />
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="sendResources('ResourcesData',record.id)"
|
||||
>发送到邮箱</a>
|
||||
<a-divider type="vertical" />
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="deleteResources('ResourcesData',record.id)"
|
||||
>删除</a>
|
||||
</span>
|
||||
</a-table>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="backupData('ResourcesData')"
|
||||
>备份</a-button>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2">
|
||||
<span slot="tab">
|
||||
<a-icon type="database" />数据库备份
|
||||
</span>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:dataSource="DataBaseData"
|
||||
>
|
||||
<span
|
||||
slot="action"
|
||||
slot-scope="text, record"
|
||||
>
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="downResources('DataBaseData',record.id)"
|
||||
>下载</a>
|
||||
<a-divider type="vertical" />
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="sendResources('DataBaseData',record.id)"
|
||||
>发送到邮箱</a>
|
||||
<a-divider type="vertical" />
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="deleteResources('DataBaseData',record.id)"
|
||||
>删除</a>
|
||||
</span>
|
||||
</a-table>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="backupData('DataBaseData')"
|
||||
>备份</a-button>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3">
|
||||
<span slot="tab">
|
||||
<a-icon type="read" />文章备份
|
||||
</span>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:dataSource="FileData"
|
||||
>
|
||||
<span
|
||||
slot="action"
|
||||
slot-scope="text, record"
|
||||
>
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="downResources('FileData',record.id)"
|
||||
>下载</a>
|
||||
<a-divider type="vertical" />
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="sendResources('FileData',record.id)"
|
||||
>发送到邮箱</a>
|
||||
<a-divider type="vertical" />
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="deleteResources('FileData',record.id)"
|
||||
>删除</a>
|
||||
</span>
|
||||
</a-table>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="backupData('FileData')"
|
||||
>备份</a-button>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
num: 0,
|
||||
columns: [
|
||||
{
|
||||
title: '文件名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '日期',
|
||||
dataIndex: 'date'
|
||||
},
|
||||
{
|
||||
title: '文件大小',
|
||||
dataIndex: 'size'
|
||||
},
|
||||
{
|
||||
title: '文件类型',
|
||||
dataIndex: 'type'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
],
|
||||
ResourcesData: [],
|
||||
DataBaseData: [],
|
||||
FileData: []
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
// 下载
|
||||
downResources(type, id) {
|
||||
if (type === 'ResourcesData') {
|
||||
alert('资源文件下载' + id)
|
||||
} else if (type === 'DataBaseData') {
|
||||
alert('数据库文件下载' + id)
|
||||
} else {
|
||||
alert('文件下载' + id)
|
||||
}
|
||||
},
|
||||
// 发送到邮箱
|
||||
sendResources(type, id) {
|
||||
if (type === 'ResourcesData') {
|
||||
alert('资源文件发送到邮箱' + id)
|
||||
} else if (type === 'DataBaseData') {
|
||||
alert('数据库文件发送到邮箱' + id)
|
||||
} else {
|
||||
alert('文件发送到邮箱' + id)
|
||||
}
|
||||
},
|
||||
// 删除
|
||||
deleteResources(type, id) {
|
||||
if (type === 'ResourcesData') {
|
||||
alert('资源文件删除' + id)
|
||||
} else if (type === 'DataBaseData') {
|
||||
alert('数据库文件删除' + id)
|
||||
} else {
|
||||
alert('文件删除' + id)
|
||||
}
|
||||
},
|
||||
// 备份
|
||||
backupData(type) {
|
||||
if (type === 'ResourcesData') {
|
||||
alert('资源文件备份')
|
||||
} else if (type === 'DataBaseData') {
|
||||
alert('数据库文件备份')
|
||||
} else {
|
||||
alert('文件备份')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -225,22 +225,6 @@ import recoveryApi from '@/api/recovery'
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
formItemLayout: {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 5 },
|
||||
lg: { span: 4 },
|
||||
xl: { span: 4 },
|
||||
xxl: { span: 3 }
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 19 },
|
||||
lg: { span: 20 },
|
||||
xl: { span: 20 },
|
||||
xxl: { span: 21 }
|
||||
}
|
||||
},
|
||||
installation: {},
|
||||
migrationUploadName: 'file',
|
||||
migrationData: null,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +1,69 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<div>
|
||||
<div class="card-content">
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
v-if="options.developer_mode"
|
||||
:xl="6"
|
||||
:lg="6"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<div slot="title">
|
||||
<a-icon type="experiment" /> 开发者选项
|
||||
</div>
|
||||
<p>点击进入开发者选项页面</p>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="float:right"
|
||||
@click="handleToDeveloperOptions()"
|
||||
>进入</a-button>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
:xl="6"
|
||||
:lg="6"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<a-card
|
||||
title="Markdown 文章导入"
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<div slot="title">
|
||||
<a-icon type="hdd" /> 博客备份
|
||||
</div>
|
||||
<p>支持备份全站数据</p>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="float:right"
|
||||
@click="()=>this.backupDrawerVisible = true"
|
||||
>备份</a-button>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
:xl="6"
|
||||
:lg="6"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<div slot="title">
|
||||
<a-icon type="file-markdown" /> Markdown 文章导入
|
||||
</div>
|
||||
<p>支持 Hexo/Jekyll 文章导入并解析元数据</p>
|
||||
<a-button
|
||||
type="primary"
|
||||
|
@ -38,19 +88,28 @@
|
|||
:uploadHandler="uploadHandler"
|
||||
></FilePondUpload>
|
||||
</a-modal>
|
||||
<BackupDrawer v-model="backupDrawerVisible"></BackupDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import backupApi from '@/api/backup'
|
||||
import BackupDrawer from './components/BackupDrawer'
|
||||
|
||||
export default {
|
||||
components: { BackupDrawer },
|
||||
data() {
|
||||
return {
|
||||
backupDrawerVisible: false,
|
||||
markdownUpload: false,
|
||||
uploadHandler: backupApi.importMarkdown
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
handleImportMarkdown() {
|
||||
this.markdownUpload = true
|
||||
|
@ -66,6 +125,9 @@ export default {
|
|||
this.$message.error(`${info.file.name} 导入失败!`)
|
||||
}
|
||||
},
|
||||
handleToDeveloperOptions() {
|
||||
this.$router.push({ name: 'DeveloperOptions' })
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="博客备份"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
>
|
||||
<a-row
|
||||
type="flex"
|
||||
align="middle"
|
||||
>
|
||||
<a-col :span="24">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '0' }"
|
||||
>
|
||||
<a-list
|
||||
itemLayout="horizontal"
|
||||
:dataSource="backupTips"
|
||||
>
|
||||
<a-list-item
|
||||
slot="renderItem"
|
||||
slot-scope="backupTip"
|
||||
>
|
||||
<a-list-item-meta :description="backupTip.description">
|
||||
<h4 slot="title">{{ backupTip.title }}</h4>
|
||||
</a-list-item-meta>
|
||||
<a-alert
|
||||
slot="extra"
|
||||
v-if="backupTip.alert"
|
||||
:message="backupTip.alert.message"
|
||||
:type="backupTip.alert.type"
|
||||
banner
|
||||
/>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
|
||||
<a-divider>历史备份</a-divider>
|
||||
|
||||
<a-list
|
||||
itemLayout="vertical"
|
||||
size="small"
|
||||
:dataSource="backups"
|
||||
>
|
||||
<a-list-item
|
||||
slot="renderItem"
|
||||
slot-scope="backup"
|
||||
>
|
||||
<a-button
|
||||
slot="extra"
|
||||
type="link"
|
||||
style="color: red"
|
||||
icon="delete"
|
||||
:loading="deleting"
|
||||
@click="handleBackupDeleteClick(backup.filename)"
|
||||
>删除</a-button>
|
||||
<a-list-item-meta>
|
||||
<a
|
||||
slot="title"
|
||||
:href="backup.downloadUrl"
|
||||
>
|
||||
<a-icon
|
||||
type="schedule"
|
||||
style="color: #52c41a"
|
||||
/>
|
||||
{{ backup.filename }}
|
||||
</a>
|
||||
<p slot="description">{{ backup.updateTime | timeAgo }}/{{ backup.fileSize | fileSizeFormat }}</p>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<div
|
||||
v-if="loading"
|
||||
class="loading-container"
|
||||
style="position: absolute;bottom: 40px; width: 100%;text-align: center;"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
type="primary"
|
||||
icon="download"
|
||||
style="marginRight: 8px"
|
||||
:loading="backuping"
|
||||
@click="handleBackupClick"
|
||||
>备份</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
icon="reload"
|
||||
:loading="loading"
|
||||
@click="handleBAckupRefreshClick"
|
||||
>刷新</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import backupApi from '@/api/backup'
|
||||
export default {
|
||||
name: 'BackupDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
return {
|
||||
backuping: false,
|
||||
loading: false,
|
||||
deleting: false,
|
||||
backups: [],
|
||||
backupTips: [
|
||||
{
|
||||
title: '博客备份',
|
||||
description:
|
||||
'将会压缩 Halo 的工作目录到临时文件中,并提供下载链接。如果附件太多的话,可能会十分耗时,请耐心等待!',
|
||||
alert: {
|
||||
type: 'warning',
|
||||
message: '注意:备份后生成的压缩文件存储在临时文件中,重启服务器会造成备份文件的丢失,所以请尽快下载!'
|
||||
}
|
||||
},
|
||||
{ title: '备份查询', description: '查询近期的备份,按照备份时间递减排序。' },
|
||||
{ title: '备份删除', description: '删除已经备份的内容。' },
|
||||
{
|
||||
title: '版本要求',
|
||||
alert: {
|
||||
type: 'warning',
|
||||
message: '注意:要求 Halo server 版本大于 v1.1.1!你可以在 【系统 | 关于】 里面找到系统的版本信息。'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'close'
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.getBackups()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getBackups() {
|
||||
this.loading = true
|
||||
backupApi
|
||||
.listHaloBackups()
|
||||
.then(response => {
|
||||
this.backups = response.data.data
|
||||
})
|
||||
.finally(() => (this.loading = false))
|
||||
},
|
||||
handleBackupClick() {
|
||||
this.backuping = true
|
||||
backupApi
|
||||
.backupHalo()
|
||||
.then(response => {
|
||||
this.$notification.success({ message: '备份成功!' })
|
||||
this.getBackups()
|
||||
})
|
||||
.finally(() => {
|
||||
this.backuping = false
|
||||
})
|
||||
},
|
||||
handleBackupDeleteClick(filename) {
|
||||
this.deleting = true
|
||||
backupApi
|
||||
.deleteHaloBackup(filename)
|
||||
.then(response => {
|
||||
this.$notification.success({ message: '删除成功!' })
|
||||
this.getBackups()
|
||||
})
|
||||
.finally(() => (this.deleting = false))
|
||||
},
|
||||
handleBAckupRefreshClick() {
|
||||
this.getBackups()
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<div
|
||||
class="card-container"
|
||||
v-if="options.developer_mode"
|
||||
>
|
||||
<a-tabs type="card">
|
||||
<a-tab-pane key="environment">
|
||||
<span slot="tab">
|
||||
<a-icon type="safety" />运行环境
|
||||
</span>
|
||||
<Environment />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="runtimeLogs">
|
||||
<span slot="tab">
|
||||
<a-icon type="code" />实时日志
|
||||
</span>
|
||||
<RuntimeLogs />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="optionsList">
|
||||
<span slot="tab">
|
||||
<a-icon type="table" />系统变量
|
||||
</span>
|
||||
<OptionsList />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="applicationConfig">
|
||||
<span slot="tab">
|
||||
<a-icon type="file-protect" />配置文件
|
||||
</span>
|
||||
<ApplicationConfig />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="staticStorage">
|
||||
<span slot="tab">
|
||||
<a-icon type="cloud" />静态存储
|
||||
</span>
|
||||
<StaticStorage />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="settings">
|
||||
<span slot="tab">
|
||||
<a-icon type="setting" />设置
|
||||
</span>
|
||||
<SettingsForm />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-alert
|
||||
v-else
|
||||
message="提示"
|
||||
description="当前没有启用开发者选项,请启用之后再访问该页面!"
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import Environment from './tabs/Environment'
|
||||
import RuntimeLogs from './tabs/RuntimeLogs'
|
||||
import SettingsForm from './tabs/SettingsForm'
|
||||
import OptionsList from './tabs/OptionsList'
|
||||
import ApplicationConfig from './tabs/ApplicationConfig'
|
||||
import StaticStorage from './tabs/StaticStorage'
|
||||
export default {
|
||||
components: {
|
||||
Environment,
|
||||
RuntimeLogs,
|
||||
SettingsForm,
|
||||
OptionsList,
|
||||
ApplicationConfig,
|
||||
StaticStorage
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options'])
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-alert
|
||||
message="注意:配置文件严格要求代码格式,上下文必须对齐,属性与值之间必须以英文冒号和空格隔开。如格式有误,将无法启动。"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="loading"
|
||||
:paragraph="{rows: 12}"
|
||||
>
|
||||
<codemirror
|
||||
v-model="content"
|
||||
:options="codemirrorOptions"
|
||||
></codemirror>
|
||||
</a-skeleton>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-popconfirm
|
||||
:title="'修改配置文件之后需重启才能生效,是否继续?'"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@confirm="handleUpdateConfig()"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="margin-right: 8px;"
|
||||
>保存</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm
|
||||
:title="'你确定要重启吗?'"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@confirm="handleRestartApplication()"
|
||||
>
|
||||
<a-button type="danger">重启</a-button>
|
||||
</a-popconfirm>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { codemirror } from 'vue-codemirror-lite'
|
||||
import 'codemirror/mode/yaml/yaml.js'
|
||||
import adminApi from '@/api/admin'
|
||||
export default {
|
||||
name: 'ApplicationConfig',
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
codemirrorOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'text/x-yaml',
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
},
|
||||
content: '',
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadConfig()
|
||||
},
|
||||
methods: {
|
||||
loadConfig() {
|
||||
this.loading = true
|
||||
adminApi.getApplicationConfig().then(response => {
|
||||
this.content = response.data.data
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleUpdateConfig() {
|
||||
adminApi.updateApplicationConfig(this.content).then(response => {
|
||||
this.$message.success(`配置保存成功!`)
|
||||
this.loadConfig()
|
||||
})
|
||||
},
|
||||
handleRestartApplication() {
|
||||
adminApi.restartApplication().then(response => {
|
||||
this.$message.success(`重启中...`)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,292 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="24"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<a-card
|
||||
title="服务器"
|
||||
:bordered="false"
|
||||
hoverable
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<table style="width:100%">
|
||||
<tbody class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>系统</td>
|
||||
<td>{{ systemProperties['os.name'].value }} {{ systemProperties['os.version'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>平台</td>
|
||||
<td>{{ systemProperties['os.arch'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>语言</td>
|
||||
<td>{{ systemProperties['user.language'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>时区</td>
|
||||
<td>{{ systemProperties['user.timezone'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>当前用户</td>
|
||||
<td>{{ systemProperties['user.name'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>用户目录</td>
|
||||
<td>{{ systemProperties['user.home'].value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</a-card>
|
||||
<a-divider dashed />
|
||||
</a-col>
|
||||
<a-col
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="24"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<a-card
|
||||
title="使用情况"
|
||||
:bordered="false"
|
||||
hoverable
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<table style="width:100%">
|
||||
<tbody class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>CPU 数量</td>
|
||||
<td>{{ system.cpu.count }} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPU 使用率</td>
|
||||
<td>{{ system.cpu.usage }} %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>JVM 最大可用内存</td>
|
||||
<td>{{ jvm.memory.max | fileSizeFormat }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>JVM 可用内存</td>
|
||||
<td>{{ jvm.memory.committed | fileSizeFormat }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>JVM 已用内存</td>
|
||||
<td>{{ jvm.memory.used | fileSizeFormat }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GC 次数</td>
|
||||
<td>{{ jvm.gc.pause.count }} 次</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</a-card>
|
||||
<a-divider dashed />
|
||||
</a-col>
|
||||
<a-col
|
||||
:xl="24"
|
||||
:lg="24"
|
||||
:md="24"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<a-card
|
||||
title="环境"
|
||||
:bordered="false"
|
||||
hoverable
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<table style="width:100%">
|
||||
<tbody class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>Java 名称</td>
|
||||
<td>{{ systemProperties['java.vm.name'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java 版本</td>
|
||||
<td>{{ systemProperties['java.version'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java Home</td>
|
||||
<td>
|
||||
<ellipsis
|
||||
:length="isMobile() ? 50 : 256"
|
||||
tooltip
|
||||
>
|
||||
{{ systemProperties['java.home'].value }}
|
||||
</ellipsis>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</a-card>
|
||||
<a-divider dashed />
|
||||
</a-col>
|
||||
<a-col
|
||||
:xl="24"
|
||||
:lg="24"
|
||||
:md="24"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
:style="{ marginBottom: '12px' }"
|
||||
>
|
||||
<a-card
|
||||
title="应用"
|
||||
:bordered="false"
|
||||
hoverable
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<table style="width:100%">
|
||||
<tbody class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>端口</td>
|
||||
<td>{{ propertiesSourcesMap['server.ports']['local.server.port'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PID</td>
|
||||
<td>{{ systemProperties['PID'].value }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>启动时间</td>
|
||||
<td>{{ system.process.startTime | moment }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>已启动时间</td>
|
||||
<td>{{ system.process.uptime | dayTime }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>启动目录</td>
|
||||
<td>
|
||||
<ellipsis
|
||||
:length="isMobile() ? 50 : 256"
|
||||
tooltip
|
||||
>
|
||||
{{ systemProperties['user.dir'].value }}
|
||||
</ellipsis>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>日志目录</td>
|
||||
<td>
|
||||
<ellipsis
|
||||
:length="isMobile() ? 50 : 256"
|
||||
tooltip
|
||||
>
|
||||
{{ systemProperties['LOG_FILE'].value }}
|
||||
</ellipsis>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div style="position: fixed;bottom: 30px;right: 30px;">
|
||||
<a-button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
icon="sync"
|
||||
size="large"
|
||||
@click="handleRefresh"
|
||||
></a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import actuatorApi from '@/api/actuator'
|
||||
export default {
|
||||
name: 'Environment',
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
return {
|
||||
propertiesSourcesMap: {},
|
||||
systemProperties: [],
|
||||
interval: null,
|
||||
system: {
|
||||
cpu: {
|
||||
count: 0,
|
||||
usage: 0
|
||||
},
|
||||
process: {
|
||||
cpuUsage: 0,
|
||||
uptime: 0,
|
||||
startTime: 0
|
||||
}
|
||||
},
|
||||
jvm: {
|
||||
memory: {
|
||||
max: 0,
|
||||
committed: 0,
|
||||
used: 0
|
||||
},
|
||||
gc: {
|
||||
pause: {
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadEnv()
|
||||
this.loadSystemInfo()
|
||||
this.loadJvmInfo()
|
||||
},
|
||||
methods: {
|
||||
loadEnv() {
|
||||
actuatorApi.env().then(response => {
|
||||
const propertiesSources = response.data.propertySources
|
||||
propertiesSources.forEach(item => {
|
||||
this.propertiesSourcesMap[item.name] = item.properties
|
||||
})
|
||||
this.systemProperties = this.propertiesSourcesMap['systemProperties']
|
||||
})
|
||||
},
|
||||
loadSystemInfo() {
|
||||
actuatorApi.getSystemCpuCount().then(response => {
|
||||
this.system.cpu.count = response.data.measurements[0].value
|
||||
})
|
||||
actuatorApi.getSystemCpuUsage().then(response => {
|
||||
this.system.cpu.usage = Number(response.data.measurements[0].value * 100).toFixed(2)
|
||||
})
|
||||
actuatorApi.getProcessUptime().then(response => {
|
||||
this.system.process.uptime = response.data.measurements[0].value
|
||||
})
|
||||
actuatorApi.getProcessStartTime().then(response => {
|
||||
this.system.process.startTime = response.data.measurements[0].value * 1000
|
||||
})
|
||||
actuatorApi.getProcessCpuUsage().then(response => {
|
||||
this.system.process.cpuUsage = response.data.measurements[0].value
|
||||
})
|
||||
},
|
||||
loadJvmInfo() {
|
||||
actuatorApi.getJvmMemoryMax().then(response => {
|
||||
this.jvm.memory.max = response.data.measurements[0].value
|
||||
})
|
||||
actuatorApi.getJvmMemoryCommitted().then(response => {
|
||||
this.jvm.memory.committed = response.data.measurements[0].value
|
||||
})
|
||||
actuatorApi.getJvmMemoryUsed().then(response => {
|
||||
this.jvm.memory.used = response.data.measurements[0].value
|
||||
})
|
||||
actuatorApi.getJvmGcPause().then(response => {
|
||||
this.jvm.gc.pause.count = response.data.measurements[0].value
|
||||
})
|
||||
},
|
||||
handleRefresh() {
|
||||
this.loadSystemInfo()
|
||||
this.loadJvmInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,338 @@
|
|||
<template>
|
||||
<div class="option-tab-wrapper">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<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-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
:md="6"
|
||||
:sm="24"
|
||||
>
|
||||
<a-form-item label="类型">
|
||||
<a-select
|
||||
v-model="queryParam.type"
|
||||
placeholder="请选择类型"
|
||||
@change="handleQuery()"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in Object.keys(optionType)"
|
||||
:key="item"
|
||||
:value="item"
|
||||
>{{ optionType[item].text }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col
|
||||
:md="12"
|
||||
:sm="24"
|
||||
>
|
||||
<span class="table-page-search-submitButtons">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleQuery()"
|
||||
>查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px;"
|
||||
@click="handleResetParam()"
|
||||
>重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="table-operator">
|
||||
<a-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="()=>this.formVisible=true"
|
||||
>新增</a-button>
|
||||
</div>
|
||||
<div style="margin-top:15px">
|
||||
<a-table
|
||||
:rowKey="option => option.id"
|
||||
:columns="columns"
|
||||
:dataSource="formattedDatas"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
>
|
||||
<ellipsis
|
||||
:length="50"
|
||||
tooltip
|
||||
slot="key"
|
||||
slot-scope="key"
|
||||
>
|
||||
{{ key }}
|
||||
</ellipsis>
|
||||
<ellipsis
|
||||
:length="50"
|
||||
tooltip
|
||||
slot="value"
|
||||
slot-scope="value"
|
||||
>
|
||||
{{ value }}
|
||||
</ellipsis>
|
||||
<span
|
||||
slot="type"
|
||||
slot-scope="typeProperty"
|
||||
>
|
||||
{{ typeProperty.text }}
|
||||
</span>
|
||||
<span
|
||||
slot="createTime"
|
||||
slot-scope="createTime"
|
||||
>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ createTime | moment }}
|
||||
</template>
|
||||
{{ createTime | timeAgo }}
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<span
|
||||
slot="updateTime"
|
||||
slot-scope="updateTime"
|
||||
>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ updateTime | moment }}
|
||||
</template>
|
||||
{{ updateTime | timeAgo }}
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<span
|
||||
slot="action"
|
||||
slot-scope="text, record"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleEditOption(record)"
|
||||
>编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
:title="'你确定要永久删除该变量?'"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@confirm="handleDeleteOption(record.id)"
|
||||
>
|
||||
<a href="javascript:;">删除</a>
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<a-modal
|
||||
v-model="formVisible"
|
||||
:title="formTitle"
|
||||
:afterClose="onFormClose"
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
@click="createOrUpdateOption()"
|
||||
>保存</a-button>
|
||||
</template>
|
||||
<a-alert
|
||||
v-if="optionToStage.type === optionType.INTERNAL.value"
|
||||
message="注意:在不知道系统变量的具体用途时,请不要随意修改!"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="Key:">
|
||||
<a-input v-model="optionToStage.key" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Value:">
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="optionToStage.value"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import optionApi from '@/api/option'
|
||||
import { mapActions } from 'vuex'
|
||||
const columns = [
|
||||
{
|
||||
title: 'Key',
|
||||
dataIndex: 'key',
|
||||
scopedSlots: { customRender: 'key' }
|
||||
},
|
||||
{
|
||||
title: 'Value',
|
||||
dataIndex: 'value',
|
||||
scopedSlots: { customRender: 'value' }
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'typeProperty',
|
||||
width: '100px',
|
||||
scopedSlots: { customRender: 'type' }
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: '200px',
|
||||
scopedSlots: { customRender: 'createTime' }
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
width: '200px',
|
||||
scopedSlots: { customRender: 'updateTime' }
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: '120px',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
export default {
|
||||
name: 'OptionsList',
|
||||
data() {
|
||||
return {
|
||||
optionType: optionApi.type,
|
||||
columns: columns,
|
||||
formVisible: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: null
|
||||
},
|
||||
queryParam: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
sort: null,
|
||||
keyword: null,
|
||||
status: null
|
||||
},
|
||||
optionToStage: {},
|
||||
loading: false,
|
||||
options: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formattedDatas() {
|
||||
return this.options.map(option => {
|
||||
option.typeProperty = this.optionType[option.type]
|
||||
return option
|
||||
})
|
||||
},
|
||||
formTitle() {
|
||||
return this.optionToStage.id ? '编辑' : '新增'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadOptionsList()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['loadOptions']),
|
||||
loadOptionsList() {
|
||||
this.loading = true
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
this.queryParam.sort = this.pagination.sort
|
||||
optionApi.query(this.queryParam).then(response => {
|
||||
this.options = response.data.data.content
|
||||
this.pagination.total = response.data.data.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
handleDeleteOption(id) {
|
||||
optionApi.delete(id).then(response => {
|
||||
this.$message.success('删除成功!')
|
||||
this.loadOptionsList()
|
||||
this.loadOptions()
|
||||
})
|
||||
},
|
||||
handleEditOption(option) {
|
||||
this.optionToStage = option
|
||||
this.formVisible = true
|
||||
},
|
||||
handlePaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.pagination.page = page
|
||||
this.pagination.size = pageSize
|
||||
this.loadOptionsList()
|
||||
},
|
||||
handleResetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.type = null
|
||||
this.handlePaginationChange(1, this.pagination.size)
|
||||
},
|
||||
onFormClose() {
|
||||
this.formVisible = false
|
||||
this.optionToStage = {}
|
||||
},
|
||||
createOrUpdateOption() {
|
||||
if (!this.optionToStage.key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.optionToStage.value) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Value 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.optionToStage.id) {
|
||||
optionApi.update(this.optionToStage.id, this.optionToStage).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
this.loadOptionsList()
|
||||
this.loadOptions()
|
||||
this.optionToStage = {}
|
||||
this.formVisible = false
|
||||
})
|
||||
} else {
|
||||
this.optionToStage.type = this.optionType.CUSTOM.value
|
||||
optionApi.create(this.optionToStage).then(response => {
|
||||
this.$message.success('保存成功!')
|
||||
this.loadOptionsList()
|
||||
this.loadOptions()
|
||||
this.optionToStage = {}
|
||||
this.formVisible = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="loading"
|
||||
:paragraph="{rows: 12}"
|
||||
>
|
||||
<codemirror
|
||||
v-model="logContent"
|
||||
:options="codemirrorOptions"
|
||||
></codemirror>
|
||||
</a-skeleton>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-select
|
||||
defaultValue="200"
|
||||
style="margin-right: 8px;width: 100px"
|
||||
@change="handleLinesChange"
|
||||
>
|
||||
<a-select-option value="200">200 行</a-select-option>
|
||||
<a-select-option value="500">500 行</a-select-option>
|
||||
<a-select-option value="800">800 行</a-select-option>
|
||||
<a-select-option value="1000">1000 行</a-select-option>
|
||||
</a-select>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="margin-right: 8px;"
|
||||
@click="()=>this.loadLogs()"
|
||||
>刷新</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click="handleDownloadLogFile()"
|
||||
>下载</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script>
|
||||
import { codemirror } from 'vue-codemirror-lite'
|
||||
import 'codemirror/mode/shell/shell.js'
|
||||
import adminApi from '@/api/admin'
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'RuntimeLogs',
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
codemirrorOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'shell',
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
},
|
||||
logContent: '',
|
||||
loading: true,
|
||||
logLines: 200
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadLogs()
|
||||
},
|
||||
methods: {
|
||||
loadLogs() {
|
||||
this.loading = true
|
||||
adminApi.getLogFiles(this.logLines).then(response => {
|
||||
this.logContent = response.data.data
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleDownloadLogFile() {
|
||||
const hide = this.$message.loading('下载中...', 0)
|
||||
adminApi
|
||||
.getLogFiles(this.logLines)
|
||||
.then(response => {
|
||||
var blob = new Blob([response.data.data])
|
||||
var downloadElement = document.createElement('a')
|
||||
var href = window.URL.createObjectURL(blob)
|
||||
downloadElement.href = href
|
||||
downloadElement.download = 'halo-log-' + moment(new Date(), 'YYYY-MM-DD-HH-mm-ss') + '.log'
|
||||
document.body.appendChild(downloadElement)
|
||||
downloadElement.click()
|
||||
document.body.removeChild(downloadElement)
|
||||
window.URL.revokeObjectURL(href)
|
||||
this.$message.success('下载成功!')
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('下载失败!')
|
||||
})
|
||||
.finally(() => {
|
||||
hide()
|
||||
})
|
||||
},
|
||||
handleLinesChange(value) {
|
||||
this.logLines = value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="开发者选项:">
|
||||
<a-switch v-model="options.developer_mode" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleSaveOptions"
|
||||
>保存</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
import optionApi from '@/api/option'
|
||||
export default {
|
||||
name: 'SettingsForm',
|
||||
data() {
|
||||
return {
|
||||
options: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadFormOptions()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['loadOptions']),
|
||||
loadFormOptions() {
|
||||
optionApi.listAll().then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
handleSaveOptions() {
|
||||
optionApi.save(this.options).then(response => {
|
||||
this.loadFormOptions()
|
||||
this.loadOptions()
|
||||
this.$message.success('保存成功!')
|
||||
if (!this.options.developer_mode) {
|
||||
this.$router.push({ name: 'ToolList' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,230 @@
|
|||
<template>
|
||||
<div class="option-tab-wrapper">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<div class="table-operator">
|
||||
<a-button
|
||||
type="primary"
|
||||
icon="cloud-upload"
|
||||
@click="() => (uploadVisible = true)"
|
||||
>上传</a-button>
|
||||
<a-button
|
||||
icon="plus"
|
||||
@click="() => (createFolderModal = true)"
|
||||
>
|
||||
新建文件夹
|
||||
</a-button>
|
||||
<a-button
|
||||
icon="sync"
|
||||
@click="loadStaticList"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
>
|
||||
刷新
|
||||
</a-button>
|
||||
</div>
|
||||
<div style="margin-top:15px">
|
||||
<a-table
|
||||
:rowKey="record => record.name"
|
||||
:columns="columns"
|
||||
:dataSource="sortedStatics"
|
||||
:pagination="false"
|
||||
size="middle"
|
||||
:loading="loading"
|
||||
>
|
||||
<span
|
||||
slot="name"
|
||||
slot-scope="name"
|
||||
>
|
||||
<ellipsis
|
||||
length="64"
|
||||
tooltip
|
||||
>
|
||||
{{ name }}
|
||||
</ellipsis>
|
||||
</span>
|
||||
<span
|
||||
slot="createTime"
|
||||
slot-scope="createTime"
|
||||
>
|
||||
{{ createTime | moment }}
|
||||
</span>
|
||||
<span
|
||||
slot="action"
|
||||
slot-scope="text, record"
|
||||
>
|
||||
<a
|
||||
href="javascript:;"
|
||||
v-if="!record.isFile"
|
||||
@click="handleUpload(record)"
|
||||
>上传</a>
|
||||
<a
|
||||
:href="options.blog_url+record.relativePath"
|
||||
target="_blank"
|
||||
v-else
|
||||
>访问</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
class="ant-dropdown-link"
|
||||
>更多</a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item
|
||||
key="1"
|
||||
v-if="!record.isFile"
|
||||
>
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="handleShowCreateFolderModal(record)"
|
||||
>创建文件夹</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="2">
|
||||
<a-popconfirm
|
||||
:title="record.isFile?'你确定要删除该文件?':'你确定要删除该文件夹?'"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@confirm="handleDelete(record.relativePath)"
|
||||
>
|
||||
<a href="javascript:;">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-card>
|
||||
<a-modal
|
||||
title="上传文件"
|
||||
v-model="uploadVisible"
|
||||
:footer="null"
|
||||
:afterClose="onUploadClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<FilePondUpload
|
||||
ref="upload"
|
||||
name="file"
|
||||
:uploadHandler="uploadHandler"
|
||||
:filed="selectedFile.relativePath"
|
||||
></FilePondUpload>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model="createFolderModal"
|
||||
:afterClose="onCreateFolderClose"
|
||||
title="创建文件夹"
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
@click="handleCreateFolder()"
|
||||
>创建</a-button>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="文件夹名:">
|
||||
<a-input
|
||||
v-model="createFolderName"
|
||||
@keyup.enter="handleCreateFolder"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import staticApi from '@/api/static'
|
||||
const columns = [
|
||||
{
|
||||
title: '文件名',
|
||||
dataIndex: 'name',
|
||||
scopedSlots: { customRender: 'name' }
|
||||
},
|
||||
{
|
||||
title: '文件类型',
|
||||
dataIndex: 'mimeType',
|
||||
scopedSlots: { customRender: 'mimeType' }
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'createTime',
|
||||
width: '200px',
|
||||
scopedSlots: { customRender: 'createTime' }
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: '120px',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
export default {
|
||||
name: 'StaticStorage',
|
||||
data() {
|
||||
return {
|
||||
columns: columns,
|
||||
statics: [],
|
||||
loading: false,
|
||||
uploadHandler: staticApi.upload,
|
||||
uploadVisible: false,
|
||||
selectedFile: {},
|
||||
createFolderModal: false,
|
||||
createFolderName: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadStaticList()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options']),
|
||||
sortedStatics() {
|
||||
const data = this.statics.slice(0)
|
||||
return data.sort(function(a, b) {
|
||||
return b.createTime - a.createTime
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadStaticList() {
|
||||
this.loading = true
|
||||
staticApi.list().then(response => {
|
||||
this.statics = response.data.data
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleDelete(path) {
|
||||
staticApi.delete(path).then(response => {
|
||||
this.$message.success(`删除成功!`)
|
||||
this.loadStaticList()
|
||||
})
|
||||
},
|
||||
handleUpload(file) {
|
||||
this.selectedFile = file
|
||||
this.uploadVisible = true
|
||||
},
|
||||
handleShowCreateFolderModal(file) {
|
||||
this.selectedFile = file
|
||||
this.createFolderModal = true
|
||||
},
|
||||
handleCreateFolder() {
|
||||
staticApi.createFolder(this.selectedFile.relativePath, this.createFolderName).then(response => {
|
||||
this.$message.success(`创建文件夹成功!`)
|
||||
this.createFolderModal = false
|
||||
this.loadStaticList()
|
||||
})
|
||||
},
|
||||
onCreateFolderClose() {
|
||||
this.selectedFile = {}
|
||||
this.createFolderName = ''
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.selectedFile = {}
|
||||
this.loadStaticList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,9 +1,12 @@
|
|||
<template>
|
||||
<div class="container-wrapper">
|
||||
<div class="halo-logo animated fadeInUp">
|
||||
<span>Halo</span>
|
||||
<span>Halo<small v-if="apiModifyVisible">API 设置</small></span>
|
||||
</div>
|
||||
<div class="animated">
|
||||
<div
|
||||
v-show="!apiModifyVisible"
|
||||
class="login-form animated"
|
||||
>
|
||||
<a-form
|
||||
layout="vertical"
|
||||
@keyup.enter.native="handleLogin"
|
||||
|
@ -61,32 +64,65 @@
|
|||
</a>
|
||||
</router-link>
|
||||
<a
|
||||
@click="handleApiModifyModalOpen"
|
||||
@click="toggleShowApiForm"
|
||||
class="tip animated fadeInUp"
|
||||
:style="{'animation-delay': '0.4s'}"
|
||||
>
|
||||
API 设置
|
||||
<a-icon type="setting" />
|
||||
</a>
|
||||
</a-row>
|
||||
|
||||
<a-modal
|
||||
title="API 设置"
|
||||
:visible="apiModifyVisible"
|
||||
@ok="handleApiModifyOk"
|
||||
@cancel="handleApiModifyCancel"
|
||||
</a-form>
|
||||
</div>
|
||||
<div
|
||||
v-show="apiModifyVisible"
|
||||
class="api-form animated"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.1s'}"
|
||||
extra="* 如果 Admin 不是独立部署,请不要更改此 API"
|
||||
>
|
||||
<a-form>
|
||||
<a-form-item extra="如果 halo admin 不是独立部署,请不要更改此 API">
|
||||
<a-input v-model="apiUrl"></a-input>
|
||||
</a-form-item>
|
||||
<a-input
|
||||
placeholder="API 地址"
|
||||
v-model="apiUrl"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
type="api"
|
||||
style="color: rgba(0,0,0,.25)"
|
||||
/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.2s'}"
|
||||
>
|
||||
<a-button
|
||||
:block="true"
|
||||
@click="handleApiUrlRestore"
|
||||
>恢复默认</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.3s'}"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
:block="true"
|
||||
@click="handleApiModifyOk"
|
||||
>保存设置</a-button>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button @click="handleApiUrlRestore">
|
||||
恢复默认
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<a-row>
|
||||
<a
|
||||
@click="toggleShowApiForm"
|
||||
class="tip animated fadeInUp"
|
||||
:style="{'animation-delay': '0.4s'}"
|
||||
>
|
||||
<a-icon type="rollback" />
|
||||
</a>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -149,67 +185,23 @@ export default {
|
|||
this.$router.replace({ name: 'Dashboard' })
|
||||
}
|
||||
},
|
||||
handleApiModifyModalOpen() {
|
||||
this.apiUrl = this.defaultApiUrl
|
||||
this.apiModifyVisible = true
|
||||
},
|
||||
handleApiModifyOk() {
|
||||
this.setApiUrl(this.apiUrl)
|
||||
this.apiModifyVisible = false
|
||||
},
|
||||
handleApiModifyCancel() {
|
||||
this.apiModifyVisible = false
|
||||
},
|
||||
handleApiUrlRestore() {
|
||||
this.restoreApiUrl()
|
||||
this.apiUrl = this.defaultApiUrl
|
||||
},
|
||||
toggleShowApiForm() {
|
||||
this.apiModifyVisible = !this.apiModifyVisible
|
||||
if (this.apiModifyVisible) {
|
||||
this.apiUrl = this.defaultApiUrl
|
||||
}
|
||||
},
|
||||
toggleHidden() {
|
||||
this.resetPasswordButton = !this.resetPasswordButton
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
body {
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container-wrapper {
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
border-radius: 5px;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
margin: -160px 0 0 -160px;
|
||||
width: 320px;
|
||||
padding: 18px 28px 28px 28px;
|
||||
box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.halo-logo {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
span {
|
||||
vertical-align: text-bottom;
|
||||
font-size: 38px;
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
color: #1790fe;
|
||||
background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
small {
|
||||
margin-left: 5px;
|
||||
font-size: 35%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide page-header-wrapper-grid-content-main">
|
||||
<div>
|
||||
<a-row :gutter="12">
|
||||
<a-col
|
||||
:lg="10"
|
||||
|
@ -40,20 +40,21 @@
|
|||
<a-icon type="mail" />{{ user.email }}
|
||||
</p>
|
||||
<p>
|
||||
<a-icon type="calendar" />{{ counts.establishDays || 0 }} 天
|
||||
<a-icon type="calendar" />{{ statistics.establishDays || 0 }} 天
|
||||
</p>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div class="general-profile">
|
||||
<a-list
|
||||
:loading="countsLoading"
|
||||
:loading="statisticsLoading"
|
||||
itemLayout="horizontal"
|
||||
>
|
||||
<a-list-item>累计发表了 {{ counts.postCount || 0 }} 篇文章。</a-list-item>
|
||||
<a-list-item>累计创建了 {{ counts.linkCount || 0 }} 个标签。</a-list-item>
|
||||
<a-list-item>累计获得了 {{ counts.commentCount || 0 }} 条评论。</a-list-item>
|
||||
<a-list-item>累计添加了 {{ counts.linkCount || 0 }} 个友链。</a-list-item>
|
||||
<a-list-item>文章总访问 {{ counts.visitCount || 0 }} 次。</a-list-item>
|
||||
<a-list-item>累计发表了 {{ statistics.postCount || 0 }} 篇文章。</a-list-item>
|
||||
<a-list-item>累计创建了 {{ statistics.categoryCount || 0 }} 个分类。</a-list-item>
|
||||
<a-list-item>累计创建了 {{ statistics.tagCount || 0 }} 个标签。</a-list-item>
|
||||
<a-list-item>累计获得了 {{ statistics.commentCount || 0 }} 条评论。</a-list-item>
|
||||
<a-list-item>累计添加了 {{ statistics.linkCount || 0 }} 个友链。</a-list-item>
|
||||
<a-list-item>文章总访问 {{ statistics.visitCount || 0 }} 次。</a-list-item>
|
||||
<a-list-item></a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
|
@ -106,13 +107,13 @@
|
|||
</span>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="原密码:">
|
||||
<a-input-password v-model="passwordParam.oldPassword"/>
|
||||
<a-input-password v-model="passwordParam.oldPassword" />
|
||||
</a-form-item>
|
||||
<a-form-item label="新密码:">
|
||||
<a-input-password v-model="passwordParam.newPassword"/>
|
||||
<a-input-password v-model="passwordParam.newPassword" />
|
||||
</a-form-item>
|
||||
<a-form-item label="确认密码:">
|
||||
<a-input-password v-model="passwordParam.confirmPassword"/>
|
||||
<a-input-password v-model="passwordParam.confirmPassword" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
|
@ -142,7 +143,7 @@
|
|||
<script>
|
||||
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
||||
import userApi from '@/api/user'
|
||||
import adminApi from '@/api/admin'
|
||||
import statisticsApi from '@/api/statistics'
|
||||
import { mapMutations, mapGetters } from 'vuex'
|
||||
import MD5 from 'md5.js'
|
||||
|
||||
|
@ -152,10 +153,10 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
countsLoading: true,
|
||||
statisticsLoading: true,
|
||||
attachmentDrawerVisible: false,
|
||||
user: {},
|
||||
counts: {},
|
||||
statistics: {},
|
||||
passwordParam: {
|
||||
oldPassword: null,
|
||||
newPassword: null,
|
||||
|
@ -171,21 +172,15 @@ export default {
|
|||
...mapGetters(['options'])
|
||||
},
|
||||
created() {
|
||||
this.loadUser()
|
||||
this.getCounts()
|
||||
this.getStatistics()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({ setUser: 'SET_USER' }),
|
||||
loadUser() {
|
||||
userApi.getProfile().then(response => {
|
||||
this.user = response.data.data
|
||||
this.profileLoading = false
|
||||
})
|
||||
},
|
||||
getCounts() {
|
||||
adminApi.counts().then(response => {
|
||||
this.counts = response.data.data
|
||||
this.countsLoading = false
|
||||
getStatistics() {
|
||||
statisticsApi.statisticsWithUser().then(response => {
|
||||
this.user = response.data.data.user
|
||||
this.statistics = response.data.data
|
||||
this.statisticsLoading = false
|
||||
})
|
||||
},
|
||||
handleUpdatePassword() {
|
||||
|
@ -242,54 +237,47 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-header-wrapper-grid-content-main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
transition: 0.3s;
|
||||
.profile-center-avatarHolder {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.profile-center-avatarHolder {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
& > .avatar {
|
||||
margin: 0 auto;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
& > .avatar {
|
||||
margin: 0 auto;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-center-detail {
|
||||
p {
|
||||
margin-bottom: 8px;
|
||||
padding-left: 26px;
|
||||
position: relative;
|
||||
}
|
||||
.username {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
}
|
||||
.profile-center-detail {
|
||||
p {
|
||||
margin-bottom: 8px;
|
||||
padding-left: 26px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
v-model="resetParam.password"
|
||||
type="password"
|
||||
placeholder="新密码"
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
|
@ -83,6 +84,7 @@
|
|||
v-model="resetParam.confirmPassword"
|
||||
type="password"
|
||||
placeholder="确认密码"
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
|
@ -148,9 +150,15 @@ export default {
|
|||
})
|
||||
return
|
||||
}
|
||||
adminApi.sendResetCode(this.resetParam).then(response => {
|
||||
this.$message.info('邮件发送成功,五分钟内有效')
|
||||
})
|
||||
const hide = this.$message.loading('发送中...', 0)
|
||||
adminApi
|
||||
.sendResetCode(this.resetParam)
|
||||
.then(response => {
|
||||
this.$message.info('邮件发送成功,五分钟内有效')
|
||||
})
|
||||
.finally(() => {
|
||||
hide()
|
||||
})
|
||||
},
|
||||
handleResetPassword() {
|
||||
if (!this.resetParam.username) {
|
||||
|
@ -203,46 +211,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
body {
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container-wrapper {
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
border-radius: 5px;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
margin: -160px 0 0 -160px;
|
||||
width: 320px;
|
||||
padding: 18px 28px 28px 28px;
|
||||
box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.halo-logo {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
span {
|
||||
vertical-align: text-bottom;
|
||||
font-size: 38px;
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
color: #1790fe;
|
||||
background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
small {
|
||||
margin-left: 5px;
|
||||
font-size: 35%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,12 +4,35 @@ const webpack = require('webpack')
|
|||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production'
|
||||
|
||||
const assetsCDN = {
|
||||
externals: {
|
||||
vue: 'Vue',
|
||||
'vue-router': 'VueRouter',
|
||||
vuex: 'Vuex',
|
||||
axios: 'axios',
|
||||
marked: 'marked'
|
||||
},
|
||||
css: [
|
||||
],
|
||||
js: [
|
||||
'//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js',
|
||||
'//cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js',
|
||||
'//cdn.jsdelivr.net/npm/vuex@3.1.1/dist/vuex.min.js',
|
||||
'//cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js',
|
||||
'//cdn.jsdelivr.net/npm/marked@0.8.0/marked.min.js'
|
||||
]
|
||||
}
|
||||
|
||||
// vue.config.js
|
||||
module.exports = {
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
|
||||
]
|
||||
],
|
||||
externals: isProd ? assetsCDN.externals : {}
|
||||
},
|
||||
|
||||
chainWebpack: (config) => {
|
||||
|
@ -37,6 +60,12 @@ module.exports = {
|
|||
.options({
|
||||
name: 'assets/[name].[hash:8].[ext]'
|
||||
})
|
||||
if (isProd) {
|
||||
config.plugin('html').tap(args => {
|
||||
args[0].cdn = assetsCDN
|
||||
return args
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
css: {
|
||||
|
|
Loading…
Reference in New Issue