|
|
@@ -0,0 +1,1097 @@
|
|
|
+<template>
|
|
|
+ <view class="container">
|
|
|
+ <!-- H5环境自定义导航栏 -->
|
|
|
+ <view class="h5-custom-navbar" v-if="isH5">
|
|
|
+ <view class="h5-navbar-left" @click="goBack">
|
|
|
+ <view class="h5-back-icon">
|
|
|
+ <view class="h5-arrow-left"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="h5-navbar-title">编辑文章</text>
|
|
|
+ <view class="h5-navbar-right"></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 表单内容区 -->
|
|
|
+ <view class="article-container" :style="isH5 ? 'margin-top: 90rpx;' : ''">
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <view class="loading-container" v-if="loading">
|
|
|
+ <view class="loading-spinner"></view>
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 编辑区域 -->
|
|
|
+ <block v-else>
|
|
|
+ <!-- 基本信息区域 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <view class="form-title">基本信息</view>
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-label">文章标题</text>
|
|
|
+ <input class="form-input" v-model="articleInfo.title" placeholder="请输入文章标题" />
|
|
|
+ </view>
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-label">文章来源</text>
|
|
|
+ <input class="form-input" v-model="articleInfo.source" placeholder="请输入文章来源" />
|
|
|
+ </view>
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-label">文章类型</text>
|
|
|
+ <radio-group class="type-group" @change="handleTypeChange">
|
|
|
+ <label class="type-item">
|
|
|
+ <radio value="tech" :checked="articleInfo.category === 'tech'" />
|
|
|
+ <text>农技知识</text>
|
|
|
+ </label>
|
|
|
+ <label class="type-item">
|
|
|
+ <radio value="policy" :checked="articleInfo.category === 'policy'" />
|
|
|
+ <text>政策解读</text>
|
|
|
+ </label>
|
|
|
+ </radio-group>
|
|
|
+ </view>
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-label">内容类型</text>
|
|
|
+ <radio-group class="type-group" @change="handleContentTypeChange">
|
|
|
+ <label class="type-item">
|
|
|
+ <radio value="article" :checked="articleInfo.contentType === 'article'" />
|
|
|
+ <text>文章</text>
|
|
|
+ </label>
|
|
|
+ <label class="type-item">
|
|
|
+ <radio value="video" :checked="articleInfo.contentType === 'video'" />
|
|
|
+ <text>视频</text>
|
|
|
+ </label>
|
|
|
+ </radio-group>
|
|
|
+ </view>
|
|
|
+ <view class="form-item" v-if="articleInfo.contentType === 'video'">
|
|
|
+ <text class="form-label">视频链接</text>
|
|
|
+ <input class="form-input" v-model="articleInfo.videoUrl" placeholder="请输入视频链接" />
|
|
|
+ </view>
|
|
|
+ <view class="form-item" v-if="articleInfo.contentType === 'video'">
|
|
|
+ <text class="form-label">视频时长</text>
|
|
|
+ <input class="form-input" v-model="articleInfo.videoDuration" placeholder="请输入视频时长,如12:30" />
|
|
|
+ </view>
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-label">文章简介</text>
|
|
|
+ <textarea class="form-textarea" v-model="articleInfo.description" placeholder="请输入文章简介" />
|
|
|
+ </view>
|
|
|
+ <!-- <view class="form-item">
|
|
|
+ <text class="form-label">缩略图</text>
|
|
|
+ <view class="upload-box" @click="chooseThumbnail">
|
|
|
+ <image v-if="articleInfo.thumbnail" :src="articleInfo.thumbnail" mode="aspectFill" class="thumbnail-preview"></image>
|
|
|
+ <view v-else class="upload-placeholder">
|
|
|
+ <text class="upload-icon">+</text>
|
|
|
+ <text>上传缩略图</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view> -->
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 文章内容编辑区 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <view class="form-title">文章内容</view>
|
|
|
+
|
|
|
+ <!-- 文章内容构建器 -->
|
|
|
+ <view class="content-builder">
|
|
|
+ <view class="section-controls">
|
|
|
+ <button class="control-btn" @click="addSection('paragraph')">添加段落</button>
|
|
|
+ <button class="control-btn" @click="addSection('title')">添加标题</button>
|
|
|
+ <button class="control-btn" @click="addSection('image')">添加图片</button>
|
|
|
+ <button class="control-btn" @click="addSection('note')">添加提示框</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 内容块 -->
|
|
|
+ <view class="content-sections">
|
|
|
+ <view v-for="(section, index) in contentSections" :key="index" class="content-section-item">
|
|
|
+ <!-- 段落 -->
|
|
|
+ <view v-if="section.type === 'paragraph'" class="section-edit-item">
|
|
|
+ <textarea class="section-textarea" v-model="section.content" placeholder="请输入段落内容"></textarea>
|
|
|
+ <view class="section-controls">
|
|
|
+ <text class="delete-btn" @click="deleteSection(index)">删除</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 标题 -->
|
|
|
+ <view v-if="section.type === 'title'" class="section-edit-item">
|
|
|
+ <input class="section-title-input" v-model="section.content" placeholder="请输入标题"></input>
|
|
|
+ <view class="section-controls">
|
|
|
+ <text class="delete-btn" @click="deleteSection(index)">删除</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 图片 -->
|
|
|
+ <view v-if="section.type === 'image'" class="section-edit-item">
|
|
|
+ <view class="image-upload-container">
|
|
|
+ <view class="upload-box" @click="chooseImage(index)">
|
|
|
+ <image v-if="section.imageUrl" :src="section.imageUrl" mode="aspectFill" class="section-image-preview"></image>
|
|
|
+ <view v-else class="upload-placeholder">
|
|
|
+ <text class="upload-icon">+</text>
|
|
|
+ <text>上传图片</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <input class="image-caption-input" v-model="section.caption" placeholder="图片说明文字"></input>
|
|
|
+ <input class="color-input" v-model="section.color" placeholder="颜色代码,例如: #8BC34A"></input>
|
|
|
+ </view>
|
|
|
+ <view class="section-controls">
|
|
|
+ <text class="delete-btn" @click="deleteSection(index)">删除</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 提示框 -->
|
|
|
+ <view v-if="section.type === 'note'" class="section-edit-item">
|
|
|
+ <view class="note-box-edit">
|
|
|
+ <input class="note-title-input" v-model="section.title" placeholder="提示框标题,例如:注意事项"></input>
|
|
|
+ <textarea class="note-content-input" v-model="section.content" placeholder="提示框内容"></textarea>
|
|
|
+ </view>
|
|
|
+ <view class="section-controls">
|
|
|
+ <text class="delete-btn" @click="deleteSection(index)">删除</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
|
|
|
+ <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 预览区 -->
|
|
|
+ <view class="preview-section">
|
|
|
+ <view class="form-title">内容预览</view>
|
|
|
+ <view class="article-content">
|
|
|
+ <rich-text :nodes="generatedHtml"></rich-text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 提交按钮 -->
|
|
|
+ <view class="submit-section">
|
|
|
+ <button class="submit-btn" @click="saveArticle">保存文章</button>
|
|
|
+ </view>
|
|
|
+ </block>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 返回顶部 -->
|
|
|
+ <view class="back-to-top" @click="scrollToTop">
|
|
|
+ <view class="top-arrow"></view>
|
|
|
+ <text class="top-text">顶部</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { createArticle, updateArticle, getArticleDetail, uploadFile } from '@/api/services/knowledge.js';
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ title: "编辑文章",
|
|
|
+ isH5: false,
|
|
|
+ loading: false,
|
|
|
+ isEdit: false, // 是否为编辑模式
|
|
|
+ id: null, // 文章ID
|
|
|
+
|
|
|
+ // 文章基本信息
|
|
|
+ articleInfo: {
|
|
|
+ id: '',
|
|
|
+ title: '',
|
|
|
+ description: '',
|
|
|
+ source: '农业技术研究院',
|
|
|
+ category: 'tech', // 默认为农技知识
|
|
|
+ contentType: 'article', // 默认为文章类型
|
|
|
+ thumbnail: '', // 缩略图
|
|
|
+ videoUrl: '', // 视频链接
|
|
|
+ videoDuration: '', // 视频时长
|
|
|
+ isRecommend: 0,
|
|
|
+ status: 1
|
|
|
+ },
|
|
|
+
|
|
|
+ // 内容块
|
|
|
+ contentSections: [],
|
|
|
+
|
|
|
+ // 生成的HTML内容
|
|
|
+ generatedHtml: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ onLoad(options) {
|
|
|
+ // 检测是否在H5环境中运行
|
|
|
+ // #ifdef H5
|
|
|
+ this.isH5 = true;
|
|
|
+ // #endif
|
|
|
+
|
|
|
+ // 设置导航栏标题
|
|
|
+ uni.setNavigationBarTitle({
|
|
|
+ title: '编辑文章'
|
|
|
+ });
|
|
|
+
|
|
|
+ // 如果有ID参数,则为编辑模式
|
|
|
+ if (options && options.id) {
|
|
|
+ this.id = options.id;
|
|
|
+ this.isEdit = true;
|
|
|
+ this.loadArticleData();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ // 监听内容区块变化,实时生成HTML
|
|
|
+ contentSections: {
|
|
|
+ handler: function() {
|
|
|
+ console.log("this.generateHtml()",this.generateHtml());
|
|
|
+
|
|
|
+ this.generateHtml();
|
|
|
+ },
|
|
|
+ deep: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ // 加载文章数据(编辑模式)
|
|
|
+ async loadArticleData() {
|
|
|
+ if (!this.id) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.loading = true;
|
|
|
+ const result = await getArticleDetail(this.id);
|
|
|
+
|
|
|
+ if (result.data.code === 200 && result.data.data) {
|
|
|
+ const articleData = result.data.data;
|
|
|
+
|
|
|
+ // 填充基本信息
|
|
|
+ this.articleInfo = {
|
|
|
+ id: articleData.id,
|
|
|
+ title: articleData.title,
|
|
|
+ description: articleData.description,
|
|
|
+ source: articleData.source,
|
|
|
+ category: articleData.type,
|
|
|
+ contentType: articleData.contentType,
|
|
|
+ thumbnail: articleData.image,
|
|
|
+ videoUrl: articleData.videoUrl,
|
|
|
+ videoDuration: articleData.duration,
|
|
|
+ isRecommend: 0,
|
|
|
+ status: 1
|
|
|
+ };
|
|
|
+
|
|
|
+ // 解析HTML内容为内容块
|
|
|
+ this.parseHtmlToSections(articleData.content);
|
|
|
+ } else {
|
|
|
+ uni.showToast({
|
|
|
+ title: result.data.msg || '获取文章详情失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取文章详情失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '网络异常,请稍后重试',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 解析HTML为内容块
|
|
|
+ parseHtmlToSections(html) {
|
|
|
+ if (!html) {
|
|
|
+ this.contentSections = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建一个临时的DOM元素来解析HTML
|
|
|
+ const parser = new DOMParser();
|
|
|
+ const doc = parser.parseFromString(html, 'text/html');
|
|
|
+ const divs = doc.querySelectorAll('div.content-section');
|
|
|
+
|
|
|
+ const sections = [];
|
|
|
+
|
|
|
+ divs.forEach(div => {
|
|
|
+ // 处理段落
|
|
|
+ const paragraph = div.querySelector('p.paragraph');
|
|
|
+ if (paragraph) {
|
|
|
+ sections.push({
|
|
|
+ type: 'paragraph',
|
|
|
+ content: paragraph.textContent
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理标题
|
|
|
+ const title = div.querySelector('h3.section-title');
|
|
|
+ if (title) {
|
|
|
+ sections.push({
|
|
|
+ type: 'title',
|
|
|
+ content: title.textContent
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理图片
|
|
|
+ const imageDiv = div.querySelector('div.section-image');
|
|
|
+ if (imageDiv) {
|
|
|
+ const colorImage = imageDiv.querySelector('div.color-image');
|
|
|
+ const imageLabel = colorImage ? colorImage.querySelector('div.image-label') : null;
|
|
|
+
|
|
|
+ sections.push({
|
|
|
+ type: 'image',
|
|
|
+ imageUrl: '', // 需要后端提供真实URL
|
|
|
+ caption: imageLabel ? imageLabel.textContent : '',
|
|
|
+ color: colorImage ? colorImage.style.backgroundColor : '#8BC34A'
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理提示框
|
|
|
+ const noteBox = div.querySelector('div.note-box');
|
|
|
+ if (noteBox) {
|
|
|
+ const noteTitle = noteBox.querySelector('div.note-title');
|
|
|
+ const noteContent = noteBox.querySelector('div.note-content');
|
|
|
+
|
|
|
+ sections.push({
|
|
|
+ type: 'note',
|
|
|
+ title: noteTitle ? noteTitle.textContent : '【注意事项】',
|
|
|
+ content: noteContent ? noteContent.textContent : ''
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.contentSections = sections;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 类型切换处理
|
|
|
+ handleTypeChange(e) {
|
|
|
+ this.articleInfo.category = e.detail.value;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 内容类型切换处理
|
|
|
+ handleContentTypeChange(e) {
|
|
|
+ this.articleInfo.contentType = e.detail.value;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加内容区块
|
|
|
+ addSection(type) {
|
|
|
+ switch(type) {
|
|
|
+ case 'paragraph':
|
|
|
+ this.contentSections.push({
|
|
|
+ type: 'paragraph',
|
|
|
+ content: ''
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 'title':
|
|
|
+ this.contentSections.push({
|
|
|
+ type: 'title',
|
|
|
+ content: ''
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 'image':
|
|
|
+ this.contentSections.push({
|
|
|
+ type: 'image',
|
|
|
+ imageUrl: '',
|
|
|
+ caption: '',
|
|
|
+ color: '#8BC34A' // 默认颜色
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 'note':
|
|
|
+ this.contentSections.push({
|
|
|
+ type: 'note',
|
|
|
+ title: '【注意事项】',
|
|
|
+ content: ''
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除区块
|
|
|
+ deleteSection(index) {
|
|
|
+ this.contentSections.splice(index, 1);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 移动区块
|
|
|
+ moveSection(index, direction) {
|
|
|
+ const newIndex = index + direction;
|
|
|
+
|
|
|
+ if (newIndex >= 0 && newIndex < this.contentSections.length) {
|
|
|
+ const item = this.contentSections[index];
|
|
|
+ this.contentSections.splice(index, 1);
|
|
|
+ this.contentSections.splice(newIndex, 0, item);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择缩略图
|
|
|
+ chooseThumbnail() {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ success: async (res) => {
|
|
|
+ const tempFilePath = res.tempFilePaths[0];
|
|
|
+ console.log("res",res);
|
|
|
+
|
|
|
+ console.log("tempFilePath",tempFilePath);
|
|
|
+ try {
|
|
|
+ uni.showLoading({ title: '上传中...' });
|
|
|
+ // 调用上传API
|
|
|
+ const uploadResult = await this.uploadImage(tempFilePath);
|
|
|
+
|
|
|
+ if (uploadResult && uploadResult.url) {
|
|
|
+ this.articleInfo.thumbnail = ""+ uploadResult.url;
|
|
|
+ } else {
|
|
|
+ throw new Error('上传失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('上传图片失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '上传图片失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ uni.hideLoading();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择内容图片
|
|
|
+ chooseImage(index) {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ success: async (res) => {
|
|
|
+ const tempFilePath = res.tempFilePaths[0];
|
|
|
+
|
|
|
+ try {
|
|
|
+ uni.showLoading({ title: '上传中...' });
|
|
|
+ // 调用上传API
|
|
|
+ const uploadResult = await this.uploadImage(tempFilePath);
|
|
|
+
|
|
|
+ if (uploadResult && uploadResult.url) {
|
|
|
+ this.$set(this.contentSections[index], 'imageUrl', uploadResult.url);
|
|
|
+ } else {
|
|
|
+ throw new Error('上传失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('上传图片失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '上传图片失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ uni.hideLoading();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传图片
|
|
|
+ async uploadImage(filePath) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ uni.uploadFile({
|
|
|
+ url: 'http://localhost:9203/file/upload', // 替换为实际的上传API
|
|
|
+ filePath: filePath,
|
|
|
+ name: 'file',
|
|
|
+ success: (uploadRes) => {
|
|
|
+ const data = JSON.parse(uploadRes.data);
|
|
|
+ if (data.code === 200 && data.data) {
|
|
|
+ resolve(data.data);
|
|
|
+ } else {
|
|
|
+ reject(new Error(data.msg || '上传失败'));
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 生成HTML内容
|
|
|
+ generateHtml() {
|
|
|
+ let html = '';
|
|
|
+
|
|
|
+ this.contentSections.forEach(section => {
|
|
|
+ switch(section.type) {
|
|
|
+ case 'paragraph':
|
|
|
+ html += `<div class="content-section">
|
|
|
+ <p class="paragraph">${section.content || ''}</p>
|
|
|
+ </div>`;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'title':
|
|
|
+ html += `<div class="content-section">
|
|
|
+ <h3 class="section-title">${section.content || ''}</h3>
|
|
|
+ </div>`;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'image':
|
|
|
+ html += `<div class="content-section">
|
|
|
+ <div class="section-image">
|
|
|
+ <div class="color-image" style="background-color: ${section.color || '#8BC34A'};">
|
|
|
+ <div class="image-label">${section.caption || ''}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'note':
|
|
|
+ html += `<div class="content-section">
|
|
|
+ <div class="note-box">
|
|
|
+ <div class="note-title">${section.title || '【注意事项】'}</div>
|
|
|
+ <div class="note-content">${section.content || ''}</div>
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.generatedHtml = html;
|
|
|
+ return html;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 验证表单
|
|
|
+ validateForm() {
|
|
|
+ if (!this.articleInfo.title) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '请输入文章标题',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.articleInfo.source) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '请输入文章来源',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.articleInfo.description) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '请输入文章简介',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if (!this.articleInfo.thumbnail) {
|
|
|
+ // uni.showToast({
|
|
|
+ // title: '请上传文章缩略图',
|
|
|
+ // icon: 'none'
|
|
|
+ // });
|
|
|
+ // return false;
|
|
|
+ // }
|
|
|
+
|
|
|
+ if (this.articleInfo.contentType === 'video') {
|
|
|
+ if (!this.articleInfo.videoUrl) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '请输入视频链接',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 文章类型时,需要检查内容是否为空
|
|
|
+ if (this.contentSections.length === 0) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '请添加文章内容',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保存文章
|
|
|
+ async saveArticle() {
|
|
|
+ if (!this.validateForm()) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.loading = true;
|
|
|
+
|
|
|
+ // 生成最终的HTML内容
|
|
|
+ const contentHtml = this.generateHtml();
|
|
|
+ console.log("contentHtml",contentHtml);
|
|
|
+ // 构建提交数据
|
|
|
+ const articleData = {
|
|
|
+ id: this.isEdit ? this.id : null,
|
|
|
+ title: this.articleInfo.title,
|
|
|
+ description: this.articleInfo.description,
|
|
|
+ content: contentHtml, // 直接传递原始HTML内容,不进行编码或转义
|
|
|
+ category: this.articleInfo.category,
|
|
|
+ thumbnail: this.articleInfo.thumbnail,
|
|
|
+ contentType: this.articleInfo.contentType,
|
|
|
+ videoUrl: this.articleInfo.videoUrl,
|
|
|
+ videoDuration: this.articleInfo.videoDuration,
|
|
|
+ source: this.articleInfo.source,
|
|
|
+ isRecommend: this.articleInfo.isRecommend,
|
|
|
+ status: this.articleInfo.status
|
|
|
+ };
|
|
|
+
|
|
|
+ let result;
|
|
|
+
|
|
|
+ if (this.isEdit) {
|
|
|
+ // 编辑模式
|
|
|
+ result = await updateArticle(articleData);
|
|
|
+ } else {
|
|
|
+ // 新建模式
|
|
|
+ result = await createArticle(articleData);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result.data.code === 200) {
|
|
|
+ uni.showToast({
|
|
|
+ title: this.isEdit ? '更新成功' : '发布成功',
|
|
|
+ icon: 'success'
|
|
|
+ });
|
|
|
+
|
|
|
+ // 成功后返回上一页
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.navigateBack();
|
|
|
+ }, 1500);
|
|
|
+ } else {
|
|
|
+ uni.showToast({
|
|
|
+ title: result.data.msg || '保存失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存文章失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '网络异常,请稍后重试',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 返回
|
|
|
+ goBack() {
|
|
|
+ uni.navigateBack();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 滚动到顶部
|
|
|
+ scrollToTop() {
|
|
|
+ uni.pageScrollTo({
|
|
|
+ scrollTop: 0,
|
|
|
+ duration: 300
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style>
|
|
|
+/* 引入原有样式 */
|
|
|
+@font-face {
|
|
|
+ font-family: 'iconfont';
|
|
|
+ src: url('https://at.alicdn.com/t/font_3442238_cosd6rj55jg.ttf') format('truetype');
|
|
|
+}
|
|
|
+
|
|
|
+@font-face {
|
|
|
+ font-family: 'Material Icons';
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ src: url(https://fonts.gstatic.com/s/materialicons/v139/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
|
|
|
+}
|
|
|
+
|
|
|
+.icon, .view-icon, .play-icon, .action-icon, .top-icon {
|
|
|
+ font-family: 'iconfont';
|
|
|
+}
|
|
|
+
|
|
|
+.material-icon {
|
|
|
+ font-family: 'Material Icons';
|
|
|
+ font-weight: normal;
|
|
|
+ font-style: normal;
|
|
|
+ font-size: 50rpx;
|
|
|
+ line-height: 1;
|
|
|
+ letter-spacing: normal;
|
|
|
+ text-transform: none;
|
|
|
+ display: inline-block;
|
|
|
+ white-space: nowrap;
|
|
|
+ word-wrap: normal;
|
|
|
+ direction: ltr;
|
|
|
+ -webkit-font-smoothing: antialiased;
|
|
|
+ color: #4CAF50;
|
|
|
+}
|
|
|
+
|
|
|
+/* 容器样式 */
|
|
|
+.container {
|
|
|
+ background-color: #f8f8f8;
|
|
|
+ min-height: 100vh;
|
|
|
+ position: relative;
|
|
|
+ padding-bottom: 120rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 文章容器 */
|
|
|
+.article-container {
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* H5导航栏样式 */
|
|
|
+.h5-custom-navbar {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 90rpx;
|
|
|
+ background-color: #fff;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 30rpx;
|
|
|
+ z-index: 100;
|
|
|
+ box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.h5-navbar-left {
|
|
|
+ width: 80rpx;
|
|
|
+ height: 80rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.h5-back-icon {
|
|
|
+ font-size: 40rpx;
|
|
|
+ color: #333;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.h5-navbar-title {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.h5-navbar-right {
|
|
|
+ width: 60rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.h5-arrow-left {
|
|
|
+ width: 24rpx;
|
|
|
+ height: 24rpx;
|
|
|
+ border-top: 4rpx solid #333;
|
|
|
+ border-left: 4rpx solid #333;
|
|
|
+ transform: rotate(-45deg);
|
|
|
+}
|
|
|
+
|
|
|
+/* 表单样式 */
|
|
|
+.form-section {
|
|
|
+ padding: 30rpx;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+ background-color: #fff;
|
|
|
+ border-bottom: 1rpx solid #eee;
|
|
|
+}
|
|
|
+
|
|
|
+.form-title {
|
|
|
+ font-size: 34rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+ padding-left: 16rpx;
|
|
|
+ border-left: 8rpx solid #4CAF50;
|
|
|
+}
|
|
|
+
|
|
|
+.form-item {
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 80rpx;
|
|
|
+ border: 1rpx solid #ddd;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+.form-textarea {
|
|
|
+ width: 100%;
|
|
|
+ height: 180rpx;
|
|
|
+ border: 1rpx solid #ddd;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+.type-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+}
|
|
|
+
|
|
|
+.type-item {
|
|
|
+ margin-right: 60rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+/* 上传框样式 */
|
|
|
+.upload-box {
|
|
|
+ width: 200rpx;
|
|
|
+ height: 200rpx;
|
|
|
+ border: 2rpx dashed #ddd;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-placeholder {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ color: #999;
|
|
|
+ font-size: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-icon {
|
|
|
+ font-size: 60rpx;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.thumbnail-preview {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 内容构建器样式 */
|
|
|
+.content-builder {
|
|
|
+ margin-top: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.section-controls {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.control-btn {
|
|
|
+ margin-right: 20rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ padding: 10rpx 24rpx;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ color: #333;
|
|
|
+ font-size: 26rpx;
|
|
|
+ border-radius: 30rpx;
|
|
|
+ border: 1rpx solid #e0e0e0;
|
|
|
+}
|
|
|
+
|
|
|
+.content-section-item {
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+ border: 1rpx solid #eee;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.section-edit-item {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.section-textarea {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 150rpx;
|
|
|
+ border: 1rpx solid #eee;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 80rpx;
|
|
|
+ border: 1rpx solid #eee;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+.section-controls {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.delete-btn, .move-btn {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+ padding: 6rpx 16rpx;
|
|
|
+ margin-left: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.delete-btn {
|
|
|
+ color: #ff5252;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图片上传区域 */
|
|
|
+.image-upload-container {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.section-image-preview {
|
|
|
+ width: 100%;
|
|
|
+ height: 300rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.image-caption-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 70rpx;
|
|
|
+ border: 1rpx solid #eee;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-top: 20rpx;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+.color-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 70rpx;
|
|
|
+ border: 1rpx solid #eee;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-top: 20rpx;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+/* 提示框编辑区域 */
|
|
|
+.note-box-edit {
|
|
|
+ border-left: 8rpx solid #8bc34a;
|
|
|
+ padding: 20rpx;
|
|
|
+ background-color: #f1f8e9;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.note-title-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 70rpx;
|
|
|
+ border: 1rpx solid #e0e0e0;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #558b2f;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ background-color: rgba(255,255,255,0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.note-content-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 150rpx;
|
|
|
+ border: 1rpx solid #e0e0e0;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #689f38;
|
|
|
+ background-color: rgba(255,255,255,0.5);
|
|
|
+}
|
|
|
+
|
|
|
+/* 预览区域 */
|
|
|
+.preview-section {
|
|
|
+ padding: 30rpx;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+ background-color: #fff;
|
|
|
+ border-top: 1rpx solid #eee;
|
|
|
+}
|
|
|
+
|
|
|
+.article-content {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ border: 1rpx dashed #ddd;
|
|
|
+ padding: 20rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 提交按钮区域 */
|
|
|
+.submit-section {
|
|
|
+ padding: 20rpx 30rpx 50rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.submit-btn {
|
|
|
+ width: 100%;
|
|
|
+ height: 90rpx;
|
|
|
+ line-height: 90rpx;
|
|
|
+ background: linear-gradient(135deg, #4CAF50, #388E3C);
|
|
|
+ color: #fff;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ border-radius: 45rpx;
|
|
|
+ text-align: center;
|
|
|
+ box-shadow: 0 6rpx 16rpx rgba(76, 175, 80, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+/* 内容预览样式 */
|
|
|
+.content-section {
|
|
|
+ margin-bottom: 40rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 34rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #2e7d32;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.paragraph {
|
|
|
+ font-size: 30rpx;
|
|
|
+ line-height: 1.8;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.section-image {
|
|
|
+ margin: 20rpx 0;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.color-image {
|
|
|
+ width: 100%;
|
|
|
+ height: 300rpx;
|
|
|
+ border-radius: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.image-label {
|
|
|
+ color: white;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
|
|
+ z-index: 5;
|
|
|
+}
|
|
|
+
|
|
|
+.note-box {
|
|
|
+ background-color: #f1f8e9;
|
|
|
+ border-left: 8rpx solid #8bc34a;
|
|
|
+ padding: 20
|
|
|
+}
|
|
|
+</style>
|