-
AI 创作工具
-
选择你需要的 AI 能力,开始创作之旅
+
+
+
+
+
+
+
+
来自 AI 在线用户的公开作品,鼠标悬停可查看生成提示词
+
+
+ 浏览全部作品
+
+
+
+
+
+
+
+
+
+
+
+ {{ artTypeName(item.ai_type) }}
+
+
+
+
+
+
+ {{ item.prompts }}
+
+
AI 生成作品
+
+ {{ item.resolution }}
+
+
+ {{ item.like_num || 0 }}
+
+
+ 查看
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- {{ tool.title }}
- {{ tool.desc }}
-
+
+
+
+
+
+
注册即可体验,按点数付费,不用订阅、不绑定套餐。
+
+
+
+
+ 开始创作
+
+
+ 查看套餐
+
+
+
+
+
+
+ {{ p }}
+
+
@@ -59,47 +463,226 @@
+
+
diff --git a/app/pages/info/course/[id].vue b/app/pages/info/course/[id].vue
new file mode 100644
index 0000000..aac344c
--- /dev/null
+++ b/app/pages/info/course/[id].vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/pages/info/course/index.vue b/app/pages/info/course/index.vue
new file mode 100644
index 0000000..ad9dc0e
--- /dev/null
+++ b/app/pages/info/course/index.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
diff --git a/app/pages/info/index.vue b/app/pages/info/index.vue
new file mode 100644
index 0000000..64d7570
--- /dev/null
+++ b/app/pages/info/index.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
diff --git a/app/pages/info/new/[id].vue b/app/pages/info/new/[id].vue
new file mode 100644
index 0000000..f20c51d
--- /dev/null
+++ b/app/pages/info/new/[id].vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/pages/info/new/index.vue b/app/pages/info/new/index.vue
new file mode 100644
index 0000000..bd3a0b6
--- /dev/null
+++ b/app/pages/info/new/index.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
diff --git a/app/pages/info/tag/[key].vue b/app/pages/info/tag/[key].vue
new file mode 100644
index 0000000..862eb31
--- /dev/null
+++ b/app/pages/info/tag/[key].vue
@@ -0,0 +1,67 @@
+
+
+
+
+
diff --git a/app/pages/info/what/[id].vue b/app/pages/info/what/[id].vue
new file mode 100644
index 0000000..59e0fcc
--- /dev/null
+++ b/app/pages/info/what/[id].vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/pages/info/what/index.vue b/app/pages/info/what/index.vue
new file mode 100644
index 0000000..996720e
--- /dev/null
+++ b/app/pages/info/what/index.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
diff --git a/app/pages/invite/index.vue b/app/pages/invite/index.vue
new file mode 100644
index 0000000..add50c1
--- /dev/null
+++ b/app/pages/invite/index.vue
@@ -0,0 +1,367 @@
+
+
+
+
+
+
+
+ 双方奖励
+
+
+
+
+ 好友注册成功后自动发放
+
+
+
+
+
+
+
+
+
+
+
+ 登录后即可复制专属邀请链接、生成微信小程序码并查看邀请奖励。
+
+
+
+
+
+
+
+
+
+ {{ loadError }}
+
+
+
+
+
+
+
+
+
+
+
每位好友仅能绑定一位邀请人,奖励以注册时的活动配置为准。
+
+
+
+ {{ activityEnabled ? '活动进行中' : '活动暂未开放' }}
+
+
+
+
+
+
+
你的邀请链接
+
+
{{ inviteUrl }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ step.desc }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/invite/landing.vue b/app/pages/invite/landing.vue
new file mode 100644
index 0000000..75c7160
--- /dev/null
+++ b/app/pages/invite/landing.vue
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+ 新用户邀请礼
+
+
+
+
+
![]()
+
AI
+
+
+
+
+
+ {{ landingTitle }}
+
+ {{ landingDesc }}
+
+
+
+
+ {{ item }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ validInvite && activityEnabled ? '奖励将自动发放到双方的点数账户。' : invalidDesc }}
+
+
+
+
+
+
+
+
+ {{ config.rule_text || '好友通过你的邀请完成新用户注册后,你和好友都可获得点数奖励。' }}
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/invite/records.vue b/app/pages/invite/records.vue
new file mode 100644
index 0000000..425571f
--- /dev/null
+++ b/app/pages/invite/records.vue
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ loadError }}
+
+
+
+
+
+ 受邀好友
+ 状态
+ 邀请奖励
+
+
+
+
+
+
+
+
+
{{ recordDescription(item) }}
+
+
+ {{ formatTime(item.reward_time || item.add_time) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
分享邀请链接,好友注册成功后会展示在这里。
+
+ 去邀请好友
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/news/detail/[id].vue b/app/pages/news/detail/[id].vue
new file mode 100644
index 0000000..3b3c710
--- /dev/null
+++ b/app/pages/news/detail/[id].vue
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/app/pages/old-photo.vue b/app/pages/old-photo.vue
new file mode 100644
index 0000000..6089953
--- /dev/null
+++ b/app/pages/old-photo.vue
@@ -0,0 +1,717 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支持黑白、泛黄、模糊、划痕或破损照片,单张不超过 10M
+
+
必选
+
+
+
+
+
![已选照片预览]()
+
+ +
+
+
+
+
+
建议主体清晰、人物未被严重遮挡;也可以直接把文件拖进来
+
+
+
+
+
+
+ 黑白照片
+ 泛黄褪色
+ 划痕破损
+
+
+
+
+
+
+
+ 按张计费
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选填
+
+
+
+
只用来补充客观信息,不要写风格化描述,避免影响人物还原。
+
+
+
+
+
+
+
+
+
{{ balanceText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
等待修复
+
上传老照片并选择档位后
+
点击"立即修复"即可看到效果
+
+
+
+
+
{{ pendingTitle }}
+
混元生图未购买并发,高峰期会排队,请耐心等待
+
+
+
+
+
+
+
已等待 {{ waitedSeconds }}s
+
下次刷新 {{ pollCountdown }}s
+
+
+
+
+
+
+
修复失败
+
{{ resultState.errorMsg }}
+
重试
+
+
+
+
+
+
+
+
+
![老照片修复结果]()
+
+
+
+
+
+
+
+
+ AI 会重绘上色,五官可能出现轻微变化,建议对照原图确认
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![修复后]()
+
![原图]()
+
+
原图
+
修复后
+
+
左右拖动中间按钮查看变化
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/pic/[id].vue b/app/pages/pic/[id].vue
new file mode 100644
index 0000000..8b6ba62
--- /dev/null
+++ b/app/pages/pic/[id].vue
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ aiTypeName }}
+
+
+ {{ detail.author_name }}
+
+
+
+ {{ formatTime(detail.add_time) }}
+
+
+
+ {{ detail.view_num }}
+
+
+
+ {{ detail.like_num }}
+
+
+
+ {{ detail.resolution }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
暂无更多作品
+
+ 查看全部图片
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/pic/index.vue b/app/pages/pic/index.vue
new file mode 100644
index 0000000..47a8aac
--- /dev/null
+++ b/app/pages/pic/index.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+ 精选 AI 在线用户生成的作品,包含 AI 绘画、AI 动漫头像等,点击查看提示词与大图。
+
+
+
+
+
+ 我也要生成
+
+
+ 看 AI 资讯
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
成为第一个分享 AI 作品的人
+
+
+
+
+
+
+
diff --git a/app/pages/portrait-photo.vue b/app/pages/portrait-photo.vue
new file mode 100644
index 0000000..3827073
--- /dev/null
+++ b/app/pages/portrait-photo.vue
@@ -0,0 +1,874 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
建议正面、五官无遮挡、光线均匀的半身照,单张不超过 10M
+
+
必选
+
+
+
+
+
![已选照片预览]()
+
+ +
+
+
+
+
+
支持自拍与半身照;也可以直接把文件拖进来
+
+
+
+
+
+
+
+
+
+
+ 不同模型价格可能不同
+
+
+
+
+
+
+
+
+
+
+ 选择生成方向
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
生成张数
+
+
+
+
+
+
一次最多 {{ maxCount }} 张,按张扣点,失败的不扣
+
+
+
+
+
+
+
+ 选填
+
+
+
+
只用来补充客观信息,风格由上方选项决定。
+
+
+
+
+
+
+
+
+
{{ balanceText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
等待生成
+
上传人像并选好风格后
+
点击"立即生成"即可看到效果
+
+
+
+
+
{{ pendingTitle }}
+
每张单独生成,混元未购买并发,高峰期会排队
+
+
+
+
+
+
+
+ {{ job.success_count || 0 }} / {{ job.count || quantity }} 张 · 已等待 {{ waitedSeconds }}s
+
+
下次刷新 {{ pollCountdown }}s
+
+
+
+
+
+
+
生成失败
+
{{ resultState.errorMsg }}
+
重试
+
+
+
+
+
+
+
+
+
+
![形象照生成结果]()
+
+
+
+
+
+
+
+ 有 {{ job.fail_count }} 张生成失败,失败的不扣点
+
+
+
+
+
+
+
+
+
+ AI 会重绘服装与背景,五官可能出现轻微变化,建议对照原图确认
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![生成结果]()
+
![原图]()
+
+
原图
+
艺术照
+
+
左右拖动中间按钮查看变化
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/privacy.vue b/app/pages/privacy.vue
new file mode 100644
index 0000000..c8ad2c7
--- /dev/null
+++ b/app/pages/privacy.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/pages/recharge.vue b/app/pages/recharge.vue
index 0c65780..e3c5a1c 100644
--- a/app/pages/recharge.vue
+++ b/app/pages/recharge.vue
@@ -1,108 +1,172 @@
-
-
-
+
+
+
+
+
+
我的点数
+
+
+ {{ balanceUnit }}
+
+
+ 查看账单明细
+
+
+
+
+
-
-
-
当前余额
-
{{ userInfo.balance || 0 }} 点
-
+
+
+
+
+
+
+
+ 点数长期有效
+
-
-
-
-
- 推荐
-
-
{{ pkg.points || pkg.point }} 点
-
¥{{ pkg.price }}
-
{{ pkg.discount }}
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
微信扫码支付
-
-
-
-
-
-
生成二维码...
+
+
-
-
-
-
-
生成失败
+
+
+
+
+
+
-
- 请使用微信扫描二维码
-
-
- 支付成功!🎉
-
-
支付金额:¥{{ selectedPrice }}
+
+
+
购买须知:
+
1、购买后长期有效,不支持退款;
+
2、同一账号点数可在小程序、H5、PC端使用;
+
3、点数适用于 AI 图片生成、高清修复、证件照、AI绘画等功能。
+
-
-
-
-
+
+
+
+
+
+
+
+ 微信扫码支付
+ 选择套餐后点击「确认充值」
+ 这里会生成支付二维码
+
+
+
+
+
+ {{ payState === 'success' ? '充值成功' : '微信扫码支付' }}
+
+
+ {{ payStateText }}
+
+
+
+
+
+ 生成二维码...
+
+
+
+
+ +{{ current?.points || 0 }} 点已到账
+
+
+
+
+
+
+
+
+ 生成失败,请重试
+
+
+
+
+ 支付金额:¥{{ current?.price_yuan || '0.00' }}
+
+
+
+
+
+
+
+
+
@@ -113,130 +177,151 @@
import VueQr from 'vue-qr'
useSeoMeta({
- title: '充值中心 - 奇想宇宙',
- description: '选择套餐,为AI创作充值点数',
+ title: '点数充值 - AI在线',
+ description: '选择套餐充值点数,点数长期有效、多端通用',
})
-const { userInfo, isLogin } = useUser()
+const { userInfo, isLogin, showLogin, refreshBalance } = useUser()
const { post } = useApi()
+const toast = useToast()
-// 套餐列表
-const products = ref([])
-const selectedId = ref(null)
-const selectedPrice = ref(0)
+const packages = ref([])
+const current = ref(null)
+const loading = ref(true)
+const balance = reactive({ points: 0 })
-// 支付状态
-const isPaying = ref(false)
-const payState = ref('idle') // idle | loading | pending | success | error
+// idle | loading | pending | success | error
+const payState = ref('idle')
const codeUrl = ref('')
const orderNo = ref('')
let pollingTimer = null
-// 获取套餐列表
-async function loadProducts() {
+const balanceText = computed(() => (Number(balance.points) < 0 ? '不限' : String(Number(balance.points) || 0)))
+const balanceUnit = computed(() => (balanceText.value === '不限' ? '' : '点'))
+const buyText = computed(() => {
+ if (!current.value) return '请选择套餐'
+ return `确认充值(${current.value.price_yuan}元)`
+})
+const payStateText = computed(() => {
+ if (payState.value === 'loading') return '正在创建订单...'
+ if (payState.value === 'pending') return '请使用微信扫描二维码完成支付'
+ if (payState.value === 'success') return '点数已到账,可以开始创作了'
+ return '订单创建失败'
+})
+
+function showMsg(msg, color = 'error') {
+ toast.add({ title: msg, color })
+}
+
+// ==================== 套餐 & 余额 ====================
+async function loadPackages() {
+ loading.value = true
try {
- const res = await post('/vip/getproductlist')
- if (res.code === 0) {
- products.value = (res.list || res.data || []).map((p) => ({
- ...p,
- point: p.point || p.points,
- }))
- }
+ const res = await post('/point/packagelist')
+ packages.value = res.list || []
+ current.value = packages.value.find(p => p.badge === '推荐') || packages.value[0] || null
+ } catch (e) {
+ packages.value = []
+ showMsg(e.message || '套餐加载失败')
+ } finally {
+ loading.value = false
+ }
+}
+
+async function loadBalance() {
+ if (!isLogin.value) return
+ try {
+ const res = await post('/point/balance')
+ balance.points = res.data?.points || 0
+ userInfo.value = { ...userInfo.value, balance: balance.points }
} catch {
- products.value = []
+ // 忽略
}
}
-// 选择套餐
-function selectProduct(pkg) {
- selectedId.value = pkg.id
- selectedPrice.value = pkg.price
+function selectPackage(pkg) {
+ if (payState.value === 'pending' || payState.value === 'loading') cancelPay()
+ current.value = pkg
}
-// 开始支付(对齐老 PC 的 buy 流程)
+// ==================== 支付(PC 走微信 NATIVE 扫码) ====================
async function startPay() {
- if (!selectedId.value) return
+ if (!current.value) return
+ if (!isLogin.value) {
+ showLogin.value = true
+ return
+ }
payState.value = 'loading'
- isPaying.value = true
-
+ codeUrl.value = ''
try {
- const res = await post('/vip/createorder', {
- product_id: selectedId.value,
- client: 1,
+ const res = await post('/point/createorder', {
+ package_id: current.value.id,
+ client: 1, // PC
})
-
- if (res.code === 0 && res.data?.code_url) {
- codeUrl.value = res.data.code_url
- orderNo.value = res.data.order_no || ''
+ const data = res.data || {}
+ // NATIVE 支付返回二维码链接(兼容多种字段命名)
+ const url = data.code_url || data.codeUrl || data.qr_code || ''
+ orderNo.value = data.order_no || ''
+ if (url) {
+ codeUrl.value = url
payState.value = 'pending'
startPolling()
} else {
payState.value = 'error'
+ showMsg(res.msg || 'PC 端支付暂不可用')
}
- } catch {
+ } catch (e) {
payState.value = 'error'
+ showMsg(e.message || '创建订单失败')
}
}
-// 轮询支付结果(对齐老 PC:每 2 秒轮询 paysuccess)
+// 轮询订单状态:服务端会主动查微信并流转订单 + 幂等入账
function startPolling() {
clearInterval(pollingTimer)
pollingTimer = setInterval(async () => {
+ if (!orderNo.value) return
try {
- const res = await post('/vip/paysuccess', { order_no: orderNo.value })
- // status == 2 表示支付成功
- if (res.code === 0 && (res.data?.status == 2 || res.data?.paid === true)) {
+ const res = await post('/point/orderstatus', { order_no: orderNo.value })
+ const d = res.data || {}
+ if (Number(d.status) === 20) {
clearInterval(pollingTimer)
payState.value = 'success'
- // 刷新余额
- await refreshUserInfo()
+ balance.points = d.balance ?? balance.points
+ refreshBalance() // 同步 header 点数
}
} catch {
- // 轮询失败不中断
+ // 单次失败不中断轮询
}
}, 2000)
}
-// 取消支付
function cancelPay() {
clearInterval(pollingTimer)
- isPaying.value = false
payState.value = 'idle'
codeUrl.value = ''
orderNo.value = ''
}
-// 支付成功完成
-function finishPaySuccess() {
- clearInterval(pollingTimer)
- isPaying.value = false
- payState.value = 'idle'
- codeUrl.value = ''
- orderNo.value = ''
- selectedId.value = null
-}
-
-// 刷新用户余额
-async function refreshUserInfo() {
- try {
- const res = await post('/user/getuserinfo')
- if (res.code === 0 && res.data) {
- userInfo.value.balance = res.data.balance || res.data.point || 0
- }
- } catch {
- // 忽略
- }
+function finishPay() {
+ cancelPay()
+ loadBalance()
}
onMounted(() => {
- loadProducts()
- if (isLogin.value) {
- refreshUserInfo()
- }
+ loadPackages()
+ loadBalance()
})
-onUnmounted(() => {
- clearInterval(pollingTimer)
+watch(isLogin, async (loggedIn) => {
+ if (!loggedIn) {
+ cancelPay()
+ balance.points = 0
+ }
+ await loadPackages()
+ if (loggedIn) await loadBalance()
})
+
+onUnmounted(() => clearInterval(pollingTimer))
diff --git a/app/pages/terms.vue b/app/pages/terms.vue
new file mode 100644
index 0000000..46f60f7
--- /dev/null
+++ b/app/pages/terms.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/pages/works.vue b/app/pages/works.vue
index ab97a55..4e00f69 100644
--- a/app/pages/works.vue
+++ b/app/pages/works.vue
@@ -1,160 +1,288 @@
-
-
我的作品
-
查看你所有的 AI 创作成果
-
-
-
-
-
+
+
-
-
+
+
+
+
+
+
登录后会同步你的作品、点数和创作记录。
+
+
+
+
+
+
+
+
-
-
-
-
-
![]()
+
+
-
- {{ work.type_name || '作品' }}
+
+
+
+
+
+
+
+
+ 名字整理中
+
+
+ {{ item.summary || item.subtitle }}
+
+
+
+
+
+
![]()
+
+
+ {{ Number(item.status) === 0 ? '生成中' : '暂无封面' }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 正在创作
+
+
+
+
+
{{ formatTime(item.add_time) }}
+
-
-
{{ work.name || '未命名' }}
-
{{ formatDate(work.created_at) }}
+
+
+
+
+
+
+
+
+
+
+
去工具箱生成第一张头像、照片或名字方案。
+
-
-
-
-
-
-
暂无作品,快去创作吧!
+
+
+ {{ list.length ? '加载更多...' : '加载中...' }}
+
+
+
+
+
没有更多了
+
-
-
-
-
+
+
diff --git a/app/pages/writing/naming.vue b/app/pages/writing/naming.vue
new file mode 100644
index 0000000..1bb08a0
--- /dev/null
+++ b/app/pages/writing/naming.vue
@@ -0,0 +1,1006 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 必填
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ birthInfoIgnored ? '已关闭,生成时不参考农历和五行' : '开启后会结合农历和五行倾向' }}
+
+
+
+
+
+
+
+
出生日期
+
{{ birthDate || '请选择日期' }}
+
+
›
+
+
+
+
出生时分
+
{{ birthClockText }}
+
+
›
+
+
+
+
+
+ {{ birthLunarText }}
+
+
+
+
+ 不知道具体时间
+
+
+
+
+
+
+
+
+
+ 选择偏好的文化来源
+
+
+
+
+
+
+
+
+
+
+
+ 用于控制生成结果的方向
+
+
+
+
+
名字字数 包含姓氏
+
+
+
+
+
+
+
+
是否叠词
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ (form.content || '').length }}/200
+
+
+
+
+
+
+
+
+
+ 可关闭出生信息参考,关闭后不结合农历和五行
+
+
+
+ 开启后可选择日期和时分,不确定时辰可选"不知道具体时间"
+
+
+
+
+
+
+
{{ balanceText }}
+
点数不足,请前往充值
+
+
+
+
+
+
+
+
+
+
+
+
+
等待生成
+
填写左侧信息并点击"开始取名"
+
AI将为您推荐最佳名字方案
+
+
+
{{ questionTitle }}
+
AI 正在分析音律、寓意和出处,内容将实时显示...
+
+ {{ streamContent }}
+
+
+
+
+
+
生成失败
+
{{ resultState.errorMsg }}
+
重试
+
+
+
+
本次没有可用名字
+
{{ result.summary || 'AI 返回的候选名未通过姓名可用性筛选,请调整条件或重新生成。' }}
+
重新生成
+
+
+
+
+
+ {{ item.name }}
+ {{ item.score }}分
+
+
{{ item.pinyin }}
+
+
+
+ {{ field.label }}
+ {{ item[field.key] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 日期下方为农历
+
+ {{ calendarRangeText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ timePreviewText }}
+
{{ shichenInfo.name }} · {{ shichenInfo.range }}
+
+
+
+
+
+
+
+
+
+
+
+
+
快捷选择分钟
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/utils/nameParse.js b/app/utils/nameParse.js
new file mode 100644
index 0000000..9c0c9d1
--- /dev/null
+++ b/app/utils/nameParse.js
@@ -0,0 +1,112 @@
+/**
+ * AI 取名结果解析(从 ai-name 页面抽出,供创作页与详情弹窗共用)
+ *
+ * 后端返回的 content 可能是:
+ * 1) JSON({ summary, names: [] })
+ * 2) JSON Lines(每行一个 {type:'summary'|'name'})
+ * 3) 【名字】+ 拼音/评分/出处… 的标记文本
+ */
+
+export function parseNameResult(content) {
+ if (!content) return { summary: '', names: [] }
+ let text = String(content).trim()
+ .replace(/^```json\s*/i, '')
+ .replace(/^```\s*/i, '')
+ .replace(/```$/i, '')
+ .trim()
+
+ const line = parseJsonLines(text)
+ if (line.names.length) return line
+
+ const marked = parseMarkedText(text)
+ if (marked.names.length) return marked
+
+ const s = text.indexOf('{')
+ const e = text.lastIndexOf('}')
+ if (s >= 0 && e > s) text = text.substring(s, e + 1)
+ try {
+ const d = JSON.parse(text)
+ return { summary: d.summary || '', names: Array.isArray(d.names) ? d.names : [] }
+ } catch {
+ return { summary: text, names: [] }
+ }
+}
+
+function parseJsonLines(text) {
+ const d = { summary: '', names: [] }
+ String(text || '').split(/\r?\n/).forEach((l) => {
+ l = l.trim()
+ if (!l) return
+ try {
+ const i = JSON.parse(l)
+ if (i.type === 'summary') d.summary = i.content || ''
+ if (i.type === 'name' && i.data) d.names.push(i.data)
+ } catch { /* 忽略非 JSON 行 */ }
+ })
+ return d
+}
+
+function parseMarkedText(text) {
+ const d = { summary: '', names: [] }
+ const v = String(text || '')
+ const sm = v.match(/整体建议[::]([\s\S]*?)(?=\n\s*【|$)/)
+ if (sm) d.summary = sm[1].trim()
+ const reg = /【([^】]+)】([\s\S]*?)(?=\n\s*【|$)/g
+ let m
+ while ((m = reg.exec(v))) {
+ const body = m[2] || ''
+ const name = resolveNameBlockTitle(m[1], body)
+ d.names.push({
+ name,
+ pinyin: matchField(body, '拼音'),
+ score: parseInt(matchField(body, '评分')) || 90,
+ source: matchField(body, '出处'),
+ wuxing: matchField(body, '五行'),
+ meaning: matchField(body, '寓意'),
+ tone: matchField(body, '音律'),
+ reason: matchField(body, '推荐'),
+ })
+ }
+ d.names = d.names.filter(i => i.name).slice(0, 5)
+ return d
+}
+
+function resolveNameBlockTitle(title, body) {
+ const n = String(title || '').trim()
+ if (!['姓名', '名字', '候选名'].includes(n)) return n
+ const fr = /^(拼音|评分|出处|五行|寓意|音律|推荐)[::]/
+ const line = String(body || '').split(/\r?\n/).map(i => i.trim()).find(i => i && !fr.test(i))
+ return line ? line.replace(/^姓名[::]/, '').trim() : n
+}
+
+function matchField(body, label) {
+ const r = new RegExp(`${label}[::]([^\\n\\r]*)`)
+ const m = String(body || '').match(r)
+ return m ? m[1].trim() : ''
+}
+
+/** 名字详情展示字段 */
+export const NAME_FIELDS = [
+ { key: 'source', label: '出处' },
+ { key: 'wuxing', label: '五行' },
+ { key: 'meaning', label: '寓意' },
+ { key: 'tone', label: '音律' },
+ { key: 'reason', label: '推荐' },
+]
+
+/** 把名字结果拼成可复制文本 */
+export function buildNameCopyText(result) {
+ const names = result?.names || []
+ if (!names.length) return ''
+ const lines = []
+ if (result.summary) lines.push(result.summary)
+ names.forEach((item) => {
+ lines.push(`${item.name}${item.score ? `(${item.score}分)` : ''}`)
+ NAME_FIELDS.forEach((f) => {
+ if (item[f.key]) lines.push(`${f.label}:${item[f.key]}`)
+ })
+ if (item.pinyin) lines.push(`拼音:${item.pinyin}`)
+ lines.push('')
+ })
+ return lines.join('\n')
+}
diff --git a/app/utils/richtext.js b/app/utils/richtext.js
new file mode 100644
index 0000000..e02539c
--- /dev/null
+++ b/app/utils/richtext.js
@@ -0,0 +1,94 @@
+/**
+ * 后台富文本清洗(协议、公告等静态内容)
+ *
+ * 背景:html 表里的正文是 Word / 旧编辑器导出的,直接渲染有两类问题:
+ * 1. 大量 `
` 空段落,叠加段间距后出现大片空白;
+ * 2. 几乎每个 span 都带内联 `font-family:宋体;font-size:14px;color:#000`,
+ * 内联样式优先级最高,会盖掉页面样式,暗色模式下黑字配深底完全不可读。
+ *
+ * 处理方式:删掉空段落,剥掉会影响排版与主题的内联属性,保留加粗、下划线等语义样式。
+ * 与 ai_uniapp_v2/utils/richtext.js 保持同一套规则(两个仓库独立,无法共享模块)。
+ */
+
+const DROP_STYLE_PROPS = [
+ 'font-family',
+ 'font-size',
+ 'color',
+ 'background',
+ 'background-color',
+ 'line-height',
+ 'margin',
+ 'margin-top',
+ 'margin-bottom',
+ 'margin-left',
+ 'margin-right',
+ 'padding',
+ 'padding-top',
+ 'padding-bottom',
+ 'width',
+ 'height',
+]
+
+/** 判断一段 html 是否只有空白内容(空格、 、
、空标签) */
+function isBlankFragment(html) {
+ const text = String(html || '')
+ .replace(/
/gi, '')
+ .replace(/<[^>]+>/g, '')
+ .replace(/ | |\u00a0/gi, '')
+ .replace(/\s/g, '')
+ return text.length === 0
+}
+
+function cleanStyleValue(style) {
+ return String(style || '')
+ .split(';')
+ .map(item => item.trim())
+ .filter(Boolean)
+ .filter((item) => {
+ const prop = item.split(':')[0].trim().toLowerCase()
+ return prop && !DROP_STYLE_PROPS.includes(prop)
+ })
+ .join('; ')
+}
+
+/**
+ * @param {string} html 后台返回的富文本
+ * @return {string} 清洗后的富文本
+ */
+export function cleanRichText(html) {
+ if (!html) return ''
+ let text = String(html)
+
+ // Word 导出的无用标签与内嵌样式表
+ text = text
+ .replace(/<\/?o:p[^>]*>/gi, '')
+ .replace(//g, '')
+ .replace(/
]*>/gi, '')
+ .replace(/