Files
Mycontent/miniprogram/pages/super-article-editor/super-article-editor.js
乘风 83a420f1b9 feat: implement audit mode restrictions across various pages
- Added checks for audit mode in VIP navigation and member detail pages, preventing access and displaying appropriate messages.
- Updated UI elements to conditionally render based on audit mode, enhancing user experience by hiding VIP-related options when in audit mode.
- Introduced loading indicators on the VIP page to manage user expectations during content validation.
- Enhanced error handling for image uploads in the article editor, ensuring a smoother content creation process.

This update aims to improve the overall user experience by enforcing access restrictions and providing clear feedback in audit mode.
2026-04-24 18:23:34 +08:00

223 lines
6.6 KiB
JavaScript

const app = getApp()
Page({
data: {
statusBarHeight: 44,
// 'input' → 'generating' → 'preview' → 'publishing'
step: 'input',
// 输入阶段
description: '',
images: [], // [{tempPath, url, uploading, error}]
// 预览/编辑阶段
title: '',
content: '',
genError: '',
meNickname: '',
},
onLoad() {
this.setData({
statusBarHeight: app.globalData.statusBarHeight || 44,
meNickname: String(app.globalData.userInfo?.nickname || '我').trim() || '我',
})
this._checkIdentity()
},
async _checkIdentity() {
const userId = app.globalData.userInfo?.id
if (!app.globalData.isLoggedIn || !userId) {
wx.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => wx.navigateBack(), 400)
return
}
try {
const res = await app.request({
url: `/api/miniprogram/my/super-stats?userId=${encodeURIComponent(userId)}`,
silent: true,
})
if (!(res?.success && res.data?.isSuperIndividual === true)) {
wx.showToast({ title: '仅超级个体可发文章', icon: 'none' })
setTimeout(() => wx.navigateBack(), 400)
}
} catch (_) {
wx.showToast({ title: '身份校验失败', icon: 'none' })
setTimeout(() => wx.navigateBack(), 400)
}
},
// ─────────────── 图片管理 ───────────────
chooseImages() {
const remain = 3 - this.data.images.length
if (remain <= 0) return
wx.chooseMedia({
count: remain,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
const newImgs = res.tempFiles.map((f) => ({
tempPath: f.tempFilePath,
url: '',
uploading: true,
error: false,
}))
const images = [...this.data.images, ...newImgs]
this.setData({ images })
// 并发上传
newImgs.forEach((img, offset) => {
const idx = this.data.images.length - newImgs.length + offset
this._uploadImage(img.tempPath, idx)
})
},
})
},
async _uploadImage(tempPath, idx) {
const baseUrl = app.globalData.baseUrl || ''
const token = wx.getStorageSync('token') || ''
return new Promise((resolve) => {
wx.uploadFile({
url: `${baseUrl}/api/upload`,
filePath: tempPath,
name: 'file',
formData: { folder: 'article-images' },
header: token ? { Authorization: `Bearer ${token}` } : {},
success: (res) => {
try {
const parsed = JSON.parse(res.data)
if (parsed.success && parsed.url) {
this._updateImage(idx, { url: parsed.url, uploading: false, error: false })
resolve(parsed.url)
return
}
} catch (_) { /* ignore */ }
this._updateImage(idx, { uploading: false, error: true })
resolve('')
},
fail: () => {
this._updateImage(idx, { uploading: false, error: true })
resolve('')
},
})
})
},
_updateImage(idx, patch) {
const images = [...this.data.images]
if (images[idx]) {
images[idx] = { ...images[idx], ...patch }
this.setData({ images })
}
},
removeImage(e) {
const idx = Number(e.currentTarget.dataset.index)
const images = this.data.images.filter((_, i) => i !== idx)
this.setData({ images })
},
// ─────────────── 输入 ───────────────
onDescInput(e) {
this.setData({ description: e.detail.value || '' })
},
onTitleInput(e) {
this.setData({ title: e.detail.value || '' })
},
onContentInput(e) {
this.setData({ content: e.detail.value || '' })
},
// ─────────────── AI 生成 ───────────────
async generateArticle() {
if (this.data.step === 'generating') return
const desc = String(this.data.description || '').trim()
if (!desc) {
wx.showToast({ title: '请先填写描述', icon: 'none' })
return
}
// 等待所有图片上传完成
const stillUploading = this.data.images.some((img) => img.uploading)
if (stillUploading) {
wx.showToast({ title: '图片上传中,请稍候', icon: 'none' })
return
}
const imageUrls = this.data.images.filter((img) => img.url).map((img) => img.url)
const userId = app.globalData.userInfo?.id || ''
this.setData({ step: 'generating', genError: '' })
try {
const res = await app.request({
url: '/api/miniprogram/super/articles/generate',
method: 'POST',
data: { userId, description: desc, imageUrls },
timeout: 90000,
})
if (!res?.success) throw new Error(res?.error || 'AI 生成失败')
this.setData({
step: 'preview',
title: String(res.data?.title || '').trim(),
content: String(res.data?.content || '').trim(),
})
} catch (err) {
this.setData({ step: 'input', genError: err.message || 'AI 生成失败,请重试' })
wx.showToast({ title: this.data.genError, icon: 'none', duration: 2500 })
}
},
backToInput() {
this.setData({ step: 'input', genError: '' })
},
// ─────────────── 发布 ───────────────
async submitArticle() {
if (this.data.step === 'publishing') return
const userId = app.globalData.userInfo?.id
const title = String(this.data.title || '').trim()
const content = String(this.data.content || '').trim()
if (!title || !content) {
wx.showToast({ title: '标题和正文不能为空', icon: 'none' })
return
}
this.setData({ step: 'publishing' })
try {
const res = await app.request({
url: '/api/miniprogram/super/articles',
method: 'POST',
data: { userId, title, content },
})
if (!res?.success) throw new Error(res?.error || '发布失败')
wx.showToast({ title: '发布成功', icon: 'success' })
setTimeout(() => wx.navigateBack(), 400)
} catch (e) {
wx.showToast({ title: e.message || '发布失败', icon: 'none' })
this.setData({ step: 'preview' })
}
},
goBack() {
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/my/my' }) })
},
// ─────────────── 手动添加 @自己 / #链接 ───────────────
insertMentionSelf() {
const nick = this.data.meNickname || '我'
this.setData({ content: `${this.data.content}@${nick} ` })
},
insertHashLink() {
this.setData({ content: `${this.data.content}#链接(https://)` })
},
})