最新同步
This commit is contained in:
@@ -3,18 +3,9 @@ const app = getApp()
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
// 'input' → 'generating' → 'preview' → 'publishing'
|
||||
step: 'input',
|
||||
|
||||
// 输入阶段
|
||||
description: '',
|
||||
images: [], // [{tempPath, url, uploading, error}]
|
||||
|
||||
// 预览/编辑阶段
|
||||
title: '',
|
||||
content: '',
|
||||
|
||||
genError: '',
|
||||
saving: false,
|
||||
meNickname: '',
|
||||
},
|
||||
|
||||
@@ -23,10 +14,10 @@ Page({
|
||||
statusBarHeight: app.globalData.statusBarHeight || 44,
|
||||
meNickname: String(app.globalData.userInfo?.nickname || '我').trim() || '我',
|
||||
})
|
||||
this._checkIdentity()
|
||||
this.ensureSuperIdentity()
|
||||
},
|
||||
|
||||
async _checkIdentity() {
|
||||
async ensureSuperIdentity() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!app.globalData.isLoggedIn || !userId) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
@@ -48,83 +39,6 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// ─────────────── 图片管理 ───────────────
|
||||
|
||||
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 || '' })
|
||||
},
|
||||
@@ -133,63 +47,25 @@ Page({
|
||||
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 })
|
||||
}
|
||||
insertMentionSelf() {
|
||||
const nick = this.data.meNickname || '我'
|
||||
this.setData({ content: `${this.data.content}@${nick} ` })
|
||||
},
|
||||
|
||||
backToInput() {
|
||||
this.setData({ step: 'input', genError: '' })
|
||||
insertHashLink() {
|
||||
this.setData({ content: `${this.data.content}#链接(https://)` })
|
||||
},
|
||||
|
||||
// ─────────────── 发布 ───────────────
|
||||
|
||||
async submitArticle() {
|
||||
if (this.data.step === 'publishing') return
|
||||
if (this.data.saving) 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' })
|
||||
wx.showToast({ title: '请填写标题和正文', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({ step: 'publishing' })
|
||||
this.setData({ saving: true })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/super/articles',
|
||||
@@ -201,22 +77,12 @@ Page({
|
||||
setTimeout(() => wx.navigateBack(), 400)
|
||||
} catch (e) {
|
||||
wx.showToast({ title: e.message || '发布失败', icon: 'none' })
|
||||
this.setData({ step: 'preview' })
|
||||
} finally {
|
||||
this.setData({ saving: false })
|
||||
}
|
||||
},
|
||||
|
||||
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://)` })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,166 +1,29 @@
|
||||
<view class="page">
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-back" bindtap="goBack">
|
||||
<icon name="chevron-left" size="44" color="rgba(255,255,255,0.85)"></icon>
|
||||
</view>
|
||||
<text class="nav-title">{{step === 'preview' || step === 'publishing' ? '预览 & 发布' : 'AI 写文章'}}</text>
|
||||
<text class="nav-title">发文章</text>
|
||||
<view class="nav-placeholder"></view>
|
||||
</view>
|
||||
<view style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
|
||||
<!-- 平台规范:深度合成-人工智能生成内容须显著标识(与页面标题同级可见) -->
|
||||
<view class="ai-gen-notice">
|
||||
<text class="ai-gen-notice__title">人工智能生成</text>
|
||||
<text class="ai-gen-notice__desc">本服务为AI生成内容,结果仅供参考,请自行核对后再发布</text>
|
||||
</view>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
STEP 1:输入阶段(input / generating)
|
||||
═══════════════════════════════════════════ -->
|
||||
<block wx:if="{{step === 'input' || step === 'generating'}}">
|
||||
|
||||
<!-- 图片上传区 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-icon">📷</text>
|
||||
<text class="section-title">添加图片</text>
|
||||
<text class="section-sub">选填,最多3张,AI 可理解图片内容</text>
|
||||
</view>
|
||||
<view class="img-grid">
|
||||
<view wx:for="{{images}}" wx:key="index" class="img-cell {{item.error ? 'img-cell--error' : ''}}">
|
||||
<image src="{{item.url || item.tempPath}}" class="img-thumb" mode="aspectFill"/>
|
||||
<!-- 上传中遮罩 -->
|
||||
<view wx:if="{{item.uploading}}" class="img-overlay">
|
||||
<view class="img-spin"></view>
|
||||
</view>
|
||||
<!-- 错误提示 -->
|
||||
<view wx:if="{{item.error && !item.uploading}}" class="img-overlay img-overlay--err">
|
||||
<text class="img-err-txt">上传失败</text>
|
||||
</view>
|
||||
<!-- 删除按钮 -->
|
||||
<view wx:if="{{!item.uploading}}" class="img-del" bindtap="removeImage" data-index="{{index}}">×</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<view wx:if="{{images.length < 3}}" class="img-add" bindtap="chooseImages">
|
||||
<text class="img-add-icon">+</text>
|
||||
<text class="img-add-label">拍照/相册</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card">
|
||||
<text class="label">标题</text>
|
||||
<view class="input-wrap">
|
||||
<input class="input" maxlength="40" placeholder="写个标题(最多40字)" value="{{title}}" bindinput="onTitleInput"/>
|
||||
</view>
|
||||
|
||||
<!-- 描述输入 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-icon">✏️</text>
|
||||
<text class="section-title">用几句话描述你想写什么</text>
|
||||
</view>
|
||||
<view class="desc-wrap">
|
||||
<textarea
|
||||
class="desc-textarea"
|
||||
maxlength="200"
|
||||
placeholder="例如:我有个朋友做了一件很小的事,结果客户留存率提升了30%,让我想到了..."
|
||||
placeholder-class="desc-placeholder"
|
||||
value="{{description}}"
|
||||
bindinput="onDescInput"
|
||||
auto-height
|
||||
disabled="{{step === 'generating'}}"
|
||||
/>
|
||||
<text class="char-count">{{description.length}}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 生成错误提示 -->
|
||||
<view wx:if="{{genError}}" class="error-tip">⚠️ {{genError}}</view>
|
||||
|
||||
<!-- AI 生成按钮 / 加载状态 -->
|
||||
<view class="ai-btn-wrap">
|
||||
<view wx:if="{{step !== 'generating'}}" class="ai-btn" bindtap="generateArticle">
|
||||
<text class="ai-btn-icon">✦</text>
|
||||
<text class="ai-btn-text">AI 一键生成文章</text>
|
||||
</view>
|
||||
|
||||
<!-- 生成中 loading 状态 -->
|
||||
<view wx:else class="ai-loading-card">
|
||||
<view class="ai-loading-stars">
|
||||
<text class="star star1">✦</text>
|
||||
<text class="star star2">✦</text>
|
||||
<text class="star star3">✦</text>
|
||||
</view>
|
||||
<text class="ai-loading-title">AI 正在创作中</text>
|
||||
<text class="ai-loading-desc">根据你的描述生成专属内容,请稍候...</text>
|
||||
<view class="ai-loading-bar">
|
||||
<view class="ai-loading-bar-fill"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</block>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
STEP 2:预览 & 编辑(preview / publishing)
|
||||
═══════════════════════════════════════════ -->
|
||||
<block wx:elif="{{step === 'preview' || step === 'publishing'}}">
|
||||
|
||||
<view class="ai-preview-tag">
|
||||
<text class="ai-preview-tag__text">以下标题与正文均为人工智能生成</text>
|
||||
</view>
|
||||
|
||||
<!-- 标题编辑 -->
|
||||
<view class="section">
|
||||
<view class="field-header">
|
||||
<text class="field-label">标题</text>
|
||||
<text class="char-count">{{title.length}}/40</text>
|
||||
</view>
|
||||
<view class="input-wrap">
|
||||
<input
|
||||
class="input"
|
||||
maxlength="40"
|
||||
placeholder="文章标题"
|
||||
value="{{title}}"
|
||||
bindinput="onTitleInput"
|
||||
disabled="{{step === 'publishing'}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<text class="label">正文</text>
|
||||
<view class="toolbar">
|
||||
<view class="tool-btn" bindtap="insertMentionSelf">@自己</view>
|
||||
<view class="tool-btn" bindtap="insertHashLink">#超链接</view>
|
||||
</view>
|
||||
|
||||
<!-- 正文编辑 -->
|
||||
<view class="section section--content">
|
||||
<view class="field-header">
|
||||
<text class="field-label">正文</text>
|
||||
<text class="char-count">{{content.length}}/5000</text>
|
||||
</view>
|
||||
<view class="textarea-wrap">
|
||||
<textarea
|
||||
class="textarea"
|
||||
maxlength="5000"
|
||||
placeholder="正文内容..."
|
||||
value="{{content}}"
|
||||
bindinput="onContentInput"
|
||||
auto-height
|
||||
disabled="{{step === 'publishing'}}"
|
||||
/>
|
||||
</view>
|
||||
<view class="textarea-wrap">
|
||||
<textarea class="textarea" maxlength="5000" placeholder="输入正文内容..." value="{{content}}" bindinput="onContentInput"/>
|
||||
</view>
|
||||
<text class="hint">提示:可直接输入 @昵称 或 #链接(https://xxx)</text>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="action-bar">
|
||||
<view class="action-secondary" bindtap="backToInput" wx:if="{{step !== 'publishing'}}">
|
||||
<text>↺ 重新生成</text>
|
||||
</view>
|
||||
<view class="action-primary {{step === 'publishing' ? 'action-primary--loading' : ''}}" bindtap="submitArticle">
|
||||
<text>{{step === 'publishing' ? '发布中...' : '发布文章'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</block>
|
||||
|
||||
<view class="submit-btn" bindtap="submitArticle">{{saving ? '发布中...' : '发布文章'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,302 +1,28 @@
|
||||
/* ── 基础 ── */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #080f1e;
|
||||
color: #f1f5f9;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
/* ── 导航栏 ── */
|
||||
.page { min-height: 100vh; background: #0b1220; color: #fff; }
|
||||
.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(8, 15, 30, 0.92);
|
||||
border-bottom: 1rpx solid rgba(148, 163, 184, 0.12);
|
||||
height: 44px; padding: 0 24rpx; background: rgba(5, 11, 20, 0.9);
|
||||
}
|
||||
.nav-back, .nav-placeholder { width: 64rpx; }
|
||||
.nav-title { font-size: 32rpx; font-weight: 700; color: #e2e8f0; }
|
||||
|
||||
/* ── 人工智能生成内容显著标识(平台合规)── */
|
||||
.ai-gen-notice {
|
||||
margin: 0 24rpx 20rpx;
|
||||
padding: 22rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, rgba(124, 58, 237, 0.22) 0%, rgba(8, 145, 178, 0.18) 100%);
|
||||
border: 2rpx solid rgba(167, 139, 250, 0.55);
|
||||
box-shadow: 0 4rpx 24rpx rgba(124, 58, 237, 0.2);
|
||||
}
|
||||
.ai-gen-notice__title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 800;
|
||||
color: #e9d5ff;
|
||||
letter-spacing: 2rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.ai-gen-notice__desc {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.55;
|
||||
color: #c4b5fd;
|
||||
}
|
||||
|
||||
.ai-preview-tag {
|
||||
margin: 0 24rpx 16rpx;
|
||||
padding: 16rpx 22rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(127, 29, 153, 0.25);
|
||||
border: 1rpx solid rgba(232, 121, 249, 0.4);
|
||||
}
|
||||
.ai-preview-tag__text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #f0abfc;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* ── 通用 Section ── */
|
||||
.section {
|
||||
margin: 24rpx 24rpx 0;
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
border: 1rpx solid rgba(148, 163, 184, 0.15);
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx 24rpx 20rpx;
|
||||
}
|
||||
.section--content { min-height: 360rpx; }
|
||||
|
||||
.section-header {
|
||||
display: flex; align-items: center; gap: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.section-icon { font-size: 34rpx; }
|
||||
.section-title { font-size: 28rpx; font-weight: 600; color: #e2e8f0; }
|
||||
.section-sub { font-size: 22rpx; color: #64748b; margin-left: 4rpx; }
|
||||
|
||||
/* ── 图片网格 ── */
|
||||
.img-grid {
|
||||
display: flex; flex-wrap: wrap; gap: 16rpx;
|
||||
}
|
||||
|
||||
.img-cell {
|
||||
position: relative;
|
||||
width: 192rpx; height: 192rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
border: 1rpx solid rgba(100, 116, 139, 0.3);
|
||||
}
|
||||
.img-cell--error { border-color: rgba(248, 113, 113, 0.5); }
|
||||
|
||||
.img-thumb { width: 100%; height: 100%; }
|
||||
|
||||
.img-overlay {
|
||||
position: absolute; inset: 0;
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
background: rgba(8, 15, 30, 0.6);
|
||||
}
|
||||
.img-overlay--err { background: rgba(127, 29, 29, 0.65); }
|
||||
.img-err-txt { font-size: 22rpx; color: #fca5a5; }
|
||||
|
||||
.img-spin {
|
||||
width: 48rpx; height: 48rpx;
|
||||
border: 4rpx solid rgba(103, 232, 249, 0.25);
|
||||
border-top-color: #67e8f9;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.img-del {
|
||||
position: absolute; top: 8rpx; right: 8rpx;
|
||||
width: 40rpx; height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(8, 15, 30, 0.75);
|
||||
color: #e2e8f0;
|
||||
font-size: 30rpx; line-height: 38rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.img-add {
|
||||
width: 192rpx; height: 192rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; gap: 8rpx;
|
||||
background: rgba(30, 41, 59, 0.5);
|
||||
border: 2rpx dashed rgba(103, 232, 249, 0.3);
|
||||
}
|
||||
.img-add-icon { font-size: 52rpx; color: #67e8f9; line-height: 1; }
|
||||
.img-add-label { font-size: 22rpx; color: #94a3b8; }
|
||||
|
||||
/* ── 描述输入框 ── */
|
||||
.desc-wrap {
|
||||
background: rgba(2, 8, 20, 0.6);
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid rgba(100, 116, 139, 0.2);
|
||||
}
|
||||
.desc-textarea {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
color: #e2e8f0;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.desc-placeholder { color: #475569; }
|
||||
|
||||
/* ── 字数统计 ── */
|
||||
.char-count {
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-size: 22rpx;
|
||||
color: #475569;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* ── 错误提示 ── */
|
||||
.error-tip {
|
||||
margin: 16rpx 24rpx 0;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
background: rgba(127, 29, 29, 0.35);
|
||||
border: 1rpx solid rgba(248, 113, 113, 0.3);
|
||||
font-size: 24rpx;
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
/* ── AI 生成按钮 ── */
|
||||
.ai-btn-wrap { margin: 32rpx 24rpx 0; }
|
||||
|
||||
.ai-btn {
|
||||
display: flex; align-items: center; justify-content: center; gap: 14rpx;
|
||||
height: 100rpx; border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #2563eb 50%, #0891b2 100%);
|
||||
box-shadow: 0 8rpx 32rpx rgba(124, 58, 237, 0.4);
|
||||
}
|
||||
.ai-btn-icon { font-size: 36rpx; color: #fff; }
|
||||
.ai-btn-text { font-size: 32rpx; font-weight: 700; color: #fff; letter-spacing: 2rpx; }
|
||||
|
||||
/* ── AI Loading 卡片 ── */
|
||||
.ai-loading-card {
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; padding: 56rpx 32rpx;
|
||||
border-radius: 24rpx;
|
||||
background: rgba(15, 23, 42, 0.95);
|
||||
border: 1rpx solid rgba(124, 58, 237, 0.35);
|
||||
box-shadow: 0 0 48rpx rgba(124, 58, 237, 0.15);
|
||||
}
|
||||
|
||||
.ai-loading-stars {
|
||||
display: flex; gap: 24rpx; margin-bottom: 32rpx;
|
||||
}
|
||||
.star {
|
||||
font-size: 40rpx; color: #a78bfa;
|
||||
animation: starPulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
.star2 { animation-delay: 0.28s; }
|
||||
.star3 { animation-delay: 0.56s; }
|
||||
|
||||
.ai-loading-title {
|
||||
font-size: 34rpx; font-weight: 700;
|
||||
color: #e2e8f0; margin-bottom: 12rpx;
|
||||
}
|
||||
.ai-loading-desc {
|
||||
font-size: 24rpx; color: #64748b;
|
||||
text-align: center; line-height: 1.6; margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.ai-loading-bar {
|
||||
width: 100%; height: 6rpx;
|
||||
background: rgba(100, 116, 139, 0.2);
|
||||
border-radius: 999rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ai-loading-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #7c3aed, #2563eb, #0891b2);
|
||||
border-radius: 999rpx;
|
||||
animation: barSlide 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ── 预览/编辑区域 ── */
|
||||
.field-header {
|
||||
display: flex; align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.field-label { font-size: 26rpx; font-weight: 600; color: #94a3b8; }
|
||||
|
||||
.input-wrap {
|
||||
background: rgba(2, 8, 20, 0.6);
|
||||
border-radius: 16rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
border: 1rpx solid rgba(100, 116, 139, 0.2);
|
||||
}
|
||||
.input { width: 100%; color: #f1f5f9; font-size: 30rpx; }
|
||||
|
||||
.textarea-wrap {
|
||||
background: rgba(2, 8, 20, 0.6);
|
||||
border-radius: 16rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
border: 1rpx solid rgba(100, 116, 139, 0.2);
|
||||
}
|
||||
.textarea { width: 100%; min-height: 480rpx; color: #f1f5f9; font-size: 28rpx; line-height: 1.7; }
|
||||
|
||||
/* ── 工具栏 ── */
|
||||
.toolbar {
|
||||
margin: 16rpx 24rpx 0;
|
||||
display: flex; gap: 12rpx;
|
||||
.nav-title { font-size: 32rpx; font-weight: 700; }
|
||||
.card {
|
||||
margin: 24rpx; padding: 28rpx; border-radius: 24rpx;
|
||||
background: rgba(15, 23, 42, 0.86); border: 1rpx solid rgba(148, 163, 184, 0.2);
|
||||
}
|
||||
.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; }
|
||||
.toolbar { margin-top: 12rpx; margin-bottom: 12rpx; display: flex; gap: 12rpx; }
|
||||
.tool-btn {
|
||||
padding: 10rpx 20rpx; border-radius: 999rpx;
|
||||
font-size: 22rpx; color: #67e8f9;
|
||||
border: 1rpx solid rgba(34, 211, 238, 0.35);
|
||||
background: rgba(8, 47, 73, 0.45);
|
||||
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);
|
||||
}
|
||||
|
||||
/* ── 底部操作栏 ── */
|
||||
.action-bar {
|
||||
position: fixed; left: 0; right: 0; bottom: 0;
|
||||
display: flex; gap: 16rpx; align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
background: rgba(8, 15, 30, 0.95);
|
||||
border-top: 1rpx solid rgba(148, 163, 184, 0.12);
|
||||
}
|
||||
|
||||
.action-secondary {
|
||||
flex: 0 0 auto;
|
||||
height: 88rpx; padding: 0 36rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: rgba(30, 41, 59, 0.7);
|
||||
border: 1rpx solid rgba(100, 116, 139, 0.3);
|
||||
color: #94a3b8;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.action-primary {
|
||||
flex: 1;
|
||||
height: 88rpx; border-radius: 16rpx;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
.textarea { width: 100%; min-height: 380rpx; color: #fff; font-size: 28rpx; line-height: 1.65; }
|
||||
.hint { display: block; margin-top: 10rpx; color: #94a3b8; font-size: 22rpx; }
|
||||
.submit-btn {
|
||||
margin-top: 24rpx; height: 88rpx; border-radius: 16rpx;
|
||||
background: linear-gradient(135deg, #22d3ee, #14b8a6);
|
||||
color: #032b35;
|
||||
font-size: 30rpx; font-weight: 700;
|
||||
}
|
||||
.action-primary--loading { opacity: 0.7; }
|
||||
|
||||
/* ── 动画 ── */
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes starPulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.35; transform: scale(0.75); }
|
||||
}
|
||||
|
||||
@keyframes barSlide {
|
||||
0% { width: 0%; margin-left: 0; }
|
||||
50% { width: 70%; margin-left: 0; }
|
||||
100% { width: 0%; margin-left: 100%; }
|
||||
color: #032b35; font-size: 30rpx; font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user