|
|
@@ -0,0 +1,542 @@
|
|
|
+<template>
|
|
|
+ <view class="app-content">
|
|
|
+ <form @submit="formSubmit">
|
|
|
+ <!-- 基础信息 -->
|
|
|
+ <view class="form-card">
|
|
|
+ <view class="card-title">基础信息</view>
|
|
|
+
|
|
|
+ <view class="form-item">
|
|
|
+ <view class="form-label required">标题</view>
|
|
|
+ <view class="form-field">
|
|
|
+ <view class="input-box">
|
|
|
+ <input
|
|
|
+ v-model="form.title"
|
|
|
+ type="text"
|
|
|
+ maxlength="20"
|
|
|
+ class="form-input"
|
|
|
+ placeholder="请输入公告标题"
|
|
|
+ placeholder-class="placeholder"
|
|
|
+ name="title"
|
|
|
+ />
|
|
|
+ <text class="char-count">{{ form.title.length }}/20</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="form-item">
|
|
|
+ <view class="form-label required">公告简介</view>
|
|
|
+ <view class="form-field">
|
|
|
+ <view class="input-box textarea-box">
|
|
|
+ <textarea
|
|
|
+ v-model="form.summary"
|
|
|
+ maxlength="200"
|
|
|
+ class="form-textarea"
|
|
|
+ placeholder="请输入公告简介"
|
|
|
+ placeholder-class="placeholder"
|
|
|
+ auto-height
|
|
|
+ name="summary"
|
|
|
+ />
|
|
|
+ <text class="char-count">{{ form.summary.length }}/200</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="form-item align-top">
|
|
|
+ <view class="form-label required">展示位置</view>
|
|
|
+ <view class="form-field">
|
|
|
+ <checkbox-group class="position-grid" @change="positionChange">
|
|
|
+ <label
|
|
|
+ class="position-item"
|
|
|
+ v-for="item in positionOptions"
|
|
|
+ :key="item.value"
|
|
|
+ >
|
|
|
+ <checkbox
|
|
|
+ :value="String(item.value)"
|
|
|
+ :checked="form.positions.indexOf(item.value) > -1"
|
|
|
+ color="#07a94b"
|
|
|
+ style="transform:scale(0.82,0.82)"
|
|
|
+ />
|
|
|
+ <text class="checkbox-text">{{ item.label }}</text>
|
|
|
+ </label>
|
|
|
+ </checkbox-group>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="form-item">
|
|
|
+ <view class="form-label required">排序</view>
|
|
|
+ <view class="form-field">
|
|
|
+ <view class="input-box">
|
|
|
+ <input
|
|
|
+ v-model="form.sort"
|
|
|
+ type="number"
|
|
|
+ class="form-input"
|
|
|
+ placeholder="请输入排序值"
|
|
|
+ placeholder-class="placeholder"
|
|
|
+ name="sort"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <text class="form-tip">数值越大,展示越靠前</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 公告内容 -->
|
|
|
+ <view class="form-card content-card">
|
|
|
+ <view class="content-header">
|
|
|
+ <text class="card-title no-margin">公告内容</text>
|
|
|
+ <text class="content-tip">可添加图片或文字</text>
|
|
|
+ </view>
|
|
|
+ <view class="editor-wrap">
|
|
|
+ <rich-media-editor
|
|
|
+ ref="richEditor"
|
|
|
+ v-model="form.imageTextVos"
|
|
|
+ @change="onContentChange"
|
|
|
+ @collect-delete="onCollectDelete"
|
|
|
+ :action="getLoginInfo.imgUploadApi"
|
|
|
+ :deferDelete="isEdit"
|
|
|
+ iconColor="#07a94b"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="footer-bar">
|
|
|
+ <button class="footer-btn preview" type="button" @click="previewContent">预览</button>
|
|
|
+ <button class="footer-btn submit" formType="submit">确认</button>
|
|
|
+ </view>
|
|
|
+ </form>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import RichMediaEditor from '@/components/RichMediaEditor/index.vue'
|
|
|
+import { getShopNotice, createShopNotice, updateShopNotice } from '@/api/shop-notice'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'shopNoticeAdd',
|
|
|
+ components: {
|
|
|
+ RichMediaEditor
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ form: {
|
|
|
+ title: '',
|
|
|
+ summary: '',
|
|
|
+ positions: [],
|
|
|
+ sort: '',
|
|
|
+ status: 0,
|
|
|
+ imageTextVos: []
|
|
|
+ },
|
|
|
+ positionOptions: [
|
|
|
+ { value: 1, label: '商城首页' },
|
|
|
+ { value: 2, label: '分类菜单' },
|
|
|
+ { value: 4, label: '购物车' },
|
|
|
+ { value: 8, label: '订单提交' }
|
|
|
+ ],
|
|
|
+ isEdit: false,
|
|
|
+ delImgQueue: []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(option) {
|
|
|
+ if (option.id) {
|
|
|
+ this.isEdit = true
|
|
|
+ this.option = option
|
|
|
+ uni.setNavigationBarTitle({ title: '编辑公告' })
|
|
|
+ this._getDet()
|
|
|
+ } else {
|
|
|
+ uni.setNavigationBarTitle({ title: '新增公告' })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ positionChange(e) {
|
|
|
+ this.form.positions = (e.detail.value || []).map(item => parseInt(item, 10))
|
|
|
+ },
|
|
|
+ onContentChange(content) {
|
|
|
+ this.form.imageTextVos = content
|
|
|
+ },
|
|
|
+ onCollectDelete(payload) {
|
|
|
+ if (!this.isEdit || !payload || !Array.isArray(payload.filePaths)) return
|
|
|
+ payload.filePaths.forEach(path => {
|
|
|
+ if (path && this.delImgQueue.indexOf(path) === -1) {
|
|
|
+ this.delImgQueue.push(path)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ _getDet() {
|
|
|
+ getShopNotice({ id: this.option.id }).then(res => {
|
|
|
+ if (this.$util.isEmpty(res.data)) return
|
|
|
+ let content = []
|
|
|
+ if (typeof res.data.content === 'string') {
|
|
|
+ try {
|
|
|
+ content = JSON.parse(res.data.content)
|
|
|
+ } catch (e) {
|
|
|
+ content = []
|
|
|
+ }
|
|
|
+ } else if (Array.isArray(res.data.content)) {
|
|
|
+ content = res.data.content
|
|
|
+ }
|
|
|
+ content = content.map(item => ({
|
|
|
+ type: item.type || 3,
|
|
|
+ content: item.content || ''
|
|
|
+ }))
|
|
|
+ this.form = {
|
|
|
+ title: res.data.title || '',
|
|
|
+ summary: res.data.summary || '',
|
|
|
+ positions: res.data.positions || [],
|
|
|
+ sort: res.data.sort !== undefined && res.data.sort !== null ? String(res.data.sort) : '',
|
|
|
+ status: Number(res.data.status) || 0,
|
|
|
+ imageTextVos: content
|
|
|
+ }
|
|
|
+ this.$nextTick(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ if (this.$refs.richEditor) {
|
|
|
+ this.$refs.richEditor.updateContent(this.form.imageTextVos)
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 校验必填项,返回错误文案,通过则返回空字符串
|
|
|
+ validateForm() {
|
|
|
+ const title = (this.form.title || '').trim()
|
|
|
+ const summary = (this.form.summary || '').trim()
|
|
|
+ if (!title) return '请输入标题'
|
|
|
+ if (title.length > 20) return '标题不能超过20个字'
|
|
|
+ if (!summary) return '请输入公告简介'
|
|
|
+ if (summary.length > 200) return '公告简介不能超过200个字'
|
|
|
+ if (!this.form.positions.length) return '请选择展示位置'
|
|
|
+ if (this.form.sort === '' || this.form.sort === null || this.form.sort === undefined) {
|
|
|
+ return '请输入排序'
|
|
|
+ }
|
|
|
+ if (!this.form.imageTextVos.length) return '请添加至少一项公告内容'
|
|
|
+ return ''
|
|
|
+ },
|
|
|
+ previewContent() {
|
|
|
+ const err = this.validateForm()
|
|
|
+ if (err) {
|
|
|
+ this.$msg(err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const previewData = {
|
|
|
+ title: this.form.title,
|
|
|
+ summary: this.form.summary,
|
|
|
+ content: this.form.imageTextVos
|
|
|
+ }
|
|
|
+ const app = getApp()
|
|
|
+ app.globalData = app.globalData || {}
|
|
|
+ app.globalData.previewData = previewData
|
|
|
+ uni.navigateTo({ url: './preview' })
|
|
|
+ },
|
|
|
+ confirmFn() {
|
|
|
+ uni.showLoading({ mask: true })
|
|
|
+ const apiCall = this.isEdit ? updateShopNotice : createShopNotice
|
|
|
+ const params = {
|
|
|
+ title: this.form.title,
|
|
|
+ summary: this.form.summary,
|
|
|
+ position: this.form.positions.join(','),
|
|
|
+ sort: Number(this.form.sort) || 0,
|
|
|
+ status: this.form.status,
|
|
|
+ content: JSON.stringify(this.form.imageTextVos)
|
|
|
+ }
|
|
|
+ if (this.isEdit) {
|
|
|
+ params.id = this.option.id
|
|
|
+ }
|
|
|
+ apiCall(params).then(res => {
|
|
|
+ uni.hideLoading()
|
|
|
+ if (res.code == 1) {
|
|
|
+ uni.$emit('refreshShopNoticeList')
|
|
|
+ this.$msg(this.isEdit ? '修改成功' : '添加成功')
|
|
|
+ if (this.isEdit && this.delImgQueue.length > 0) {
|
|
|
+ const { delImages } = require('@/api/pic-text')
|
|
|
+ delImages({ filePaths: Array.from(new Set(this.delImgQueue)) }).finally(() => {
|
|
|
+ this.delImgQueue = []
|
|
|
+ })
|
|
|
+ }
|
|
|
+ uni.navigateBack({ delta: 1 })
|
|
|
+ } else {
|
|
|
+ this.$msg(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ uni.hideLoading()
|
|
|
+ this.$msg('提交失败,请稍后重试')
|
|
|
+ })
|
|
|
+ },
|
|
|
+ formSubmit() {
|
|
|
+ const err = this.validateForm()
|
|
|
+ if (err) {
|
|
|
+ this.$msg(err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.confirmFn()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.app-content {
|
|
|
+ min-height: 100vh;
|
|
|
+ padding: 24rpx 24rpx calc(160rpx + env(safe-area-inset-bottom));
|
|
|
+ background: #f3f4f6;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.form-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 32rpx 28rpx;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.card-title {
|
|
|
+ font-size: 34rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a1a;
|
|
|
+ margin-bottom: 28rpx;
|
|
|
+
|
|
|
+ &.no-margin {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.form-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 28rpx;
|
|
|
+
|
|
|
+.form-item.align-top {
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.form-label {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 168rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.form-item:not(.align-top) .form-label {
|
|
|
+ line-height: 80rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-item.align-top .form-label {
|
|
|
+ line-height: 1.4;
|
|
|
+ padding-top: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-field {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.input-box {
|
|
|
+ background: #f5f6f8;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 0 24rpx;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &.textarea-box {
|
|
|
+ padding-top: 12rpx;
|
|
|
+ padding-bottom: 12rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.form-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 80rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ padding-right: 80rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.form-textarea {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 80rpx;
|
|
|
+ padding: 8rpx 80rpx 8rpx 0;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.5;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder {
|
|
|
+ color: #c0c0c0;
|
|
|
+}
|
|
|
+
|
|
|
+.form-label.required::before {
|
|
|
+ content: '*';
|
|
|
+ color: #e64340;
|
|
|
+ margin-right: 6rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.char-count {
|
|
|
+ position: absolute;
|
|
|
+ right: 20rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.input-box:not(.textarea-box) .char-count {
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+}
|
|
|
+
|
|
|
+.textarea-box .char-count {
|
|
|
+ bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-tip {
|
|
|
+ display: block;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ padding: 10rpx 16rpx;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.position-grid {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 24rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.position-item {
|
|
|
+ width: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding-right: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.checkbox-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-left: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.content-card {
|
|
|
+ padding-bottom: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.content-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.content-tip {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.editor-wrap {
|
|
|
+ margin: 0 -28rpx;
|
|
|
+
|
|
|
+ ::v-deep .rich-media-editor {
|
|
|
+ border: none;
|
|
|
+ border-radius: 0;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .content-area {
|
|
|
+ min-height: 280rpx;
|
|
|
+ padding: 0 28rpx 20rpx;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .empty-placeholder {
|
|
|
+ min-height: 280rpx;
|
|
|
+ margin: 0;
|
|
|
+ background: #f5f6f8;
|
|
|
+ border: 2rpx dashed #d9dce3;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '+';
|
|
|
+ font-size: 56rpx;
|
|
|
+ color: #07a94b;
|
|
|
+ font-weight: 300;
|
|
|
+ line-height: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .empty-text {
|
|
|
+ color: #999;
|
|
|
+ font-size: 26rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .bottom-toolbar {
|
|
|
+ height: auto;
|
|
|
+ padding: 24rpx 28rpx 32rpx;
|
|
|
+ border-top: none;
|
|
|
+ background: #fff;
|
|
|
+ gap: 20rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .toolbar-btn {
|
|
|
+ flex: 1;
|
|
|
+ padding: 24rpx 0;
|
|
|
+ background: #eef8f1;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ gap: 12rpx;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: #dff0e4;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .toolbar-btn .btn-text {
|
|
|
+ color: #07a94b;
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.footer-bar {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ display: flex;
|
|
|
+ gap: 24rpx;
|
|
|
+ padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom));
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.footer-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 88rpx;
|
|
|
+ line-height: 88rpx;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 32rpx;
|
|
|
+ border-radius: 44rpx;
|
|
|
+ border: none;
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ border: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.preview {
|
|
|
+ color: #07a94b;
|
|
|
+ background: #eef8f1;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.submit {
|
|
|
+ color: #fff;
|
|
|
+ background: linear-gradient(90deg, #07a94b, #2ec46a);
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|