diff --git a/docs/superpowers/plans/2026-09-22-yangmadao-aigc.md b/docs/superpowers/plans/2026-09-22-yangmadao-aigc.md new file mode 100644 index 0000000..4d2cef0 --- /dev/null +++ b/docs/superpowers/plans/2026-09-22-yangmadao-aigc.md @@ -0,0 +1,58 @@ +# Yangmadao AIGC Migration Implementation Plan + +> **For agentic workers:** Use subagent-driven-development for the isolated AIGC subtree and requesting-code-review for final review. Keep all mutations outside that subtree with the root agent. + +**Goal:** 将 duohua-new 的完整 AIGC 迁入养马岛独立客户端,接入旅行记录入口及 Tailwind,客户端配置不变。 +**Architecture:** 通过 Git 指定来源提交迁移 AIGC,复用目标认证和请求;受控隐私、导航封装在分包中,Tailwind 使用 tw: 前缀隔离旧工具类。 +**Tech Stack:** Existing uni-app/Vue 3/Vite 5, Tailwind CSS 4, weapp-tailwindcss, Yarn 1, node:test. + +## Task 1: Baseline and immutable client configuration + +- [x] Read both branch versions and compare package versions and component contracts. +- [x] Run `node --test scripts/update-appid.test.cjs` (5 passing). +- [x] Confirm target branch clean and record source/target commit hashes in design. + +## Task 2: AIGC subtree + +- [x] Copy every tracked file below `src/pages-aigc/` from source commit using `git show :` and preserve UTF-8. +- [x] Copy source `src/components/Privacy/` to `src/pages-aigc/components/Privacy/`; copy source `src/components/TopNavBar/` to `src/pages-aigc/components/TopNavBar/`; update only AIGC imports. +- [x] Convert existing Tailwind template utilities in transferred files to complete `tw:` prefixed classes, adding explicit box sizing where preflight was relied upon. Preserve component-specific CSS selectors and state classes. +- [x] Adapt recharge footer `$theme-color-600` to `$theme-color-500`; leave `src/uni.scss` unchanged. +- [x] Keep task creation, polling, payment recovery, sharing and image upload behavior from source. + +## Task 3: Integration and build pipeline + +- [x] Add nine source AIGC page registrations to target `src/pages.json` without altering existing routes. +- [x] Add `travelAIGC: "Command.travelAIGC"` to target `src/model/ChatModel.js`; change first quick access title/type and login-gated destination. +- [x] Transfer source GeneratorPhotoComponent Vue and styles, prefix Tailwind classes; retain ChatMainList's existing generator render branch. +- [x] Add source Tailwind dependencies and postinstall to package.json, retain target scripts. Update Yarn lock consistently, do not regenerate package-lock.json. +- [x] Add `src/app.css` with theme and utilities imports using `prefix(tw)`, omit global preflight, scan only application source, import from main.js. +- [x] Add PostCSS Tailwind and UnifiedViteWeappTailwindcssPlugin in vite.config.js; set cssEntries, rem2rpx and disable injected global cssPreflight; retain original assetFileNames. +- [x] Add sensitive request logging support without changing base URL, token selection or clientId; import existing removeAccessToken function used on 424. + +## Task 4: Behavior checks and review + +- [x] Before integration code changes, add node:test execution tests for quick access entry, generator data variants and request confidentiality; run to see intended failures. +- [x] Run `node --test scripts/update-appid.test.cjs scripts/aigc-migration.test.cjs` after edits. +- [x] Run `yarn build:mp-weixin`, inspect all AIGC routes and generated prefixed utility selectors, check no unprefixed Tailwind spacing and global reset override legacy SCSS. +- [x] Run development compile once if production class conversion requires comparison. +- [x] Run `git diff --check` and compare protected client files with target baseline. +- [x] Request independent review while root performs final build/artifact inspection; resolve findings and rerun affected checks. +- [x] Leave completed changes on yangmadao-zhinian-8 for user inspection; report actual validation and external integration limits. + + +## Completion evidence + +- Completed in the existing clean `yangmadao-zhinian-8` checkout after the user switched branches; no additional worktree was created and no changes were made to `duohua-new`. +- Added 87 AIGC files (83 original plus isolated Privacy/TopNavBar components). +- Review identified target router lacked internal path support. Added source-equivalent HTTP(S)/internal routing and a real router regression test. +- Fixed inherited share initial null rendering and waited for the target's existing `getEvnUrl()` before requesting the shared work. Environment configuration files and addresses remain unchanged. +- Fixed navbar deep selectors after class prefixing using a stable semantic class. +- `node --test scripts/update-appid.test.cjs scripts/aigc-migration.test.cjs`: 14 passed. +- `yarn build:mp-weixin`: passed; complete output for all nine AIGC pages verified. +- `yarn dev:mp-weixin`: initial compilation passed; watcher stopped after verification. +- Generated CSS verified: legacy `.m-4` remains 4px and `.w-50` remains 50%; `tw:` utilities are escaped correctly; no global box-sizing reset was introduced. +- `git diff --check`: passed. New source files also pass UTF-8/LF/trailing whitespace checks. +- Protected client files are byte-identical to target baseline. No versions changed for pre-existing ranges retained in yarn.lock; package-lock.json is untouched. +- Final independent review found no remaining blocking issues. +- Changes are uncommitted on the requested target branch. Actual generation, WeChat authorization, sharing and payment still need target-client device/backend testing. diff --git a/docs/superpowers/specs/2026-09-22-yangmadao-aigc-design.md b/docs/superpowers/specs/2026-09-22-yangmadao-aigc-design.md new file mode 100644 index 0000000..24bdd3e --- /dev/null +++ b/docs/superpowers/specs/2026-09-22-yangmadao-aigc-design.md @@ -0,0 +1,25 @@ +# 养马岛 AIGC 定向迁移 + +来源:`duohua-new` 的 `a37eaa6e4cd776ee9d5e423f63a3799ac0d7e45c`。 +目标基线:`yangmadao-zhinian-8` 的 `340ecc9165684467c01a1f803e21040f86ba3a8f`。 + +用户已确认迁移功能,强调目标是独立客户端。禁止修改 appid、clientId、环境地址和主题配置。保留目标分支的 manifest.json、project.config.json、constant/base.js、request/base/config.js、request/base/baseUrl.js、uni.scss、client-configs.json 及客户切换脚本。 + +## 实施范围 + +1. 迁入来源分支 pages-aigc 全部九个页面及其内部依赖,注册为普通分包。 +2. ChatQuickAccess 首项快速预定改为旅行记录,使用 Command.travelAIGC,checkToken 成功后 navigateTo('/pages-aigc/home/home');其他快捷项沿用目标分支。 +3. 更新已存在的 GeneratorPhotoComponent,兼容 componentNameParams 和 aigc 数据;沿用后端 jumpUrl 的内外链路由契约。核验 ChatMainList 的导入、toolCall 传递与渲染条件;补齐目标路由缺少的 HTTP(S) 外链与内部页面分流。 +4. 补齐 Tailwind CSS 4、PostCSS、weapp-tailwindcss、postinstall、CSS 入口。新工具类使用 tw: 前缀,省略全局 preflight,避免覆盖旧 SCSS 的 w-50、m-4 等不同语义。保留原 Vite 静态资源命名规则和客户端脚本。 +5. 新版受控隐私组件与新版导航组件放入 AIGC 内部,保持主包旧组件契约;将迁入代码中的 theme-color-600 引用适配至已有 theme-color-500,不修改主题配置。 +6. 请求层仅增加 AIGC 所需的 sensitive 日志屏蔽能力和补齐已有 424 分支缺少的 removeAccessToken 导入。保留原客户端与地址选择逻辑。 + +## 验证 + +- 先运行目标已有的 scripts/update-appid.test.cjs 建立基线。 +- 行为测试覆盖旅行记录登录门控、卡片新旧数据与路由、敏感请求与公开分享请求。 +- 微信开发与生产编译检查新分包、prefixed 工具类、旧样式以及产物是否齐全。 +- 对上述受保护配置逐文件与目标基线比较,必须完全一致。 +- 真实生成、充值支付、分享打开及相册授权需要目标客户端的微信真机/后端联调,不发起真实支付。 + +收尾修复:分享页标题在数据加载前安全回退;分享请求先等待目标原有 getEvnUrl(),避免冷启动抢跑。内部导航组件采用 nav-bar-content 稳定选择器,保持页面原有间距。 diff --git a/package.json b/package.json index c100ef3..c98dd3f 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "YGChatCS", "version": "1.0.0", "scripts": { + "postinstall": "node ./node_modules/weapp-tailwindcss/bin/weapp-tailwindcss.js patch", "dev:app": "uni -p app", "dev:app-android": "uni -p app-android", "dev:app-ios": "uni -p app-ios", @@ -73,9 +74,13 @@ "@dcloudio/uni-cli-shared": "3.0.0-4070620250821001", "@dcloudio/uni-stacktracey": "3.0.0-4070620250821001", "@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001", + "@tailwindcss/postcss": "4.3.0", "@vue/runtime-core": "^3.4.21", "@vue/tsconfig": "^0.1.3", "sass": "1.58.3", - "vite": "5.2.8" - } + "vite": "5.2.8", + "tailwindcss": "4.3.0", + "weapp-tailwindcss": "4.12.0" + }, + "packageManager": "yarn@1.22.22" } diff --git a/project.config.json b/project.config.json index d102f1b..56e87fc 100644 --- a/project.config.json +++ b/project.config.json @@ -1,5 +1,5 @@ { - "appid": "wx5e79df5996572539", + "appid": "wxdf49bb973417cde3", "compileType": "miniprogram", "libVersion": "3.8.10", "packOptions": { diff --git a/scripts/aigc-migration.test.cjs b/scripts/aigc-migration.test.cjs new file mode 100644 index 0000000..c6d4b8a --- /dev/null +++ b/scripts/aigc-migration.test.cjs @@ -0,0 +1,191 @@ +const assert = require("node:assert/strict"); +const { readFileSync } = require("node:fs"); +const path = require("node:path"); +const vm = require("node:vm"); +const { execFileSync } = require("node:child_process"); +const { test } = require("node:test"); + +const root = path.resolve(__dirname, ".."); +const read = (file) => readFileSync(path.join(root, file), "utf8"); + +// Execute the actual page handlers while substituting only platform/lifecycle APIs. +function loadScript(file, names, dependencies = {}) { + const source = read(file); + const script = file.endsWith(".vue") + ? source.match(/ + + diff --git a/src/pages-aigc/agreement/styles/index.scss b/src/pages-aigc/agreement/styles/index.scss new file mode 100644 index 0000000..ac4f7d0 --- /dev/null +++ b/src/pages-aigc/agreement/styles/index.scss @@ -0,0 +1,54 @@ +.aigc-agreement-page { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + overflow: hidden; + background: #ffffff; +} + +.aigc-agreement-scroll { + height: 0; + flex: 1 1 auto; + min-height: 0; + background: #ffffff; +} + +.aigc-agreement-document { + min-height: 100%; + padding: 20px 16px calc(24px + env(safe-area-inset-bottom)); + box-sizing: border-box; +} + +.aigc-agreement-state { + min-height: 240px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px; + color: #7b8798; + font-size: 14px; + line-height: 22px; + box-sizing: border-box; +} + +.aigc-agreement-retry { + width: 120px; + height: 42px; + display: flex; + align-items: center; + justify-content: center; + margin: 16px 0 0; + padding: 0; + border-radius: 999px; + background: #102942; + color: #ffffff; + font-size: 14px; + line-height: 20px; + font-weight: 800; +} + +.aigc-agreement-retry::after { + border: 0; +} diff --git a/src/pages-aigc/components/AigcTopBar/index.vue b/src/pages-aigc/components/AigcTopBar/index.vue new file mode 100644 index 0000000..8299438 --- /dev/null +++ b/src/pages-aigc/components/AigcTopBar/index.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/pages-aigc/components/AigcTopBar/styles/index.scss b/src/pages-aigc/components/AigcTopBar/styles/index.scss new file mode 100644 index 0000000..06a67df --- /dev/null +++ b/src/pages-aigc/components/AigcTopBar/styles/index.scss @@ -0,0 +1,137 @@ +@font-face { + font-family: znicons; + src: url("@/static/fonts/znicons.ttf"); +} + +.aigc-topbar { + position: relative; + z-index: 3; + flex: 0 0 auto; + padding: var(--aigc-status-height) 12px 8px; + box-sizing: border-box; + background: linear-gradient( + 180deg, + rgba(235, 249, 255, 0.92) 0%, + rgba(240, 251, 247, 0.72) 66%, + rgba(240, 251, 247, 0) 100% + ); +} + +.aigc-nav-row { + height: var(--aigc-nav-height); + display: flex; + align-items: center; + gap: 10px; + padding-right: var(--aigc-capsule-safe-width); + box-sizing: border-box; +} + +.aigc-topbar-back { + flex: 0 0 36px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + margin: 0; + padding: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.86); + color: #172033; + box-shadow: 0 8px 18px rgba(18, 48, 56, 0.08); +} + +.aigc-back-icon { + display: flex; + align-items: center; + justify-content: center; + transform: translateX(-1px); +} + +.aigc-back-icon.is-home { + transform: none; +} + +.aigc-topbar-actions { + flex: 1 1 auto; + width: auto; + min-width: 0; + height: 36px; + display: grid; + grid-template-columns: minmax(0, 1fr) 36px; + align-items: center; + gap: 6px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.86); + box-shadow: 0 8px 18px rgba(18, 48, 56, 0.08); + box-sizing: border-box; +} + +.aigc-history-button, +.aigc-wallet-button { + height: 36px; + min-width: 0; + display: flex; + align-items: center; + margin: 0; + padding: 0; + border-radius: 50px; + box-sizing: border-box; +} + +.aigc-history-button { + width: 36px; + flex: 0 0 36px; + justify-content: center; + border-radius: 50%; + background: transparent; + color: #436783; +} + +.aigc-history-icon { + display: flex; + align-items: center; + justify-content: center; +} + +.aigc-wallet-button { + justify-content: space-between; + gap: 6px; + padding: 0 8px; + background: #102942; + color: #ffffff; +} + +.aigc-wallet-label { + height: 24px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + padding: 0 8px; + border-radius: 999px; + background: rgba(32, 207, 117, 0.18); + color: #50e093; + font-size: 10px; + line-height: 24px; + font-weight: 900; + white-space: nowrap; +} + +.aigc-wallet-points { + min-width: 0; + display: inline-flex; + align-items: baseline; + overflow: hidden; + color: #ffffff; + font-size: 10px; + line-height: 1; + font-weight: 800; + white-space: nowrap; +} + +.aigc-wallet-number { + margin-right: 2px; + font-size: 15px; + line-height: 1; + font-weight: 900; +} diff --git a/src/pages-aigc/components/Privacy/index.vue b/src/pages-aigc/components/Privacy/index.vue new file mode 100644 index 0000000..b6f38fd --- /dev/null +++ b/src/pages-aigc/components/Privacy/index.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/pages-aigc/components/Privacy/styles/index.scss b/src/pages-aigc/components/Privacy/styles/index.scss new file mode 100644 index 0000000..d0ecc42 --- /dev/null +++ b/src/pages-aigc/components/Privacy/styles/index.scss @@ -0,0 +1,66 @@ +.privacy { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + z-index: 9999; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: end; +} + +.title { + font-size: 20px; + font-weight: bold; + color: #000; + padding-bottom: 20rpx; +} + +.content { + position: relative; + background-color: #fff; + padding: 15px; + border-radius: 20px 20px 0 0; +} + +.des { + line-height: 21px; +} + +.link { + color: #007aff; +} + +.btns { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 0 40px; +} + +.reject, +.agree { + border-radius: $uni-border-radius-50px; + width: 45%; + border: none; + font-size: 18px; + margin: 0; + + &::after { + border: none; + } +} + +.reject { + color: #000; + background-color: #f5f5f5; + border-radius: $uni-border-radius-50px; +} + +.agree { + color: #fff; + background-color: #007aff; +} diff --git a/src/pages-aigc/components/TopNavBar/index.vue b/src/pages-aigc/components/TopNavBar/index.vue new file mode 100644 index 0000000..7273aca --- /dev/null +++ b/src/pages-aigc/components/TopNavBar/index.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/src/pages-aigc/components/TopNavBar/styles/index.scss b/src/pages-aigc/components/TopNavBar/styles/index.scss new file mode 100644 index 0000000..9b8ed1d --- /dev/null +++ b/src/pages-aigc/components/TopNavBar/styles/index.scss @@ -0,0 +1,48 @@ +// TopNavBar 组件样式 +.top-nav-bar { + width: 100%; + background-color: $theme-color-100; + + &--fixed { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 999; + } +} + +.nav-bar-left, +.nav-bar-right { + width: 30px; + height: 30px; +} + +.nav-bar-center { + flex: 1; + padding: 0 20px; // 为左右按钮留出空间 + + // 居中对齐(默认) + &--center { + justify-content: center; + + .nav-bar-title { + text-align: center; + } + } + + // 左对齐 + &--left { + justify-content: flex-start; + padding-left: 20px; // 为返回按钮留出更多空间 + + .nav-bar-title { + text-align: left; + } + } +} + +// 固定导航栏时的页面内容适配 +.page-with-fixed-navbar { + padding-top: calc(var(--status-bar-height, 44px) + 44px); +} diff --git a/src/pages-aigc/constant/aigc.js b/src/pages-aigc/constant/aigc.js new file mode 100644 index 0000000..f7557eb --- /dev/null +++ b/src/pages-aigc/constant/aigc.js @@ -0,0 +1,29 @@ +import { currentClientType } from "@/constant/base"; + +const CLIENT_TYPE = currentClientType(); +const AIGC_CONSENT_AGREED = `${CLIENT_TYPE}_AIGC_CONSENT_AGREED`; +const AIGC_GIFT_POINTS_TOAST_CLOSED = `${CLIENT_TYPE}_AIGC_GIFT_POINTS_TOAST_CLOSED`; + +export const getAigcConsentAgreed = () => { + return uni.getStorageSync(AIGC_CONSENT_AGREED); +}; + +export const setAigcConsentAgreed = (agreed = true) => { + return uni.setStorageSync(AIGC_CONSENT_AGREED, agreed); +}; + +export const removeAigcConsentAgreed = () => { + return uni.removeStorageSync(AIGC_CONSENT_AGREED); +}; + +export const getAigcGiftPointsToastClosed = () => { + return uni.getStorageSync(AIGC_GIFT_POINTS_TOAST_CLOSED); +}; + +export const setAigcGiftPointsToastClosed = (closed = true) => { + return uni.setStorageSync(AIGC_GIFT_POINTS_TOAST_CLOSED, closed); +}; + +export const removeAigcGiftPointsToastClosed = () => { + return uni.removeStorageSync(AIGC_GIFT_POINTS_TOAST_CLOSED); +}; diff --git a/src/pages-aigc/detail/components/ContinuationCard/index.vue b/src/pages-aigc/detail/components/ContinuationCard/index.vue new file mode 100644 index 0000000..5f678c6 --- /dev/null +++ b/src/pages-aigc/detail/components/ContinuationCard/index.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/pages-aigc/detail/components/ContinuationCard/styles/index.scss b/src/pages-aigc/detail/components/ContinuationCard/styles/index.scss new file mode 100644 index 0000000..321d6fa --- /dev/null +++ b/src/pages-aigc/detail/components/ContinuationCard/styles/index.scss @@ -0,0 +1,64 @@ +.continuation-card { + min-height: 84px; + display: grid; + grid-template-columns: minmax(0, 1fr) 116px; + align-items: center; + gap: 8px; + margin-top: 12px; + padding: 14px; + border-radius: 18px; + background: + linear-gradient(135deg, rgba(32, 207, 117, 0.14), rgba(255, 255, 255, 0.82)), + rgba(255, 255, 255, 0.82); + box-sizing: border-box; +} + +.continuation-copy { + min-width: 0; +} + +.continuation-title, +.continuation-desc, +.continuation-cost { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.continuation-title { + color: #172033; + font-size: 14px; + line-height: 19px; + font-weight: 900; +} + +.continuation-desc { + margin-top: 4px; + color: #667386; + font-size: 11px; + line-height: 16px; + font-weight: 800; +} + +.continuation-button { + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 999px; + background: #172951; + color: #ffffff; + font-size: 12px; + line-height: 40px; + font-weight: 900; + white-space: nowrap; +} + +.continuation-cost { + grid-column: 1 / -1; + color: #12a765; + font-size: 10px; + line-height: 13px; + font-weight: 900; +} diff --git a/src/pages-aigc/detail/components/RegenerateConfirmPopup/index.vue b/src/pages-aigc/detail/components/RegenerateConfirmPopup/index.vue new file mode 100644 index 0000000..e05ed22 --- /dev/null +++ b/src/pages-aigc/detail/components/RegenerateConfirmPopup/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/pages-aigc/detail/components/RegenerateConfirmPopup/styles/index.scss b/src/pages-aigc/detail/components/RegenerateConfirmPopup/styles/index.scss new file mode 100644 index 0000000..104e7d1 --- /dev/null +++ b/src/pages-aigc/detail/components/RegenerateConfirmPopup/styles/index.scss @@ -0,0 +1,87 @@ +.regenerate-dialog { + width: 318px; + max-width: calc(100vw - 56px); + padding: 30px 26px 24px; + border-radius: 24px; + background: #ffffff; + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.26); + box-sizing: border-box; +} + +.regenerate-dialog-title { + display: block; + color: #111827; + font-size: 20px; + line-height: 28px; + font-weight: 900; + text-align: center; +} + +.regenerate-dialog-desc { + display: block; + margin-top: 14px; + color: #667386; + font-size: 14px; + line-height: 22px; + font-weight: 800; + text-align: center; +} + +.regenerate-dialog-balance { + min-height: 36px; + display: flex; + align-items: center; + justify-content: center; + margin-top: 14px; + padding: 8px 12px; + border-radius: 14px; + background: #effaf5; + color: #0fa567; + font-size: 12px; + line-height: 18px; + font-weight: 900; + text-align: center; + box-sizing: border-box; +} + +.regenerate-dialog-actions { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; + margin-top: 28px; +} + +.regenerate-dialog-button { + min-width: 0; + height: 54px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 999px; + font-size: 16px; + line-height: 22px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; + box-sizing: border-box; +} + +.regenerate-dialog-button.is-cancel { + border: 1px solid #e2e6ea; + background: #ffffff; + color: #172033; +} + +.regenerate-dialog-button.is-primary { + background: #061e34; + color: #ffffff; +} + +.regenerate-dialog-button.is-pressed { + opacity: 0.86; +} + +.regenerate-dialog-button.is-disabled { + opacity: 0.6; +} diff --git a/src/pages-aigc/detail/components/ResultActions/index.vue b/src/pages-aigc/detail/components/ResultActions/index.vue new file mode 100644 index 0000000..9a22d97 --- /dev/null +++ b/src/pages-aigc/detail/components/ResultActions/index.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/pages-aigc/detail/components/ResultActions/styles/index.scss b/src/pages-aigc/detail/components/ResultActions/styles/index.scss new file mode 100644 index 0000000..9f789a0 --- /dev/null +++ b/src/pages-aigc/detail/components/ResultActions/styles/index.scss @@ -0,0 +1,41 @@ +.result-actions { + display: grid; + grid-template-columns: 1fr 1.2fr; + gap: 10px; + margin-top: 18px; +} + +.result-action { + width: 100%; + height: 48px; + margin: 0; + padding: 0; + box-sizing: border-box; + border: 0; + display: flex; + align-items: center; + justify-content: center; + border-radius: 999px; + font-size: 15px; + line-height: 20px; + font-weight: 900; +} + +.result-action::after { + border: 0; +} + +.result-action.is-primary { + background: #20cf75; + color: #ffffff; + box-shadow: 0 8px 16px rgba(32, 207, 117, 0.18); +} + +.result-action.is-secondary { + background: rgba(255, 255, 255, 0.78); + color: #526073; +} + +.result-action.is-disabled { + opacity: 0.65; +} diff --git a/src/pages-aigc/detail/components/ResultMeta/index.vue b/src/pages-aigc/detail/components/ResultMeta/index.vue new file mode 100644 index 0000000..03092ae --- /dev/null +++ b/src/pages-aigc/detail/components/ResultMeta/index.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/src/pages-aigc/detail/components/ResultMeta/styles/index.scss b/src/pages-aigc/detail/components/ResultMeta/styles/index.scss new file mode 100644 index 0000000..9fab3df --- /dev/null +++ b/src/pages-aigc/detail/components/ResultMeta/styles/index.scss @@ -0,0 +1,39 @@ +.result-meta { + margin-top: 14px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; +} + +.result-meta-item { + min-width: 0; + height: 62px; + padding: 10px 8px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.78); + color: #172033; + font-size: 11px; + line-height: 15px; + font-weight: 900; + text-align: center; + box-sizing: border-box; +} + +.result-meta-label, +.result-meta-value { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.result-meta-label { + margin-bottom: 5px; + color: #8c98a8; + font-size: 10px; + line-height: 13px; +} + +.result-meta-value { + color: #173252; +} diff --git a/src/pages-aigc/detail/components/ResultPreview/index.vue b/src/pages-aigc/detail/components/ResultPreview/index.vue new file mode 100644 index 0000000..888de14 --- /dev/null +++ b/src/pages-aigc/detail/components/ResultPreview/index.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/pages-aigc/detail/components/ResultPreview/styles/index.scss b/src/pages-aigc/detail/components/ResultPreview/styles/index.scss new file mode 100644 index 0000000..19bf559 --- /dev/null +++ b/src/pages-aigc/detail/components/ResultPreview/styles/index.scss @@ -0,0 +1,164 @@ +.result-preview { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + box-sizing: border-box; + border-radius: 26px; + background: var(--result-accent, #0a4d46); + color: #ffffff; + box-shadow: 0 18px 34px rgba(17, 35, 49, 0.12); +} + +.result-preview-image { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + display: block; +} + +.result-preview-shade { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: linear-gradient( + 180deg, + rgba(0, 0, 0, 0.02) 0%, + rgba(0, 0, 0, 0.16) 48%, + rgba(0, 0, 0, 0.74) 100% + ); + pointer-events: none; + z-index: 1; +} + +.result-preview-caption { + position: absolute; + right: 18px; + bottom: 18px; + left: 18px; + pointer-events: none; + z-index: 2; +} + +.result-video-state { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 4; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; +} + +.result-video-state.is-loading { + pointer-events: none; + background: rgba(9, 25, 39, 0.2); +} + +.result-video-state.is-ended, +.result-video-state.is-error { + background: rgba(9, 25, 39, 0.52); +} + +.result-video-loading-icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(9, 25, 39, 0.48); + animation: result-video-spin 1s linear infinite; +} + +.result-video-state-text, +.result-video-error-title { + color: #ffffff; + font-size: 13px; + line-height: 20px; + font-weight: 700; +} + +.result-video-error-title { + font-size: 15px; +} + +.result-video-state-action { + min-width: 132px; + height: 48px; + padding: 0 20px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + border-radius: 24px; + background: rgba(255, 255, 255, 0.94); + color: #17324d; + font-size: 14px; + line-height: 20px; + font-weight: 800; + box-shadow: 0 8px 22px rgba(9, 25, 39, 0.22); +} + +.result-video-state-action.is-pressed { + transform: scale(0.97); + opacity: 0.9; +} + +@keyframes result-video-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.result-preview-badge { + height: 20px; + display: inline-flex; + align-items: center; + padding: 0 8px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.2); + color: #ffffff; + font-size: 9px; + line-height: 20px; + font-weight: 900; + letter-spacing: 0.7px; +} + +.result-preview-title, +.result-preview-desc { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.result-preview-title { + margin-top: 12px; + color: #ffffff; + font-size: 23px; + line-height: 30px; + font-weight: 900; +} + +.result-preview-desc { + margin-top: 6px; + color: rgba(255, 255, 255, 0.78); + font-size: 12px; + line-height: 18px; + font-weight: 800; +} diff --git a/src/pages-aigc/detail/detail.vue b/src/pages-aigc/detail/detail.vue new file mode 100644 index 0000000..7fcf38d --- /dev/null +++ b/src/pages-aigc/detail/detail.vue @@ -0,0 +1,642 @@ + + + + + diff --git a/src/pages-aigc/detail/styles/index.scss b/src/pages-aigc/detail/styles/index.scss new file mode 100644 index 0000000..96186a5 --- /dev/null +++ b/src/pages-aigc/detail/styles/index.scss @@ -0,0 +1,67 @@ +.aigc-detail-page { + position: relative; + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + overflow: hidden; + color: #172033; + background: + radial-gradient( + 130% 78% at 0% 0%, + rgba(255, 249, 226, 0.76), + rgba(255, 249, 226, 0) 42% + ), + linear-gradient(180deg, #effaf5 0%, #f4fbf7 58%, #eef8ff 100%); + box-sizing: border-box; +} + +.aigc-detail-content { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 0 12px calc(12px + env(safe-area-inset-bottom)); + box-sizing: border-box; +} + +.aigc-detail-preview { + display: block; + flex: 1 1 500px; + width: 100%; + min-height: 360px; + max-height: 500px; +} + +.aigc-detail-controls { + flex: 0 0 auto; +} + +.aigc-detail-text-actions { + height: 50px; + display: grid; + grid-template-columns: repeat(3, 1fr); + align-items: center; + gap: 0; + margin: 12px 22px 0; + border-radius: 50px; + background: rgba(255, 255, 255, 0.42); +} + +.aigc-detail-text-action { + position: relative; + min-width: 0; + overflow: hidden; + color: #20a765; + font-size: 12px; + line-height: 18px; + font-weight: 900; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +.aigc-detail-text-action + .aigc-detail-text-action { + border-left: 1px solid rgba(32, 167, 101, 0.16); +} diff --git a/src/pages-aigc/home/components/AigcConsentDialog/index.vue b/src/pages-aigc/home/components/AigcConsentDialog/index.vue new file mode 100644 index 0000000..da22666 --- /dev/null +++ b/src/pages-aigc/home/components/AigcConsentDialog/index.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/pages-aigc/home/components/AigcConsentDialog/styles/index.scss b/src/pages-aigc/home/components/AigcConsentDialog/styles/index.scss new file mode 100644 index 0000000..67e1dcf --- /dev/null +++ b/src/pages-aigc/home/components/AigcConsentDialog/styles/index.scss @@ -0,0 +1,75 @@ +.aigc-consent-overlay { + width: 100vw; + padding: 0 28px; + box-sizing: border-box; +} + +.aigc-consent-dialog { + width: 100%; + padding: 34px 28px 26px; + border-radius: 24px; + background: #ffffff; + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.26); + box-sizing: border-box; +} + +.aigc-consent-title { + display: block; + margin-bottom: 24px; + color: #111827; + font-size: 19px; + line-height: 26px; + font-weight: 900; + text-align: center; +} + +.aigc-consent-content { + color: #606776; + font-size: 14px; + line-height: 25px; + font-weight: 700; + text-align: justify; +} + +.aigc-consent-rule { + color: #18bd78; + font-weight: 900; +} + +.aigc-consent-actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px; + margin-top: 28px; +} + +.aigc-consent-button { + width: 100%; + height: 54px; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + border-radius: 999px; + font-size: 16px; + line-height: 22px; + font-weight: 900; + box-sizing: border-box; +} + +.aigc-consent-button::after { + border: 0; +} + +.aigc-consent-cancel { + border: 1px solid #e2e6ea; + background: #ffffff; + color: #172033; +} + +.aigc-consent-agree { + border: 1px solid #061c31; + background: #061c31; + color: #ffffff; +} diff --git a/src/pages-aigc/home/components/FlowSteps/index.vue b/src/pages-aigc/home/components/FlowSteps/index.vue new file mode 100644 index 0000000..4c310f2 --- /dev/null +++ b/src/pages-aigc/home/components/FlowSteps/index.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/pages-aigc/home/components/FlowSteps/styles/index.scss b/src/pages-aigc/home/components/FlowSteps/styles/index.scss new file mode 100644 index 0000000..821ffb0 --- /dev/null +++ b/src/pages-aigc/home/components/FlowSteps/styles/index.scss @@ -0,0 +1,50 @@ +.flow-steps { + height: 55px; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0; + align-items: center; + margin: 12px 16px 0; + padding: 4px; + border-radius: 18px; + background: rgba(255, 255, 255, 0.62); + box-shadow: 0 8px 18px rgba(18, 48, 56, 0.05); + box-sizing: border-box; +} + +.flow-step { + min-width: 0; + height: 38px; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + padding: 0 4px; + border-radius: 14px; + background: transparent; + color: #576276; + font-size: 12px; + line-height: 14px; + font-weight: 900; + white-space: nowrap; + box-sizing: border-box; +} + +.flow-index { + flex: 0 0 18px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #20cf75; + color: #ffffff; + font-size: 12px; + line-height: 18px; + text-align: center; +} + +.flow-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/pages-aigc/home/components/GiftPointsToast/index.vue b/src/pages-aigc/home/components/GiftPointsToast/index.vue new file mode 100644 index 0000000..bfadc11 --- /dev/null +++ b/src/pages-aigc/home/components/GiftPointsToast/index.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/pages-aigc/home/components/GiftPointsToast/styles/index.scss b/src/pages-aigc/home/components/GiftPointsToast/styles/index.scss new file mode 100644 index 0000000..a72c0ff --- /dev/null +++ b/src/pages-aigc/home/components/GiftPointsToast/styles/index.scss @@ -0,0 +1,63 @@ +.gift-points-toast { + position: absolute; + z-index: 6; + top: 4px; + right: 16px; + left: 16px; + height: 50px; + display: flex; + align-items: center; + gap: 8px; + overflow: hidden; + padding: 7px 7px 7px 10px; + border: 1px solid rgba(94, 171, 255, 0.56); + border-radius: 18px; + background: #172951f0; + box-shadow: 0 10px 22px rgba(15, 68, 120, 0.18); + box-sizing: border-box; +} + +.gift-points-badge { + flex: 0 0 111px; + width: 111px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 999px; + background: #20cf75; + color: #ffffff; + font-size: 12px; + line-height: 30px; + font-weight: 900; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22); + box-sizing: border-box; +} + +.gift-points-message { + flex: 1 1 auto; + min-width: 0; + max-height: 32px; + overflow: hidden; + color: rgba(239, 250, 255, 0.94); + font-size: 11px; + line-height: 16px; + font-weight: 800; + text-overflow: ellipsis; +} + +.gift-points-close { + flex: 0 0 28px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(255, 255, 255, 0.12); + box-sizing: border-box; +} diff --git a/src/pages-aigc/home/components/TemplateCard/index.vue b/src/pages-aigc/home/components/TemplateCard/index.vue new file mode 100644 index 0000000..bba952d --- /dev/null +++ b/src/pages-aigc/home/components/TemplateCard/index.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/pages-aigc/home/components/TemplateCard/styles/index.scss b/src/pages-aigc/home/components/TemplateCard/styles/index.scss new file mode 100644 index 0000000..648e5a9 --- /dev/null +++ b/src/pages-aigc/home/components/TemplateCard/styles/index.scss @@ -0,0 +1,200 @@ +.template-card { + position: relative; + flex: 0 0 var(--template-card-width, 318px); + width: var(--template-card-width, 318px); + height: var(--template-card-height, 494px); + overflow: hidden; + display: flex; + flex-direction: column; + box-sizing: border-box; + border-radius: var(--template-card-radius, 28px); + color: #ffffff; + text-align: left; + box-shadow: 0 3px 8px rgba(24, 37, 50, 0.12); + opacity: 0.82; + transition: + box-shadow 0.2s ease, + opacity 0.2s ease; +} + +.template-card.is-active { + border-color: rgba(255, 255, 255, 0.34); + box-shadow: 0 5px 12px rgba(24, 37, 50, 0.16); + opacity: 1; +} + +.template-card.is-postcard .template-copy { + background: + linear-gradient( + 180deg, + rgba(255, 255, 255, 0.06), + rgba(255, 255, 255, 0) 34% + ), + linear-gradient(180deg, #0a4d46, #082226); +} + +.template-card.is-postcard .template-copy::before { + background: linear-gradient(180deg, rgba(10, 77, 70, 0), #0a4d46); +} + +.template-media { + position: relative; + flex: 0 0 var(--template-card-media-height, 358px); + height: var(--template-card-media-height, 358px); + overflow: hidden; + background: #0a4d46; +} + +.template-media-placeholder, +.template-image, +.template-video { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + display: block; +} + +.template-media-placeholder { + background: + radial-gradient( + 90% 48% at 50% 30%, + rgba(255, 255, 255, 0.12), + rgba(255, 255, 255, 0) 70% + ), + linear-gradient(160deg, #17383a 0%, #0f7069 58%, #0a4d46 100%); +} + +.template-image { + z-index: 1; +} + +.template-video { + z-index: 2; + opacity: 0; + pointer-events: none; + transition: opacity 0.18s ease; +} + +.template-video.is-ready { + opacity: 1; +} + +.template-media-shade { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 3; + background: + radial-gradient( + 92% 45% at 50% 28%, + rgba(255, 255, 255, 0.08), + rgba(255, 255, 255, 0) 62% + ), + linear-gradient( + 180deg, + rgba(4, 22, 24, 0), + rgba(4, 22, 24, 0.02) 58%, + rgba(4, 22, 24, 0.24) + ); + pointer-events: none; +} + +.template-copy { + position: relative; + flex: 0 0 var(--template-card-copy-height, 136px); + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + padding: 15px 18px 14px; + background: + linear-gradient( + 180deg, + rgba(255, 255, 255, 0.06), + rgba(255, 255, 255, 0) 34% + ), + linear-gradient(180deg, #0f7069, #092128); + box-sizing: border-box; +} + +.template-copy::before { + content: ""; + position: absolute; + left: 0; + right: 0; + top: -34px; + height: 34px; + background: linear-gradient(180deg, rgba(15, 112, 105, 0), #0f7069); + pointer-events: none; +} + +.template-badge { + position: absolute; + top: 14px; + right: 18px; + height: 24px; + display: inline-flex; + align-items: center; + padding: 0 9px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.16); + color: rgba(255, 255, 255, 0.9); + font-size: 10px; + line-height: 24px; + font-weight: 900; +} + +.template-title { + width: 178px; + color: #ffffff; + font-size: 17px; + line-height: 22px; + font-weight: 900; +} + +.template-desc { + width: 178px; + margin-top: 4px; + color: rgba(255, 255, 255, 0.76); + font-size: 11px; + line-height: 15px; + font-weight: 800; +} + +.template-note { + max-width: 172px; + margin-top: 5px; + color: rgba(255, 255, 255, 0.86); + font-size: 9px; + line-height: 13px; + font-weight: 900; +} + +.template-select { + position: absolute; + right: 16px; + bottom: 16px; + min-width: 108px; + height: 40px; + display: inline-flex; + align-items: center; + justify-content: center; + margin: 0; + padding: 0 16px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.9); + color: #172033; + font-size: 13px; + line-height: 20px; + font-weight: 900; +} + +.template-select::after { + border: 0; +} diff --git a/src/pages-aigc/home/components/TemplateCarousel/index.vue b/src/pages-aigc/home/components/TemplateCarousel/index.vue new file mode 100644 index 0000000..f25e4be --- /dev/null +++ b/src/pages-aigc/home/components/TemplateCarousel/index.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/src/pages-aigc/home/components/TemplateCarousel/styles/index.scss b/src/pages-aigc/home/components/TemplateCarousel/styles/index.scss new file mode 100644 index 0000000..47c8c09 --- /dev/null +++ b/src/pages-aigc/home/components/TemplateCarousel/styles/index.scss @@ -0,0 +1,80 @@ +.template-carousel { + max-width: 100vw; + overflow: hidden; +} + +.template-heading { + height: 34px; + display: flex; + align-items: center; + justify-content: space-between; + margin: 0 16px 20px; +} + +.template-heading-title, +.template-heading-subtitle { + display: block; +} + +.template-heading-title { + color: #172033; + font-size: 16px; + line-height: 21px; + font-weight: 900; +} + +.template-heading-subtitle { + margin-top: 1px; + color: #7a8596; + font-size: 10px; + line-height: 13px; + font-weight: 800; +} + +.template-rail { + width: 100%; + height: var(--template-rail-height, 560px); +} + +.template-slide { + width: 100%; + height: var(--template-rail-height, 560px); + display: flex; + align-items: flex-start; + justify-content: center; + padding: 0 calc(var(--template-track-gap, 12px) / 2); + box-sizing: border-box; +} + +.template-slide-card { + width: var(--template-card-width, 318px); + height: var(--template-card-height, 494px); + flex: 0 0 var(--template-card-width, 318px); + transform-origin: center; + transform: scale(0.94); + transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1); +} + +.template-slide-card.is-active { + transform: scale(1); +} + +.template-dots { + height: 14px; + display: flex; + justify-content: center; + gap: 6px; + margin-top: 5px; +} + +.template-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: rgba(23, 32, 51, 0.16); +} + +.template-dot.is-active { + width: 18px; + background: #20cf75; +} diff --git a/src/pages-aigc/home/home.vue b/src/pages-aigc/home/home.vue new file mode 100644 index 0000000..eec4d9c --- /dev/null +++ b/src/pages-aigc/home/home.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/src/pages-aigc/home/styles/index.scss b/src/pages-aigc/home/styles/index.scss new file mode 100644 index 0000000..3e1e9eb --- /dev/null +++ b/src/pages-aigc/home/styles/index.scss @@ -0,0 +1,26 @@ +.aigc-home-page { + position: relative; + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + overflow: hidden; + color: #172033; + background: + radial-gradient( + 130% 78% at 0% 0%, + rgba(255, 249, 226, 0.76), + rgba(255, 249, 226, 0) 42% + ), + linear-gradient(180deg, #effaf5 0%, #f4fbf7 58%, #eef8ff 100%); + box-sizing: border-box; +} + +.aigc-home-browser { + position: relative; + flex: 1 1 0; + min-height: 0; + overflow: hidden; + padding: 4px 0 0; + box-sizing: border-box; +} diff --git a/src/pages-aigc/hooks/useCurrentCredit.js b/src/pages-aigc/hooks/useCurrentCredit.js new file mode 100644 index 0000000..370fdb2 --- /dev/null +++ b/src/pages-aigc/hooks/useCurrentCredit.js @@ -0,0 +1,22 @@ +import { ref } from "vue"; +import { getCurrentCredit } from "@/pages-aigc/request/AigcApi.js"; + +export function useCurrentCredit() { + const pointBalance = ref(0); + + const fetchCurrentCredit = async () => { + try { + const res = await getCurrentCredit(); + if (res?.code === 0) { + pointBalance.value = Number(res.data) || 0; + } + } catch (error) { + console.error("获取当前积分失败", error); + } + }; + + return { + pointBalance, + fetchCurrentCredit, + }; +} diff --git a/src/pages-aigc/pointsDetails/components/LedgerCard/index.vue b/src/pages-aigc/pointsDetails/components/LedgerCard/index.vue new file mode 100644 index 0000000..5009448 --- /dev/null +++ b/src/pages-aigc/pointsDetails/components/LedgerCard/index.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/pages-aigc/pointsDetails/components/LedgerCard/styles/index.scss b/src/pages-aigc/pointsDetails/components/LedgerCard/styles/index.scss new file mode 100644 index 0000000..2f0207e --- /dev/null +++ b/src/pages-aigc/pointsDetails/components/LedgerCard/styles/index.scss @@ -0,0 +1,73 @@ +.ledger-card { + position: relative; + min-height: 78px; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + padding: 14px; + border-radius: 18px; + background: rgba(255, 255, 255, 0.84); + box-sizing: border-box; +} + +.ledger-card-copy { + min-width: 0; + padding-right: 48px; +} + +.ledger-title, +.ledger-task, +.ledger-time { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ledger-title { + color: #172033; + font-size: 14px; + line-height: 20px; + font-weight: 900; +} + +.ledger-task { + margin-top: 2px; + color: #667386; + font-size: 11px; + line-height: 15px; + font-weight: 800; +} + +.ledger-time { + margin-top: 2px; + color: #7a9abd; + font-size: 10px; + line-height: 13px; + font-weight: 900; +} + +.ledger-delta { + color: #172033; + font-size: 15px; + line-height: 20px; + font-weight: 900; +} + +.ledger-delta.is-plus { + color: #12a765; +} + +.ledger-delta.is-minus { + color: #172033; +} + +.ledger-status { + position: absolute; + right: 14px; + bottom: 12px; + color: #7a8596; + font-size: 10px; + line-height: 13px; + font-weight: 900; +} diff --git a/src/pages-aigc/pointsDetails/components/LedgerList/index.vue b/src/pages-aigc/pointsDetails/components/LedgerList/index.vue new file mode 100644 index 0000000..ab44503 --- /dev/null +++ b/src/pages-aigc/pointsDetails/components/LedgerList/index.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/pages-aigc/pointsDetails/components/LedgerList/styles/index.scss b/src/pages-aigc/pointsDetails/components/LedgerList/styles/index.scss new file mode 100644 index 0000000..bf8ca2f --- /dev/null +++ b/src/pages-aigc/pointsDetails/components/LedgerList/styles/index.scss @@ -0,0 +1,5 @@ +.ledger-list { + display: grid; + gap: 10px; + margin-top: 12px; +} diff --git a/src/pages-aigc/pointsDetails/pointsDetails.vue b/src/pages-aigc/pointsDetails/pointsDetails.vue new file mode 100644 index 0000000..bc2dc46 --- /dev/null +++ b/src/pages-aigc/pointsDetails/pointsDetails.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/pages-aigc/pointsDetails/styles/index.scss b/src/pages-aigc/pointsDetails/styles/index.scss new file mode 100644 index 0000000..0d07a10 --- /dev/null +++ b/src/pages-aigc/pointsDetails/styles/index.scss @@ -0,0 +1,15 @@ +.points-details-page { + position: relative; + width: 100vw; + min-height: 100vh; + overflow: hidden; + color: #172033; + background: linear-gradient(180deg, #effaf5 0%, #eef8ff 100%); + box-sizing: border-box; +} + +.points-details-content { + min-height: 0; + padding: 0 18px 48px; + box-sizing: border-box; +} diff --git a/src/pages-aigc/progress/components/GeneratingProgressPanel/index.vue b/src/pages-aigc/progress/components/GeneratingProgressPanel/index.vue new file mode 100644 index 0000000..0b2acc9 --- /dev/null +++ b/src/pages-aigc/progress/components/GeneratingProgressPanel/index.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/src/pages-aigc/progress/components/GeneratingProgressPanel/styles/index.scss b/src/pages-aigc/progress/components/GeneratingProgressPanel/styles/index.scss new file mode 100644 index 0000000..2ef5b80 --- /dev/null +++ b/src/pages-aigc/progress/components/GeneratingProgressPanel/styles/index.scss @@ -0,0 +1,227 @@ +.generating-task-panel { + width: 100%; + min-width: 0; + box-sizing: border-box; +} + +.generating-task-status { + position: relative; + min-height: 134px; + overflow: hidden; + display: block; + padding: 18px 18px 16px; + border-radius: 22px; + background: + radial-gradient(96% 120% at 96% 0%, rgba(32, 207, 117, 0.1), rgba(32, 207, 117, 0) 52%), + rgba(255, 255, 255, 0.84); + box-shadow: 0 12px 28px rgba(15, 29, 37, 0.08); + box-sizing: border-box; +} + +.generating-task-badge { + height: 24px; + display: inline-flex; + align-items: center; + padding: 0 10px; + border-radius: 999px; + background: rgba(32, 207, 117, 0.13); + color: #0e9f61; + font-size: 11px; + line-height: 24px; + font-weight: 900; +} + +.generating-task-title, +.generating-task-desc, +.generating-task-hint { + display: block; +} + +.generating-task-title { + margin-top: 16px; + color: #172033; + font-size: 24px; + line-height: 30px; + font-weight: 900; +} + +.generating-task-desc { + margin-top: 5px; + overflow: hidden; + color: #526073; + font-size: 12px; + line-height: 18px; + font-weight: 800; + text-overflow: ellipsis; + white-space: nowrap; +} + +.generating-task-hint { + margin-top: 10px; + color: #91a0b1; + font-size: 11px; + line-height: 15px; + font-weight: 800; +} + +.generating-progress-row { + height: 52px; + display: grid; + grid-template-columns: minmax(0, 1fr) 42px; + align-items: center; + gap: 10px; + margin-top: 18px; +} + +.generating-progress-track { + width: 100%; + height: 12px; + overflow: hidden; + border-radius: 999px; + background: rgba(255, 255, 255, 0.8); +} + +.generating-progress-fill { + height: 100%; + min-width: 24px; + border-radius: 999px; + background: linear-gradient(90deg, #20cf75 0%, #f5bd32 100%); + transition: width 280ms ease; +} + +.generating-progress-text { + color: #172033; + font-size: 15px; + line-height: 20px; + font-weight: 900; + text-align: right; + white-space: nowrap; +} + +.generating-step-list { + display: grid; + gap: 9px; + margin-top: 12px; +} + +.generating-step-item { + height: 42px; + display: flex; + align-items: center; + gap: 10px; + padding: 0 12px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.72); + box-sizing: border-box; +} + +.generating-step-dot { + flex: 0 0 18px; + width: 18px; + height: 18px; + border: 2px solid #c7d2df; + border-radius: 50%; + box-sizing: border-box; +} + +.generating-step-item.is-complete .generating-step-dot { + border-color: #20cf75; + background: #20cf75; + box-shadow: inset 0 0 0 4px #ffffff; +} + +.generating-step-label { + min-width: 0; + overflow: hidden; + color: #7d899a; + font-size: 13px; + line-height: 18px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +.generating-step-item.is-complete .generating-step-label { + color: #172033; +} + +.generating-background-note { + margin-top: 16px; + padding: 14px; + border-radius: 18px; + background: + linear-gradient(135deg, rgba(32, 207, 117, 0.11), rgba(255, 255, 255, 0.9) 54%), + rgba(255, 255, 255, 0.84); + color: #172033; + box-shadow: 0 10px 22px rgba(18, 48, 56, 0.07); + box-sizing: border-box; +} + +.generating-note-title, +.generating-note-desc, +.generating-freeze-text { + display: block; +} + +.generating-note-title { + font-size: 14px; + line-height: 19px; + font-weight: 900; +} + +.generating-note-desc { + margin-top: 4px; + color: #526073; + font-size: 11px; + line-height: 16px; + font-weight: 800; +} + +.generating-actions { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 10px; + margin-top: 12px; +} + +.generating-action { + min-width: 0; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 999px; + font-size: 15px; + line-height: 20px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +.generating-action.is-primary { + background: #20cf75; + color: #ffffff; + box-shadow: 0 8px 16px rgba(32, 207, 117, 0.18); +} + +.generating-action.is-secondary { + background: rgba(255, 255, 255, 0.78); + color: #526073; +} + +.generating-action.is-pressed { + opacity: 0.82; +} + +.generating-freeze-text { + overflow: hidden; + margin-top: 8px; + color: #8c98a8; + font-size: 10px; + line-height: 14px; + font-weight: 800; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/pages-aigc/progress/progress.vue b/src/pages-aigc/progress/progress.vue new file mode 100644 index 0000000..53aef91 --- /dev/null +++ b/src/pages-aigc/progress/progress.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/src/pages-aigc/progress/styles/index.scss b/src/pages-aigc/progress/styles/index.scss new file mode 100644 index 0000000..07c1d92 --- /dev/null +++ b/src/pages-aigc/progress/styles/index.scss @@ -0,0 +1,28 @@ +.aigc-progress-page { + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + overflow: hidden; + color: #172033; + background: + radial-gradient( + 130% 78% at 0% 0%, + rgba(255, 249, 226, 0.76), + rgba(255, 249, 226, 0) 42% + ), + linear-gradient(180deg, #effaf5 0%, #f4fbf7 58%, #eef8ff 100%); + box-sizing: border-box; +} + +.aigc-progress-body { + flex: 1 1 0; + min-height: 0; + overflow: hidden; +} + +.aigc-progress-content { + min-height: 100%; + padding: 12px 12px 16px; + box-sizing: border-box; +} diff --git a/src/pages-aigc/recharge/components/BalanceCard/index.vue b/src/pages-aigc/recharge/components/BalanceCard/index.vue new file mode 100644 index 0000000..33c5c50 --- /dev/null +++ b/src/pages-aigc/recharge/components/BalanceCard/index.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/pages-aigc/recharge/components/BalanceCard/styles/index.scss b/src/pages-aigc/recharge/components/BalanceCard/styles/index.scss new file mode 100644 index 0000000..53085c0 --- /dev/null +++ b/src/pages-aigc/recharge/components/BalanceCard/styles/index.scss @@ -0,0 +1,60 @@ +.balance-card { + min-height: 116px; + display: grid; + grid-template-columns: 1fr 78px; + align-items: center; + gap: 12px; + padding: 18px; + border-radius: 22px; + background: #172951; + color: #ffffff; + box-sizing: border-box; +} + +.balance-copy { + min-width: 0; +} + +.balance-label, +.balance-value, +.balance-rule { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.balance-label { + color: rgba(255, 255, 255, 0.66); + font-size: 11px; + line-height: 15px; + font-weight: 800; +} + +.balance-value { + margin: 4px 0; + color: #ffffff; + font-size: 34px; + line-height: 39px; + font-weight: 900; +} + +.balance-rule { + color: rgba(255, 255, 255, 0.66); + font-size: 11px; + line-height: 15px; + font-weight: 800; +} + +.balance-ledger { + height: 34px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + color: #ffffff; + font-size: 12px; + line-height: 34px; + font-weight: 900; +} diff --git a/src/pages-aigc/recharge/components/CustomAmountCard/index.vue b/src/pages-aigc/recharge/components/CustomAmountCard/index.vue new file mode 100644 index 0000000..5b31776 --- /dev/null +++ b/src/pages-aigc/recharge/components/CustomAmountCard/index.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/pages-aigc/recharge/components/CustomAmountCard/styles/index.scss b/src/pages-aigc/recharge/components/CustomAmountCard/styles/index.scss new file mode 100644 index 0000000..7cfd912 --- /dev/null +++ b/src/pages-aigc/recharge/components/CustomAmountCard/styles/index.scss @@ -0,0 +1,84 @@ +.custom-card { + min-height: 90px; + display: grid; + grid-template-columns: minmax(0, 1fr) 118px; + grid-template-rows: 28px auto; + column-gap: 14px; + row-gap: 2px; + align-items: center; + margin-top: 12px; + padding: 14px 16px; + border: 1.5px solid transparent; + border-radius: 18px; + background: rgba(255, 255, 255, 0.8); + box-sizing: border-box; +} + +.custom-card.is-selected { + border-color: #20cf75; +} + +.custom-title { + grid-column: 1; + grid-row: 1; + display: block; + overflow: hidden; + color: #172033; + font-size: 14px; + line-height: 28px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +.custom-input-wrap { + grid-column: 2; + grid-row: 1 / 3; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 0 0 18px; + border-left: 1px solid rgba(82, 96, 115, 0.12); + color: #172033; + font-size: 17px; + line-height: 56px; + font-weight: 900; + box-sizing: border-box; +} + +.custom-currency { + flex: 0 0 auto; +} + +.custom-input { + width: 52px; + height: 56px; + min-height: 56px; + border: 0; + background: transparent; + color: #172033; + font-size: 18px; + line-height: 56px; + font-weight: 900; +} + +.custom-hint { + grid-column: 1; + grid-row: 2; + min-width: 0; + display: grid; + gap: 1px; + color: #7a8596; + font-size: 11px; + line-height: 15px; + font-weight: 800; +} + +.custom-hint text { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/pages-aigc/recharge/components/RechargeFooter/index.vue b/src/pages-aigc/recharge/components/RechargeFooter/index.vue new file mode 100644 index 0000000..7f789af --- /dev/null +++ b/src/pages-aigc/recharge/components/RechargeFooter/index.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/pages-aigc/recharge/components/RechargeFooter/styles/index.scss b/src/pages-aigc/recharge/components/RechargeFooter/styles/index.scss new file mode 100644 index 0000000..370cba7 --- /dev/null +++ b/src/pages-aigc/recharge/components/RechargeFooter/styles/index.scss @@ -0,0 +1,79 @@ +.recharge-footer { + position: sticky; + bottom: 12px; + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 16px; + padding: 12px; + border-radius: 20px; + background: rgba(255, 255, 255, 0.9); + box-shadow: 0 10px 24px rgba(18, 48, 56, 0.1); + box-sizing: border-box; +} + +.recharge-agreement { + display: flex; + align-items: center; + box-sizing: border-box; +} + +.recharge-agreement.is-disabled { + opacity: 0.56; +} + +.recharge-agreement-text, +.recharge-agreement-link { + font-size: 14px; + line-height: 24px; +} + +.recharge-agreement-text { + color: #667386; +} + +.recharge-agreement-link { + color: $theme-color-500; + font-weight: 700; +} + +.recharge-payment-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 142px; + align-items: center; + gap: 10px; +} + +.recharge-gain { + min-width: 0; + overflow: hidden; + color: #667386; + font-size: 12px; + line-height: 18px; + font-weight: 800; + text-overflow: ellipsis; + white-space: nowrap; +} + +.recharge-gain-value { + color: #172033; + font-weight: 900; +} + +.recharge-pay-button { + height: 46px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 999px; + background: #000000; + color: #ffffff; + font-size: 14px; + line-height: 46px; + font-weight: 900; + white-space: nowrap; +} + +.recharge-pay-button.is-disabled { + opacity: 0.48; +} diff --git a/src/pages-aigc/recharge/components/RechargePackageList/index.vue b/src/pages-aigc/recharge/components/RechargePackageList/index.vue new file mode 100644 index 0000000..a73a21d --- /dev/null +++ b/src/pages-aigc/recharge/components/RechargePackageList/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/pages-aigc/recharge/components/RechargePackageList/styles/index.scss b/src/pages-aigc/recharge/components/RechargePackageList/styles/index.scss new file mode 100644 index 0000000..5d54315 --- /dev/null +++ b/src/pages-aigc/recharge/components/RechargePackageList/styles/index.scss @@ -0,0 +1,71 @@ +.package-section { + margin-top: 18px; +} + +.package-title { + display: block; + margin-bottom: 10px; + color: #172033; + font-size: 15px; + line-height: 20px; + font-weight: 900; +} + +.package-list { + display: grid; + gap: 10px; +} + +.package-card { + height: 74px; + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 8px; + padding: 12px 14px; + border: 1.5px solid transparent; + border-radius: 18px; + background: rgba(255, 255, 255, 0.82); + text-align: left; + box-sizing: border-box; +} + +.package-card.is-selected { + border-color: #20cf75; + background: rgba(255, 255, 255, 0.72); +} + +.package-copy { + min-width: 0; +} + +.package-points, +.package-origin, +.package-price { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.package-points { + color: #172033; + font-size: 16px; + line-height: 22px; + font-weight: 900; +} + +.package-origin { + margin-top: 6px; + color: #7a8596; + font-size: 11px; + line-height: 15px; + font-weight: 800; +} + +.package-price { + color: #12a765; + font-size: 16px; + line-height: 22px; + font-weight: 900; +} diff --git a/src/pages-aigc/recharge/recharge.vue b/src/pages-aigc/recharge/recharge.vue new file mode 100644 index 0000000..01a2c77 --- /dev/null +++ b/src/pages-aigc/recharge/recharge.vue @@ -0,0 +1,435 @@ + + + + + diff --git a/src/pages-aigc/recharge/services/pendingVirtualRecharge.js b/src/pages-aigc/recharge/services/pendingVirtualRecharge.js new file mode 100644 index 0000000..cb6cce0 --- /dev/null +++ b/src/pages-aigc/recharge/services/pendingVirtualRecharge.js @@ -0,0 +1,26 @@ +import { currentClientType } from "@/constant/base.js"; + +const PENDING_VIRTUAL_RECHARGE_ORDER_KEY = `${currentClientType()}_AIGC_PENDING_VIRTUAL_RECHARGE_ORDER`; + +const getStorageApi = () => (typeof uni !== "undefined" ? uni : null); + +export function getPendingVirtualRechargeOrderNo(storage = getStorageApi()) { + const orderNo = storage?.getStorageSync?.(PENDING_VIRTUAL_RECHARGE_ORDER_KEY); + return typeof orderNo === "string" ? orderNo.trim() : ""; +} + +export function setPendingVirtualRechargeOrderNo(orderNo, storage = getStorageApi()) { + const normalizedOrderNo = String(orderNo || "").trim(); + if (!normalizedOrderNo) { + throw new Error("待确认充值订单号不能为空"); + } + + return storage?.setStorageSync?.( + PENDING_VIRTUAL_RECHARGE_ORDER_KEY, + normalizedOrderNo + ); +} + +export function removePendingVirtualRechargeOrderNo(storage = getStorageApi()) { + return storage?.removeStorageSync?.(PENDING_VIRTUAL_RECHARGE_ORDER_KEY); +} diff --git a/src/pages-aigc/recharge/services/wechatVirtualPayment.js b/src/pages-aigc/recharge/services/wechatVirtualPayment.js new file mode 100644 index 0000000..a4c9c0f --- /dev/null +++ b/src/pages-aigc/recharge/services/wechatVirtualPayment.js @@ -0,0 +1,84 @@ +import { + normalizeVirtualPaymentOrder, + normalizeWechatClientPlatform, +} from "../utils/virtualRecharge.js"; + +export { getWechatLoginCode } from "../../services/wechatLogin.js"; + +const MIN_VIRTUAL_PAYMENT_SDK_VERSION = "2.19.2"; + +const getWechatApi = () => { + // #ifdef MP-WEIXIN + if (typeof wx !== "undefined") return wx; + // #endif + return null; +}; + +export function compareVersion(versionA, versionB) { + const versionsA = String(versionA || "").split("."); + const versionsB = String(versionB || "").split("."); + const length = Math.max(versionsA.length, versionsB.length); + + while (versionsA.length < length) versionsA.push("0"); + while (versionsB.length < length) versionsB.push("0"); + + for (let index = 0; index < length; index += 1) { + const numberA = Number.parseInt(versionsA[index], 10) || 0; + const numberB = Number.parseInt(versionsB[index], 10) || 0; + if (numberA > numberB) return 1; + if (numberA < numberB) return -1; + } + + return 0; +} + +export function assertWechatVirtualPaymentCapability(wechatApi = getWechatApi()) { + if (!wechatApi) { + throw new Error("当前环境不支持微信虚拟支付"); + } + + const appBaseInfo = + typeof wechatApi.getAppBaseInfo === "function" + ? wechatApi.getAppBaseInfo() + : wechatApi.getSystemInfoSync?.() || {}; + const versionSupported = + compareVersion(appBaseInfo?.SDKVersion, MIN_VIRTUAL_PAYMENT_SDK_VERSION) >= 0; + const apiSupported = + typeof wechatApi.requestVirtualPayment === "function" && + (versionSupported || wechatApi.canIUse?.("requestVirtualPayment")); + + if (!apiSupported) { + throw new Error("当前微信版本不支持微信虚拟支付"); + } +} + +export function getWechatClientPlatform(wechatApi = getWechatApi()) { + assertWechatVirtualPaymentCapability(wechatApi); + const deviceInfo = + typeof wechatApi.getDeviceInfo === "function" + ? wechatApi.getDeviceInfo() + : wechatApi.getSystemInfoSync?.() || {}; + const clientPlatform = normalizeWechatClientPlatform(deviceInfo?.platform); + + if (!clientPlatform) { + throw new Error("当前客户端平台不支持微信虚拟支付"); + } + + return clientPlatform; +} + +export function requestWechatVirtualPayment(paymentData, wechatApi = getWechatApi()) { + assertWechatVirtualPaymentCapability(wechatApi); + const order = normalizeVirtualPaymentOrder(paymentData); + + return new Promise((resolve, reject) => { + wechatApi.requestVirtualPayment({ + signData: order.signData, + paySig: order.paySig, + signature: order.signature, + mode: order.mode, + success: resolve, + fail: reject, + }); + }); +} diff --git a/src/pages-aigc/recharge/styles/index.scss b/src/pages-aigc/recharge/styles/index.scss new file mode 100644 index 0000000..75317db --- /dev/null +++ b/src/pages-aigc/recharge/styles/index.scss @@ -0,0 +1,31 @@ +.aigc-recharge-page { + position: relative; + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + min-height: 812px; + overflow: hidden; + color: #172033; + background: + radial-gradient( + 130% 78% at 0% 0%, + rgba(255, 249, 226, 0.76), + rgba(255, 249, 226, 0) 42% + ), + linear-gradient(180deg, #effaf5 0%, #f4fbf7 58%, #eef8ff 100%); + box-sizing: border-box; +} + +.aigc-recharge-content { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + padding: 24px 12px; + scrollbar-width: none; + box-sizing: border-box; +} + +.aigc-recharge-content::-webkit-scrollbar { + display: none; +} diff --git a/src/pages-aigc/recharge/utils/virtualRecharge.js b/src/pages-aigc/recharge/utils/virtualRecharge.js new file mode 100644 index 0000000..9673319 --- /dev/null +++ b/src/pages-aigc/recharge/utils/virtualRecharge.js @@ -0,0 +1,136 @@ +const ALLOWED_CLIENT_PLATFORMS = new Set([ + "ANDROID", + "HARMONY", + "WINDOWS", + "IOS", +]); + +const REQUIRED_PAYMENT_ORDER_FIELDS = [ + "rechargeOrderNo", + "paymentOrderNo", + "signData", + "paySig", + "signature", +]; + +export const VIRTUAL_RECHARGE_STATUS_KIND = Object.freeze({ + PAID: "paid", + REFUNDED: "refunded", + FAILED: "failed", + PENDING: "pending", + UNKNOWN: "unknown", +}); + +export function normalizeWechatClientPlatform(platform) { + const normalizedPlatform = String(platform || "").toLowerCase(); + const platformMap = { + android: "ANDROID", + ios: "IOS", + ohos: "HARMONY", + ohos_pc: "HARMONY", + windows: "WINDOWS", + }; + + return platformMap[normalizedPlatform] || ""; +} + +export function buildVirtualRechargeOrderPayload({ + mode, + rechargeOptionCode, + rechargeAmount, + wxLoginCode, + clientPlatform, +} = {}) { + const normalizedLoginCode = String(wxLoginCode || "").trim(); + const normalizedPlatform = String(clientPlatform || "").trim().toUpperCase(); + + if (!normalizedLoginCode) { + throw new Error("微信登录凭证无效"); + } + + if (!ALLOWED_CLIENT_PLATFORMS.has(normalizedPlatform)) { + throw new Error("当前客户端平台不支持微信虚拟支付"); + } + + const commonPayload = { + wxLoginCode: normalizedLoginCode, + clientPlatform: normalizedPlatform, + }; + + if (mode === "package") { + const normalizedOptionCode = String(rechargeOptionCode || "").trim(); + if (!normalizedOptionCode) { + throw new Error("请选择充值档位"); + } + + return { + rechargeOptionCode: normalizedOptionCode, + ...commonPayload, + }; + } + + if (mode === "custom") { + const amountText = String(rechargeAmount ?? "").trim(); + if (!/^\d{1,6}$/.test(amountText)) { + throw new Error("充值金额必须为1至999999的整数"); + } + + const normalizedAmount = Number(amountText); + if (normalizedAmount < 1 || normalizedAmount > 999999) { + throw new Error("充值金额必须为1至999999的整数"); + } + + return { + rechargeAmount: normalizedAmount, + ...commonPayload, + }; + } + + throw new Error("请选择充值方式"); +} + +export function normalizeVirtualPaymentOrder(orderData) { + const normalizedOrder = orderData && typeof orderData === "object" ? orderData : {}; + const hasRequiredFields = REQUIRED_PAYMENT_ORDER_FIELDS.every((field) => { + return typeof normalizedOrder[field] === "string" && normalizedOrder[field].trim(); + }); + + if (!hasRequiredFields || normalizedOrder.mode !== "short_series_coin") { + throw new Error("虚拟支付订单参数错误"); + } + + return { + rechargeOrderNo: normalizedOrder.rechargeOrderNo, + paymentOrderNo: normalizedOrder.paymentOrderNo, + mode: normalizedOrder.mode, + signData: normalizedOrder.signData, + paySig: normalizedOrder.paySig, + signature: normalizedOrder.signature, + }; +} + +export function classifyVirtualRechargeStatus(status) { + switch (status) { + case "PAID": + return VIRTUAL_RECHARGE_STATUS_KIND.PAID; + case "REFUNDED": + return VIRTUAL_RECHARGE_STATUS_KIND.REFUNDED; + case "CREATE_FAILED": + case "PAYMENT_ABNORMAL": + return VIRTUAL_RECHARGE_STATUS_KIND.FAILED; + case "CREATING": + case "UNPAID": + return VIRTUAL_RECHARGE_STATUS_KIND.PENDING; + default: + return VIRTUAL_RECHARGE_STATUS_KIND.UNKNOWN; + } +} + +export function formatFenAmount(amountFen) { + const normalizedAmount = Number(amountFen); + if (!Number.isInteger(normalizedAmount) || normalizedAmount < 0) { + return "0.00"; + } + + return (normalizedAmount / 100).toFixed(2); +} diff --git a/src/pages-aigc/record/components/RecordCard/index.vue b/src/pages-aigc/record/components/RecordCard/index.vue new file mode 100644 index 0000000..887e7ac --- /dev/null +++ b/src/pages-aigc/record/components/RecordCard/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/pages-aigc/record/components/RecordCard/styles/index.scss b/src/pages-aigc/record/components/RecordCard/styles/index.scss new file mode 100644 index 0000000..64beac5 --- /dev/null +++ b/src/pages-aigc/record/components/RecordCard/styles/index.scss @@ -0,0 +1,94 @@ +.record-card { + height: 90px; + display: grid; + grid-template-columns: 64px minmax(0, 1fr) 54px; + align-items: center; + gap: 12px; + padding: 12px; + border-radius: 16px; + background: linear-gradient(180deg, #ffffff 0%, #fbfdfc 100%); + border: 1px solid rgba(224, 236, 230, 0.92); + box-shadow: 0 10px 24px rgba(29, 58, 47, 0.055); + box-sizing: border-box; +} + +.record-card + .record-card { + margin-top: 0; +} + +.record-card-image { + width: 64px; + height: 64px; + display: block; + border-radius: 10px; + background: #edf5f1; + overflow: hidden; +} + +.record-card-media-placeholder { + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #edf5f1 0%, #f6faf8 100%); +} + +.record-card-media-text { + color: #263f52; + font-size: 11px; + line-height: 16px; + font-weight: 800; +} + +.record-card-copy { + min-width: 0; +} + +.record-card-title, +.record-card-time, +.record-card-status { + display: block; +} + +.record-card-title { + color: #162235; + font-size: 15px; + line-height: 20px; + font-weight: 900; +} + +.record-card-time { + margin-top: 6px; + color: #7d8c97; + font-size: 11px; + line-height: 15px; + font-weight: 700; +} + +.record-card-status { + margin-top: 4px; + color: #10b86b; + font-size: 10px; + line-height: 13px; + font-weight: 900; +} + +.record-card-action { + justify-self: end; + width: 48px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 999px; + background: #eef8f3; + color: #263f52; + font-size: 11px; + line-height: 28px; + font-weight: 800; +} + +.ellipsis-1 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/pages-aigc/record/components/RecordList/index.vue b/src/pages-aigc/record/components/RecordList/index.vue new file mode 100644 index 0000000..c759dc7 --- /dev/null +++ b/src/pages-aigc/record/components/RecordList/index.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/pages-aigc/record/components/RecordList/styles/index.scss b/src/pages-aigc/record/components/RecordList/styles/index.scss new file mode 100644 index 0000000..94149a7 --- /dev/null +++ b/src/pages-aigc/record/components/RecordList/styles/index.scss @@ -0,0 +1,20 @@ +.record-list-wrap { + margin-top: 0; +} + +.record-list { + display: flex; + flex-direction: column; + gap: 12px; + padding: 0 12px; +} + +.record-note { + display: block; + margin-top: 28px; + color: #9aa8b6; + font-size: 11px; + line-height: 16px; + font-weight: 700; + text-align: center; +} diff --git a/src/pages-aigc/record/record.vue b/src/pages-aigc/record/record.vue new file mode 100644 index 0000000..a7caf36 --- /dev/null +++ b/src/pages-aigc/record/record.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/pages-aigc/record/styles/index.scss b/src/pages-aigc/record/styles/index.scss new file mode 100644 index 0000000..5e24b7c --- /dev/null +++ b/src/pages-aigc/record/styles/index.scss @@ -0,0 +1,41 @@ +.aigc-record-page { + position: relative; + width: 100vw; + min-height: 100vh; + overflow: hidden; + padding: 0 18px; + background: #ffffff; + color: #162235; + box-sizing: border-box; +} + +.aigc-record-nav { + position: relative; + min-height: 82px; + margin-bottom: 12px; +} + +.aigc-record-nav :deep(.top-nav-bar) { + background: transparent; +} + +.aigc-record-nav :deep(.top-nav-bar > .nav-bar-content) { + padding-right: 0; + padding-left: 0; +} + +.aigc-record-nav :deep(.nav-bar-left), +.aigc-record-nav :deep(.nav-bar-right) { + width: 34px; + height: 34px; +} + +.aigc-record-nav :deep(.nav-bar-center) { + height: 34px; +} + +.aigc-record-nav :deep(.nav-bar-center text) { + font-size: 16px; + line-height: 22px; + font-weight: 900; +} diff --git a/src/pages-aigc/request/AigcApi.js b/src/pages-aigc/request/AigcApi.js new file mode 100644 index 0000000..ab6b04e --- /dev/null +++ b/src/pages-aigc/request/AigcApi.js @@ -0,0 +1,88 @@ +import request from "@/request/base/request"; + +function getAigcTemplateList() { + return request.get("/hotelBiz/aigc/aigcTemplateList", {}); +} + +function getAigcTemplateItemList(args) { + return request.post("/hotelBiz/aigc/aigcTemplateItemList", args); +} + +function getAigcGeneratorTaskList(args) { + return request.post( + "/hotelBiz/aigcGeneratorTask/aigcGeneratorTaskList", + args, + ); +} + +function getAigcGeneratorTaskDetail(args) { + return request.post( + "/hotelBiz/aigcGeneratorTask/aigcGeneratorTaskDetail", + args, + ); +} + +function createAigcGeneratorTask(args) { + return request.post( + "/hotelBiz/aigcGeneratorTask/createAigcGeneratorTask", + args, + { sensitive: true }, + ); +} + +function createAigcImageToVideoTask(args) { + return request.post( + "/hotelBiz/aigcGeneratorTask/createAigcImageToVideoTask", + args, + { sensitive: true }, + ); +} + +function getAigcImageToVideoItemDetail(args) { + return request.post( + "/hotelBiz/aigcGeneratorTask/aigcImageToVideoItemDetail", + args, + ); +} + +function getCurrentCredit() { + return request.get("/hotelBiz/credit/current", {}); +} + +function getCreditLedgerPage(args) { + return request.post("/hotelBiz/credit/ledger/page", args); +} + +function getPointsTopUpAgreement() { + return request.get("/hotelBiz/mainScene/pointsTopUpAgreement", {}); +} + +function getUserAgreement() { + return request.get("/hotelBiz/mainScene/userAgreement", {}); +} + +function createAigcGeneratorTaskShare(args) { + return request.post("/hotelBiz/aigcGeneratorTask/share/create", args); +} + +function getAigcGeneratorTaskShareDetail(args) { + return request.post("/hotelBiz/aigcGeneratorTask/share/detail", args, { + noToken: true, + }); +} + +export { + getAigcTemplateList, + getAigcTemplateItemList, + getAigcGeneratorTaskList, + getAigcGeneratorTaskDetail, + createAigcGeneratorTask, + createAigcImageToVideoTask, + getAigcImageToVideoItemDetail, + getCurrentCredit, + getCreditLedgerPage, + getPointsTopUpAgreement, + getUserAgreement, + createAigcGeneratorTaskShare, + getAigcGeneratorTaskShareDetail, +}; diff --git a/src/pages-aigc/request/VirtualRechargeApi.js b/src/pages-aigc/request/VirtualRechargeApi.js new file mode 100644 index 0000000..8d7029c --- /dev/null +++ b/src/pages-aigc/request/VirtualRechargeApi.js @@ -0,0 +1,44 @@ +import request from "@/request/base/request"; + +const VIRTUAL_RECHARGE_BASE_PATH = "/hotelBiz/credit/virtual-recharge"; + +const getVirtualRechargeOrderPath = (rechargeOrderNo) => { + const normalizedOrderNo = String(rechargeOrderNo || "").trim(); + if (!normalizedOrderNo) { + throw new Error("积分充值订单号不能为空"); + } + + return `${VIRTUAL_RECHARGE_BASE_PATH}/orders/${encodeURIComponent(normalizedOrderNo)}`; +}; + +function getVirtualRechargeOptions() { + return request.get(`${VIRTUAL_RECHARGE_BASE_PATH}/options`, {}); +} + +function createVirtualRechargeOrder(args) { + return request.post(`${VIRTUAL_RECHARGE_BASE_PATH}/orders`, args, { + sensitive: true, + }); +} + +function confirmVirtualRechargeOrder(rechargeOrderNo, args = {}) { + const wxLoginCode = String(args?.wxLoginCode || "").trim(); + const payload = wxLoginCode ? { wxLoginCode } : {}; + + return request.post( + `${getVirtualRechargeOrderPath(rechargeOrderNo)}/confirm`, + payload, + { sensitive: true } + ); +} + +function getVirtualRechargeOrder(rechargeOrderNo) { + return request.get(getVirtualRechargeOrderPath(rechargeOrderNo), {}); +} + +export { + confirmVirtualRechargeOrder, + createVirtualRechargeOrder, + getVirtualRechargeOptions, + getVirtualRechargeOrder, +}; diff --git a/src/pages-aigc/services/wechatLogin.js b/src/pages-aigc/services/wechatLogin.js new file mode 100644 index 0000000..6403a40 --- /dev/null +++ b/src/pages-aigc/services/wechatLogin.js @@ -0,0 +1,26 @@ +const getWechatApi = () => { + // #ifdef MP-WEIXIN + if (typeof wx !== "undefined") return wx; + // #endif + return null; +}; + +export function getWechatLoginCode(wechatApi = getWechatApi()) { + if (!wechatApi || typeof wechatApi.login !== "function") { + return Promise.reject(new Error("当前环境无法获取微信登录凭证")); + } + + return new Promise((resolve, reject) => { + wechatApi.login({ + success: (result) => { + const code = String(result?.code || "").trim(); + if (code) { + resolve(code); + return; + } + reject(new Error("微信登录凭证获取失败")); + }, + fail: () => reject(new Error("微信登录凭证获取失败")), + }); + }); +} diff --git a/src/pages-aigc/sharedWork/sharedWork.vue b/src/pages-aigc/sharedWork/sharedWork.vue new file mode 100644 index 0000000..356f5af --- /dev/null +++ b/src/pages-aigc/sharedWork/sharedWork.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/pages-aigc/sharedWork/styles/index.scss b/src/pages-aigc/sharedWork/styles/index.scss new file mode 100644 index 0000000..860d26b --- /dev/null +++ b/src/pages-aigc/sharedWork/styles/index.scss @@ -0,0 +1,112 @@ +.shared-work-page { + position: relative; + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + overflow: hidden; + color: #173a5f; + box-sizing: border-box; +} + +.shared-work-nav { + position: relative; + z-index: 3; + flex: 0 0 auto; + background: rgba(197, 229, 248, 0.96); +} + +.shared-work-nav :deep(.top-nav-bar > .nav-bar-content) { + padding-right: 12px; + padding-left: 12px; +} + +.shared-work-nav :deep(.nav-bar-center text) { + font-size: 17px; + line-height: 23px; + font-weight: 900; +} + +.shared-work-scroll { + flex: 1 1 0; + width: 100%; + height: 0; + padding-top: 10px; + padding-bottom: 104px; + background-color: #fff; + box-sizing: border-box; +} + +.shared-work-media { + width: 100%; + height: 500px; + display: block; + background: #b8dced; +} + +.shared-work-meta { + padding: 18px 18px 118px; +} + +.shared-work-title, +.shared-work-type { + display: block; +} + +.shared-work-title { + overflow: hidden; + color: #173a5f; + font-size: 20px; + line-height: 27px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +.shared-work-type { + margin-top: 4px; + color: #0b7b60; + font-size: 12px; + line-height: 17px; + font-weight: 800; +} + +.shared-work-empty { + min-height: 420px; + display: flex; + align-items: center; + justify-content: center; +} + +.shared-work-cta-wrap { + position: fixed; + z-index: 4; + right: 0; + bottom: 0; + left: 0; + padding: 18px 16px calc(14px + env(safe-area-inset-bottom)); + box-sizing: border-box; +} + +.shared-work-cta { + width: 100%; + height: 54px; + margin: 0; + padding: 0; + border: 0; + border-radius: 999px; + background: #20c99a; + color: #ffffff; + font-size: 16px; + line-height: 54px; + font-weight: 900; + box-shadow: 0 10px 22px rgba(32, 201, 154, 0.2); +} + +.shared-work-cta::after { + border: 0; +} + +.shared-work-cta.is-pressed { + opacity: 0.84; +} diff --git a/src/pages-aigc/useTemplate/components/GenerateConfirmPanel/index.vue b/src/pages-aigc/useTemplate/components/GenerateConfirmPanel/index.vue new file mode 100644 index 0000000..0894429 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/GenerateConfirmPanel/index.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/GenerateConfirmPanel/styles/index.scss b/src/pages-aigc/useTemplate/components/GenerateConfirmPanel/styles/index.scss new file mode 100644 index 0000000..e2e89e6 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/GenerateConfirmPanel/styles/index.scss @@ -0,0 +1,124 @@ +.generate-confirm-panel { + margin-top: 16px; +} + +.generate-points-summary { + height: 80px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.generate-point-card { + min-width: 0; + height: 80px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 18px; + background: rgba(255, 255, 255, 0.68); + box-shadow: 0 8px 18px rgba(18, 48, 56, 0.04); + box-sizing: border-box; +} + +.generate-point-label, +.generate-point-value { + display: block; + max-width: 100%; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +.generate-point-label { + color: #7a8596; + font-size: 11px; + line-height: 16px; + font-weight: 900; +} + +.generate-point-value { + margin-top: 7px; + color: #102942; + font-size: 21px; + line-height: 27px; + font-weight: 900; +} + +.generate-freeze-note { + min-height: 86px; + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 14px; + padding: 17px 16px; + border-radius: 18px; + background: rgba(255, 255, 255, 0.72); + box-shadow: 0 8px 18px rgba(18, 48, 56, 0.04); + box-sizing: border-box; +} + +.generate-freeze-title, +.generate-freeze-desc { + display: block; + overflow: hidden; + text-overflow: ellipsis; +} + +.generate-freeze-title { + color: #172033; + font-size: 14px; + line-height: 20px; + font-weight: 900; + white-space: nowrap; +} + +.generate-freeze-desc { + margin-top: 6px; + color: #52637a; + font-size: 12px; + line-height: 18px; + font-weight: 800; +} + +.generate-confirm-actions { + height: 48px; + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr); + gap: 10px; + margin-top: 18px; +} + +.generate-action { + min-width: 0; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 999px; + font-size: 15px; + line-height: 22px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; + box-sizing: border-box; +} + +.generate-action.is-primary { + background: #20cf75; + color: #ffffff; + box-shadow: 0 12px 24px rgba(32, 207, 117, 0.25); +} + +.generate-action.is-secondary { + background: rgba(255, 255, 255, 0.82); + color: #3d5066; +} + +.generate-action.is-pressed { + opacity: 0.86; +} diff --git a/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/index.vue b/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/index.vue new file mode 100644 index 0000000..b87639b --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/index.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/styles/index.scss b/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/styles/index.scss new file mode 100644 index 0000000..588da24 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/styles/index.scss @@ -0,0 +1,111 @@ +.photo-confirm-drawer { + position: relative; + width: 100vw; + max-width: 100%; + min-height: 446px; + margin-top: -34px; + padding: 30px 38px 28px; + border-radius: 28px 28px 0 0; + background: #ffffff; + box-shadow: 0 -8px 28px rgba(18, 35, 48, 0.08); + box-sizing: border-box; +} + +.photo-confirm-close { + position: absolute; + left: 24px; + top: 28px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #9aa4b2; +} + +.photo-confirm-title { + display: block; + margin: 6px 0 23px; + color: #111827; + font-size: 18px; + line-height: 24px; + font-weight: 900; + text-align: center; +} + +.photo-confirm-card { + width: 100%; + min-height: 74px; + display: flex; + align-items: center; + gap: 14px; + padding: 13px 16px; + border-radius: 16px; + background: #f0fbfd; + box-sizing: border-box; +} + +.photo-confirm-avatar { + flex: 0 0 48px; + width: 48px; + height: 48px; + overflow: hidden; + border-radius: 50%; + background: #d8f1f7; +} + +.photo-confirm-avatar-image { + width: 64px; + height: 92px; + display: block; + margin: -14px 0 0 -8px; +} + +.photo-confirm-copy { + min-width: 0; + flex: 1 1 auto; +} + +.photo-confirm-card-title, +.photo-confirm-card-desc { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.photo-confirm-card-title { + color: #172033; + font-size: 13px; + line-height: 19px; + font-weight: 900; +} + +.photo-confirm-card-desc { + margin-top: 2px; + color: #56708c; + font-size: 11px; + line-height: 16px; + font-weight: 800; +} + +.photo-confirm-primary { + width: 100%; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + margin-top: 26px; + border-radius: 999px; + background: #061e34; + color: #ffffff; + font-size: 16px; + line-height: 22px; + font-weight: 900; + box-sizing: border-box; +} + +.photo-confirm-primary.is-pressed { + opacity: 0.86; +} diff --git a/src/pages-aigc/useTemplate/components/PhotoGuidePanel/index.vue b/src/pages-aigc/useTemplate/components/PhotoGuidePanel/index.vue new file mode 100644 index 0000000..84adddd --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PhotoGuidePanel/index.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/PhotoGuidePanel/styles/index.scss b/src/pages-aigc/useTemplate/components/PhotoGuidePanel/styles/index.scss new file mode 100644 index 0000000..d7eea88 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PhotoGuidePanel/styles/index.scss @@ -0,0 +1,140 @@ +.photo-guide-panel { + position: relative; + width: 100vw; + max-width: 100%; + min-height: 448px; + margin-top: -34px; + padding: 30px 26px 24px; + border-radius: 28px 28px 0 0; + background: #ffffff; + box-shadow: 0 -8px 28px rgba(18, 35, 48, 0.08); + box-sizing: border-box; +} + +.photo-guide-close { + position: absolute; + left: 24px; + top: 28px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #9aa4b2; +} + +.photo-guide-title { + display: block; + margin: 6px 0 24px; + color: #111827; + font-size: 18px; + line-height: 24px; + font-weight: 900; + text-align: center; +} + +.photo-guide-section + .photo-guide-section { + margin-top: 22px; +} + +.photo-guide-section-title { + display: block; + margin: 0 0 12px; + color: #667386; + font-size: 13px; + line-height: 18px; + font-weight: 800; +} + +.photo-guide-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.photo-example-card { + min-width: 0; + text-align: center; +} + +.photo-example-thumb { + width: 80px; + height: 80px; + display: block; + overflow: hidden; + margin: 0 auto; + border: 2px solid transparent; + border-radius: 14px; + background: #f2f4f7; + box-sizing: border-box; +} + +.photo-example-image { + width: 100%; + height: 100%; + display: block; +} + +.is-positive .photo-example-thumb { + border-color: rgba(22, 191, 118, 0.34); +} + +.is-negative .photo-example-thumb { + border-color: rgba(255, 80, 79, 0.34); +} + +.photo-example-caption { + position: relative; + display: inline-block; + max-width: 100%; + overflow: hidden; + margin-top: 8px; + padding-left: 15px; + color: #16bf76; + font-size: 11px; + line-height: 15px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +.photo-example-caption::before { + content: "✓"; + position: absolute; + left: 0; + top: 1px; + width: 12px; + height: 12px; + border-radius: 50%; + background: #16bf76; + color: #ffffff; + font-size: 9px; + line-height: 12px; + text-align: center; +} + +.is-negative .photo-example-caption { + color: #ff504f; +} + +.is-negative .photo-example-caption::before { + content: "×"; + background: #ff504f; +} + +.photo-guide-primary { + width: 100%; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + margin-top: 24px; + border-radius: 999px; + background: #061e34; + color: #ffffff; + font-size: 16px; + line-height: 22px; + font-weight: 900; + box-sizing: border-box; +} diff --git a/src/pages-aigc/useTemplate/components/PhotoPickDrawer/index.vue b/src/pages-aigc/useTemplate/components/PhotoPickDrawer/index.vue new file mode 100644 index 0000000..d9a34c5 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PhotoPickDrawer/index.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/PhotoPickDrawer/styles/index.scss b/src/pages-aigc/useTemplate/components/PhotoPickDrawer/styles/index.scss new file mode 100644 index 0000000..c47808b --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PhotoPickDrawer/styles/index.scss @@ -0,0 +1,145 @@ +.photo-pick-drawer { + position: relative; + width: 100vw; + max-width: 100%; + min-height: 336px; + margin-top: -34px; + padding: 22px 18px 20px; + border-radius: 26px 26px 0 0; + background: #ffffff; + box-shadow: 0 -8px 28px rgba(18, 35, 48, 0.08); + box-sizing: border-box; +} + +.photo-pick-close { + position: absolute; + left: 18px; + top: 22px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; +} + +.photo-pick-close.is-disabled { + opacity: 0.48; +} + +.photo-pick-title { + display: block; + margin: 1px 0 17px; + color: #111827; + font-size: 18px; + line-height: 24px; + font-weight: 900; + text-align: center; +} + +.photo-pick-card { + width: 100%; + gap: 6px; + padding: 14px 18px; + border: 1.5px dashed rgba(32, 207, 117, 0.46); + border-radius: 20px; + background: #f2fbf6; + box-sizing: border-box; +} + +.photo-pick-card-title, +.photo-pick-card-desc { + display: block; + overflow: hidden; + text-overflow: ellipsis; +} + +.photo-pick-card-title { + color: #172033; + font-size: 16px; + line-height: 22px; + font-weight: 700; + white-space: nowrap; + margin-bottom: 12px; +} + +.photo-pick-card-desc { + color: #667386; + font-size: 12px; + line-height: 18px; + font-weight: 800; +} + +.photo-pick-actions { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + margin-top: 14px; +} + +.photo-pick-action { + min-width: 0; + min-height: 66px; + padding: 8px 10px; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + border-radius: 18px; + background: #ffffff; + color: #172033; + box-shadow: 0 8px 18px rgba(18, 48, 56, 0.08); + transition: opacity 180ms ease; +} + +.photo-pick-action.is-primary { + background: #20cf75; + color: #ffffff; + box-shadow: 0 12px 22px rgba(32, 207, 117, 0.22); +} + +.photo-pick-action-title, +.photo-pick-action-desc { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.photo-pick-action-title { + font-size: 15px; + line-height: 20px; + font-weight: 900; +} + +.photo-pick-action-desc { + color: #7a8596; + font-size: 10px; + line-height: 14px; + font-weight: 800; +} + +.photo-pick-action.is-primary .photo-pick-action-desc { + color: rgba(255, 255, 255, 0.82); +} + +.photo-pick-action.is-pressed { + opacity: 0.82; +} + +.photo-pick-action.is-disabled { + opacity: 0.58; +} + +.photo-pick-hint { + display: block; + margin: 10px 0 0; + color: #7a8596; + font-size: 12px; + line-height: 18px; + font-weight: 800; + text-align: center; +} diff --git a/src/pages-aigc/useTemplate/components/PointInsufficientDialog/index.vue b/src/pages-aigc/useTemplate/components/PointInsufficientDialog/index.vue new file mode 100644 index 0000000..f8b9087 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PointInsufficientDialog/index.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/PointInsufficientDialog/styles/index.scss b/src/pages-aigc/useTemplate/components/PointInsufficientDialog/styles/index.scss new file mode 100644 index 0000000..a56a518 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/PointInsufficientDialog/styles/index.scss @@ -0,0 +1,130 @@ +.point-dialog { + width: 318px; + max-width: calc(100vw - 56px); + padding: 30px 26px 24px; + border-radius: 24px; + background: #ffffff; + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.26); + box-sizing: border-box; +} + +.point-recharge-dialog { + padding-top: 24px; +} + +.point-dialog-badge { + width: fit-content; + height: 26px; + display: flex; + align-items: center; + margin: 0 auto 12px; + padding: 0 12px; + border-radius: 999px; + background: #e9f9f1; + color: #0fa567; + font-size: 12px; + line-height: 26px; + font-weight: 900; +} + +.point-dialog-title { + display: block; + margin-bottom: 14px; + color: #111827; + font-size: 20px; + line-height: 28px; + font-weight: 900; + text-align: center; +} + +.point-dialog-desc { + display: block; + margin: 0; + color: #667386; + font-size: 14px; + line-height: 22px; + font-weight: 800; + text-align: center; +} + +.point-dialog-summary { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + margin-top: 18px; +} + +.point-summary-item { + min-width: 0; + padding: 11px 6px 10px; + border-radius: 15px; + background: #f3fbf6; + color: #172033; + text-align: center; + box-sizing: border-box; +} + +.point-summary-label, +.point-summary-value { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.point-summary-label { + color: #7b8798; + font-size: 10px; + line-height: 13px; + font-weight: 800; +} + +.point-summary-value { + margin-top: 3px; + color: #172033; + font-size: 18px; + line-height: 24px; + font-weight: 900; +} + +.point-summary-item.is-shortage .point-summary-value { + color: #0fa567; +} + +.point-dialog-actions { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; + margin-top: 28px; +} + +.point-dialog-button { + min-width: 0; + height: 54px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 999px; + font-size: 16px; + line-height: 22px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; + box-sizing: border-box; +} + +.point-dialog-button.is-cancel { + border: 1px solid #e2e6ea; + background: #ffffff; + color: #172033; +} + +.point-dialog-button.is-primary { + background: #061e34; + color: #ffffff; +} + +.point-dialog-button.is-pressed { + opacity: 0.86; +} diff --git a/src/pages-aigc/useTemplate/components/TemplateVersionHero/index.vue b/src/pages-aigc/useTemplate/components/TemplateVersionHero/index.vue new file mode 100644 index 0000000..05d3e66 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/TemplateVersionHero/index.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/TemplateVersionHero/styles/index.scss b/src/pages-aigc/useTemplate/components/TemplateVersionHero/styles/index.scss new file mode 100644 index 0000000..d715b27 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/TemplateVersionHero/styles/index.scss @@ -0,0 +1,116 @@ +.template-version-hero { + position: relative; + height: 380px; + overflow: hidden; + border-radius: 24px; + background: var(--template-accent, #0a4d46); + color: #ffffff; + box-shadow: 0 16px 30px rgba(15, 29, 37, 0.1); +} + +.template-version-hero.is-confirm { + height: 248px; + border-radius: 18px; + box-shadow: 0 12px 24px rgba(15, 29, 37, 0.08); +} + +.template-version-image, +.template-version-video { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + display: block; +} + +.template-version-shade { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: linear-gradient( + 180deg, + rgba(8, 22, 28, 0.05) 0%, + rgba(8, 22, 28, 0.18) 46%, + rgba(8, 22, 28, 0.72) 100% + ); +} + +.template-version-copy { + position: absolute; + z-index: 1; + right: 18px; + bottom: 18px; + left: 18px; + min-width: 0; +} + +.template-version-badge { + height: 22px; + display: inline-flex; + align-items: center; + max-width: 132px; + overflow: hidden; + padding: 0 9px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.78); + color: #172033; + font-size: 10px; + line-height: 22px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +.template-version-title, +.template-version-desc { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.template-version-title { + margin-top: 10px; + color: #ffffff; + font-size: 24px; + line-height: 30px; + font-weight: 900; +} + +.template-version-desc { + margin-top: 5px; + color: rgba(255, 255, 255, 0.82); + font-size: 12px; + line-height: 18px; + font-weight: 800; +} + +.template-version-hero.is-confirm .template-version-copy { + right: 16px; + bottom: 18px; + left: 16px; +} + +.template-version-hero.is-confirm .template-version-badge { + height: 22px; + max-width: 88px; + line-height: 22px; +} + +.template-version-hero.is-confirm .template-version-title { + margin-top: 9px; + font-size: 24px; + line-height: 30px; +} + +.template-version-hero.is-confirm .template-version-desc { + max-width: 294px; + margin-top: 4px; + font-size: 12px; + line-height: 18px; +} diff --git a/src/pages-aigc/useTemplate/components/VersionOptionCard/index.vue b/src/pages-aigc/useTemplate/components/VersionOptionCard/index.vue new file mode 100644 index 0000000..f540b90 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/VersionOptionCard/index.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/VersionOptionCard/styles/index.scss b/src/pages-aigc/useTemplate/components/VersionOptionCard/styles/index.scss new file mode 100644 index 0000000..ae94ac7 --- /dev/null +++ b/src/pages-aigc/useTemplate/components/VersionOptionCard/styles/index.scss @@ -0,0 +1,68 @@ +.version-option-card { + min-height: 84px; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + padding: 14px 16px; + border: 1px solid rgba(255, 255, 255, 0.72); + border-radius: 20px; + background: rgba(255, 255, 255, 0.82); + text-align: left; + box-shadow: 0 8px 18px rgba(18, 48, 56, 0.05); + box-sizing: border-box; + transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; +} + +.version-option-card.is-selected { + border-color: rgba(32, 207, 117, 0.64); + box-shadow: 0 10px 22px rgba(32, 207, 117, 0.1); +} + +.version-option-card.is-pressed { + opacity: 0.82; +} + +.version-option-copy { + min-width: 0; +} + +.version-option-title, +.version-option-desc, +.version-option-type, +.version-option-cost { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.version-option-title { + color: #172033; + font-size: 15px; + line-height: 21px; + font-weight: 900; +} + +.version-option-desc { + margin-top: 4px; + color: #7a8596; + font-size: 11px; + line-height: 16px; + font-weight: 800; +} + +.version-option-type { + margin-top: 4px; + color: #9aa6b8; + font-size: 10px; + line-height: 14px; + font-weight: 800; +} + +.version-option-cost { + color: #12a765; + font-size: 15px; + line-height: 20px; + font-weight: 900; +} diff --git a/src/pages-aigc/useTemplate/components/VersionOptionList/index.vue b/src/pages-aigc/useTemplate/components/VersionOptionList/index.vue new file mode 100644 index 0000000..fb6e0fc --- /dev/null +++ b/src/pages-aigc/useTemplate/components/VersionOptionList/index.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/pages-aigc/useTemplate/components/VersionOptionList/styles/index.scss b/src/pages-aigc/useTemplate/components/VersionOptionList/styles/index.scss new file mode 100644 index 0000000..7d8630b --- /dev/null +++ b/src/pages-aigc/useTemplate/components/VersionOptionList/styles/index.scss @@ -0,0 +1,5 @@ +.version-option-list { + display: grid; + gap: 10px; + margin-top: 14px; +} diff --git a/src/pages-aigc/useTemplate/data/photoGuideExamples.js b/src/pages-aigc/useTemplate/data/photoGuideExamples.js new file mode 100644 index 0000000..021693d --- /dev/null +++ b/src/pages-aigc/useTemplate/data/photoGuideExamples.js @@ -0,0 +1,41 @@ +export const positiveExamples = [ + { + id: "portrait", + label: "单人正脸", + variant: "portrait", + imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/1.png", + }, + { + id: "smile", + label: "表情自然", + variant: "smile", + imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/2.png", + }, + { + id: "scene", + label: "背景干净", + variant: "scene", + imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/3.png", + }, +]; + +export const negativeExamples = [ + { + id: "blur", + label: "非清晰正脸", + variant: "blur", + imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/4.png", + }, + { + id: "cover", + label: "面部有遮挡", + variant: "cover", + imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/5.png", + }, + { + id: "group", + label: "多人合照", + variant: "group", + imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/6.png", + }, +]; diff --git a/src/pages-aigc/useTemplate/styles/index.scss b/src/pages-aigc/useTemplate/styles/index.scss new file mode 100644 index 0000000..0f36d42 --- /dev/null +++ b/src/pages-aigc/useTemplate/styles/index.scss @@ -0,0 +1,54 @@ +.aigc-use-template-page { + position: relative; + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + overflow: hidden; + color: #172033; + background: + radial-gradient( + 130% 78% at 0% 0%, + rgba(255, 249, 226, 0.76), + rgba(255, 249, 226, 0) 42% + ), + linear-gradient(180deg, #effaf5 0%, #f4fbf7 58%, #eef8ff 100%); + box-sizing: border-box; +} + +.aigc-use-template-body { + position: relative; + flex: 1 1 0; + min-height: 0; + overflow: hidden; +} + +.aigc-use-template-content { + height: 100%; + min-height: 0; + overflow: hidden; + padding: 18px; + box-sizing: border-box; +} + +.aigc-use-template-popup-host { + position: fixed; + z-index: 30; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +.aigc-use-template-note { + display: block; + margin: 12px 4px 0; + color: #7a8596; + font-size: 11px; + line-height: 17px; + font-weight: 800; + text-align: center; +} diff --git a/src/pages-aigc/useTemplate/useTemplate.vue b/src/pages-aigc/useTemplate/useTemplate.vue new file mode 100644 index 0000000..13e0753 --- /dev/null +++ b/src/pages-aigc/useTemplate/useTemplate.vue @@ -0,0 +1,487 @@ + + + + + diff --git a/src/pages.json b/src/pages.json index 438dbe6..287e378 100644 --- a/src/pages.json +++ b/src/pages.json @@ -1,119 +1,178 @@ -{ - "pages": [ - { - "path": "pages/index/index", - "style": { - "navigationStyle": "custom" - } - }, - { - "path": "pages/login/index", - "style": { - "navigationStyle": "custom" - } - }, - { - "path": "pages/goods/index", - "style": { - "navigationStyle": "custom" - } - }, - { - "path": "pages/goods/album/index", - "style": { - "navigationStyle": "custom" - } - }, - { - "path": "pages/webview/index", - "style": { - "navigationStyle": "custom", - "backgroundColor": "#FFFFFF", - "navigationBarBackgroundColor": "#FFFFFF", - "navigationBarTextStyle": "black" - } - }, - { - "path": "pages/ChatMain/ChatLongAnswer/index", - "style": { - "navigationStyle": "custom" - } - } - ], - "subPackages": [ - { - "root": "pages-order", - "pages": [ - { - "path": "order/list", - "style": { - "navigationStyle": "custom" - } - }, - { - "path": "order/detail", - "style": { - "navigationStyle": "custom" - } - } - ] - }, - { - "root": "pages-service", - "pages": [ - { - "path": "order/list", - "style": { - "navigationStyle": "custom" - } - } - ] - }, - { - "root": "pages-quick", - "pages": [ - { - "path": "list", - "style": { - "navigationStyle": "custom" - } - } - ] - }, - { - "root": "pages-booking", - "pages": [ - { - "path": "index", - "style": { - "navigationStyle": "custom" - } - } - ] - }, - { - "root": "pages-bridge", - "pages": [ - { - "path": "UploadImage", - "style": { - "navigationStyle": "custom" - } - }, - { - "path": "SaveImage", - "style": { - "navigationStyle": "custom" - } - } - ] - } - ], - "globalStyle": { - "navigationBarTextstyle": "black", - "navigationBarTitleText": "", - "navigationBarBackgroundcolor": "#F8F8F8", - "backgroundColor": "#F8F8F8" - }, - "uniIdRouter": {} +{ + "pages": [ + { + "path": "pages/index/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/login/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/goods/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/goods/album/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/webview/index", + "style": { + "navigationStyle": "custom", + "backgroundColor": "#FFFFFF", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/ChatMain/ChatLongAnswer/index", + "style": { + "navigationStyle": "custom" + } + } + ], + "subPackages": [ + { + "root": "pages-order", + "pages": [ + { + "path": "order/list", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "order/detail", + "style": { + "navigationStyle": "custom" + } + } + ] + }, + { + "root": "pages-service", + "pages": [ + { + "path": "order/list", + "style": { + "navigationStyle": "custom" + } + } + ] + }, + { + "root": "pages-quick", + "pages": [ + { + "path": "list", + "style": { + "navigationStyle": "custom" + } + } + ] + }, + { + "root": "pages-booking", + "pages": [ + { + "path": "index", + "style": { + "navigationStyle": "custom" + } + } + ] + }, + { + "root": "pages-bridge", + "pages": [ + { + "path": "UploadImage", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "SaveImage", + "style": { + "navigationStyle": "custom" + } + } + ] + }, + { + "root": "pages-aigc", + "pages": [ + { + "path": "home/home", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "agreement/agreement", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "useTemplate/useTemplate", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "progress/progress", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "detail/detail", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "sharedWork/sharedWork", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "recharge/recharge", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pointsDetails/pointsDetails", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "record/record", + "style": { + "navigationStyle": "custom" + } + } + ] + } + ], + "globalStyle": { + "navigationBarTextstyle": "black", + "navigationBarTitleText": "", + "navigationBarBackgroundcolor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + }, + "uniIdRouter": {} } \ No newline at end of file diff --git a/src/pages/ChatMain/ChatQuickAccess/index.vue b/src/pages/ChatMain/ChatQuickAccess/index.vue index 2e1ce16..f403425 100644 --- a/src/pages/ChatMain/ChatQuickAccess/index.vue +++ b/src/pages/ChatMain/ChatQuickAccess/index.vue @@ -1,10 +1,10 @@