|
|
@@ -28,11 +28,10 @@
|
|
|
</template>
|
|
|
|
|
|
<el-table :data="tableData" stripe border style="width:100%">
|
|
|
- <el-table-column label="操作" width="200" fixed="left">
|
|
|
+ <el-table-column label="操作" width="140" fixed="left">
|
|
|
<template #default="scope">
|
|
|
<el-button type="text" size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
|
|
<el-button type="text" size="small" @click="showLinkDialog(scope.row.id)">链接</el-button>
|
|
|
- <el-button type="text" size="small" style="color:#F56C6C" @click="handleDelete(scope.row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="id" label="页面ID" width="120" align="center" />
|
|
|
@@ -86,6 +85,56 @@
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
+ <!-- 微信分享卡片配置 -->
|
|
|
+ <div class="block-card">
|
|
|
+ <div class="block-header">
|
|
|
+ <span><el-icon><ChatDotRound /></el-icon> 微信分享卡片配置</span>
|
|
|
+ <el-checkbox v-model="editData.shareConfig.enabled" @change="isModified = true" class="share-toggle-checkbox">自定义微信分享</el-checkbox>
|
|
|
+ </div>
|
|
|
+ <div class="block-body" v-if="editData.shareConfig.enabled">
|
|
|
+ <div class="share-card-body">
|
|
|
+ <div class="share-thumb-section">
|
|
|
+ <div class="share-field-label">分享缩略图 <span class="required-star">*</span></div>
|
|
|
+ <div class="share-thumb-upload" @click="triggerShareUpload">
|
|
|
+ <img v-if="editData.shareConfig.thumbnail" :src="editData.shareConfig.thumbnail" class="share-thumb-img" />
|
|
|
+ <div v-else class="upload-placeholder">
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="upload-overlay">
|
|
|
+ <el-icon><Upload /></el-icon>
|
|
|
+ <span>点击上传</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="share-thumb-tip">建议 100x100</div>
|
|
|
+ <input ref="shareUpload" type="file" accept="image/*" style="display:none" @change="onShareImageUpload" />
|
|
|
+ </div>
|
|
|
+ <div class="share-fields">
|
|
|
+ <el-form label-position="top">
|
|
|
+ <el-form-item>
|
|
|
+ <template #label>
|
|
|
+ 分享标题 <span class="required-star">*</span>
|
|
|
+ </template>
|
|
|
+ <el-input
|
|
|
+ v-model="editData.shareConfig.title"
|
|
|
+ placeholder="请输入分享标题"
|
|
|
+ @input="isModified = true"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="分享描述">
|
|
|
+ <el-input
|
|
|
+ v-model="editData.shareConfig.desc"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入分享描述语"
|
|
|
+ @input="isModified = true"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 模块列表 -->
|
|
|
<div v-for="(block, bIdx) in editData.blocks" :key="block.id" class="block-card">
|
|
|
<!-- 模块操作栏 -->
|
|
|
@@ -221,12 +270,19 @@
|
|
|
</el-form-item>
|
|
|
<!-- 弹窗配置 -->
|
|
|
<div v-if="btn.actionType === 'dialog' && btn.dialog" class="dialog-config">
|
|
|
- <div class="dialog-config-title"><el-icon><Document /></el-icon> 弹窗内容配置(包含取消、确认按钮)</div>
|
|
|
+ <div class="dialog-config-title"><el-icon><Document /></el-icon> 弹窗内容配置</div>
|
|
|
<el-form-item label="弹窗标题">
|
|
|
<el-input v-model="btn.dialog.title" size="small" placeholder="弹窗标题" @input="isModified = true" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="弹窗描述">
|
|
|
- <el-input v-model="btn.dialog.desc" type="textarea" :rows="2" size="small" placeholder="弹窗正文提示..." @input="isModified = true" />
|
|
|
+ <div class="dialog-desc-editor">
|
|
|
+ <quill-editor
|
|
|
+ v-model:content="btn.dialog.desc"
|
|
|
+ content-type="html"
|
|
|
+ :options="quillOptions"
|
|
|
+ @change="isModified = true"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</el-form-item>
|
|
|
<el-row :gutter="16">
|
|
|
<el-col :span="12">
|
|
|
@@ -404,6 +460,15 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
+ createDefaultShareConfig(overrides = {}) {
|
|
|
+ return {
|
|
|
+ enabled: false,
|
|
|
+ thumbnail: '',
|
|
|
+ title: '',
|
|
|
+ desc: '',
|
|
|
+ ...overrides
|
|
|
+ }
|
|
|
+ },
|
|
|
ensureReactiveField(target, key, value) {
|
|
|
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
|
|
target[key] = value
|
|
|
@@ -429,6 +494,7 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
normalizeAssemblyData(data) {
|
|
|
+ data.shareConfig = this.createDefaultShareConfig(data.shareConfig || {})
|
|
|
const blocks = data.blocks || []
|
|
|
blocks.forEach(block => {
|
|
|
if (block.type !== 'buttonGroup') return
|
|
|
@@ -441,12 +507,8 @@ export default {
|
|
|
createEmptyAssembly() {
|
|
|
return {
|
|
|
id: '', name: '', status: 'draft',
|
|
|
- blocks: [
|
|
|
- { type: 'image', id: genBlockId(), data: { imageUrl: '' } },
|
|
|
- { type: 'privilege', id: genBlockId(), data: { title: '权益标题', items: [{ id: '1', title: '权益名称', desc: '权益简介' }] } },
|
|
|
- { type: 'richtext', id: genBlockId(), data: { content: '<p>组装页富文本区块内容...</p>' } },
|
|
|
- { type: 'buttonGroup', id: genBlockId(), data: { buttons: [this.createDefaultButton({ text: '点击配置', style: 'primary' })] } }
|
|
|
- ]
|
|
|
+ shareConfig: this.createDefaultShareConfig(),
|
|
|
+ blocks: []
|
|
|
}
|
|
|
},
|
|
|
async fetchList() {
|
|
|
@@ -506,6 +568,16 @@ export default {
|
|
|
ElMessage.error('保存失败:按钮模块中存在未填写的跳转链接!')
|
|
|
return
|
|
|
}
|
|
|
+ if (data.shareConfig.enabled) {
|
|
|
+ if (!data.shareConfig.thumbnail) {
|
|
|
+ ElMessage.error('保存失败:分享缩略图为必填项!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!data.shareConfig.title.trim()) {
|
|
|
+ ElMessage.error('保存失败:分享标题为必填项!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
if (!data.name.trim()) data.name = '未命名组装页'
|
|
|
data.status = type === 'publish' ? 'published' : 'draft'
|
|
|
|
|
|
@@ -520,6 +592,22 @@ export default {
|
|
|
if (this.quillReady) this.isModified = true
|
|
|
},
|
|
|
|
|
|
+ triggerShareUpload() {
|
|
|
+ const ref = this.$refs.shareUpload
|
|
|
+ if (ref) ref.click()
|
|
|
+ },
|
|
|
+ onShareImageUpload(event) {
|
|
|
+ const file = event.target.files[0]
|
|
|
+ if (!file) return
|
|
|
+ const reader = new FileReader()
|
|
|
+ reader.onload = (e) => {
|
|
|
+ this.editData.shareConfig.thumbnail = e.target.result
|
|
|
+ this.isModified = true
|
|
|
+ }
|
|
|
+ reader.readAsDataURL(file)
|
|
|
+ event.target.value = ''
|
|
|
+ },
|
|
|
+
|
|
|
// 模块操作
|
|
|
addBlock(type) {
|
|
|
const block = { type, id: genBlockId(), data: {} }
|
|
|
@@ -642,6 +730,76 @@ export default {
|
|
|
.info-item { display: flex; align-items: center; gap: 8px; }
|
|
|
.info-label { font-size: 13px; color: #909399; white-space: nowrap; }
|
|
|
|
|
|
+.required-star { color: #F56C6C; }
|
|
|
+.dialog-desc-editor {
|
|
|
+ width: 100%;
|
|
|
+ background: #fafafa;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.dialog-desc-editor .ql-container { height: 100px; font-size: 13px; }
|
|
|
+.dialog-desc-editor .ql-toolbar { background: #fff; border: none; border-bottom: 1px solid #ebeef5; }
|
|
|
+.share-toggle-checkbox {
|
|
|
+ position: absolute;
|
|
|
+ right: 0;
|
|
|
+ top: 0;
|
|
|
+ z-index: 10;
|
|
|
+ padding: 6px 8px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ border-left: 1px solid #ebeef5;
|
|
|
+ border-radius: 0 0 0 8px;
|
|
|
+ margin: 0;
|
|
|
+ color: #409EFF;
|
|
|
+ font-weight: normal;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+.share-card-body {
|
|
|
+ display: flex;
|
|
|
+ gap: 40px;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 0 4px;
|
|
|
+}
|
|
|
+.share-thumb-section {
|
|
|
+ width: 108px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.share-field-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.share-thumb-upload {
|
|
|
+ width: 108px;
|
|
|
+ height: 108px;
|
|
|
+ background: #eef6fc;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.share-thumb-upload:hover .upload-overlay { display: flex; }
|
|
|
+.share-thumb-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+.share-thumb-tip {
|
|
|
+ margin-top: 8px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+.share-fields {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
.block-card {
|
|
|
background: #fff; border-radius: 8px; border: 1px solid #ebeef5;
|
|
|
margin-bottom: 16px; overflow: hidden; position: relative;
|
|
|
@@ -713,7 +871,7 @@ export default {
|
|
|
|
|
|
.button-config-item {
|
|
|
position: relative; border: 1px solid #ebeef5; border-radius: 6px;
|
|
|
- padding: 24px 122px 20px 20px; margin-bottom: 12px; background: #fafafa;
|
|
|
+ padding: 24px 20px 20px; margin-bottom: 12px; background: #fafafa;
|
|
|
}
|
|
|
.btn-index {
|
|
|
position: absolute; left: 0; top: 0;
|
|
|
@@ -729,6 +887,7 @@ export default {
|
|
|
font-weight: 400;
|
|
|
}
|
|
|
.button-form .el-select { width: 100%; }
|
|
|
+.button-form > .el-row { padding-right: 102px; }
|
|
|
.btn-config-actions {
|
|
|
position: absolute; right: 20px; top: 32px;
|
|
|
display: flex; gap: 6px;
|