diff --git a/miniprogram/pages/super-article-editor/super-article-editor.js b/miniprogram/pages/super-article-editor/super-article-editor.js index 2dfb28c2..b043a03a 100644 --- a/miniprogram/pages/super-article-editor/super-article-editor.js +++ b/miniprogram/pages/super-article-editor/super-article-editor.js @@ -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 { diff --git a/miniprogram/pages/super-article-editor/super-article-editor.wxml b/miniprogram/pages/super-article-editor/super-article-editor.wxml index ad20622e..2256dada 100644 --- a/miniprogram/pages/super-article-editor/super-article-editor.wxml +++ b/miniprogram/pages/super-article-editor/super-article-editor.wxml @@ -8,72 +8,89 @@ - - - AI 帮你写 - 参考图片(可选) - - + 添加图片 - - - - {{item.name}} - 上传中… - 文本已载入 - 图片 - 附件 + + + + + 配图 + {{bodyImages.length}}/9 - 移除 - - 从相册或相机添加参考图,可选(单次最多 9 张),上传后与提示词一并交给 AI 分析。单张建议不超 30MB。 - - 提示词 - - + 可选 · 弹窗内「AI 帮你写」会与下方配图共用 · 发布后展示在正文下方 + + + + 上传中… + × + + + + + + + 单张建议不超 30MB - {{generating ? '生成中…' : '生成'}} + + + + + 正文 + + + + 快捷插入 + @自己 + ✨ AI 帮你写 + + + + + - - - - @自己 - #链接 - - - - - - - - - 上传中… - × - - - + - - - 配图 {{bodyImages.length}}/9 · 展示在文字下方 · 单张建议不超 30MB · 可直接输入 @昵称 或 #链接(https://xxx) - - - {{saving ? (editArticleId ? '提交中…' : '发布中…') : (editArticleId ? '提交审核' : '发布')}} + + + + + + AI 帮你写 + 关闭 + + 提示词 + 当前配图一并交给模型;生成后写入正文,可关闭弹窗继续编辑 + 提示词 + + + + + {{generating ? '生成中…' : '生成并填入正文'}} + + + + 开发中... diff --git a/miniprogram/pages/super-article-editor/super-article-editor.wxss b/miniprogram/pages/super-article-editor/super-article-editor.wxss index 8c349ec4..6b79bec7 100644 --- a/miniprogram/pages/super-article-editor/super-article-editor.wxss +++ b/miniprogram/pages/super-article-editor/super-article-editor.wxss @@ -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; + } + diff --git a/miniprogram/pages/super-article-mine/super-article-mine.js b/miniprogram/pages/super-article-mine/super-article-mine.js index a28e9e77..8daf5de6 100644 --- a/miniprogram/pages/super-article-mine/super-article-mine.js +++ b/miniprogram/pages/super-article-mine/super-article-mine.js @@ -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, diff --git a/miniprogram/pages/super-article-mine/super-article-mine.wxml b/miniprogram/pages/super-article-mine/super-article-mine.wxml index b80844e4..e8b13fa5 100644 --- a/miniprogram/pages/super-article-mine/super-article-mine.wxml +++ b/miniprogram/pages/super-article-mine/super-article-mine.wxml @@ -31,9 +31,10 @@ {{item.auditLabel}} {{item.timeText}} - {{item.title}} - {{item.preview}} - 动态 + {{item.contentTrim}} + {{item.titleTrim}} + {{item.preview}} + 动态 diff --git a/miniprogram/pages/super-article-mine/super-article-mine.wxss b/miniprogram/pages/super-article-mine/super-article-mine.wxss index b8789aed..d8cd9791 100644 --- a/miniprogram/pages/super-article-mine/super-article-mine.wxss +++ b/miniprogram/pages/super-article-mine/super-article-mine.wxss @@ -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 { diff --git a/miniprogram/pages/super-moments/super-moments.js b/miniprogram/pages/super-moments/super-moments.js index 18480cc8..9152b96b 100644 --- a/miniprogram/pages/super-moments/super-moments.js +++ b/miniprogram/pages/super-moments/super-moments.js @@ -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() } diff --git a/miniprogram/pages/super-moments/super-moments.wxml b/miniprogram/pages/super-moments/super-moments.wxml index f6074f65..ce3fdfa1 100644 --- a/miniprogram/pages/super-moments/super-moments.wxml +++ b/miniprogram/pages/super-moments/super-moments.wxml @@ -58,4 +58,15 @@ 已经到底啦 + + + + + + diff --git a/miniprogram/pages/super-moments/super-moments.wxss b/miniprogram/pages/super-moments/super-moments.wxss index 30ee52de..ed1b0d0f 100644 --- a/miniprogram/pages/super-moments/super-moments.wxss +++ b/miniprogram/pages/super-moments/super-moments.wxss @@ -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); +}