This commit is contained in:
oneao committed 2025-12-22 22:30:20 +08:00
1 parent 9aa4a72321
commit 9bf7c40b70
40 files changed
+918 -189

No files matched your search

@@ -1,10 +1,324 @@
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import type { PostEditorForm } from '@/types/post'
import type { PostTag } from '@/types/post/tag'
import type { UploadImageResponse } from '@/utils/file'
import { ElMessage } from 'element-plus'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import CustomLoading from '@/components/CustomLoading.vue'
import ImageUploadCard from '@/components/ImageUploadCard.vue'
import WangEditor from '@/components/wang-editor/index.vue'
import { addPostApi } from '@/request/api/post'
import { getPostTagApi } from '@/request/api/post/tag'
import { uploadImage, uploadToAliyun } from '@/utils/file'
const wangEditorRef = ref<any>(null)
const formRef = ref<FormInstance>()
const loading = reactive({
init: true,
confirm: false,
})
const showPushDrawer = ref<boolean>(false)
const imageUpload = ref<UploadImageResponse | null>(null)
const tagOptions = ref<PostTag[]>([])
const statusOptions = [
{
label: '草稿',
value: 0,
},
{
label: '发布',
value: 1,
},
]
const oldForm = ref<PostEditorForm>({
id: 0,
title: '',
content: '',
summary: '',
cover: '',
tags: [],
publishTime: '',
updateTime: '',
status: 0,
})
const form = ref<PostEditorForm>({
id: 0,
title: '',
content: '',
summary: '',
cover: '',
tags: [],
publishTime: '',
updateTime: '',
status: 0,
})
const formRules = {
title: [
{ required: true, message: '文章标题不能为空', trigger: 'blur' },
],
summary: [
{ required: true, message: '摘要不能为空', trigger: 'blur' },
],
status: [
{ required: true, message: '状态不能为空', trigger: 'blur' },
],
}
const isUpdate = computed(() => form.value.id !== 0)
onMounted(() => {
loading.init = false
oldForm.value = JSON.parse(JSON.stringify(form.value))
})
async function handleShowPushDrawer() {
loading.init = true
showPushDrawer.value = true
if (!isUpdate.value) {
form.value.summary = (wangEditorRef.value.getText() || '').slice(0, 100)
}
await getPostTagApi().then((res) => {
if (res.isSuccess) {
tagOptions.value = res.data
}
else {
ElMessage.error(res.message || '获取帖子标签失败,请稍后再试')
}
}).catch((err) => {
console.log(err)
ElMessage.error('获取帖子标签失败,请稍后再试')
})
loading.init = false
}
async function handleSave(formEl: FormInstance | undefined) {
if (!formEl)
return
await formEl.validate(async (valid) => {
if (!valid)
return
if (form.value.content === '') {
ElMessage.warning('文章内容不能为空')
return
}
loading.confirm = true
if (form.value.cover !== oldForm.value.cover) {
if (form.value.cover && imageUpload.value !== null) {
const aliyunRes = await uploadToAliyun(imageUpload.value.blob, imageUpload.value.name, imageUpload.value.size, false)
form.value.cover = aliyunRes.url
}
}
addPostApi({
title: form.value.title,
content: form.value.content,
summary: form.value.summary,
status: form.value.status,
cover: form.value.cover,
tags: form.value.tags,
}).then((res) => {
if (res.isSuccess) {
console.log(res)
}
else {
ElMessage.error(res.message || '保存失败,请稍后再试')
}
}).catch((err) => {
console.log(err)
ElMessage.error('保存失败,请稍后再试')
}).finally(() => {
loading.confirm = false
showPushDrawer.value = false
})
})
}
</script>
<template>
<div class="" />
<div class="editor-container">
<div class="toolbar">
<input v-model="form.title" placeholder="输入标题" class="editor-title-input" maxlength="100">
<div class="toolbar-extra">
<div v-if="form.publishTime" class="time">
更新于:{{ form.publishTime }}
</div>
<el-button type="primary" @click="handleShowPushDrawer()">
发布
</el-button>
</div>
</div>
<WangEditor ref="wangEditorRef" v-model="form.content" :show-direcotry="true" />
</div>
<el-drawer v-model="showPushDrawer" title="发布文章">
<CustomLoading v-if="loading.init" :in-container="true" />
<el-form
v-else
ref="formRef"
:model="form"
:rules="formRules"
label-width="auto"
>
<el-form-item label="文章标题" prop="title">
<el-input
v-model="form.title"
placeholder="请输入文字标题"
size="large"
show-word-limit
maxlength="100"
type="textarea"
/>
</el-form-item>
<el-form-item label="标签" prop="tags">
<el-select v-model="form.tags" placeholder="请选择标签">
<el-option
v-for="item in tagOptions"
:key="item.id"
:label="item.name"
:value="item.name"
/>
</el-select>
</el-form-item>
<el-form-item label="文章封面 " prop="cover">
<ImageUploadCard
v-model="form.cover"
@change="(data) => {
imageUpload = data
}"
/>
</el-form-item>
<el-form-item label="文章摘要" prop="summary">
<el-input
v-model="form.summary"
placeholder="请输入文章摘要"
size="large"
show-word-limit
maxlength="100"
type="textarea"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div style="flex: auto">
<el-button>
取消
</el-button>
<el-button type="primary" :loading="loading.confirm" @click="handleSave(formRef)">
确认并发布
</el-button>
</div>
</template>
</el-drawer>
</template>
<style lang="scss" scoped>
//
.editor-container {
padding: 16px 32px;
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.editor-title-input {
width: 1000px;
border: none;
font-size: 24px;
padding: 8px 0px;
box-sizing: border-box;
outline: none;
resize: none;
line-height: 1.5;
}
.toolbar-extra {
.time {
font-size: 14px;
color: #666;
}
}
}
}
.upload-card {
width: 120px;
height: 120px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
cursor: pointer;
transition: border-color 0.3s ease;
padding: 4px;
&:hover {
border-color: #409eff;
}
&.loading {
pointer-events: none;
opacity: 0.6;
}
.upload-inner {
font-size: 14px;
color: #999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
}
.upload-icon {
font-size: 18px;
}
.upload-loading-icon {
font-size: 18px;
}
.upload-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
}
}
.form-cover {
height: 192px;
width: 128px;
}
</style>