功能:通过人工智能辅助和用户界面改进,增强超级文章编辑器的功能
- 集成人工智能写作辅助功能,用户可在编辑器中输入提示并直接生成内容。 - 更新了用户界面组件,以提升用户体验,包括新增了一个用于发布动态的浮动操作按钮,以及改进了图片上传的布局。 - 重构了内容处理方式,以支持光标管理和动态内容插入,从而增强了编辑功能。 - 通过更新样式和响应式设计元素,改进了视觉反馈和可访问性。 此次更新旨在简化文章创建流程,并增强超级文章编辑器内的用户互动。
This commit is contained in:
@@ -4,19 +4,15 @@
|
||||
const app = getApp()
|
||||
const { uploadByApi } = require('../../utils/miniprogramUpload')
|
||||
|
||||
const TEXT_EXTS = ['.txt', '.md', '.json', '.csv', '.log', '.yaml', '.yml']
|
||||
const IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.gif', '.webp']
|
||||
const MAX_TEXT_READ_BYTES = 1024 * 1024
|
||||
const MAX_REFERENCE_SEND = 28000
|
||||
|
||||
function newAttachId() {
|
||||
return `f_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
/** 「AI 帮你写」卡片:参考图 + 提示词 + 生成 */
|
||||
/** 「AI 帮你写」入口(弹窗内填写提示词并生成) */
|
||||
showAiWriteSection: true,
|
||||
aiAssistVisible: false,
|
||||
statusBarHeight: 44,
|
||||
auditMode: false,
|
||||
title: '',
|
||||
@@ -25,9 +21,10 @@ Page({
|
||||
saving: false,
|
||||
meNickname: '',
|
||||
aiPrompt: '',
|
||||
attachedFiles: [],
|
||||
generating: false,
|
||||
editArticleId: '',
|
||||
/** 正文光标,用于 @自己 插入 */
|
||||
contentCursor: 0,
|
||||
},
|
||||
|
||||
/** 遮挡层拦截滚动穿透 */
|
||||
@@ -35,10 +32,33 @@ Page({
|
||||
|
||||
syncAuditMode() {
|
||||
const audit = !!app.globalData.auditMode
|
||||
this.setData({ auditMode: audit })
|
||||
const patch = { auditMode: audit }
|
||||
if (audit) patch.aiAssistVisible = false
|
||||
this.setData(patch)
|
||||
return audit
|
||||
},
|
||||
|
||||
openAiAssist() {
|
||||
this.syncAuditMode()
|
||||
if (this.data.auditMode || app.globalData.auditMode) {
|
||||
wx.showToast({ title: '审核模式下不可用', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.data.showAiWriteSection) return
|
||||
this.setData({ aiAssistVisible: true })
|
||||
},
|
||||
|
||||
closeAiAssist() {
|
||||
if (this.data.generating) {
|
||||
wx.showToast({ title: '生成中,请稍候…', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({ aiAssistVisible: false })
|
||||
},
|
||||
|
||||
/** 阻止弹层内容区点击冒泡到遮罩(部分机型兼容) */
|
||||
noopModalTap() {},
|
||||
|
||||
onLoad(options) {
|
||||
const editId = options && options.id != null ? String(options.id).trim() : ''
|
||||
if (editId) {
|
||||
@@ -114,6 +134,7 @@ Page({
|
||||
title: '',
|
||||
content: String(article.content || ''),
|
||||
bodyImages,
|
||||
contentCursor: String(article.content || '').length,
|
||||
})
|
||||
} catch (e) {
|
||||
wx.showToast({ title: (e && e.message) || '加载失败', icon: 'none' })
|
||||
@@ -130,21 +151,45 @@ Page({
|
||||
},
|
||||
|
||||
onContentInput(e) {
|
||||
this.setData({ content: e.detail.value || '' })
|
||||
const v = e.detail.value != null ? String(e.detail.value) : ''
|
||||
let cursor = e.detail.cursor
|
||||
if (typeof cursor !== 'number' || Number.isNaN(cursor)) cursor = v.length
|
||||
cursor = Math.max(0, Math.min(cursor, v.length))
|
||||
this.setData({ contentCursor: cursor })
|
||||
},
|
||||
|
||||
onContentBlur(e) {
|
||||
const v = e.detail.value != null ? String(e.detail.value) : ''
|
||||
let cursor = e.detail.cursor
|
||||
if (typeof cursor !== 'number' || Number.isNaN(cursor)) cursor = v.length
|
||||
cursor = Math.max(0, Math.min(cursor, v.length))
|
||||
this.setData({ contentCursor: cursor })
|
||||
},
|
||||
|
||||
insertAtContentCursor(insert) {
|
||||
if (this.data.auditMode) return false
|
||||
const raw = this.data.content != null ? String(this.data.content) : ''
|
||||
const seg = String(insert || '')
|
||||
if (!seg) return false
|
||||
let pos = this.data.contentCursor
|
||||
if (typeof pos !== 'number' || Number.isNaN(pos)) pos = raw.length
|
||||
pos = Math.max(0, Math.min(pos, raw.length))
|
||||
const next = raw.slice(0, pos) + seg + raw.slice(pos)
|
||||
const newCursor = pos + seg.length
|
||||
this.setData({
|
||||
content: next,
|
||||
contentCursor: newCursor,
|
||||
})
|
||||
return true
|
||||
},
|
||||
|
||||
insertMentionSelf() {
|
||||
if (this.data.auditMode) return
|
||||
const nick = this.data.meNickname || '我'
|
||||
this.setData({ content: `${this.data.content}@${nick} ` })
|
||||
this.insertAtContentCursor(`@${nick} `)
|
||||
},
|
||||
|
||||
insertHashLink() {
|
||||
if (this.data.auditMode) return
|
||||
this.setData({ content: `${this.data.content}#链接(https://)` })
|
||||
},
|
||||
|
||||
/** 编辑区配图(发布后展示在正文前),最多 9 张 */
|
||||
/** 动态配图(发动态展示 + AI 生成多模态共用),最多 9 张 */
|
||||
chooseArticleImages() {
|
||||
this.syncAuditMode()
|
||||
if (this.data.auditMode || app.globalData.auditMode) {
|
||||
@@ -255,160 +300,11 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
/** 参考素材:仅从相册/相机选图片并上传(AI 侧按图理解) */
|
||||
chooseReferenceImages() {
|
||||
this.syncAuditMode()
|
||||
if (this.data.auditMode || app.globalData.auditMode) {
|
||||
wx.showToast({ title: '审核模式下不可用', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this._pickReferenceImagesFromAlbum()
|
||||
},
|
||||
|
||||
_pickReferenceImagesFromAlbum() {
|
||||
const mapFromChooseMedia = (res) => {
|
||||
const files = res.tempFiles || []
|
||||
return files.map((f, i) => ({
|
||||
path: f.tempFilePath,
|
||||
name: `参考图_${Date.now()}_${i}.jpg`,
|
||||
size: f.size || 0,
|
||||
}))
|
||||
}
|
||||
const mapFromChooseImage = (res) => {
|
||||
const paths = res.tempFilePaths || []
|
||||
const t = Date.now()
|
||||
return paths.map((path, i) => ({
|
||||
path,
|
||||
name: `参考图_${t}_${i}.jpg`,
|
||||
size: 0,
|
||||
}))
|
||||
}
|
||||
if (typeof wx.chooseMedia === 'function') {
|
||||
wx.chooseMedia({
|
||||
count: 9,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
sizeType: ['compressed'],
|
||||
success: (res) => this.handlePickedFiles(mapFromChooseMedia(res)),
|
||||
fail: (err) => {
|
||||
const em = String((err && err.errMsg) || '')
|
||||
if (/cancel|取消/i.test(em)) return
|
||||
wx.showToast({ title: '选择图片失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if (typeof wx.chooseImage === 'function') {
|
||||
wx.chooseImage({
|
||||
count: 9,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => this.handlePickedFiles(mapFromChooseImage(res)),
|
||||
fail: (err) => {
|
||||
const em = String((err && err.errMsg) || '')
|
||||
if (/cancel|取消/i.test(em)) return
|
||||
wx.showToast({ title: '选择图片失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
wx.showToast({ title: '当前环境不支持选图片', icon: 'none' })
|
||||
},
|
||||
|
||||
_pickUploadUrl(up) {
|
||||
if (!up || typeof up !== 'object') return ''
|
||||
return String(up.url || up.fullUrl || up.data?.url || up.data?.fullUrl || '').trim()
|
||||
},
|
||||
|
||||
_patchAttach(id, patch) {
|
||||
const next = this.data.attachedFiles.map((x) =>
|
||||
x.id === id ? Object.assign({}, x, patch) : x,
|
||||
)
|
||||
this.setData({ attachedFiles: next })
|
||||
},
|
||||
|
||||
_removeAttachById(id) {
|
||||
this.setData({
|
||||
attachedFiles: this.data.attachedFiles.filter((x) => x.id !== id),
|
||||
})
|
||||
},
|
||||
|
||||
async handlePickedFiles(tempFiles) {
|
||||
if (!tempFiles.length) return
|
||||
for (const f of tempFiles) {
|
||||
const name = String(f.name || '未命名').trim() || '未命名'
|
||||
const filePath = f.path
|
||||
const size = f.size || 0
|
||||
const dot = name.lastIndexOf('.')
|
||||
const ext = dot >= 0 ? name.slice(dot).toLowerCase() : ''
|
||||
const id = newAttachId()
|
||||
|
||||
if (size > 29 * 1024 * 1024) {
|
||||
wx.showToast({ title: `${name} 超过30MB上限`, icon: 'none' })
|
||||
continue
|
||||
}
|
||||
|
||||
if (IMAGE_EXTS.includes(ext)) {
|
||||
this.setData({
|
||||
attachedFiles: this.data.attachedFiles.concat([
|
||||
{ id, name, kind: 'image', url: '', uploading: true },
|
||||
]),
|
||||
})
|
||||
try {
|
||||
const up = await uploadByApi({ filePath, folder: 'article-images' })
|
||||
const url = this._pickUploadUrl(up)
|
||||
if (!url) throw new Error('no url')
|
||||
this._patchAttach(id, { url, uploading: false })
|
||||
} catch (_) {
|
||||
this._removeAttachById(id)
|
||||
wx.showToast({ title: `${name} 上传失败`, icon: 'none' })
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (TEXT_EXTS.includes(ext) && size <= MAX_TEXT_READ_BYTES && size >= 0) {
|
||||
try {
|
||||
const fs = wx.getFileSystemManager()
|
||||
const textContent = fs.readFileSync(filePath, 'utf8')
|
||||
this.setData({
|
||||
attachedFiles: this.data.attachedFiles.concat([
|
||||
{ id, name, kind: 'text', textContent, uploading: false },
|
||||
]),
|
||||
})
|
||||
} catch (_) {
|
||||
await this._uploadBinaryAttach(id, name, filePath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
await this._uploadBinaryAttach(id, name, filePath)
|
||||
}
|
||||
},
|
||||
|
||||
async _uploadBinaryAttach(id, name, filePath) {
|
||||
this.setData({
|
||||
attachedFiles: this.data.attachedFiles.concat([
|
||||
{ id, name, kind: 'binary', url: '', uploading: true },
|
||||
]),
|
||||
})
|
||||
try {
|
||||
const up = await uploadByApi({ filePath, folder: 'book-attachments' })
|
||||
const url = this._pickUploadUrl(up)
|
||||
if (!url) throw new Error('no url')
|
||||
this._patchAttach(id, { url, uploading: false })
|
||||
} catch (_) {
|
||||
this._removeAttachById(id)
|
||||
wx.showToast({ title: `${name} 上传失败`, icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
removeAttachedFile(e) {
|
||||
if (this.data.auditMode) return
|
||||
const id = e.currentTarget.dataset.id
|
||||
if (!id) return
|
||||
this._removeAttachById(id)
|
||||
},
|
||||
|
||||
async generateArticle() {
|
||||
if (this.data.auditMode || app.globalData.auditMode) return
|
||||
if (this.data.generating) return
|
||||
@@ -418,34 +314,20 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
if (this.data.attachedFiles.some((x) => x.uploading)) {
|
||||
wx.showToast({ title: '请等待图片上传完成', icon: 'none' })
|
||||
if (this.data.bodyImages.some((x) => x.uploading)) {
|
||||
wx.showToast({ title: '请等待配图上传完成', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const prompt = String(this.data.aiPrompt || '').trim()
|
||||
const parts = []
|
||||
for (const f of this.data.attachedFiles) {
|
||||
if (f.kind === 'text' && f.textContent) {
|
||||
parts.push(`--- ${f.name} ---\n${f.textContent}`)
|
||||
}
|
||||
}
|
||||
let referenceText = parts.join('\n\n').trim()
|
||||
if (referenceText.length > MAX_REFERENCE_SEND) {
|
||||
referenceText =
|
||||
referenceText.slice(0, MAX_REFERENCE_SEND) + '\n\n(本地参考文末已截断)'
|
||||
}
|
||||
const referenceText = ''
|
||||
const materialUrls = []
|
||||
const imageUrls = this.data.bodyImages
|
||||
.map((x) => String(x.url || '').trim())
|
||||
.filter(Boolean)
|
||||
|
||||
const materialUrls = this.data.attachedFiles
|
||||
.filter((x) => x.kind === 'binary' && x.url)
|
||||
.map((x) => x.url)
|
||||
|
||||
const imageUrls = this.data.attachedFiles
|
||||
.filter((x) => x.kind === 'image' && x.url)
|
||||
.map((x) => x.url)
|
||||
|
||||
if (!prompt && !referenceText && materialUrls.length === 0 && imageUrls.length === 0) {
|
||||
wx.showToast({ title: '请填写提示词或添加参考图', icon: 'none' })
|
||||
if (!prompt && imageUrls.length === 0) {
|
||||
wx.showToast({ title: '请填写提示词或添加配图', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -470,7 +352,13 @@ Page({
|
||||
if (title && merged) merged = `${title}\n\n${merged}`
|
||||
else if (title) merged = title
|
||||
if (!merged.trim()) throw new Error('生成结果不完整')
|
||||
this.setData({ title: '', content: merged.trim() })
|
||||
const mergedStr = merged.trim()
|
||||
this.setData({
|
||||
title: '',
|
||||
content: mergedStr,
|
||||
contentCursor: mergedStr.length,
|
||||
aiAssistVisible: false,
|
||||
})
|
||||
wx.showToast({ title: '已填入正文', icon: 'success' })
|
||||
} catch (e) {
|
||||
wx.showToast({
|
||||
@@ -522,13 +410,13 @@ Page({
|
||||
})
|
||||
}
|
||||
if (!res?.success) throw new Error(res?.error || '发布失败')
|
||||
if (editId) {
|
||||
wx.showToast({ title: '已提交审核', icon: 'success' })
|
||||
setTimeout(() => wx.navigateBack(), 450)
|
||||
} else {
|
||||
if (!editId) {
|
||||
this.setData({ title: '', content: '', bodyImages: [] })
|
||||
wx.showToast({ title: '已提交审核', icon: 'success' })
|
||||
}
|
||||
wx.showToast({ title: '已提交审核', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
wx.redirectTo({ url: '/pages/super-article-mine/super-article-mine' })
|
||||
}, 450)
|
||||
} catch (e) {
|
||||
wx.showToast({ title: e.message || '发布失败', icon: 'none' })
|
||||
} finally {
|
||||
|
||||
@@ -8,72 +8,89 @@
|
||||
</view>
|
||||
<view style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
|
||||
<!-- AI:参考图片 + 提示词(开关:showAiWriteSection) -->
|
||||
<view wx:if="{{showAiWriteSection}}" class="card">
|
||||
<text class="section-title">AI 帮你写</text>
|
||||
<text class="label">参考图片(可选)</text>
|
||||
<view class="file-actions">
|
||||
<view class="add-file-btn" bindtap="chooseReferenceImages">+ 添加图片</view>
|
||||
</view>
|
||||
<view wx:for="{{attachedFiles}}" wx:key="id" class="file-row">
|
||||
<view class="file-main">
|
||||
<text class="file-name">{{item.name}}</text>
|
||||
<text wx:if="{{item.uploading}}" class="file-meta">上传中…</text>
|
||||
<text wx:elif="{{item.kind === 'text'}}" class="file-meta ok">文本已载入</text>
|
||||
<text wx:elif="{{item.kind === 'image'}}" class="file-meta ok">图片</text>
|
||||
<text wx:else class="file-meta ok">附件</text>
|
||||
<!-- 主编曲面:配图 → 正文(快捷插入含 AI,点开底部弹层) -->
|
||||
<view class="composer-sheet">
|
||||
<view class="sheet-section">
|
||||
<view class="section-head">
|
||||
<text class="section-title-text">配图</text>
|
||||
<text class="section-badge">{{bodyImages.length}}/9</text>
|
||||
</view>
|
||||
<text class="file-remove" data-id="{{item.id}}" bindtap="removeAttachedFile">移除</text>
|
||||
</view>
|
||||
<text class="hint">从相册或相机添加参考图,可选(单次最多 9 张),上传后与提示词一并交给 AI 分析。单张建议不超 30MB。</text>
|
||||
|
||||
<text class="label">提示词</text>
|
||||
<view class="textarea-wrap">
|
||||
<textarea class="textarea prompt-textarea" maxlength="2000" placeholder="描述主题、语气、要点…(可与参考图组合)" value="{{aiPrompt}}" bindinput="onAiPromptInput" disabled="{{auditMode}}"></textarea>
|
||||
<text class="section-desc">可选 · 弹窗内「AI 帮你写」会与下方配图共用 · 发布后展示在正文下方</text>
|
||||
<view class="moment-photo-grid">
|
||||
<view wx:for="{{bodyImages}}" wx:key="id" class="moment-photo-cell">
|
||||
<image wx:if="{{item.url}}" class="moment-photo-img" mode="aspectFill" src="{{item.url}}" />
|
||||
<view wx:if="{{item.uploading}}" class="moment-photo-mask">上传中…</view>
|
||||
<text class="moment-photo-remove" data-id="{{item.id}}" catchtap="removeArticleImage">×</text>
|
||||
</view>
|
||||
<view wx:if="{{bodyImages.length < 9}}" class="moment-photo-cell moment-photo-add" bindtap="chooseArticleImages">
|
||||
<text class="moment-photo-add-plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="hint-micro">单张建议不超 30MB</text>
|
||||
</view>
|
||||
|
||||
<view class="generate-btn {{generating ? 'generate-btn-loading' : ''}}" bindtap="generateArticle">{{generating ? '生成中…' : '生成'}}</view>
|
||||
<view class="section-divider"></view>
|
||||
|
||||
<view class="sheet-section sheet-section--body">
|
||||
<view class="section-head">
|
||||
<text class="section-title-text">正文</text>
|
||||
<text class="section-meta">最多 5000 字</text>
|
||||
</view>
|
||||
<view class="inline-toolbar">
|
||||
<text class="toolbar-label">快捷插入</text>
|
||||
<view class="tool-btn" hover-class="tool-btn-hover" catchtap="insertMentionSelf">@自己</view>
|
||||
<view wx:if="{{showAiWriteSection && !auditMode}}" class="tool-btn" hover-class="tool-btn-hover" catchtap="openAiAssist">✨ AI 帮你写</view>
|
||||
</view>
|
||||
<view class="textarea-wrap moment-textarea-wrap">
|
||||
<textarea
|
||||
class="textarea moment-textarea"
|
||||
maxlength="5000"
|
||||
placeholder="写点什么…支持 Markdown、多段落"
|
||||
model:value="{{content}}"
|
||||
bindinput="onContentInput"
|
||||
bindblur="onContentBlur"
|
||||
cursor="{{contentCursor}}"
|
||||
disabled="{{auditMode}}"
|
||||
auto-height
|
||||
show-confirm-bar="{{false}}"
|
||||
></textarea>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 朋友圈式:先文字,再九宫格配图(+ 在最后) -->
|
||||
<view class="card moment-card">
|
||||
<view class="toolbar">
|
||||
<view class="tool-btn" bindtap="insertMentionSelf">@自己</view>
|
||||
<view class="tool-btn" bindtap="insertHashLink">#链接</view>
|
||||
</view>
|
||||
<view class="textarea-wrap moment-textarea-wrap">
|
||||
<textarea
|
||||
class="textarea moment-textarea"
|
||||
maxlength="5000"
|
||||
placeholder="这一刻的想法…"
|
||||
value="{{content}}"
|
||||
bindinput="onContentInput"
|
||||
disabled="{{auditMode}}"
|
||||
auto-height
|
||||
show-confirm-bar="{{false}}"
|
||||
></textarea>
|
||||
</view>
|
||||
|
||||
<view class="moment-photo-grid">
|
||||
<view wx:for="{{bodyImages}}" wx:key="id" class="moment-photo-cell">
|
||||
<image wx:if="{{item.url}}" class="moment-photo-img" mode="aspectFill" src="{{item.url}}" />
|
||||
<view wx:if="{{item.uploading}}" class="moment-photo-mask">上传中…</view>
|
||||
<text class="moment-photo-remove" data-id="{{item.id}}" catchtap="removeArticleImage">×</text>
|
||||
</view>
|
||||
<view wx:if="{{bodyImages.length < 9}}" class="moment-photo-cell moment-photo-add" bindtap="chooseArticleImages">
|
||||
<text class="moment-photo-add-plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="hint moment-hint">配图 {{bodyImages.length}}/9 · 展示在文字下方 · 单张建议不超 30MB · 可直接输入 @昵称 或 #链接(https://xxx)</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部发布(避让微信胶囊) -->
|
||||
<view wx:if="{{!auditMode}}" class="bottom-publish-bar">
|
||||
<view class="bottom-publish-btn {{saving ? 'bottom-publish-btn-disabled' : ''}}" bindtap="submitArticle">
|
||||
<text>{{saving ? (editArticleId ? '提交中…' : '发布中…') : (editArticleId ? '提交审核' : '发布')}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI 底部弹层 -->
|
||||
<view wx:if="{{aiAssistVisible}}" class="ai-modal-root" catchtouchmove="preventMove">
|
||||
<view class="ai-modal-mask" catchtap="closeAiAssist"></view>
|
||||
<view class="ai-modal-panel" catchtap="noopModalTap">
|
||||
<view class="ai-modal-head">
|
||||
<text class="ai-modal-title">AI 帮你写</text>
|
||||
<text class="ai-modal-close" catchtap="closeAiAssist">关闭</text>
|
||||
</view>
|
||||
<text class="ai-modal-desc">提示词 + 当前配图一并交给模型;生成后写入正文,可关闭弹窗继续编辑</text>
|
||||
<text class="label-ai">提示词</text>
|
||||
<view class="textarea-wrap textarea-wrap-ai">
|
||||
<textarea
|
||||
class="textarea prompt-textarea-inner"
|
||||
maxlength="2000"
|
||||
placeholder="例如:根据配图写一篇派对复盘…"
|
||||
value="{{aiPrompt}}"
|
||||
bindinput="onAiPromptInput"
|
||||
disabled="{{auditMode}}"
|
||||
show-confirm-bar="{{false}}"
|
||||
></textarea>
|
||||
</view>
|
||||
<view class="generate-btn {{generating ? 'generate-btn-loading' : ''}}" bindtap="generateArticle">
|
||||
{{generating ? '生成中…' : '生成并填入正文'}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{auditMode}}" class="audit-mode-mask" catchtouchmove="preventMove">
|
||||
<text class="audit-mode-text">开发中...</text>
|
||||
</view>
|
||||
|
||||
@@ -1,248 +1,790 @@
|
||||
.page {
|
||||
|
||||
min-height: 100vh;
|
||||
background: #0b1220;
|
||||
|
||||
background: linear-gradient(180deg, #080f18 0%, #0b1220 32%, #0b1220 100%);
|
||||
|
||||
color: #fff;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(168rpx + env(safe-area-inset-bottom));
|
||||
|
||||
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.nav-bar {
|
||||
position: fixed; left: 0; right: 0; top: 0; z-index: 10;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
height: 44px; padding: 0 24rpx; background: rgba(5, 11, 20, 0.9);
|
||||
|
||||
position: fixed;
|
||||
|
||||
left: 0;
|
||||
|
||||
right: 0;
|
||||
|
||||
top: 0;
|
||||
|
||||
z-index: 10;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
height: 44px;
|
||||
|
||||
padding: 0 24rpx;
|
||||
|
||||
background: rgba(5, 11, 20, 0.92);
|
||||
|
||||
border-bottom: 1rpx solid rgba(94, 234, 212, 0.08);
|
||||
|
||||
}
|
||||
.nav-back { width: 64rpx; flex-shrink: 0; display: flex; align-items: center; }
|
||||
.nav-placeholder { width: 64rpx; flex-shrink: 0; }
|
||||
.nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: 700; }
|
||||
|
||||
.nav-back {
|
||||
|
||||
width: 64rpx;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
|
||||
width: 64rpx;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
|
||||
flex: 1;
|
||||
|
||||
text-align: center;
|
||||
|
||||
font-size: 32rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ========== 主编曲面 ========== */
|
||||
|
||||
.composer-sheet {
|
||||
|
||||
margin: 16rpx 24rpx 24rpx;
|
||||
|
||||
border-radius: 28rpx;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background: rgba(15, 23, 42, 0.92);
|
||||
|
||||
border: 1rpx solid rgba(148, 163, 184, 0.22);
|
||||
|
||||
box-shadow: 0 24rpx 48rpx rgba(0, 0, 0, 0.35);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.sheet-section {
|
||||
|
||||
padding: 28rpx 28rpx 26rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.sheet-section--body {
|
||||
|
||||
padding-top: 24rpx;
|
||||
|
||||
padding-bottom: 28rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.sheet-section--ai {
|
||||
|
||||
padding-top: 24rpx;
|
||||
|
||||
padding-bottom: 30rpx;
|
||||
|
||||
background: linear-gradient(
|
||||
|
||||
180deg,
|
||||
|
||||
rgba(8, 47, 73, 0.22) 0%,
|
||||
|
||||
rgba(15, 23, 42, 0.05) 100%
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.section-divider {
|
||||
|
||||
height: 1rpx;
|
||||
|
||||
margin: 0 28rpx;
|
||||
|
||||
background: rgba(148, 163, 184, 0.14);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.section-head {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
gap: 16rpx;
|
||||
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.section-title-text {
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
color: #f1f5f9;
|
||||
|
||||
letter-spacing: 1rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.section-badge {
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
color: #22d3ee;
|
||||
|
||||
padding: 6rpx 16rpx;
|
||||
|
||||
border-radius: 999rpx;
|
||||
|
||||
background: rgba(34, 211, 238, 0.12);
|
||||
|
||||
border: 1rpx solid rgba(34, 211, 238, 0.28);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.section-meta {
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #94a3b8;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.section-tag-ai {
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #a5f3fc;
|
||||
|
||||
padding: 4rpx 14rpx;
|
||||
|
||||
border-radius: 999rpx;
|
||||
|
||||
border: 1rpx solid rgba(94, 234, 212, 0.35);
|
||||
|
||||
background: rgba(34, 211, 238, 0.08);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.section-desc {
|
||||
|
||||
display: block;
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #94a3b8;
|
||||
|
||||
line-height: 1.55;
|
||||
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.hint-micro {
|
||||
|
||||
display: block;
|
||||
|
||||
margin-top: 14rpx;
|
||||
|
||||
font-size: 20rpx;
|
||||
|
||||
color: #64748b;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.inline-toolbar {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 12rpx;
|
||||
|
||||
margin-bottom: 14rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toolbar-label {
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #64748b;
|
||||
|
||||
margin-right: 4rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tool-btn {
|
||||
|
||||
padding: 12rpx 22rpx;
|
||||
|
||||
border-radius: 999rpx;
|
||||
|
||||
font-size: 24rpx;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
color: #67e8f9;
|
||||
|
||||
border: 1rpx solid rgba(34, 211, 238, 0.4);
|
||||
|
||||
background: rgba(8, 47, 73, 0.5);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tool-btn-hover {
|
||||
|
||||
opacity: 0.82;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.label-ai {
|
||||
|
||||
display: block;
|
||||
|
||||
font-size: 24rpx;
|
||||
|
||||
color: #cbd5e1;
|
||||
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
margin-top: 4rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.textarea-wrap,
|
||||
|
||||
.textarea-wrap-ai {
|
||||
|
||||
background: rgba(2, 6, 23, 0.78);
|
||||
|
||||
border-radius: 18rpx;
|
||||
|
||||
padding: 18rpx 20rpx;
|
||||
|
||||
border: 1rpx solid rgba(148, 163, 184, 0.12);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.textarea-wrap-ai {
|
||||
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.moment-textarea-wrap {
|
||||
|
||||
padding: 20rpx 22rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.textarea {
|
||||
|
||||
width: 100%;
|
||||
|
||||
min-height: 320rpx;
|
||||
|
||||
color: #fff;
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
line-height: 1.65;
|
||||
|
||||
background: transparent;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.moment-textarea {
|
||||
|
||||
min-height: 280rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.prompt-textarea-inner {
|
||||
|
||||
min-height: 160rpx;
|
||||
|
||||
width: 100%;
|
||||
|
||||
font-size: 28rpx;
|
||||
|
||||
line-height: 1.55;
|
||||
|
||||
color: #f1f5f9;
|
||||
|
||||
background: transparent;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.generate-btn {
|
||||
|
||||
margin-top: 8rpx;
|
||||
|
||||
height: 88rpx;
|
||||
|
||||
border-radius: 18rpx;
|
||||
|
||||
border: 1rpx solid rgba(34, 211, 238, 0.5);
|
||||
|
||||
color: #ecfeff;
|
||||
|
||||
font-size: 28rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
background: linear-gradient(135deg, rgba(8, 47, 73, 0.85), rgba(15, 118, 110, 0.35));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.generate-btn-loading {
|
||||
|
||||
opacity: 0.65;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.bottom-publish-bar {
|
||||
|
||||
position: fixed;
|
||||
|
||||
left: 0;
|
||||
|
||||
right: 0;
|
||||
|
||||
bottom: 0;
|
||||
|
||||
z-index: 50;
|
||||
|
||||
padding: 20rpx 28rpx;
|
||||
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
|
||||
background: rgba(11, 18, 32, 0.96);
|
||||
|
||||
border-top: 1rpx solid rgba(94, 234, 212, 0.12);
|
||||
|
||||
}
|
||||
|
||||
.bottom-publish-btn {
|
||||
|
||||
height: 92rpx;
|
||||
|
||||
border-radius: 18rpx;
|
||||
|
||||
background: linear-gradient(135deg, #22d3ee, #14b8a6);
|
||||
|
||||
color: #032b35;
|
||||
|
||||
font-size: 32rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.bottom-publish-btn-disabled {
|
||||
|
||||
opacity: 0.65;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
.card {
|
||||
margin: 24rpx; padding: 28rpx; border-radius: 24rpx;
|
||||
background: rgba(15, 23, 42, 0.86); border: 1rpx solid rgba(148, 163, 184, 0.2);
|
||||
}
|
||||
.section-title {
|
||||
display: block; font-size: 26rpx; font-weight: 700; color: #e2e8f0;
|
||||
margin-bottom: 20rpx; letter-spacing: 2rpx;
|
||||
}
|
||||
.label { display: block; font-size: 24rpx; color: #cbd5e1; margin-bottom: 12rpx; }
|
||||
.input-wrap, .textarea-wrap { background: rgba(2, 6, 23, 0.75); border-radius: 16rpx; padding: 18rpx 20rpx; }
|
||||
.input { width: 100%; color: #fff; font-size: 28rpx; background: transparent; }
|
||||
.file-actions { margin-bottom: 12rpx; }
|
||||
.add-file-btn {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
padding: 14rpx 28rpx; border-radius: 16rpx; font-size: 26rpx; font-weight: 600;
|
||||
color: #22d3ee; border: 1rpx solid rgba(34, 211, 238, 0.45);
|
||||
background: rgba(8, 47, 73, 0.45);
|
||||
}
|
||||
.file-row {
|
||||
display: flex; align-items: flex-start; justify-content: space-between;
|
||||
gap: 16rpx; padding: 16rpx 18rpx; margin-bottom: 12rpx;
|
||||
border-radius: 14rpx; background: rgba(2, 6, 23, 0.55); border: 1rpx solid rgba(148, 163, 184, 0.12);
|
||||
}
|
||||
.file-main { flex: 1; min-width: 0; }
|
||||
.file-name { display: block; font-size: 26rpx; color: #f1f5f9; word-break: break-all; line-height: 1.45; }
|
||||
.file-meta { display: block; font-size: 22rpx; color: #94a3b8; margin-top: 6rpx; }
|
||||
.file-meta.ok { color: #5eead4; }
|
||||
.file-remove {
|
||||
flex-shrink: 0; font-size: 24rpx; color: #f97316; padding: 8rpx 12rpx;
|
||||
}
|
||||
.body-img-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.body-img-cell {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 14rpx;
|
||||
overflow: hidden;
|
||||
background: rgba(2, 6, 23, 0.65);
|
||||
border: 1rpx solid rgba(148, 163, 184, 0.18);
|
||||
}
|
||||
.body-img-thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.body-img-mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
background: rgba(15, 23, 42, 0.72);
|
||||
}
|
||||
.body-img-remove {
|
||||
position: absolute;
|
||||
right: 6rpx;
|
||||
top: 4rpx;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
border-radius: 999rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
.prompt-textarea { min-height: 200rpx; }
|
||||
.generate-btn {
|
||||
margin-top: 20rpx; height: 88rpx; border-radius: 16rpx;
|
||||
border: 1rpx solid rgba(34, 211, 238, 0.55); color: #a5f3fc; font-size: 30rpx; font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: rgba(8, 47, 73, 0.45);
|
||||
}
|
||||
.generate-btn-loading { opacity: 0.7; pointer-events: none; }
|
||||
.toolbar { margin-top: 12rpx; margin-bottom: 12rpx; display: flex; gap: 12rpx; }
|
||||
.tool-btn {
|
||||
padding: 10rpx 18rpx; border-radius: 999rpx; font-size: 22rpx;
|
||||
color: #67e8f9; border: 1rpx solid rgba(34, 211, 238, 0.35); background: rgba(8, 47, 73, 0.45);
|
||||
}
|
||||
.textarea {
|
||||
width: 100%; min-height: 380rpx; color: #fff; font-size: 28rpx; line-height: 1.65;
|
||||
background: transparent;
|
||||
}
|
||||
.hint { display: block; margin-top: 10rpx; color: #94a3b8; font-size: 22rpx; }
|
||||
|
||||
/* 发动态 · 配图宫格(参考朋友圈) */
|
||||
.moment-card .toolbar {
|
||||
margin-top: 0;
|
||||
}
|
||||
.moment-textarea-wrap {
|
||||
margin-top: 8rpx;
|
||||
padding: 20rpx 22rpx;
|
||||
}
|
||||
.moment-textarea {
|
||||
min-height: 240rpx;
|
||||
width: 100%;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 配图宫格 */
|
||||
|
||||
.moment-photo-grid {
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.moment-photo-cell {
|
||||
position: relative;
|
||||
width: calc((100% - 32rpx) / 3);
|
||||
padding-bottom: calc((100% - 32rpx) / 3);
|
||||
height: 0;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background: rgba(2, 6, 23, 0.65);
|
||||
border: 1rpx solid rgba(148, 163, 184, 0.15);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.moment-photo-img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.moment-photo-mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
background: rgba(15, 23, 42, 0.72);
|
||||
z-index: 1;
|
||||
}
|
||||
.moment-photo-remove {
|
||||
position: absolute;
|
||||
right: 6rpx;
|
||||
top: 4rpx;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
border-radius: 999rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
.moment-photo-add {
|
||||
border: 2rpx dashed rgba(148, 163, 184, 0.35);
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.moment-photo-add-plus {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -52%);
|
||||
font-size: 64rpx;
|
||||
font-weight: 300;
|
||||
color: rgba(148, 163, 184, 0.85);
|
||||
line-height: 1;
|
||||
}
|
||||
.moment-hint {
|
||||
margin-top: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
/* 审核模式:盖住整页(含自定义导航),高于 .nav-bar 的 z-index: 10 */
|
||||
.audit-mode-mask {
|
||||
position: fixed;
|
||||
.moment-photo-cell {
|
||||
|
||||
position: relative;
|
||||
|
||||
width: calc((100% - 32rpx) / 3);
|
||||
|
||||
padding-bottom: calc((100% - 32rpx) / 3);
|
||||
|
||||
height: 0;
|
||||
|
||||
border-radius: 14rpx;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background: rgba(2, 6, 23, 0.65);
|
||||
|
||||
border: 1rpx solid rgba(148, 163, 184, 0.15);
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
.moment-photo-img {
|
||||
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
top: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: block;
|
||||
|
||||
}
|
||||
|
||||
.moment-photo-mask {
|
||||
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
|
||||
right: 0;
|
||||
|
||||
top: 0;
|
||||
|
||||
bottom: 0;
|
||||
z-index: 10000;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #94a3b8;
|
||||
|
||||
background: rgba(15, 23, 42, 0.72);
|
||||
|
||||
z-index: 1;
|
||||
|
||||
}
|
||||
|
||||
.moment-photo-remove {
|
||||
|
||||
position: absolute;
|
||||
|
||||
right: 6rpx;
|
||||
|
||||
top: 4rpx;
|
||||
|
||||
width: 44rpx;
|
||||
|
||||
height: 44rpx;
|
||||
|
||||
line-height: 40rpx;
|
||||
|
||||
text-align: center;
|
||||
|
||||
font-size: 32rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
color: #fff;
|
||||
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
|
||||
border-radius: 999rpx;
|
||||
|
||||
z-index: 2;
|
||||
|
||||
}
|
||||
|
||||
.moment-photo-add {
|
||||
|
||||
border: 2rpx dashed rgba(148, 163, 184, 0.35);
|
||||
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.moment-photo-add-plus {
|
||||
|
||||
position: absolute;
|
||||
|
||||
left: 50%;
|
||||
|
||||
top: 50%;
|
||||
|
||||
transform: translate(-50%, -52%);
|
||||
|
||||
font-size: 64rpx;
|
||||
|
||||
font-weight: 300;
|
||||
|
||||
color: rgba(148, 163, 184, 0.85);
|
||||
|
||||
line-height: 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* AI 底部弹层 */
|
||||
|
||||
.ai-modal-root {
|
||||
|
||||
position: fixed;
|
||||
|
||||
left: 0;
|
||||
|
||||
right: 0;
|
||||
|
||||
top: 0;
|
||||
|
||||
bottom: 0;
|
||||
|
||||
z-index: 600;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: flex-end;
|
||||
|
||||
}
|
||||
|
||||
.ai-modal-mask {
|
||||
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
|
||||
right: 0;
|
||||
|
||||
top: 0;
|
||||
|
||||
bottom: 0;
|
||||
|
||||
background: rgba(0, 0, 0, 0.58);
|
||||
|
||||
}
|
||||
|
||||
.ai-modal-panel {
|
||||
|
||||
position: relative;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
border-radius: 28rpx 28rpx 0 0;
|
||||
|
||||
padding: 28rpx 28rpx 32rpx;
|
||||
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
|
||||
max-height: 78vh;
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
background: rgba(15, 23, 42, 0.98);
|
||||
|
||||
border: 1rpx solid rgba(148, 163, 184, 0.22);
|
||||
|
||||
border-bottom: none;
|
||||
|
||||
box-shadow: 0 -12rpx 48rpx rgba(0, 0, 0, 0.45);
|
||||
|
||||
}
|
||||
|
||||
.ai-modal-head {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
.ai-modal-title {
|
||||
|
||||
font-size: 32rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
color: #f1f5f9;
|
||||
|
||||
}
|
||||
|
||||
.ai-modal-close {
|
||||
|
||||
font-size: 26rpx;
|
||||
|
||||
color: #67e8f9;
|
||||
|
||||
padding: 12rpx 16rpx;
|
||||
|
||||
}
|
||||
|
||||
.ai-modal-desc {
|
||||
|
||||
display: block;
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #94a3b8;
|
||||
|
||||
line-height: 1.55;
|
||||
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.audit-mode-mask {
|
||||
|
||||
position: fixed;
|
||||
|
||||
left: 0;
|
||||
|
||||
right: 0;
|
||||
|
||||
top: 0;
|
||||
|
||||
bottom: 0;
|
||||
|
||||
z-index: 10000;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
background: rgba(11, 18, 32, 0.94);
|
||||
|
||||
}
|
||||
|
||||
.audit-mode-text {
|
||||
|
||||
font-size: 36rpx;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
|
||||
letter-spacing: 4rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -78,11 +78,15 @@ function mapRows(rows) {
|
||||
return (rows || []).map((r) => {
|
||||
const st = String(r.auditStatus || 'approved').toLowerCase()
|
||||
const thumb = buildThumbSlots(r.images)
|
||||
const contentTrim = String(r.content || '').trim()
|
||||
const titleTrim = String(r.title || '').trim()
|
||||
return Object.assign({}, r, {
|
||||
auditStatus: st === 'pending' || st === 'rejected' ? st : 'approved',
|
||||
auditLabel: auditLabel(st),
|
||||
timeText: relativeTime(r.createdAt),
|
||||
preview: String(r.preview || '').trim(),
|
||||
contentTrim,
|
||||
titleTrim,
|
||||
rejectReason: String(r.rejectReason || '').trim(),
|
||||
showThumbRow: thumb.showThumbRow,
|
||||
thumbSlots: thumb.thumbSlots,
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
<text class="audit-tag audit-{{item.auditStatus}}">{{item.auditLabel}}</text>
|
||||
<text class="feed-time">{{item.timeText}}</text>
|
||||
</view>
|
||||
<text class="feed-title" wx:if="{{item.title}}">{{item.title}}</text>
|
||||
<text class="feed-title feed-title-fallback" wx:elif="{{item.preview}}">{{item.preview}}</text>
|
||||
<text class="feed-title feed-title-fallback" wx:else>动态</text>
|
||||
<view class="feed-title" wx:if="{{item.contentTrim}}">{{item.contentTrim}}</view>
|
||||
<view class="feed-title feed-title-fallback" wx:elif="{{item.titleTrim}}">{{item.titleTrim}}</view>
|
||||
<view class="feed-title feed-title-fallback" wx:elif="{{item.preview}}">{{item.preview}}</view>
|
||||
<view class="feed-title feed-title-fallback" wx:else>动态</view>
|
||||
|
||||
<view class="feed-thumb-row" wx:if="{{item.showThumbRow}}" catchtap="noop">
|
||||
<view class="thumb-cell" wx:for="{{item.thumbSlots}}" wx:for-item="th" wx:key="slotIdx">
|
||||
|
||||
@@ -146,14 +146,19 @@
|
||||
}
|
||||
|
||||
.feed-title {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
margin-bottom: 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.feed-title-fallback {
|
||||
|
||||
@@ -96,6 +96,8 @@ Page({
|
||||
hasMore: true,
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
/** 超级个体:右下角发动态入口 */
|
||||
showSuperPublishFab: false,
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
@@ -108,6 +110,7 @@ Page({
|
||||
return
|
||||
}
|
||||
this.reloadFeed()
|
||||
this.refreshSuperPublishFab()
|
||||
},
|
||||
|
||||
async onShow() {
|
||||
@@ -123,6 +126,29 @@ Page({
|
||||
if (tabBar && tabBar.loadFeatureConfig) tabBar.loadFeatureConfig()
|
||||
if (tabBar && tabBar.updateSelected) tabBar.updateSelected()
|
||||
}
|
||||
this.refreshSuperPublishFab()
|
||||
},
|
||||
|
||||
/** 仅超级个体展示发动态悬浮按钮 */
|
||||
async refreshSuperPublishFab() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
let show = false
|
||||
if (userId && app.globalData.isLoggedIn) {
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: `/api/miniprogram/my/super-stats?userId=${encodeURIComponent(userId)}`,
|
||||
silent: true,
|
||||
})
|
||||
show = !!(res?.success && res.data?.isSuperIndividual === true)
|
||||
} catch (_) {
|
||||
show = false
|
||||
}
|
||||
}
|
||||
this.setData({ showSuperPublishFab: show })
|
||||
},
|
||||
|
||||
goPublishMoment() {
|
||||
wx.navigateTo({ url: '/pages/super-article-editor/super-article-editor' })
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
@@ -139,6 +165,7 @@ Page({
|
||||
}
|
||||
try {
|
||||
await this.reloadFeed()
|
||||
await this.refreshSuperPublishFab()
|
||||
} finally {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
@@ -58,4 +58,15 @@
|
||||
<text wx:elif="{{!hasMore}}">已经到底啦</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 超级个体:右下角发动态(层级高于自定义 TabBar) -->
|
||||
<view
|
||||
wx:if="{{showSuperPublishFab}}"
|
||||
class="moments-fab-publish"
|
||||
hover-class="moments-fab-publish-hover"
|
||||
bindtap="goPublishMoment"
|
||||
aria-label="发动态"
|
||||
>
|
||||
<text class="moments-fab-plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -166,3 +166,38 @@
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 超级个体发动态:右下角圆钮(与读页分享 FAB、TabBar 品牌青一致) */
|
||||
.moments-fab-publish {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
bottom: calc(156rpx + env(safe-area-inset-bottom));
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #00ced1 0%, #20b2aa 100%);
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 206, 209, 0.42);
|
||||
z-index: 1002;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.15s ease, opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.moments-fab-publish-hover {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.moments-fab-publish:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
/* 原生「+」,避免 icon 组件 SVG 在 FAB 内偶发不渲染 */
|
||||
.moments-fab-plus {
|
||||
font-size: 56rpx;
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
margin-top: -4rpx;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user