article-editor.vue 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. <template>
  2. <view class="container">
  3. <!-- H5环境自定义导航栏 -->
  4. <view class="h5-custom-navbar" v-if="isH5">
  5. <view class="h5-navbar-left" @click="goBack">
  6. <view class="h5-back-icon">
  7. <view class="h5-arrow-left"></view>
  8. </view>
  9. </view>
  10. <text class="h5-navbar-title">编辑文章</text>
  11. <view class="h5-navbar-right"></view>
  12. </view>
  13. <!-- 表单内容区 -->
  14. <view class="article-container" :style="isH5 ? 'margin-top: 90rpx;' : ''">
  15. <!-- 加载状态 -->
  16. <view class="loading-container" v-if="loading">
  17. <view class="loading-spinner"></view>
  18. <text class="loading-text">加载中...</text>
  19. </view>
  20. <!-- 编辑区域 -->
  21. <block v-else>
  22. <!-- 基本信息区域 -->
  23. <view class="form-section">
  24. <view class="form-title">基本信息</view>
  25. <view class="form-item">
  26. <text class="form-label">文章标题</text>
  27. <input class="form-input" v-model="articleInfo.title" placeholder="请输入文章标题" />
  28. </view>
  29. <view class="form-item">
  30. <text class="form-label">文章来源</text>
  31. <input class="form-input" v-model="articleInfo.source" placeholder="请输入文章来源" />
  32. </view>
  33. <view class="form-item">
  34. <text class="form-label">文章类型</text>
  35. <radio-group class="type-group" @change="handleTypeChange">
  36. <label class="type-item">
  37. <radio value="tech" :checked="articleInfo.category === 'tech'" />
  38. <text>农技知识</text>
  39. </label>
  40. <label class="type-item">
  41. <radio value="policy" :checked="articleInfo.category === 'policy'" />
  42. <text>政策解读</text>
  43. </label>
  44. </radio-group>
  45. </view>
  46. <view class="form-item">
  47. <text class="form-label">内容类型</text>
  48. <radio-group class="type-group" @change="handleContentTypeChange">
  49. <label class="type-item">
  50. <radio value="article" :checked="articleInfo.contentType === 'article'" />
  51. <text>文章</text>
  52. </label>
  53. <label class="type-item">
  54. <radio value="video" :checked="articleInfo.contentType === 'video'" />
  55. <text>视频</text>
  56. </label>
  57. </radio-group>
  58. </view>
  59. <view class="form-item" v-if="articleInfo.contentType === 'video'">
  60. <text class="form-label">视频链接</text>
  61. <input class="form-input" v-model="articleInfo.videoUrl" placeholder="请输入视频链接" />
  62. </view>
  63. <view class="form-item" v-if="articleInfo.contentType === 'video'">
  64. <text class="form-label">视频时长</text>
  65. <input class="form-input" v-model="articleInfo.videoDuration" placeholder="请输入视频时长,如12:30" />
  66. </view>
  67. <view class="form-item">
  68. <text class="form-label">文章简介</text>
  69. <textarea class="form-textarea" v-model="articleInfo.description" placeholder="请输入文章简介" />
  70. </view>
  71. <!-- <view class="form-item">
  72. <text class="form-label">缩略图</text>
  73. <view class="upload-box" @click="chooseThumbnail">
  74. <image v-if="articleInfo.thumbnail" :src="articleInfo.thumbnail" mode="aspectFill" class="thumbnail-preview"></image>
  75. <view v-else class="upload-placeholder">
  76. <text class="upload-icon">+</text>
  77. <text>上传缩略图</text>
  78. </view>
  79. </view>
  80. </view> -->
  81. </view>
  82. <!-- 文章内容编辑区 -->
  83. <view class="form-section">
  84. <view class="form-title">文章内容</view>
  85. <!-- 文章内容构建器 -->
  86. <view class="content-builder">
  87. <view class="section-controls">
  88. <button class="control-btn" @click="addSection('paragraph')">添加段落</button>
  89. <button class="control-btn" @click="addSection('title')">添加标题</button>
  90. <button class="control-btn" @click="addSection('image')">添加图片</button>
  91. <button class="control-btn" @click="addSection('note')">添加提示框</button>
  92. </view>
  93. <!-- 内容块 -->
  94. <view class="content-sections">
  95. <view v-for="(section, index) in contentSections" :key="index" class="content-section-item">
  96. <!-- 段落 -->
  97. <view v-if="section.type === 'paragraph'" class="section-edit-item">
  98. <textarea class="section-textarea" v-model="section.content" placeholder="请输入段落内容"></textarea>
  99. <view class="section-controls">
  100. <text class="delete-btn" @click="deleteSection(index)">删除</text>
  101. <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
  102. <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
  103. </view>
  104. </view>
  105. <!-- 标题 -->
  106. <view v-if="section.type === 'title'" class="section-edit-item">
  107. <input class="section-title-input" v-model="section.content" placeholder="请输入标题"></input>
  108. <view class="section-controls">
  109. <text class="delete-btn" @click="deleteSection(index)">删除</text>
  110. <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
  111. <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
  112. </view>
  113. </view>
  114. <!-- 图片 -->
  115. <view v-if="section.type === 'image'" class="section-edit-item">
  116. <view class="image-upload-container">
  117. <view class="upload-box" @click="chooseImage(index)">
  118. <image v-if="section.imageUrl" :src="section.imageUrl" mode="aspectFill" class="section-image-preview"></image>
  119. <view v-else class="upload-placeholder">
  120. <text class="upload-icon">+</text>
  121. <text>上传图片</text>
  122. </view>
  123. </view>
  124. <input class="image-caption-input" v-model="section.caption" placeholder="图片说明文字"></input>
  125. <input class="color-input" v-model="section.color" placeholder="颜色代码,例如: #8BC34A"></input>
  126. </view>
  127. <view class="section-controls">
  128. <text class="delete-btn" @click="deleteSection(index)">删除</text>
  129. <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
  130. <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
  131. </view>
  132. </view>
  133. <!-- 提示框 -->
  134. <view v-if="section.type === 'note'" class="section-edit-item">
  135. <view class="note-box-edit">
  136. <input class="note-title-input" v-model="section.title" placeholder="提示框标题,例如:注意事项"></input>
  137. <textarea class="note-content-input" v-model="section.content" placeholder="提示框内容"></textarea>
  138. </view>
  139. <view class="section-controls">
  140. <text class="delete-btn" @click="deleteSection(index)">删除</text>
  141. <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
  142. <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
  143. </view>
  144. </view>
  145. </view>
  146. </view>
  147. </view>
  148. </view>
  149. <!-- 预览区 -->
  150. <view class="preview-section">
  151. <view class="form-title">内容预览</view>
  152. <view class="article-content">
  153. <rich-text :nodes="generatedHtml"></rich-text>
  154. </view>
  155. </view>
  156. <!-- 提交按钮 -->
  157. <view class="submit-section">
  158. <button class="submit-btn" @click="saveArticle">保存文章</button>
  159. </view>
  160. </block>
  161. </view>
  162. <!-- 返回顶部 -->
  163. <view class="back-to-top" @click="scrollToTop">
  164. <view class="top-arrow"></view>
  165. <text class="top-text">顶部</text>
  166. </view>
  167. </view>
  168. </template>
  169. <script>
  170. import { createArticle, updateArticle, getArticleDetail, uploadFile } from '@/api/services/knowledge.js';
  171. export default {
  172. data() {
  173. return {
  174. title: "编辑文章",
  175. isH5: false,
  176. loading: false,
  177. isEdit: false, // 是否为编辑模式
  178. id: null, // 文章ID
  179. // 文章基本信息
  180. articleInfo: {
  181. id: '',
  182. title: '',
  183. description: '',
  184. source: '农业技术研究院',
  185. category: 'tech', // 默认为农技知识
  186. contentType: 'article', // 默认为文章类型
  187. thumbnail: '', // 缩略图
  188. videoUrl: '', // 视频链接
  189. videoDuration: '', // 视频时长
  190. isRecommend: 0,
  191. status: 1
  192. },
  193. // 内容块
  194. contentSections: [],
  195. // 生成的HTML内容
  196. generatedHtml: ''
  197. }
  198. },
  199. onLoad(options) {
  200. // 检测是否在H5环境中运行
  201. // #ifdef H5
  202. this.isH5 = true;
  203. // #endif
  204. // 设置导航栏标题
  205. uni.setNavigationBarTitle({
  206. title: '编辑文章'
  207. });
  208. // 如果有ID参数,则为编辑模式
  209. if (options && options.id) {
  210. this.id = options.id;
  211. this.isEdit = true;
  212. this.loadArticleData();
  213. }
  214. },
  215. watch: {
  216. // 监听内容区块变化,实时生成HTML
  217. contentSections: {
  218. handler: function() {
  219. console.log("this.generateHtml()",this.generateHtml());
  220. this.generateHtml();
  221. },
  222. deep: true
  223. }
  224. },
  225. methods: {
  226. // 加载文章数据(编辑模式)
  227. async loadArticleData() {
  228. if (!this.id) return;
  229. try {
  230. this.loading = true;
  231. const result = await getArticleDetail(this.id);
  232. if (result.data.code === 200 && result.data.data) {
  233. const articleData = result.data.data;
  234. // 填充基本信息
  235. this.articleInfo = {
  236. id: articleData.id,
  237. title: articleData.title,
  238. description: articleData.description,
  239. source: articleData.source,
  240. category: articleData.type,
  241. contentType: articleData.contentType,
  242. thumbnail: articleData.image,
  243. videoUrl: articleData.videoUrl,
  244. videoDuration: articleData.duration,
  245. isRecommend: 0,
  246. status: 1
  247. };
  248. // 解析HTML内容为内容块
  249. this.parseHtmlToSections(articleData.content);
  250. } else {
  251. uni.showToast({
  252. title: result.data.msg || '获取文章详情失败',
  253. icon: 'none'
  254. });
  255. }
  256. } catch (error) {
  257. console.error('获取文章详情失败:', error);
  258. uni.showToast({
  259. title: '网络异常,请稍后重试',
  260. icon: 'none'
  261. });
  262. } finally {
  263. this.loading = false;
  264. }
  265. },
  266. // 解析HTML为内容块
  267. parseHtmlToSections(html) {
  268. if (!html) {
  269. this.contentSections = [];
  270. return;
  271. }
  272. // 创建一个临时的DOM元素来解析HTML
  273. const parser = new DOMParser();
  274. const doc = parser.parseFromString(html, 'text/html');
  275. const divs = doc.querySelectorAll('div.content-section');
  276. const sections = [];
  277. divs.forEach(div => {
  278. // 处理段落
  279. const paragraph = div.querySelector('p.paragraph');
  280. if (paragraph) {
  281. sections.push({
  282. type: 'paragraph',
  283. content: paragraph.textContent
  284. });
  285. }
  286. // 处理标题
  287. const title = div.querySelector('h3.section-title');
  288. if (title) {
  289. sections.push({
  290. type: 'title',
  291. content: title.textContent
  292. });
  293. }
  294. // 处理图片
  295. const imageDiv = div.querySelector('div.section-image');
  296. if (imageDiv) {
  297. const colorImage = imageDiv.querySelector('div.color-image');
  298. const imageLabel = colorImage ? colorImage.querySelector('div.image-label') : null;
  299. sections.push({
  300. type: 'image',
  301. imageUrl: '', // 需要后端提供真实URL
  302. caption: imageLabel ? imageLabel.textContent : '',
  303. color: colorImage ? colorImage.style.backgroundColor : '#8BC34A'
  304. });
  305. }
  306. // 处理提示框
  307. const noteBox = div.querySelector('div.note-box');
  308. if (noteBox) {
  309. const noteTitle = noteBox.querySelector('div.note-title');
  310. const noteContent = noteBox.querySelector('div.note-content');
  311. sections.push({
  312. type: 'note',
  313. title: noteTitle ? noteTitle.textContent : '【注意事项】',
  314. content: noteContent ? noteContent.textContent : ''
  315. });
  316. }
  317. });
  318. this.contentSections = sections;
  319. },
  320. // 类型切换处理
  321. handleTypeChange(e) {
  322. this.articleInfo.category = e.detail.value;
  323. },
  324. // 内容类型切换处理
  325. handleContentTypeChange(e) {
  326. this.articleInfo.contentType = e.detail.value;
  327. },
  328. // 添加内容区块
  329. addSection(type) {
  330. switch(type) {
  331. case 'paragraph':
  332. this.contentSections.push({
  333. type: 'paragraph',
  334. content: ''
  335. });
  336. break;
  337. case 'title':
  338. this.contentSections.push({
  339. type: 'title',
  340. content: ''
  341. });
  342. break;
  343. case 'image':
  344. this.contentSections.push({
  345. type: 'image',
  346. imageUrl: '',
  347. caption: '',
  348. color: '#8BC34A' // 默认颜色
  349. });
  350. break;
  351. case 'note':
  352. this.contentSections.push({
  353. type: 'note',
  354. title: '【注意事项】',
  355. content: ''
  356. });
  357. break;
  358. }
  359. },
  360. // 删除区块
  361. deleteSection(index) {
  362. this.contentSections.splice(index, 1);
  363. },
  364. // 移动区块
  365. moveSection(index, direction) {
  366. const newIndex = index + direction;
  367. if (newIndex >= 0 && newIndex < this.contentSections.length) {
  368. const item = this.contentSections[index];
  369. this.contentSections.splice(index, 1);
  370. this.contentSections.splice(newIndex, 0, item);
  371. }
  372. },
  373. // 选择缩略图
  374. chooseThumbnail() {
  375. uni.chooseImage({
  376. count: 1,
  377. success: async (res) => {
  378. const tempFilePath = res.tempFilePaths[0];
  379. console.log("res",res);
  380. console.log("tempFilePath",tempFilePath);
  381. try {
  382. uni.showLoading({ title: '上传中...' });
  383. // 调用上传API
  384. const uploadResult = await this.uploadImage(tempFilePath);
  385. if (uploadResult && uploadResult.url) {
  386. this.articleInfo.thumbnail = ""+ uploadResult.url;
  387. } else {
  388. throw new Error('上传失败');
  389. }
  390. } catch (error) {
  391. console.error('上传图片失败:', error);
  392. uni.showToast({
  393. title: '上传图片失败',
  394. icon: 'none'
  395. });
  396. } finally {
  397. uni.hideLoading();
  398. }
  399. }
  400. });
  401. },
  402. // 选择内容图片
  403. chooseImage(index) {
  404. uni.chooseImage({
  405. count: 1,
  406. success: async (res) => {
  407. const tempFilePath = res.tempFilePaths[0];
  408. try {
  409. uni.showLoading({ title: '上传中...' });
  410. // 调用上传API
  411. const uploadResult = await this.uploadImage(tempFilePath);
  412. if (uploadResult && uploadResult.url) {
  413. this.$set(this.contentSections[index], 'imageUrl', uploadResult.url);
  414. } else {
  415. throw new Error('上传失败');
  416. }
  417. } catch (error) {
  418. console.error('上传图片失败:', error);
  419. uni.showToast({
  420. title: '上传图片失败',
  421. icon: 'none'
  422. });
  423. } finally {
  424. uni.hideLoading();
  425. }
  426. }
  427. });
  428. },
  429. // 上传图片
  430. async uploadImage(filePath) {
  431. return new Promise((resolve, reject) => {
  432. uni.uploadFile({
  433. url: 'http://localhost:9203/file/upload', // 替换为实际的上传API
  434. filePath: filePath,
  435. name: 'file',
  436. success: (uploadRes) => {
  437. const data = JSON.parse(uploadRes.data);
  438. if (data.code === 200 && data.data) {
  439. resolve(data.data);
  440. } else {
  441. reject(new Error(data.msg || '上传失败'));
  442. }
  443. },
  444. fail: (error) => {
  445. reject(error);
  446. }
  447. });
  448. });
  449. },
  450. // 生成HTML内容
  451. generateHtml() {
  452. let html = '';
  453. this.contentSections.forEach(section => {
  454. switch(section.type) {
  455. case 'paragraph':
  456. html += `<div class="content-section">
  457. <p class="paragraph">${section.content || ''}</p>
  458. </div>`;
  459. break;
  460. case 'title':
  461. html += `<div class="content-section">
  462. <h3 class="section-title">${section.content || ''}</h3>
  463. </div>`;
  464. break;
  465. case 'image':
  466. html += `<div class="content-section">
  467. <div class="section-image">
  468. <div class="color-image" style="background-color: ${section.color || '#8BC34A'};">
  469. <div class="image-label">${section.caption || ''}</div>
  470. </div>
  471. </div>
  472. </div>`;
  473. break;
  474. case 'note':
  475. html += `<div class="content-section">
  476. <div class="note-box">
  477. <div class="note-title">${section.title || '【注意事项】'}</div>
  478. <div class="note-content">${section.content || ''}</div>
  479. </div>
  480. </div>`;
  481. break;
  482. }
  483. });
  484. this.generatedHtml = html;
  485. return html;
  486. },
  487. // 验证表单
  488. validateForm() {
  489. if (!this.articleInfo.title) {
  490. uni.showToast({
  491. title: '请输入文章标题',
  492. icon: 'none'
  493. });
  494. return false;
  495. }
  496. if (!this.articleInfo.source) {
  497. uni.showToast({
  498. title: '请输入文章来源',
  499. icon: 'none'
  500. });
  501. return false;
  502. }
  503. if (!this.articleInfo.description) {
  504. uni.showToast({
  505. title: '请输入文章简介',
  506. icon: 'none'
  507. });
  508. return false;
  509. }
  510. // if (!this.articleInfo.thumbnail) {
  511. // uni.showToast({
  512. // title: '请上传文章缩略图',
  513. // icon: 'none'
  514. // });
  515. // return false;
  516. // }
  517. if (this.articleInfo.contentType === 'video') {
  518. if (!this.articleInfo.videoUrl) {
  519. uni.showToast({
  520. title: '请输入视频链接',
  521. icon: 'none'
  522. });
  523. return false;
  524. }
  525. } else {
  526. // 文章类型时,需要检查内容是否为空
  527. if (this.contentSections.length === 0) {
  528. uni.showToast({
  529. title: '请添加文章内容',
  530. icon: 'none'
  531. });
  532. return false;
  533. }
  534. }
  535. return true;
  536. },
  537. // 保存文章
  538. async saveArticle() {
  539. if (!this.validateForm()) return;
  540. try {
  541. this.loading = true;
  542. // 生成最终的HTML内容
  543. const contentHtml = this.generateHtml();
  544. console.log("contentHtml",contentHtml);
  545. // 构建提交数据
  546. const articleData = {
  547. id: this.isEdit ? this.id : null,
  548. title: this.articleInfo.title,
  549. description: this.articleInfo.description,
  550. content: contentHtml, // 直接传递原始HTML内容,不进行编码或转义
  551. category: this.articleInfo.category,
  552. thumbnail: this.articleInfo.thumbnail,
  553. contentType: this.articleInfo.contentType,
  554. videoUrl: this.articleInfo.videoUrl,
  555. videoDuration: this.articleInfo.videoDuration,
  556. source: this.articleInfo.source,
  557. isRecommend: this.articleInfo.isRecommend,
  558. status: this.articleInfo.status
  559. };
  560. let result;
  561. if (this.isEdit) {
  562. // 编辑模式
  563. result = await updateArticle(articleData);
  564. } else {
  565. // 新建模式
  566. result = await createArticle(articleData);
  567. }
  568. if (result.data.code === 200) {
  569. uni.showToast({
  570. title: this.isEdit ? '更新成功' : '发布成功',
  571. icon: 'success'
  572. });
  573. // 成功后返回上一页
  574. setTimeout(() => {
  575. uni.navigateBack();
  576. }, 1500);
  577. } else {
  578. uni.showToast({
  579. title: result.data.msg || '保存失败',
  580. icon: 'none'
  581. });
  582. }
  583. } catch (error) {
  584. console.error('保存文章失败:', error);
  585. uni.showToast({
  586. title: '网络异常,请稍后重试',
  587. icon: 'none'
  588. });
  589. } finally {
  590. this.loading = false;
  591. }
  592. },
  593. // 返回
  594. goBack() {
  595. uni.navigateBack();
  596. },
  597. // 滚动到顶部
  598. scrollToTop() {
  599. uni.pageScrollTo({
  600. scrollTop: 0,
  601. duration: 300
  602. });
  603. }
  604. }
  605. };
  606. </script>
  607. <style>
  608. /* 引入原有样式 */
  609. @font-face {
  610. font-family: 'iconfont';
  611. src: url('https://at.alicdn.com/t/font_3442238_cosd6rj55jg.ttf') format('truetype');
  612. }
  613. @font-face {
  614. font-family: 'Material Icons';
  615. font-style: normal;
  616. font-weight: 400;
  617. src: url(https://fonts.gstatic.com/s/materialicons/v139/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
  618. }
  619. .icon, .view-icon, .play-icon, .action-icon, .top-icon {
  620. font-family: 'iconfont';
  621. }
  622. .material-icon {
  623. font-family: 'Material Icons';
  624. font-weight: normal;
  625. font-style: normal;
  626. font-size: 50rpx;
  627. line-height: 1;
  628. letter-spacing: normal;
  629. text-transform: none;
  630. display: inline-block;
  631. white-space: nowrap;
  632. word-wrap: normal;
  633. direction: ltr;
  634. -webkit-font-smoothing: antialiased;
  635. color: #4CAF50;
  636. }
  637. /* 容器样式 */
  638. .container {
  639. background-color: #f8f8f8;
  640. min-height: 100vh;
  641. position: relative;
  642. padding-bottom: 120rpx;
  643. }
  644. /* 文章容器 */
  645. .article-container {
  646. background-color: #fff;
  647. border-radius: 0;
  648. overflow: hidden;
  649. }
  650. /* H5导航栏样式 */
  651. .h5-custom-navbar {
  652. position: fixed;
  653. top: 0;
  654. left: 0;
  655. right: 0;
  656. height: 90rpx;
  657. background-color: #fff;
  658. display: flex;
  659. align-items: center;
  660. padding: 0 30rpx;
  661. z-index: 100;
  662. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  663. }
  664. .h5-navbar-left {
  665. width: 80rpx;
  666. height: 80rpx;
  667. display: flex;
  668. align-items: center;
  669. justify-content: center;
  670. }
  671. .h5-back-icon {
  672. font-size: 40rpx;
  673. color: #333;
  674. display: flex;
  675. align-items: center;
  676. justify-content: center;
  677. }
  678. .h5-navbar-title {
  679. flex: 1;
  680. text-align: center;
  681. font-size: 32rpx;
  682. font-weight: bold;
  683. color: #333;
  684. }
  685. .h5-navbar-right {
  686. width: 60rpx;
  687. }
  688. .h5-arrow-left {
  689. width: 24rpx;
  690. height: 24rpx;
  691. border-top: 4rpx solid #333;
  692. border-left: 4rpx solid #333;
  693. transform: rotate(-45deg);
  694. }
  695. /* 表单样式 */
  696. .form-section {
  697. padding: 30rpx;
  698. margin-bottom: 30rpx;
  699. background-color: #fff;
  700. border-bottom: 1rpx solid #eee;
  701. }
  702. .form-title {
  703. font-size: 34rpx;
  704. font-weight: bold;
  705. color: #333;
  706. margin-bottom: 30rpx;
  707. padding-left: 16rpx;
  708. border-left: 8rpx solid #4CAF50;
  709. }
  710. .form-item {
  711. margin-bottom: 30rpx;
  712. }
  713. .form-label {
  714. display: block;
  715. font-size: 28rpx;
  716. color: #666;
  717. margin-bottom: 12rpx;
  718. }
  719. .form-input {
  720. width: 100%;
  721. height: 80rpx;
  722. border: 1rpx solid #ddd;
  723. border-radius: 8rpx;
  724. padding: 0 20rpx;
  725. font-size: 28rpx;
  726. color: #333;
  727. background-color: #f9f9f9;
  728. }
  729. .form-textarea {
  730. width: 100%;
  731. height: 180rpx;
  732. border: 1rpx solid #ddd;
  733. border-radius: 8rpx;
  734. padding: 20rpx;
  735. font-size: 28rpx;
  736. color: #333;
  737. background-color: #f9f9f9;
  738. }
  739. .type-group {
  740. display: flex;
  741. flex-direction: row;
  742. }
  743. .type-item {
  744. margin-right: 60rpx;
  745. font-size: 28rpx;
  746. color: #333;
  747. }
  748. /* 上传框样式 */
  749. .upload-box {
  750. width: 200rpx;
  751. height: 200rpx;
  752. border: 2rpx dashed #ddd;
  753. border-radius: 8rpx;
  754. display: flex;
  755. flex-direction: column;
  756. align-items: center;
  757. justify-content: center;
  758. background-color: #f9f9f9;
  759. }
  760. .upload-placeholder {
  761. display: flex;
  762. flex-direction: column;
  763. align-items: center;
  764. color: #999;
  765. font-size: 24rpx;
  766. }
  767. .upload-icon {
  768. font-size: 60rpx;
  769. margin-bottom: 10rpx;
  770. }
  771. .thumbnail-preview {
  772. width: 100%;
  773. height: 100%;
  774. border-radius: 8rpx;
  775. }
  776. /* 内容构建器样式 */
  777. .content-builder {
  778. margin-top: 20rpx;
  779. }
  780. .section-controls {
  781. display: flex;
  782. flex-wrap: wrap;
  783. margin-bottom: 30rpx;
  784. }
  785. .control-btn {
  786. margin-right: 20rpx;
  787. margin-bottom: 20rpx;
  788. padding: 10rpx 24rpx;
  789. background-color: #f5f5f5;
  790. color: #333;
  791. font-size: 26rpx;
  792. border-radius: 30rpx;
  793. border: 1rpx solid #e0e0e0;
  794. }
  795. .content-section-item {
  796. margin-bottom: 30rpx;
  797. border: 1rpx solid #eee;
  798. border-radius: 8rpx;
  799. padding: 20rpx;
  800. }
  801. .section-edit-item {
  802. position: relative;
  803. }
  804. .section-textarea {
  805. width: 100%;
  806. min-height: 150rpx;
  807. border: 1rpx solid #eee;
  808. border-radius: 8rpx;
  809. padding: 20rpx;
  810. font-size: 28rpx;
  811. color: #333;
  812. margin-bottom: 20rpx;
  813. background-color: #f9f9f9;
  814. }
  815. .section-title-input {
  816. width: 100%;
  817. height: 80rpx;
  818. border: 1rpx solid #eee;
  819. border-radius: 8rpx;
  820. padding: 0 20rpx;
  821. font-size: 30rpx;
  822. font-weight: bold;
  823. color: #333;
  824. margin-bottom: 20rpx;
  825. background-color: #f9f9f9;
  826. }
  827. .section-controls {
  828. display: flex;
  829. justify-content: flex-end;
  830. }
  831. .delete-btn, .move-btn {
  832. font-size: 24rpx;
  833. color: #999;
  834. padding: 6rpx 16rpx;
  835. margin-left: 20rpx;
  836. }
  837. .delete-btn {
  838. color: #ff5252;
  839. }
  840. /* 图片上传区域 */
  841. .image-upload-container {
  842. width: 100%;
  843. margin-bottom: 20rpx;
  844. }
  845. .section-image-preview {
  846. width: 100%;
  847. height: 300rpx;
  848. border-radius: 8rpx;
  849. }
  850. .image-caption-input {
  851. width: 100%;
  852. height: 70rpx;
  853. border: 1rpx solid #eee;
  854. border-radius: 8rpx;
  855. padding: 0 20rpx;
  856. font-size: 26rpx;
  857. color: #333;
  858. margin-top: 20rpx;
  859. background-color: #f9f9f9;
  860. }
  861. .color-input {
  862. width: 100%;
  863. height: 70rpx;
  864. border: 1rpx solid #eee;
  865. border-radius: 8rpx;
  866. padding: 0 20rpx;
  867. font-size: 26rpx;
  868. color: #333;
  869. margin-top: 20rpx;
  870. background-color: #f9f9f9;
  871. }
  872. /* 提示框编辑区域 */
  873. .note-box-edit {
  874. border-left: 8rpx solid #8bc34a;
  875. padding: 20rpx;
  876. background-color: #f1f8e9;
  877. margin-bottom: 20rpx;
  878. border-radius: 8rpx;
  879. }
  880. .note-title-input {
  881. width: 100%;
  882. height: 70rpx;
  883. border: 1rpx solid #e0e0e0;
  884. border-radius: 8rpx;
  885. padding: 0 20rpx;
  886. font-size: 28rpx;
  887. font-weight: bold;
  888. color: #558b2f;
  889. margin-bottom: 20rpx;
  890. background-color: rgba(255,255,255,0.5);
  891. }
  892. .note-content-input {
  893. width: 100%;
  894. height: 150rpx;
  895. border: 1rpx solid #e0e0e0;
  896. border-radius: 8rpx;
  897. padding: 20rpx;
  898. font-size: 26rpx;
  899. color: #689f38;
  900. background-color: rgba(255,255,255,0.5);
  901. }
  902. /* 预览区域 */
  903. .preview-section {
  904. padding: 30rpx;
  905. margin-bottom: 30rpx;
  906. background-color: #fff;
  907. border-top: 1rpx solid #eee;
  908. }
  909. .article-content {
  910. margin-top: 20rpx;
  911. border: 1rpx dashed #ddd;
  912. padding: 20rpx;
  913. border-radius: 8rpx;
  914. }
  915. /* 提交按钮区域 */
  916. .submit-section {
  917. padding: 20rpx 30rpx 50rpx;
  918. }
  919. .submit-btn {
  920. width: 100%;
  921. height: 90rpx;
  922. line-height: 90rpx;
  923. background: linear-gradient(135deg, #4CAF50, #388E3C);
  924. color: #fff;
  925. font-size: 32rpx;
  926. font-weight: bold;
  927. border-radius: 45rpx;
  928. text-align: center;
  929. box-shadow: 0 6rpx 16rpx rgba(76, 175, 80, 0.3);
  930. }
  931. /* 内容预览样式 */
  932. .content-section {
  933. margin-bottom: 40rpx;
  934. }
  935. .section-title {
  936. font-size: 34rpx;
  937. font-weight: bold;
  938. color: #2e7d32;
  939. margin-bottom: 20rpx;
  940. display: block;
  941. }
  942. .paragraph {
  943. font-size: 30rpx;
  944. line-height: 1.8;
  945. color: #333;
  946. margin-bottom: 20rpx;
  947. display: block;
  948. }
  949. .section-image {
  950. margin: 20rpx 0;
  951. width: 100%;
  952. }
  953. .color-image {
  954. width: 100%;
  955. height: 300rpx;
  956. border-radius: 0;
  957. display: flex;
  958. align-items: center;
  959. justify-content: center;
  960. position: relative;
  961. overflow: hidden;
  962. }
  963. .image-label {
  964. color: white;
  965. font-size: 32rpx;
  966. font-weight: bold;
  967. text-shadow: 0 2px 4px rgba(0,0,0,0.5);
  968. z-index: 5;
  969. }
  970. .note-box {
  971. background-color: #f1f8e9;
  972. border-left: 8rpx solid #8bc34a;
  973. padding: 20
  974. }
  975. </style>