article-edit.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. <template>
  2. <view class="app-page bg-white pa-12">
  3. <!-- 工具栏区域 -->
  4. <view class="tool-bar-wrap e-fixed bg-white pt-12 pb-16 border-b-1">
  5. <view class="tool-bar flex flex-center text-grey-darken-2">
  6. <text class="halohtmlicon icon-undo" data-method="undo" @click="fnOnToolBarEdit"></text>
  7. <text class="halohtmlicon icon-redo" data-method="redo" @click="fnOnToolBarEdit"></text>
  8. <text class="halohtmlicon icon-img" data-method="insertImg" @click="fnOnToolBarEdit"></text>
  9. <text class="halohtmlicon icon-video" data-method="insertVideo" @click="fnOnToolBarEdit"></text>
  10. <text class="halohtmlicon icon-link" data-method="insertLink" @click="fnOnToolBarEdit"></text>
  11. <text class="halohtmlicon icon-text" data-method="insertText" @click="fnOnToolBarEdit"></text>
  12. <text class="halohtmlicon icon-line" data-method="insertHtml" data-param="<hr style='margin:10px 0;'/>" @click="fnOnToolBarEdit"></text>
  13. </view>
  14. <view class="tool-bar flex flex-center text-grey-darken-2 mt-16">
  15. <text class="halohtmlicon icon-heading" @click="fnOnInsertHead"></text>
  16. <text
  17. class="halohtmlicon icon-quote"
  18. data-method="insertHtml"
  19. data-param="<blockquote style='padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5'>引用</blockquote>"
  20. @click="fnOnToolBarEdit"
  21. ></text>
  22. <text class="halohtmlicon icon-table" @click="fnOnInsertTable"></text>
  23. <text class="halohtmlicon icon-code" @click="fnOnInsertCode"></text>
  24. <text class="halohtmlicon icon-emoji" data-type="emoji" data-title="插入表情" @click="fnOnOpenDialog"></text>
  25. <text class="halohtmlicon icon-template" data-type="template" data-title="插入模板" @click="fnOnOpenDialog"></text>
  26. <text v-if="editable" class="flex-1 text-align-center iconfont icon-eye" style="font-size: 44rpx;" @click="fnOnPreview()"></text>
  27. <text v-else class="halohtmlicon icon-edit" @click="fnOnPreview()"></text>
  28. </view>
  29. </view>
  30. <!-- 编辑区域 -->
  31. <view class="edit-wrap bg-white round-3">
  32. <mp-html
  33. class="evan-markdown"
  34. lazy-load
  35. ref="markdown"
  36. :editable="editable"
  37. :domain="markdownConfig.domain"
  38. :loading-img="markdownConfig.loadingGif"
  39. :scroll-table="true"
  40. selectable="force"
  41. :tag-style="markdownConfig.tagStyle"
  42. :container-style="markdownConfig.containStyle"
  43. :content="form.content"
  44. :markdown="true"
  45. :showLineNumber="true"
  46. :showLanguageName="true"
  47. :copyByLongPress="true"
  48. />
  49. </view>
  50. <view class="fixed-bottom bg-white pa-24 ">
  51. <view class="btn-wrap flex flex-center">
  52. <block v-if="queryParams.postsId">
  53. <tm-button :width="200" :height="75" block theme="light-blue" @click="fnSaveAtPublish()">我要发布</tm-button>
  54. <tm-button class="ml-24" :width="200" :height="75" block theme="blue-grey" @click="fnRefreshData()">重新加载</tm-button>
  55. <tm-button class="ml-24" :width="200" :height="75" block theme="red" @click="fnClear()">清空内容</tm-button>
  56. </block>
  57. <block v-else>
  58. <tm-button :width="300" :height="75" block theme="light-blue" @click="fnSaveAtPublish()">我要发布</tm-button>
  59. <tm-button class="ml-24" theme="red" :width="300" :height="75" block @click="fnClear()">清空内容</tm-button>
  60. </block>
  61. </view>
  62. </view>
  63. <!-- 附件选择文件 -->
  64. <attachment-select
  65. v-if="attachmentsSelect.show"
  66. :title="attachmentsSelect.title"
  67. @on-select="fnOnAttachmentsSelect"
  68. @on-close="attachmentsSelect.show = false"
  69. ></attachment-select>
  70. <!-- 弹窗 -->
  71. <block v-if="modal">
  72. <view class="mask" />
  73. <view class="modal">
  74. <view class="modal_title">{{ modal.title }}</view>
  75. <view class="flex flex-col flex-center" v-if="modal.type == 'table'">
  76. <tm-input required title="输入行数" input-type="number" v-model="modal.rows"></tm-input>
  77. <tm-input required title="输入列数" input-type="number" v-model="modal.cols"></tm-input>
  78. </view>
  79. <block v-else><input class="modal_input" :value="modal.value" maxlength="-1" auto-focus @input="modalInput" /></block>
  80. <view class="modal_foot">
  81. <view class="modal_button" @tap="modalCancel">取消</view>
  82. <view class="modal_button" style="color:#576b95;border-left:1px solid rgba(0,0,0,.1)" @tap="modalConfirm">确定</view>
  83. </view>
  84. </view>
  85. </block>
  86. <!-- 底部弹窗 -->
  87. <tm-poup v-model="dialog.show" position="bottom" height="auto" @change="fnOnCloseDialog()">
  88. <text class="poup-close" @click="fnOnCloseDialog(false)">×</text>
  89. <view class="poup-head pa-24 pb-0 text-align-center text-weight-b ">{{ dialog.title }}</view>
  90. <view class="poup-body pa-36">
  91. <!-- 表情区域 -->
  92. <block v-if="dialog.type == 'emoji'">
  93. <view class="flex" v-for="(item, index) in emojis" :key="index">
  94. <view class="flex-1 mt-6 mb-6 text-size-xl" v-for="(emoji, index) in item" :key="emoji" :data-emoji="emoji" @click="fnOnInsertEmoji">{{ emoji }}</view>
  95. </view>
  96. </block>
  97. <!-- 模板区域 -->
  98. <block v-else-if="dialog.type == 'template'">
  99. <block v-for="(template, index) in templates" :key="index">
  100. <rich-text :nodes="template" data-method="insertHtml" :data-param="template" @click="fnOnToolBarEdit"></rich-text>
  101. </block>
  102. </block>
  103. </view>
  104. </tm-poup>
  105. <!-- 发布设置弹窗 -->
  106. </view>
  107. </template>
  108. <script>
  109. import { getAdminAccessToken } from '@/utils/auth.js';
  110. import MarkdownConfig from '@/common/markdown/markdown.config.js';
  111. import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
  112. import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
  113. import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
  114. import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
  115. import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
  116. import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
  117. import tmSwitch from '@/tm-vuetify/components/tm-switch/tm-switch.vue';
  118. import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
  119. import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
  120. import attachmentSelect from '@/components/attachment-select/attachment-select.vue';
  121. export default {
  122. components: {
  123. mpHtml,
  124. tmSkeleton,
  125. tmButton,
  126. tmEmpty,
  127. tmTranslate,
  128. tmFlotbutton,
  129. tmSwitch,
  130. tmPoup,
  131. tmInput,
  132. attachmentSelect
  133. },
  134. data() {
  135. return {
  136. loading: 'loading',
  137. markdownConfig: MarkdownConfig,
  138. queryParams: {
  139. postsId: undefined
  140. },
  141. status: true,
  142. form: {
  143. content: '',
  144. keepRaw: true,
  145. originalContent: '',
  146. formatContent: '',
  147. type: 'PUBLIC'
  148. },
  149. modal: null,
  150. editable: true,
  151. attachmentsSelect: {
  152. title: '选择附件',
  153. show: false
  154. },
  155. dialog: {
  156. show: false,
  157. type: ''
  158. },
  159. // 用于插入的 emoji 表情
  160. emojis: [
  161. ['😄', '😷', '😂', '😝', '😳', '😱', '😔', '😒', '😉'],
  162. ['😎', '😭', '😍', '😘', '🤔', '😕', '🙃', '🤑', '😲'],
  163. ['🙄', '😤', '😴', '🤓', '😡', '😑', '😮', '🤒', '🤮']
  164. ],
  165. // 用于插入的 html 模板
  166. templates: [
  167. '<section style="text-align: center; margin: 0px auto;"><section style="border-radius: 4px; border: 1px solid #757576; display: inline-block; padding: 5px 20px;"><span style="font-size: 18px; color: #595959;">标题</span></section></section>',
  168. '<div style="width: 100%; box-sizing: border-box; border-radius: 5px; background-color: #f6f6f6; padding: 10px; margin: 10px 0"><div>卡片</div><div style="font-size: 12px; color: gray">正文</div></div>',
  169. '<div style="border: 1px solid gray; box-shadow: 3px 3px 0px #cfcfce; padding: 10px; margin: 10px 0">段落</div>'
  170. ]
  171. };
  172. },
  173. onLoad() {
  174. const postsId = this.$Route.query.postsId;
  175. if (postsId == undefined) {
  176. this.fnSetPageTitle('新增文章');
  177. this.form.content = uni.getStorageSync('posts-content');
  178. } else {
  179. this.fnSetPageTitle('编辑文章');
  180. this.queryParams.postsId = postsId;
  181. this.fnGetData();
  182. }
  183. },
  184. onReady() {
  185. /**
  186. * @description 设置获取链接的方法
  187. * @param {String} type 链接的类型(img/video/audio/link)
  188. * @param {String} value 修改链接时,这里会传入旧值
  189. * @returns {Promise} 返回线上地址
  190. * type 为音视频时可以返回一个数组作为源地址
  191. * type 为 audio 时,可以返回一个 object,包含 src、name、author、poster 等字段
  192. */
  193. this.$refs.markdown.getSrc = (type, value) => {
  194. return new Promise((resolve, reject) => {
  195. this.checkEditable()
  196. .then(res => {
  197. if (type === 'img' || type === 'video') {
  198. uni.showActionSheet({
  199. itemList: ['本地选取', '附件选取'],
  200. success: res => {
  201. if (res.tapIndex === 0) {
  202. // 本地选取
  203. if (type === 'img') {
  204. uni.chooseImage({
  205. count: value === undefined ? 9 : 1,
  206. success: res => {
  207. // #ifdef MP-WEIXIN
  208. if (res.tempFilePaths.length == 1 && wx.editImage) {
  209. // 单张图片时进行编辑
  210. wx.editImage({
  211. src: res.tempFilePaths[0],
  212. complete: res2 => {
  213. uni.showLoading({
  214. title: '上传中'
  215. });
  216. this.fnFileUpload(res2.tempFilePath || res.tempFilePaths[0], type).then(res => {
  217. uni.hideLoading();
  218. resolve(res);
  219. });
  220. }
  221. });
  222. } else {
  223. // #endif
  224. uni.showLoading({
  225. title: '上传中'
  226. });
  227. (async () => {
  228. const arr = [];
  229. for (let item of res.tempFilePaths) {
  230. // 依次上传
  231. const src = await this.fnFileUpload(item, type);
  232. arr.push(src);
  233. }
  234. return arr;
  235. })().then(res => {
  236. uni.hideLoading();
  237. resolve(res);
  238. });
  239. // #ifdef MP-WEIXIN
  240. }
  241. // #endif
  242. },
  243. fail: reject
  244. });
  245. } else {
  246. uni.chooseVideo({
  247. success: res => {
  248. uni.showLoading({
  249. title: '上传中'
  250. });
  251. this.fnFileUpload(res.tempFilePath, type).then(res => {
  252. uni.hideLoading();
  253. resolve(res);
  254. });
  255. },
  256. fail: reject
  257. });
  258. }
  259. } else {
  260. // 远程链接
  261. this.callback = {
  262. resolve,
  263. reject
  264. };
  265. this.attachmentsSelect.title = type === 'img' ? '选取图片' : '选取视频';
  266. this.attachmentsSelect.show = true;
  267. }
  268. }
  269. });
  270. } else {
  271. this.callback = {
  272. resolve,
  273. reject
  274. };
  275. let title;
  276. if (type === 'audio') {
  277. title = '音频链接';
  278. } else if (type === 'link') {
  279. title = '链接地址';
  280. }
  281. this.$set(this, 'modal', {
  282. title,
  283. type: 'link',
  284. value
  285. });
  286. }
  287. })
  288. .catch(err => {});
  289. });
  290. };
  291. },
  292. methods: {
  293. // 加载文章数据
  294. fnGetData(refresh = false) {
  295. this.loading = 'loading';
  296. uni.showLoading({
  297. mask: true,
  298. title: '加载中...'
  299. });
  300. this.$httpApi.admin
  301. .getPostsById(this.queryParams.postsId)
  302. .then(res => {
  303. if (res.status == 200) {
  304. this.fnSetPageTitle(`正在编辑 ${res.data.title}`);
  305. this.$set(this, 'form', res.data);
  306. if (refresh) {
  307. this.$nextTick(() => {
  308. this.$refs.markdown.setContent(res.data.content);
  309. });
  310. }
  311. this.loading = 'success';
  312. uni.hideLoading();
  313. } else {
  314. this.loading = 'error';
  315. this.fnSetPageTitle(`文章加载失败`);
  316. uni.$tm.toast('文章加载失败,请点击下方重新加载!');
  317. }
  318. })
  319. .catch(err => {
  320. console.error(err);
  321. this.loading = 'error';
  322. this.fnSetPageTitle(`文章加载失败`);
  323. uni.$tm.toast('文章加载失败,请点击下方重新加载!');
  324. });
  325. },
  326. fnRefreshData(showConfirm = true) {
  327. if (showConfirm) {
  328. uni.$eShowModal({
  329. title: '提示',
  330. content: '您当前的编辑可能未保存,确定要重新加载吗?',
  331. showCancel: true,
  332. cancelText: '否',
  333. cancelColor: '#999999',
  334. confirmText: '是',
  335. confirmColor: '#03a9f4'
  336. })
  337. .then(res => {
  338. this.fnGetData(true);
  339. })
  340. .catch(err => {});
  341. } else {
  342. this.fnGetData(true);
  343. }
  344. },
  345. // 检查是否可编辑
  346. checkEditable() {
  347. return new Promise((resolve, reject) => {
  348. if (this.editable) {
  349. resolve();
  350. } else {
  351. uni.showModal({
  352. title: '提示',
  353. content: '需要继续编辑吗?',
  354. success: res => {
  355. if (res.confirm) {
  356. this.editable = true;
  357. resolve();
  358. } else {
  359. reject();
  360. }
  361. }
  362. });
  363. }
  364. });
  365. },
  366. // 调用编辑操作
  367. fnOnToolBarEdit(e) {
  368. this.$refs.markdown[e.currentTarget.dataset.method](e.currentTarget.dataset.param);
  369. },
  370. // 监听附件选择
  371. fnOnAttachmentsSelect(file) {
  372. this.attachmentsSelect.show = false;
  373. this.callback.resolve(file.path);
  374. },
  375. // 处理模态框
  376. modalInput(e) {
  377. this.value = e.detail.value;
  378. },
  379. modalConfirm() {
  380. if (this.modal.type == 'table') {
  381. if (this.modal.rows <= 0) {
  382. return uni.$tm.toast('行数必须大于0');
  383. }
  384. if (this.modal.cols <= 0) {
  385. return uni.$tm.toast('列数必须大于0');
  386. }
  387. }
  388. this.callback.resolve(this.value || this.modal.value || '');
  389. this.$set(this, 'modal', null);
  390. },
  391. modalCancel() {
  392. this.callback.reject();
  393. this.$set(this, 'modal', null);
  394. },
  395. // 上传图片方法
  396. fnFileUpload(src, type) {
  397. return new Promise((resolve, reject) => {
  398. uni.uploadFile({
  399. filePath: src,
  400. header: {
  401. 'admin-authorization': getAdminAccessToken()
  402. },
  403. url: this.$baseApiUrl + '/api/admin/attachments/upload',
  404. name: 'file',
  405. success: upladRes => {
  406. const _uploadRes = JSON.parse(upladRes.data);
  407. if (_uploadRes.status == 200) {
  408. resolve(_uploadRes.data.path);
  409. } else {
  410. uni.$tm.toast(_uploadRes.message);
  411. reject();
  412. }
  413. },
  414. fail: err => {
  415. uni.$tm.toast(err.message);
  416. reject();
  417. }
  418. });
  419. });
  420. },
  421. // 插入 head 系列标签
  422. fnOnInsertHead() {
  423. this.checkEditable()
  424. .then(() => {
  425. const _hList = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
  426. wx.showActionSheet({
  427. itemList: _hList,
  428. success: res => {
  429. let tagName = _hList[res.tapIndex];
  430. this.$refs.markdown.insertHtml(`<${tagName}>标题</${tagName}>`);
  431. }
  432. });
  433. })
  434. .catch(() => {});
  435. },
  436. // 插入表格
  437. fnOnInsertTable() {
  438. this.checkEditable()
  439. .then(() => {
  440. this.$set(this, 'modal', {
  441. title: '插入表格',
  442. type: 'table',
  443. rows: 1,
  444. cols: 1,
  445. value: this.value
  446. });
  447. this.callback = {
  448. resolve: () => {
  449. this.$refs.markdown.insertTable(this.modal.rows, this.modal.cols);
  450. },
  451. reject: () => {}
  452. };
  453. })
  454. .catch(() => {});
  455. },
  456. // 保存插入表格
  457. fnOnSaveInsertTable() {
  458. this.callback.resolve(this.value || this.modal.value || '');
  459. },
  460. // 插入代码
  461. fnOnInsertCode() {
  462. this.checkEditable()
  463. .then(() => {
  464. uni.showActionSheet({
  465. itemList: ['html', 'css', 'javascript', 'json'],
  466. success: res => {
  467. const lan = ['html', 'css', 'javascript', 'json'][res.tapIndex];
  468. this.$refs.markdown.insertHtml(`<pre><code class="language-${lan}">${lan} code</code></pre>`);
  469. }
  470. });
  471. })
  472. .catch(() => {});
  473. },
  474. // 插入 emoji
  475. fnOnInsertEmoji(e) {
  476. this.$refs.markdown.insertHtml(e.currentTarget.dataset.emoji);
  477. this.fnOnCloseDialog();
  478. },
  479. // 处理底部弹窗
  480. fnOnOpenDialog(e) {
  481. this.checkEditable()
  482. .then(() => {
  483. this.dialog.type = e.currentTarget.dataset.type;
  484. this.dialog.title = e.currentTarget.dataset.title;
  485. this.dialog.show = true;
  486. })
  487. .catch(() => {});
  488. },
  489. fnOnCloseDialog(e) {
  490. if (e == false) {
  491. this.dialog.show = false;
  492. this.dialog.type = '';
  493. }
  494. },
  495. fnOnPreview() {
  496. this.editable = !this.editable;
  497. if (this.editable) {
  498. uni.$tm.toast('您已进入编辑模式!');
  499. } else {
  500. uni.$tm.toast('您已进入预览模式!');
  501. let _content = this.$refs.markdown.getContent();
  502. if (_content === '<p></p>') {
  503. _content = '';
  504. }
  505. this.form.content = _content;
  506. uni.setStorageSync('posts-content', _content);
  507. }
  508. },
  509. // 发布
  510. fnSaveAtPublish() {
  511. uni.showLoading({
  512. mask: true,
  513. title: '保存中...'
  514. });
  515. const _content = this.$refs.markdown.getContent();
  516. if (!_content.trim()) {
  517. return uni.$tm.toast('请输入内容!');
  518. }
  519. this.form.content = _content;
  520. this.form.formatContent = _content;
  521. this.form.originalContent = this.$refs.markdown.getText();
  522. uni.setStorageSync('posts-content', _content);
  523. uni.setStorageSync('posts-content-source', this.form.originalContent);
  524. if (this.form.id) {
  525. this.$Router.push({
  526. path: '/pagesB/articles/article-setting',
  527. query: { postsId: this.form.id, postTitle: this.form.title, isEdit: 1, from: 'edit' }
  528. });
  529. } else {
  530. this.$Router.push({ path: '/pagesB/articles/article-setting', query: { postsId: '', postTitle: '', isEdit: 0, from: 'edit' } });
  531. }
  532. return;
  533. if (this.form) {
  534. this.$httpApi.admin
  535. .updatePostsById(this.form.id, this.form)
  536. .then(res => {
  537. if (res.status == 200) {
  538. uni.$tm.toast('保存成功!');
  539. uni.setStorageSync('posts-content', '');
  540. setTimeout(() => {
  541. uni.$emit('refresh-article-list');
  542. }, 500);
  543. } else {
  544. uni.$tm.toast('保存失败,请重试!');
  545. }
  546. })
  547. .catch(err => {
  548. uni.$tm.toast('保存失败,请重试!');
  549. });
  550. } else {
  551. this.$httpApi.admin
  552. .createPosts(this.form)
  553. .then(res => {
  554. if (res.status == 200) {
  555. uni.$tm.toast('保存成功!');
  556. uni.setStorageSync('posts-content', '');
  557. setTimeout(() => {
  558. uni.$emit('refresh-article-list');
  559. }, 500);
  560. } else {
  561. uni.$tm.toast('保存失败,请重试!');
  562. }
  563. })
  564. .catch(err => {
  565. uni.$tm.toast('保存失败,请重试!');
  566. });
  567. }
  568. },
  569. fnClear() {
  570. uni.$eShowModal({
  571. title: '提示',
  572. content: '确定清空当前内容吗?',
  573. showCancel: true,
  574. cancelText: '否',
  575. cancelColor: '#999999',
  576. confirmText: '是',
  577. confirmColor: '#03a9f4'
  578. })
  579. .then(res => {
  580. this.$refs.markdown.clear();
  581. uni.setStorageSync('posts-content', '');
  582. uni.setStorageSync('posts-content-source', '');
  583. this.fnToTopPage();
  584. })
  585. .catch(() => {});
  586. }
  587. }
  588. };
  589. </script>
  590. <style lang="scss" scoped>
  591. .app-page {
  592. width: 100vw;
  593. min-height: 100vh;
  594. box-sizing: border-box;
  595. padding-bottom: 236rpx;
  596. }
  597. .fixed-bottom {
  598. position: fixed;
  599. left: 0;
  600. bottom: 0;
  601. right: 0;
  602. box-sizing: border-box;
  603. box-shadow: 0rpx -4rpx 24rpx rgba(0, 0, 0, 0.05);
  604. border-radius: 24rpx 24rpx 0 0;
  605. }
  606. .edit-wrap {
  607. box-sizing: border-box;
  608. padding-top: 150rpx;
  609. }
  610. .modal {
  611. width: 80vw;
  612. position: fixed;
  613. top: 50%;
  614. left: 50%;
  615. transform: translate(-50%, -50%);
  616. background-color: #fff;
  617. border-radius: 24rpx;
  618. }
  619. .modal_title {
  620. padding-top: 42rpx;
  621. padding-bottom: 32rpx;
  622. font-size: 34rpx;
  623. font-weight: 700;
  624. text-align: center;
  625. }
  626. .modal_input {
  627. display: block;
  628. padding: 12rpx 12rpx;
  629. margin: 24rpx;
  630. margin-top: 0;
  631. font-size: 30rpx;
  632. border: 2rpx solid #dfe2e5;
  633. border-radius: 6rpx;
  634. }
  635. .modal_foot {
  636. display: flex;
  637. line-height: 100rpx;
  638. font-weight: bold;
  639. border-top: 2rpx solid rgba(0, 0, 0, 0.1);
  640. }
  641. .modal_button {
  642. flex: 1;
  643. text-align: center;
  644. }
  645. /* 蒙版 */
  646. .mask {
  647. position: fixed;
  648. top: 0;
  649. right: 0;
  650. bottom: 0;
  651. left: 0;
  652. background-color: black;
  653. opacity: 0.5;
  654. }
  655. .poup-close {
  656. position: absolute;
  657. right: 30rpx;
  658. top: 8rpx;
  659. font-size: 50rpx;
  660. }
  661. </style>