484 lines
15 KiB
Vue
484 lines
15 KiB
Vue
<template>
|
||
<view class="uni-container">
|
||
<view class="header">
|
||
<view class="back">
|
||
<view @click="navigateBack" style="margin-left: 15px;display: flex;align-items: center">
|
||
<uni-icons type="back" size="24"></uni-icons>
|
||
<text>返回</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<uni-forms ref="form" :label-width="90" :model="formData" validateTrigger="submit" err-show-type="toast">
|
||
<view class="edit-box">
|
||
<view class="title">
|
||
<textarea
|
||
class="uni-input"
|
||
v-model="formData.title"
|
||
auto-height
|
||
placeholder="文章标题"
|
||
@input="() => autoSaveContent && autoSaveContent()"
|
||
/>
|
||
</view>
|
||
<editor-component @textchange="onTextChange" @ready="onEditorReady"></editor-component>
|
||
<view class="settings">
|
||
<uni-forms-item name="excerpt" label="文章摘要">
|
||
<textarea
|
||
class="excerpt"
|
||
placeholder="文章摘要"
|
||
auto-height
|
||
v-model="formData.excerpt"
|
||
@input="() => autoSaveContent && autoSaveContent()"
|
||
></textarea>
|
||
</uni-forms-item>
|
||
<uni-forms-item name="thumbnail" label="封面" required>
|
||
<view class="thumb-mode">
|
||
<radio-group @change="thumbModeChange">
|
||
<radio value="1" class="mode" :checked="thumbMode === 1">单图</radio>
|
||
<!-- <radio value="3" class="mode" :checked="thumbMode === 3">三图</radio> -->
|
||
<radio value="0" class="mode" :checked="thumbMode === 0">无封面图</radio>
|
||
</radio-group>
|
||
</view>
|
||
<view class="thumbnail-list" v-if="thumbMode > 0">
|
||
<view class="thumbnail" v-for="(thumbnail, index) in formData.thumbnail" :key="thumbnail.source">
|
||
<template v-if="thumbnail.source">
|
||
<view class="remove-thumbnail" @click="() => removeThumbnail(index)">
|
||
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
|
||
</view>
|
||
<image :src="thumbnail.src" mode="aspectFill" style="width: 150px; height: 150px;"></image>
|
||
</template>
|
||
<template v-else>
|
||
<uni-icons
|
||
@click="() => openInsertImageDrawer(index)"
|
||
class="placeholder"
|
||
type="plusempty"
|
||
size="60"
|
||
color="#e8e8e8"
|
||
></uni-icons>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
<view v-if="thumbMode > 0" style="color: #999; font-size: 13px; margin-top: 10px;">
|
||
<text>为了保证最佳效果展示;请上传16:9的封面图片</text>
|
||
</view>
|
||
</uni-forms-item>
|
||
<uni-forms-item v-if="false" name="user_id" label="作者" required>
|
||
<uni-data-picker v-model="formData.user_id" style="width: 200px;" collection="uni-id-users"
|
||
where="role=='uni-cms-author'||role=='admin'" field="nickname as text, _id as value"></uni-data-picker>
|
||
</uni-forms-item>
|
||
<uni-forms-item name="category_id" label="分类">
|
||
<uni-data-picker v-model="formData.category_id" style="width: 200px;"
|
||
collection="uni-cms-categories" field="name as text, _id as value"></uni-data-picker>
|
||
</uni-forms-item>
|
||
<!-- <uni-forms-item name="is_sticky" label="是否置顶">-->
|
||
<!-- <switch @change="binddata('is_sticky', $event.detail.value)" style="transform:scale(0.75)" :checked="formData.is_sticky"></switch>-->
|
||
<!-- </uni-forms-item>-->
|
||
<!-- <uni-forms-item name="is_essence" label="阅读加精">-->
|
||
<!-- <switch @change="binddata('is_essence', $event.detail.value)" style="transform:scale(0.75)" :checked="formData.is_essence"></switch>-->
|
||
<!-- </uni-forms-item>-->
|
||
<!-- <uni-forms-item name="comment_status" label="开放评论">-->
|
||
<!-- <uni-data-checkbox v-model="formData.comment_status"-->
|
||
<!-- :localdata="formOptions.comment_status_localdata"></uni-data-checkbox>-->
|
||
<!-- </uni-forms-item>-->
|
||
</view>
|
||
<view class="uni-button-group m" style="padding-bottom: 50px">
|
||
<button v-if="formData.article_status === 0" class="uni-button" style="width: 100px;margin-right: 10px;"
|
||
@click="submit(0)">保存</button>
|
||
<button type="primary" class="uni-button" style="width: 100px;" @click="submit(1)">更新</button>
|
||
</view>
|
||
</view>
|
||
</uni-forms>
|
||
<view class="footer">
|
||
<view class="wrap">
|
||
<view class="left">
|
||
<text class="auto-save" v-if="autoSave">
|
||
{{ autoSave.status === 1 ? '自动保存中...' : '已自动保存' }}
|
||
</text>
|
||
<text class="word-count" v-if="wordCount !== null">共 {{ wordCount }} 字</text>
|
||
</view>
|
||
<view class="right">
|
||
<view class="uni-button-group">
|
||
<!-- #ifdef H5 -->
|
||
<button v-if="formData.article_status === 0" class="uni-button"
|
||
style="width: 100px;margin-right: 10px;" @click="previewArticle">预览</button>
|
||
<!-- #endif -->
|
||
<button v-if="formData.article_status === 0" class="uni-button"
|
||
style="width: 100px;margin-right: 10px;" @click="submit(0)">保存</button>
|
||
<button type="primary" class="uni-button" style="width: 100px;" @click="submit(1)">{{
|
||
formData.article_status === 0 ? '发布' : '更新' }}</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<uni-drawer
|
||
class="insert-image-drawer"
|
||
v-if="drawerWidth"
|
||
ref="insertImageDrawer"
|
||
mode="right"
|
||
:width="drawerWidth"
|
||
>
|
||
<uni-media-library
|
||
mode="picker"
|
||
:selected-count="1"
|
||
:media-tabs="['image']"
|
||
@onInsert="onInsertCover"
|
||
></uni-media-library>
|
||
</uni-drawer>
|
||
|
||
<!-- #ifdef H5 -->
|
||
<uni-popup ref="popup" type="dialog" v-if="formDataId">
|
||
<article-preview :id="formDataId" @close="() => $refs.popup.close()"></article-preview>
|
||
</uni-popup>
|
||
<!-- #endif -->
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 引入编辑器组件
|
||
import EditorComponent from "@/uni_modules/uni-cms/components/editor/editor.vue"
|
||
// 引入Delta转换函数
|
||
import { translateInputContent, translateOutputContent } from '@/uni_modules/uni-cms/common/translate-content'
|
||
// 引入表单验证器
|
||
import { validator } from '@/uni_modules/uni-cms/common/validator/uni-cms-articles.js'
|
||
// 自动保存mixin
|
||
import autoSaveMixin from '@/uni_modules/uni-cms/common/auto-save-mixin.js';
|
||
import {parseImageUrl} from "@/uni_modules/uni-cms/common/parse-image-url";
|
||
|
||
// 引入文章预览组件
|
||
// #ifdef H5
|
||
import ArticlePreview from "@/uni_modules/uni-cms/components/preview/preview.vue";
|
||
// #endif
|
||
|
||
// 引入数据库相关
|
||
const db = uniCloud.database();
|
||
const dbCollectionName = 'uni-cms-articles';
|
||
|
||
// 根据字段获取验证器
|
||
function getValidator(fields) {
|
||
let result = {}
|
||
for (let key in validator) {
|
||
if (fields.includes(key)) {
|
||
result[key] = validator[key]
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
export default {
|
||
components: {
|
||
EditorComponent,
|
||
// #ifdef H5
|
||
ArticlePreview
|
||
// #endif
|
||
},
|
||
mixins: [autoSaveMixin],
|
||
data() {
|
||
// 初始化表单数据
|
||
let formData = {
|
||
"user_id": "",
|
||
"category_id": "",
|
||
"title": "",
|
||
"content": null,
|
||
"excerpt": "",
|
||
"article_status": 0,
|
||
"is_sticky": null,
|
||
"is_essence": null,
|
||
"comment_status": null,
|
||
"thumbnail": [{}],
|
||
"publish_date": null
|
||
}
|
||
return {
|
||
// 初始化表单数据的id
|
||
formDataId: '',
|
||
// 表单数据
|
||
formData,
|
||
// 表单选项
|
||
formOptions: {
|
||
"comment_status_localdata": [
|
||
{
|
||
"value": 0,
|
||
"text": "关闭"
|
||
},
|
||
{
|
||
"value": 1,
|
||
"text": "开放"
|
||
}
|
||
]
|
||
},
|
||
// 表单验证规则
|
||
rules: {
|
||
...getValidator(Object.keys(formData))
|
||
},
|
||
// 编辑器格式
|
||
formats: {},
|
||
// 字数统计
|
||
wordCount: null,
|
||
// 插入图片抽屉宽度
|
||
drawerWidth: 0,
|
||
// 封面图展示方式
|
||
thumbMode: 1,
|
||
}
|
||
},
|
||
// 当页面准备好时,设置表单验证规则
|
||
onReady() {
|
||
this.$refs.form.setRules(this.rules)
|
||
},
|
||
// 当页面加载时,如果有id则获取表单数据
|
||
onLoad(e) {
|
||
if (e.id) {
|
||
const id = e.id
|
||
this.formDataId = id
|
||
this.getDetail(id)
|
||
}
|
||
},
|
||
mounted () {
|
||
const sysinfo = uni.getSystemInfoSync()
|
||
this.drawerWidth = sysinfo.windowWidth * .8
|
||
},
|
||
methods: {
|
||
// 封面图展示方式改变
|
||
thumbModeChange (e) {
|
||
const coverCount = Number(e.detail.value)
|
||
this.formData.thumbnail = Array.from(new Array(coverCount)).map((item, index) => {
|
||
if (this.formData.thumbnail[index]) {
|
||
return this.formData.thumbnail[index]
|
||
} else {
|
||
return {}
|
||
}
|
||
})
|
||
this.thumbMode = coverCount
|
||
},
|
||
/**
|
||
* 获取表单数据
|
||
* @param {String} id - 文章id
|
||
*/
|
||
getDetail(id) {
|
||
// 显示加载中
|
||
uni.showLoading({
|
||
mask: true
|
||
})
|
||
// 从数据库中获取文章数据
|
||
db.collection(dbCollectionName).doc(id).field("is_admin,user_id,category_id,title,content,excerpt,article_status,is_sticky,is_essence,comment_status,thumbnail,publish_date").get().then((res) => {
|
||
const data = res.result.data[0]
|
||
if (data) {
|
||
// 将获取到的数据赋值给formData
|
||
this.formData = data
|
||
// 如果有缩略图,则设置缩略图预览
|
||
const thumbnail = typeof this.formData.thumbnail === 'string' ? [this.formData.thumbnail] : this.formData.thumbnail
|
||
|
||
// 更新封面图展示方式
|
||
this.thumbMode = this.formData.thumbnail && this.formData.thumbnail.length ? this.formData.thumbnail.length : 0
|
||
// 设置编辑器内容
|
||
this.setContents()
|
||
|
||
// 处理封面图
|
||
parseImageUrl(thumbnail).then(res => {
|
||
this.formData.thumbnail = res
|
||
})
|
||
}
|
||
}).catch((err) => {
|
||
// 显示错误提示
|
||
uni.showModal({
|
||
content: err.message || '请求服务失败',
|
||
showCancel: false
|
||
})
|
||
}).finally(() => {
|
||
// 隐藏加载中
|
||
uni.hideLoading()
|
||
})
|
||
},
|
||
/**
|
||
* 当编辑器准备好时,设置编辑器内容
|
||
* @param {Object} editorCtx - 编辑器上下文
|
||
*/
|
||
onEditorReady(editorCtx) {
|
||
// 如果编辑器上下文不存在,则返回
|
||
if (!editorCtx) return
|
||
|
||
// 将编辑器上下文赋值给组件实例的editorCtx属性
|
||
this.editorCtx = editorCtx
|
||
|
||
// 设置编辑器内容
|
||
this.setContents()
|
||
},
|
||
|
||
/**
|
||
* 设置编辑器内容
|
||
*/
|
||
setContents() {
|
||
// 检查编辑器上下文是否存在
|
||
if (this.editorCtx && this.formData.content) {
|
||
// #ifdef H5
|
||
// 如果是H5平台,则直接使用formData中的内容
|
||
const content = this.formData.content
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
// 如果不是H5平台,则需要将formData中的内容转换为编辑器所需的格式
|
||
const content = translateInputContent(this.formData.content)
|
||
// #endif
|
||
|
||
// 设置编辑器内容
|
||
this.editorCtx.setContents({
|
||
delta: content
|
||
})
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 验证表单并提交
|
||
* @param {Number} status - 文章状态
|
||
*/
|
||
submit(status) {
|
||
// 检查文章标题是否存在
|
||
if (!this.formData.title) {
|
||
// 隐藏加载中
|
||
uni.hideLoading()
|
||
// 显示文章标题必填的提示
|
||
return uni.showToast({
|
||
icon: 'none',
|
||
title: '文章标题必填',
|
||
})
|
||
}
|
||
|
||
if (this.formData.thumbnail) {
|
||
for (const thumbnail of this.formData.thumbnail) {
|
||
if (!thumbnail.source) {
|
||
// 如果封面图为空,提示用户
|
||
uni.hideLoading()
|
||
return uni.showToast({
|
||
icon: 'none',
|
||
title: '封面图必填',
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
return new Promise(resolve => {
|
||
// 验证表单
|
||
this.$refs.form.validate().then((res) => {
|
||
// 获取编辑器内容
|
||
this.editorCtx.getContents({
|
||
success: async (e) => {
|
||
// 提交表单
|
||
await this.submitForm({
|
||
...res,
|
||
thumbnail: this.formData.thumbnail.map(item => item.source),
|
||
article_status: status,
|
||
title: this.formData.title,
|
||
content: translateOutputContent(e.delta),
|
||
publish_date: this.formData.publish_date ? this.formData.publish_date : Date.now()
|
||
})
|
||
|
||
resolve()
|
||
}
|
||
})
|
||
}).catch((e) => {
|
||
console.error(e)
|
||
})
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 提交表单
|
||
* @param {Object} value - 表单数据
|
||
*/
|
||
submitForm(value) {
|
||
uni.showLoading({
|
||
mask: true
|
||
})
|
||
// 使用 clientDB 提交数据
|
||
return db.collection(dbCollectionName).doc(this.formDataId).update(value).then(() => {
|
||
// 提交成功后,根据文章状态弹出不同的提示信息
|
||
uni.showToast({
|
||
icon: 'none',
|
||
title: value.article_status === 0 ? '保存成功' : this.formData.article_status === 0 ? '发布成功' : '更新成功'
|
||
})
|
||
|
||
try {
|
||
// 触发父页面事件,刷新数据
|
||
this.getOpenerEventChannel().emit('refreshData')
|
||
} catch (e) { }
|
||
|
||
// 清除自动保存内容
|
||
this.clearAutoSaveStorage && this.clearAutoSaveStorage()
|
||
|
||
// 延迟 500ms 后,如果文章状态为已发布,返回上一页。
|
||
setTimeout(() => {
|
||
if (value.article_status === 1) {
|
||
uni.navigateBack()
|
||
}
|
||
}, 500)
|
||
}).catch((err) => {
|
||
uni.showModal({
|
||
content: err.message || '请求服务失败',
|
||
showCancel: false
|
||
})
|
||
}).finally(() => {
|
||
uni.hideLoading()
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 文本内容变化时更新字数统计
|
||
* @param {Object} e - 文本内容变化事件
|
||
*/
|
||
onTextChange(e) {
|
||
this.wordCount = e.detail
|
||
|
||
// 自动保存
|
||
this.autoSaveContent && this.autoSaveContent()
|
||
},
|
||
|
||
/**
|
||
* 返回上一页或跳转到文章列表页
|
||
*/
|
||
navigateBack() {
|
||
const pages = getCurrentPages()
|
||
|
||
if (pages.length > 1) {
|
||
uni.navigateBack(-1)
|
||
} else {
|
||
uni.redirectTo({
|
||
url: '/uni_modules/uni-cms/pages/article/list/list'
|
||
})
|
||
}
|
||
},
|
||
|
||
openInsertImageDrawer(index) {
|
||
this.$refs.insertImageDrawer.open()
|
||
this.coverIndex = index
|
||
},
|
||
async onInsertCover(selectMediaItems) {
|
||
const coverIndex = this.coverIndex !== null ? this.coverIndex : 0
|
||
const image = selectMediaItems[0]
|
||
const newThumbnail = [...this.formData.thumbnail]
|
||
const parseImages = await parseImageUrl(image.src)
|
||
|
||
newThumbnail[coverIndex] = parseImages[0]
|
||
|
||
this.$refs.insertImageDrawer.close()
|
||
this.formData.thumbnail = newThumbnail
|
||
},
|
||
removeThumbnail(index) {
|
||
const newThumbnail = [...this.formData.thumbnail]
|
||
newThumbnail[index] = {}
|
||
|
||
this.formData.thumbnail = newThumbnail
|
||
this.autoGetCover = false
|
||
},
|
||
async previewArticle () {
|
||
if (this.formDataId) {
|
||
// 先保存文章
|
||
await this.submit(0)
|
||
this.$refs.popup.open()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@import '@/uni_modules/uni-cms/common/style/article-detail.scss';
|
||
</style>
|