提交后台前端基础框架

This commit is contained in:
wanghao
2025-03-12 12:48:13 +08:00
parent 67a3fc68c9
commit 8e19156555
174 changed files with 36652 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
<template>
<div>
<p>笔记附件列表({{ files.length }})</p>
<div class="annex-box lum-scrollbar">
<input ref="uploads" type="file" @change="uploadAnnex" />
<div class="annex-main">
<p v-show="files.length == 0" class="empty-text">
暂无附件
</p>
<div v-for="(file, i) in files" :key="file.id" class="file-item">
<div class="suffix">{{ file.file_suffix }}</div>
<div class="content">
<div class="filename">{{ file.original_name }}</div>
<div class="filetool">
<span>{{ formateTime(file.created_at) }}</span>
<span class="size">
{{ formateSize(file.file_size) }}
</span>
<div class="tools">
<i class="el-icon-download" @click="downloadAnnex(file.id)" />
<i class="el-icon-delete" @click="deleteAnnex(file.id, i)" />
</div>
</div>
</div>
</div>
</div>
<div class="annex-footer">
<p class="notice-text">最多可支持上传{{ maxNum }}个附件</p>
<el-button
type="primary"
size="small"
icon="el-icon-upload"
:disabled="files.length >= maxNum"
:loading="loadStatus"
@click="$refs.uploads.click()"
>{{ loadStatus ? '上传中...' : '上传附件' }}
</el-button>
</div>
</div>
</div>
</template>
<script>
import {
ServeDeleteArticleAnnex,
ServeDownloadAnnex as downloadAnnex,
ServeUploadArticleAnnex,
} from '@/api/article'
import { formateSize, formateTime, parseTime } from '@/utils/functions'
export default {
name: 'NoteAnnexBox',
props: {
id: {
type: Number,
default: 0,
},
files: {
type: Array,
default() {
return []
},
},
},
data() {
return {
loadStatus: false,
disabled: false,
maxNum: 10,
}
},
methods: {
// 格式化文件大小
formateSize,
// 格式化时间显示格式
formateTime,
// 下载笔记附件
downloadAnnex,
// 删除笔记附件
deleteAnnex(annex_id, index) {
ServeDeleteArticleAnnex({
annex_id,
}).then(({ code }) => {
if (code == 200) {
this.$delete(this.files, index)
}
})
},
// 上传笔记附件文件
uploadAnnex(e) {
if (e.target.files.length == 0) {
return false
}
let file = e.target.files[0]
if (file.size / (1024 * 1024) > 5) {
this.$message('笔记附件不能大于5M!')
return false
}
let fileData = new FormData()
fileData.append('annex', file)
fileData.append('article_id', this.id)
this.loadStatus = true
ServeUploadArticleAnnex(fileData)
.then(({ code, data }) => {
if (code == 200) {
this.files.push({
id: data.id,
original_name: data.original_name,
created_at: parseTime(new Date()),
file_size: data.file_size,
file_suffix: data.file_suffix,
})
}
})
.finally(() => {
this.loadStatus = false
})
},
},
}
</script>
<style lang="less" scoped>
/* 文件管理弹出层 */
.annex-box {
width: 300px;
min-height: 50px;
max-height: 675px;
background-color: white;
overflow-y: auto;
.annex-main {
min-height: 30px;
border-bottom: 1px solid rgb(239, 233, 233);
margin-bottom: 8px;
padding: 5px 0;
.empty-text {
color: #969292;
font-size: 12px;
margin-top: 10px;
}
.file-item {
height: 50px;
margin-bottom: 5px;
margin-top: 10px;
.suffix {
width: 50px;
height: 100%;
background-color: #ffcc80;
border-radius: 3px;
float: left;
line-height: 50px;
text-align: center;
color: white;
}
.content {
float: left;
width: 247px;
height: 100%;
.filename {
padding-left: 5px;
color: #172b4d;
font-size: 14px;
font-weight: 400;
line-height: 1.6;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.filetool {
color: #505f79;
font-size: 12px;
font-weight: 400;
line-height: 1.6;
padding-left: 5px;
margin-top: 9px;
position: relative;
span {
margin: 0 3px;
&.size {
color: #3a8ee6;
}
}
}
.tools {
position: absolute;
top: -5px;
right: 5px;
width: 55px;
height: 24px;
text-align: right;
line-height: 28px;
i {
font-size: 16px;
cursor: pointer;
margin-right: 5px;
}
.el-icon-download {
color: #66b1ff;
}
.el-icon-delete {
color: red;
}
}
}
}
}
input {
display: none;
}
.annex-footer {
.notice-text {
font-size: 12px;
color: #ccc;
text-align: left;
float: left;
padding-top: 10px;
}
button {
float: right;
}
}
}
</style>

View File

@@ -0,0 +1,206 @@
<template>
<div class="lum-dialog-mask">
<el-container
class="lum-dialog-box animated bounceInDown"
v-outside="close"
>
<el-header height="60px" class="header no-select">
<p> 30 天删除的附件({{ tableData.length }})</p>
<p class="tools">
<i class="el-icon-close" @click="close" />
</p>
</el-header>
<el-main class="main lum-scrollbar">
<el-table :data="tableData" size="mini">
<div slot="empty">暂无相关数据</div>
<el-table-column
prop="original_name"
label="附件名称"
width="180"
:show-overflow-tooltip="true"
>
<template slot-scope="scope">
<el-button type="text" @click="downloadAnnex(scope.row.id)">{{
scope.row.original_name
}}</el-button>
</template>
</el-table-column>
<el-table-column
prop="title"
label="所属笔记"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column
prop="day"
label="剩余天数"
align="center"
width="80"
>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100"
align="center"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="recoverAnnex(scope.row)"
>恢复</el-button
>
<el-popover
placement="top"
@hide="lock(false)"
@show="lock(true)"
:ref="`popover-${scope.$index}`"
>
<p style="margin-bottom: 10px">
{{
scope.row.original_name
}}附件您确定要永久删除吗<br />
</p>
<div style="text-align: right; margin: 0">
<el-button
size="mini"
type="text"
@click="
scope._self.$refs[`popover-${scope.$index}`].doClose()
"
>
取消</el-button
>
<el-button
type="primary"
size="mini"
@click="deleteAnnex(scope.row, scope.$index)"
>确定</el-button
>
</div>
<el-button
slot="reference"
type="text"
size="small"
style="color: #ec5252; margin-left: 5px"
>删除</el-button
>
</el-popover>
</template>
</el-table-column>
</el-table>
</el-main>
<el-footer class="footer" height="30px">
<p class="footer-tip">移动至回收站的附件和笔记将在 30 天后自动清除</p>
</el-footer>
</el-container>
</div>
</template>
<script>
import Vue from 'vue'
import { Table, TableColumn } from 'element-ui'
Vue.use(Table)
Vue.use(TableColumn)
import {
ServeGetRecoverAnnexList,
ServeRecoverArticleAnnex,
ServeDownloadAnnex,
ServeForeverDeleteAnnex,
} from '@/api/article'
export default {
name: 'NoteAnnexRecycle',
data() {
return {
tableData: [],
closeLock: false,
}
},
created() {
this.loadList()
},
methods: {
loadList() {
ServeGetRecoverAnnexList().then(res => {
if (res.code == 200) {
this.tableData = res.data.rows
}
})
},
// 关闭当前窗口
close() {
if (!this.closeLock) this.$emit('close')
},
// 给遮罩层加锁
lock(value) {
this.closeLock = value
},
// 恢复附件
recoverAnnex(data, index) {
ServeRecoverArticleAnnex({
annex_id: data.id,
}).then(res => {
if (res.code == 200) {
this.tableData.splice(index, 1)
}
})
},
//永久删除附件
deleteAnnex(data, index) {
ServeForeverDeleteAnnex({
annex_id: data.id,
})
.then(res => {
this.$refs[`popover-${index}`].doClose()
this.lock(false)
if (res.code == 200) {
this.tableData.splice(index, 1)
} else {
this.$notify({
message: '附件删除失败...',
})
}
})
.catch(() => {
this.$refs[`popover-${index}`].doClose()
this.lock(false)
})
},
//下载笔记附件
downloadAnnex: ServeDownloadAnnex,
},
}
</script>
<style lang="less" scoped>
.lum-dialog-box {
width: 700px;
height: 80%;
max-width: 700px;
.main {
padding-top: 0;
}
.footer {
.footer-tip {
color: #a7afbc;
font-size: 12px;
font-weight: 400;
line-height: 1.6;
}
}
}
/deep/ .tab-header-row .cell {
font-size: 14px;
font-weight: 400;
color: rgb(172, 167, 167);
}
</style>

View File

@@ -0,0 +1,220 @@
<template>
<div class="tag-manage">
<div class="title">
<span>已选择</span>
</div>
<div class="tag-groups">
<p
v-for="(tag, i) in tags"
v-show="tag.isSelectd"
:key="i"
class="tag-item"
>
<span>{{ tag.name }}</span>
<i class="el-icon-close" @click="active(tag.id, tag.isSelectd)" />
</p>
</div>
<div class="title">
<span>标签栏</span>
</div>
<div class="tag-groups">
<p
v-for="(tag, i) in tags"
:key="i"
class="tag-item"
:class="{ active: tag.isSelectd }"
@click="active(tag.id, tag.isSelectd)"
>
<span>{{ tag.name }}</span>
</p>
</div>
<el-button
v-show="!isInput"
type="primary"
class="addbtn"
@click="isInput = !isInput"
>+ 添加标签
</el-button>
<div class="tag-input" v-show="isInput">
<input
ref="editTaginput"
type="text"
placeholder="回车保存..."
v-model.trim="tagText"
@keyup.enter="save"
/>
<el-button type="primary" size="small" @click="isInput = false"
>取消编辑
</el-button>
</div>
</div>
</template>
<script>
import { ServeUpdateArticleTag, ServeEditArticleTag } from '@/api/article'
export default {
name: 'NoteTagBox',
props: {
note_id: {
type: Number,
default: 0,
},
tag_ids: {
type: Array,
default() {
return []
},
},
},
data() {
return {
ids: [],
isInput: false,
tagText: '',
}
},
created() {
this.ids = this.tag_ids.map(tag => tag.id)
},
computed: {
tags() {
let tags = []
this.$store.state.note.tags.forEach(tag => {
tags.push({
id: tag.id,
name: tag.tag_name,
isSelectd: this.ids.includes(tag.id),
})
})
return tags
},
},
methods: {
// 设置笔记标签事件
active(tag_id, isSelect) {
if (isSelect) {
this.ids.forEach((item, index) => {
if (item == tag_id) {
this.ids.splice(index, 1)
}
})
} else {
this.ids.push(tag_id)
}
ServeUpdateArticleTag({
article_id: this.note_id,
tags: this.getSelectTags(),
})
},
// 获取选中的标签ids
getSelectTags() {
let ids = []
for (let item of this.tags) {
if (item.isSelectd) ids.push(item.id)
}
return ids
},
// 保存标签事件
save() {
let tag_name = this.tagText
ServeEditArticleTag({
tag_id: 0,
tag_name,
}).then(({ code, data }) => {
if (code !== 200) return false
this.$store.commit('PUSH_NOTE_TAG', {
id: data.id,
tag_name: tag_name,
count: 0,
})
this.tagText = ''
this.isInput = false
})
},
},
}
</script>
<style lang="less" scoped>
.tag-manage {
.title {
height: 20px;
line-height: 20px;
font-size: 14px;
color: #ccc;
border-bottom: 1px solid #f0e9e9;
padding-bottom: 5px;
position: relative;
}
.tag-groups {
padding: 8px 8px 8px 0;
cursor: pointer;
.tag-item {
display: inline-block;
height: 25px;
line-height: 25px;
padding: 0 10px;
font-size: 12px;
box-sizing: border-box;
white-space: nowrap;
margin: 0 3px 5px 0;
color: #409eff;
background: rgba(64, 158, 255, 0.1);
border-radius: 1px;
i {
cursor: pointer;
margin-left: 5px;
}
&.active {
background: #70b5fb;
color: #ffffff;
}
}
}
.addbtn {
height: 33px;
width: 100%;
margin-top: 20px;
line-height: 9px;
border-radius: 3px;
}
.tag-input {
margin-top: 20px;
min-height: 30px;
display: flex;
align-items: center;
input {
width: 200px;
height: 30px;
border: 1px solid #66b1ff;
padding: 0 5px;
border-radius: 3px;
margin-right: 5px;
&::-webkit-input-placeholder {
font-size: 13px;
color: #ccc;
}
}
}
}
</style>