Commit 527c65d0 by chunhong.mu

perf(*): 优化模板

parent 17eea8db
...@@ -529,9 +529,28 @@ pnpm -F admin type-check ...@@ -529,9 +529,28 @@ pnpm -F admin type-check
**环境文件**:位于各子包根目录,`.env.development``.env.test``.env.staging``.env.production` **环境文件**:位于各子包根目录,`.env.development``.env.test``.env.staging``.env.production`
#### 模板自带的环境值(重要)
为了**开箱可体验**,模板对四个包做了区别对待:
| 文件 | 模板中的状态 | 原因 |
|------|-------------|------|
| `.env.development``.env.test` | **已预填一套可用的测试环境值** | 拉下来 `pnpm dev` 就能看到登录页、图形验证码正常显示,不需要先去要配置 |
| `.env.staging``.env.production` | **故意留空** | 模板工程不携带生产凭证;上线前必须自己填 |
预填的是**测试环境**的地址与 `client_id``client_secret` 为测试值 `123456`),只够跑通登录/验证码这类匿名接口。
> ⚠️ **派生新项目后请务必替换 `.env.development` / `.env.test` 里的所有值**,否则你的开发环境会一直打在别人的测试后端上。需要改的是:`VITE_APP_UNIPLAT`、`VITE_APP_CLIENT_ID`、`VITE_APP_CLIENT_SECRET`、通行证四个地址,以及 `src/config/index.ts` 里带 `TODO` 的 `rootEntrance` 与 `domainService`。
**为什么验证码依赖 `VITE_APP_UNIPLAT`**`verifyService.generateImage()`**同步方法**,它只是用随机 seed 拼出 `{uniplatApi}/general/imageToVerify/{seed}` 这个图片地址,真正的加载由 `<img>` / `<image>` 完成。所以 `VITE_APP_UNIPLAT` 为空时,地址会退化成同源相对路径 `/general/imageToVerify/123`——dev server 会用 `index.html` 兜底返回 **200 的 HTML**,浏览器拿到 HTML 当图片解析,只能渲染成破图,且在 Network 面板里看起来还是「成功」,非常难排查。因此:
- `@common/sdk``uniplatApi` 为空时会 `console.error` 明确报出来;
- `generateImage()` 检测到地址不是 `http(s)://` 开头时返回空 `img`,避免把破图塞给 UI;
- 三个登录页在拿不到验证码时渲染「加载失败,点击重试」占位,并监听 `error` 事件。
> ⚠️ **不要在 `.env.*` 里设置 `NODE_ENV`**。Vite / Nuxt 会根据执行的命令自动设置(dev → `development`,build → `production`);手写 `NODE_ENV=production` 会导致 `pnpm dev:test` 之类的 dev server 报 “NODE_ENV=production is not supported in the .env file” 警告。 > ⚠️ **不要在 `.env.*` 里设置 `NODE_ENV`**。Vite / Nuxt 会根据执行的命令自动设置(dev → `development`,build → `production`);手写 `NODE_ENV=production` 会导致 `pnpm dev:test` 之类的 dev server 报 “NODE_ENV=production is not supported in the .env file” 警告。
**变量清单**模板中值为空,需按项目填写): **变量清单**`dev`/`test` 已预填,`staging`/`production` 需按项目填写):
| 变量 | 说明 | 必填 | | 变量 | 说明 | 必填 |
|------|------|------| |------|------|------|
......
...@@ -6,14 +6,17 @@ ...@@ -6,14 +6,17 @@
## 这是一个模板工程 ## 这是一个模板工程
**开箱即可体验**`.env.development` / `.env.test` 已预填一套可用的**测试环境**配置,`pnpm install` 后直接 `pnpm dev:project` 就能看到登录页、图形验证码正常显示。`.env.staging` / `.env.production` 故意留空——模板不携带生产凭证。
从本仓库派生新项目后,**先做这几件事** 从本仓库派生新项目后,**先做这几件事**
1. 全仓搜索 `TODO:` 与占位符,逐项替换为项目实际值(应用名、`rootEntrance``domainService`、分享标题等) 1. **替换 `.env.development` / `.env.test` 里的预填值**,否则开发环境会一直打在别人的测试后端上(`VITE_APP_UNIPLAT` / `VITE_APP_CLIENT_ID` / `VITE_APP_CLIENT_SECRET` + 通行证四个地址)
2. 填写各包的 `.env.*`(模板里所有值都是空的,`VITE_APP_UNIPLAT` / `VITE_APP_CLIENT_ID` 必填) 2. 填写 `.env.staging` / `.env.production`(当前为空)
3. 删除不需要的包,或用 `pnpm new:project` 新增包 3. 全仓搜索 `TODO:`,逐项替换为项目实际值(应用名、`rootEntrance``domainService`、分享标题等)
4. 更新本文件与 `ARCHITECTURE.md` 里的项目描述 4. 删除不需要的包,或用 `pnpm new:project` 新增包
5. 更新本文件与 `ARCHITECTURE.md` 里的项目描述
**不要把业务域名、appid、client secret 写回模板**——所有环境相关值一律走 `.env.*`,代码里只读 `config` **不要把生产域名、appid、client secret 写回模板**——所有环境相关值一律走 `.env.*`,代码里只读 `config`
## 快速开始 ## 快速开始
...@@ -99,10 +102,12 @@ types/ 类型定义 ...@@ -99,10 +102,12 @@ types/ 类型定义
| `--mode staging` | `.env.staging` | `stage` | | `--mode staging` | `.env.staging` | `stage` |
| `--mode production` | `.env.production` | `prod` | | `--mode production` | `.env.production` | `prod` |
取值与 `@common/sdk``Environment` 枚举一致(`dev` / `test` / `stage` / `prod`)。 取值与 `@common/sdk``Environment` 枚举一致(`dev` / `test` / `stage` / `prod`)。`dev`/`test` 已预填测试环境值,`staging`/`prod` 为空。
⚠️ **不要在 `.env.*` 里写 `NODE_ENV`**——Vite/Nuxt 会按命令自动设置,手写 `NODE_ENV=production` 会让 `dev:test` 之类的 dev server 报警告。 ⚠️ **不要在 `.env.*` 里写 `NODE_ENV`**——Vite/Nuxt 会按命令自动设置,手写 `NODE_ENV=production` 会让 `dev:test` 之类的 dev server 报警告。
⚠️ **图形验证码依赖 `VITE_APP_UNIPLAT`**`generateImage()` 是同步方法,只拼出 `{uniplatApi}/general/imageToVerify/{seed}` 图片地址。该变量为空时地址退化成相对路径,dev server 用 `index.html` 兜底返回 **200 的 HTML**,表现为破图但网络面板显示成功。已加三重防护:SDK 报 `console.error``generateImage()` 返回空 `img`、登录页渲染「点击重试」占位。
## 可用 skills ## 可用 skills
| skill | 用途 | | skill | 用途 |
......
...@@ -120,6 +120,14 @@ export class Sdk { ...@@ -120,6 +120,14 @@ export class Sdk {
public init(config: SdkConfig, adapter?: AxiosAdapter) { public init(config: SdkConfig, adapter?: AxiosAdapter) {
const baseUrl = config.uniplatApi const baseUrl = config.uniplatApi
if (!baseUrl) {
// 模板工程默认为空,未填时所有请求(含图形验证码图片)都会退化成
// 相对地址打到本地 dev server 上,表现为验证码不显示、接口静默失败
console.error(
"[@common/sdk] uniplatApi 为空,请在对应包的 .env.* 中填写 VITE_APP_UNIPLAT。" +
"未配置时图形验证码无法显示、所有接口请求都会失败。",
)
}
this.uniplatSdk = new UniplatSdk({ sse: false }) this.uniplatSdk = new UniplatSdk({ sse: false })
this.uniplatSdk.global.baseUrl = baseUrl this.uniplatSdk.global.baseUrl = baseUrl
......
...@@ -22,11 +22,28 @@ export function createVerifyService(sdk: UniplatSdk, clientId: string) { ...@@ -22,11 +22,28 @@ export function createVerifyService(sdk: UniplatSdk, clientId: string) {
return { return {
/** /**
* 生成图形验证码 * 生成图形验证码
*
* 同步方法:SDK 只是用随机 seed 拼出图片 URL,由 `<img>` 自行加载。
*
* 若 `uniplatApi` 未配置,SDK 的 baseUrl 会退化成 `/`,拼出的地址是
* 同源相对路径(dev server 会用 index.html 兜底返回 200 的 HTML),
* `<img>` 只能渲染成破图。这里直接返回空 `img`,让调用方能明确区分
* “未配置”与“真的拿到了验证码”。
*/ */
generateImage(): VerifyImageResult { generateImage(): VerifyImageResult {
const result = sdk.getVerifyImageAndSeed() as any const result = sdk.getVerifyImageAndSeed() as any
const img = String(result.img || "")
const configured = /^https?:\/\//i.test(img)
if (!configured) {
console.error(
`[@common/sdk] 图形验证码地址不可用(${img || "空"}),` +
"请检查 VITE_APP_UNIPLAT 是否已配置。",
)
}
return { return {
img: result.img, img: configured ? img : "",
seed: String(result.seed), seed: String(result.seed),
} }
}, },
......
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=dev APP_ENV=dev
VITE_APP_ENV=dev VITE_APP_ENV=dev
...@@ -7,22 +9,22 @@ VITE_DEBUG=true ...@@ -7,22 +9,22 @@ VITE_DEBUG=true
VITE_APP_LOG_ENV=0 VITE_APP_LOG_ENV=0
# ===== 统一业务平台(必填)===== # ===== 统一业务平台(必填)=====
VITE_APP_UNIPLAT= VITE_APP_UNIPLAT=http://hro.test-api.qqxb.jinsehuaqin.com:8800
VITE_APP_UNIPLAT_WEBSOCKET_URI= VITE_APP_UNIPLAT_WEBSOCKET_URI=ws://hro.channel.jinsehuaqin.com:8080/ws
# ===== 账号中心 / 通行证 ===== # ===== 账号中心 / 通行证 =====
VITE_APP_WWW_WORK_APP_URL= VITE_APP_WWW_WORK_APP_URL=http://106.120.107.150:5000
VITE_APP_API_WORK_APP_URL= VITE_APP_API_WORK_APP_URL=http://106.120.107.150:8090
VITE_APP_API_WORK_ORG_URL= VITE_APP_API_WORK_ORG_URL=http://106.120.107.150:7771
VITE_APP_USER_CENTER= VITE_APP_USER_CENTER=http://106.120.107.150:8080
# ===== 客户端凭证(必填,向后端申请)===== # ===== 客户端凭证(必填,向后端申请)=====
VITE_APP_CLIENT_ID= VITE_APP_CLIENT_ID=qqqf-admin-web
VITE_APP_CLIENT_SECRET= VITE_APP_CLIENT_SECRET=123456
# ===== 支付 ===== # ===== 支付 =====
VITE_APP_PAY_URL= VITE_APP_PAY_URL=https://payment-api.teammix.com
# ===== 静态资源 / H5 ===== # ===== 静态资源 / H5 =====
VITE_APP_H5_URL= VITE_APP_H5_URL=https://static.qinqinxiaobao.com/flb-mp
VITE_APP_LAND_PAGE= VITE_APP_LAND_PAGE=https://static.qinqinxiaobao.com/flb-mp
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=production APP_ENV=production
VITE_APP_ENV=prod VITE_APP_ENV=prod
......
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=staging APP_ENV=staging
VITE_APP_ENV=stage VITE_APP_ENV=stage
......
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=test APP_ENV=test
VITE_APP_ENV=test VITE_APP_ENV=test
...@@ -7,22 +9,22 @@ VITE_DEBUG=true ...@@ -7,22 +9,22 @@ VITE_DEBUG=true
VITE_APP_LOG_ENV=2 VITE_APP_LOG_ENV=2
# ===== 统一业务平台(必填)===== # ===== 统一业务平台(必填)=====
VITE_APP_UNIPLAT= VITE_APP_UNIPLAT=http://hro.test-api.qqxb.jinsehuaqin.com:8800
VITE_APP_UNIPLAT_WEBSOCKET_URI= VITE_APP_UNIPLAT_WEBSOCKET_URI=ws://hro.channel.jinsehuaqin.com:8080/ws
# ===== 账号中心 / 通行证 ===== # ===== 账号中心 / 通行证 =====
VITE_APP_WWW_WORK_APP_URL= VITE_APP_WWW_WORK_APP_URL=http://106.120.107.150:5000
VITE_APP_API_WORK_APP_URL= VITE_APP_API_WORK_APP_URL=http://106.120.107.150:8090
VITE_APP_API_WORK_ORG_URL= VITE_APP_API_WORK_ORG_URL=http://106.120.107.150:7771
VITE_APP_USER_CENTER= VITE_APP_USER_CENTER=http://106.120.107.150:8080
# ===== 客户端凭证(必填,向后端申请)===== # ===== 客户端凭证(必填,向后端申请)=====
VITE_APP_CLIENT_ID= VITE_APP_CLIENT_ID=qqqf-admin-web
VITE_APP_CLIENT_SECRET= VITE_APP_CLIENT_SECRET=123456
# ===== 支付 ===== # ===== 支付 =====
VITE_APP_PAY_URL= VITE_APP_PAY_URL=https://payment-api.teammix.com
# ===== 静态资源 / H5 ===== # ===== 静态资源 / H5 =====
VITE_APP_H5_URL= VITE_APP_H5_URL=https://static.qinqinxiaobao.com/flb-mp
VITE_APP_LAND_PAGE= VITE_APP_LAND_PAGE=https://static.qinqinxiaobao.com/flb-mp
...@@ -5,6 +5,10 @@ export type { LoginRegisterResult, VerifyImageResult } ...@@ -5,6 +5,10 @@ export type { LoginRegisterResult, VerifyImageResult }
/** /**
* 获取图形验证码 * 获取图形验证码
*
* 注意:这是同步方法,SDK 只负责拼出图片 URL,实际加载由 `<img>` 完成。
* 因此拿不到验证码时不会 reject,需要判断 `img` 是否为空 + 监听 `<img>`
* 的 error 事件。
*/ */
export function getVerifyImage(): VerifyImageResult { export function getVerifyImage(): VerifyImageResult {
return verifyService.generateImage() return verifyService.generateImage()
......
...@@ -16,13 +16,13 @@ export const config: AdminConfig = { ...@@ -16,13 +16,13 @@ export const config: AdminConfig = {
appName: '管理后台', appName: '管理后台',
uniplatApi: import.meta.env.VITE_APP_UNIPLAT || '', uniplatApi: import.meta.env.VITE_APP_UNIPLAT || '',
// TODO: 替换为后端分配的平台入口标识 // TODO: 替换为后端分配的平台入口标识
rootEntrance: '', rootEntrance: '统一业务平台',
logEnv: (import.meta.env.VITE_APP_ENV || Environment.Dev) as Environment, logEnv: (import.meta.env.VITE_APP_ENV || Environment.Dev) as Environment,
clientId: (import.meta.env.VITE_APP_CLIENT_ID || '') as ClientId, clientId: (import.meta.env.VITE_APP_CLIENT_ID || '') as ClientId,
domainService: { domainService: {
// TODO: 替换为项目实际的子项目名与服务名 // TODO: 替换为项目实际的子项目名与服务名(当前为测试环境的示例值)
subProjectName: '', subProjectName: 'welfare_v2',
serviceName: '', serviceName: 'smart_app_api',
}, },
passportUrl: import.meta.env.VITE_APP_WWW_WORK_APP_URL || '', passportUrl: import.meta.env.VITE_APP_WWW_WORK_APP_URL || '',
} }
......
...@@ -41,12 +41,24 @@ ...@@ -41,12 +41,24 @@
placeholder="请输入图形验证码" placeholder="请输入图形验证码"
/> />
<img <img
v-if="verifyImage.img" v-if="verifyImage.img && !verifyImageFailed"
:src="verifyImage.img" :src="verifyImage.img"
class="verify-img" class="verify-img"
alt="验证码" alt="验证码"
title="点击刷新"
@click="refreshVerifyImage" @click="refreshVerifyImage"
@error="verifyImageFailed = true"
/> />
<!-- 拿不到验证码时给出可点击的占位,而不是留一个破图 -->
<button
v-else
class="verify-img verify-img--fallback"
type="button"
title="点击重试"
@click="refreshVerifyImage"
>
加载失败<br />点击重试
</button>
</div> </div>
<div class="form-item form-item-row"> <div class="form-item form-item-row">
...@@ -101,6 +113,7 @@ ...@@ -101,6 +113,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { VerifyImageResult } from '@common/sdk'
import { SMS_COUNTDOWN } from '@common/utils/constants' import { SMS_COUNTDOWN } from '@common/utils/constants'
import { isPhone } from '@common/utils/regex' import { isPhone } from '@common/utils/regex'
import { ref } from 'vue' import { ref } from 'vue'
...@@ -135,18 +148,18 @@ const passwordForm = ref({ ...@@ -135,18 +148,18 @@ const passwordForm = ref({
const smsLoading = ref(false) const smsLoading = ref(false)
const passwordLoading = ref(false) const passwordLoading = ref(false)
const countdown = ref(0) const countdown = ref(0)
const verifyImage = ref({ img: '', seed: '' }) const verifyImage = ref<VerifyImageResult>({ img: '', seed: '' })
const verifyImageFailed = ref(false)
let timer: ReturnType<typeof setInterval> | null = null let timer: ReturnType<typeof setInterval> | null = null
// 获取图形验证码 // 获取图形验证码(同步拼 URL,加载失败由 <img> 的 error 事件兜住)
async function refreshVerifyImage() { function refreshVerifyImage() {
try { verifyImageFailed.value = false
const res = await getVerifyImage() verifyImage.value = getVerifyImage()
verifyImage.value = res as any if (!verifyImage.value.img) {
} verifyImageFailed.value = true
catch (e) { Toast.error('获取图形验证码失败,请检查 VITE_APP_UNIPLAT 配置')
Toast.error('获取图形验证码失败')
} }
} }
...@@ -325,6 +338,16 @@ refreshVerifyImage() ...@@ -325,6 +338,16 @@ refreshVerifyImage()
height: 44px; height: 44px;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
flex-shrink: 0;
}
.verify-img--fallback {
border: 1px dashed #ddd;
background: #fafafa;
color: #999;
font-size: 12px;
line-height: 1.3;
padding: 0;
} }
.sms-btn { .sms-btn {
......
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=dev APP_ENV=dev
VITE_APP_ENV=dev VITE_APP_ENV=dev
...@@ -7,29 +9,29 @@ VITE_DEBUG=true ...@@ -7,29 +9,29 @@ VITE_DEBUG=true
VITE_APP_LOG_ENV=0 VITE_APP_LOG_ENV=0
# ===== 统一业务平台(必填)===== # ===== 统一业务平台(必填)=====
VITE_APP_UNIPLAT= VITE_APP_UNIPLAT=http://hro.test-api.qqxb.jinsehuaqin.com:8800
VITE_APP_UNIPLAT_WEBSOCKET_URI= VITE_APP_UNIPLAT_WEBSOCKET_URI=ws://hro.channel.jinsehuaqin.com:8080/ws
# ===== 账号中心 / 通行证 ===== # ===== 账号中心 / 通行证 =====
VITE_APP_WWW_WORK_APP_URL= VITE_APP_WWW_WORK_APP_URL=http://106.120.107.150:5000
VITE_APP_API_WORK_APP_URL= VITE_APP_API_WORK_APP_URL=http://106.120.107.150:8090
VITE_APP_API_WORK_ORG_URL= VITE_APP_API_WORK_ORG_URL=http://106.120.107.150:7771
VITE_APP_USER_CENTER= VITE_APP_USER_CENTER=http://106.120.107.150:8080
# ===== 客户端凭证(必填,向后端申请)===== # ===== 客户端凭证(必填,向后端申请)=====
VITE_APP_CLIENT_ID= VITE_APP_CLIENT_ID=hrs100_wechat_mini_app
VITE_APP_CLIENT_SECRET= VITE_APP_CLIENT_SECRET=123456
# ===== 支付 ===== # ===== 支付 =====
VITE_APP_PAY_URL= VITE_APP_PAY_URL=https://payment-api.teammix.com
# ===== 静态资源 / H5 ===== # ===== 静态资源 / H5 =====
VITE_APP_H5_URL= VITE_APP_H5_URL=https://static.qinqinxiaobao.com/flb-mp
VITE_APP_LAND_PAGE= VITE_APP_LAND_PAGE=https://static.qinqinxiaobao.com/flb-mp
# ===== 小程序 / 公众号 appid ===== # ===== 小程序 / 公众号 appid =====
VITE_MP_APPID= VITE_MP_APPID=wx71116f4df50d7f93
VITE_MP_ACCOUNT_APPID= VITE_MP_ACCOUNT_APPID=wx67ad4269eb98b531
# ===== 第三方 ===== # ===== 第三方 =====
VITE_MAP_KEY= VITE_MAP_KEY=
......
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=production APP_ENV=production
VITE_APP_ENV=prod VITE_APP_ENV=prod
...@@ -33,3 +35,7 @@ VITE_MP_ACCOUNT_APPID= ...@@ -33,3 +35,7 @@ VITE_MP_ACCOUNT_APPID=
# ===== 第三方 ===== # ===== 第三方 =====
VITE_MAP_KEY= VITE_MAP_KEY=
# ===== 测试账号(仅非生产环境)=====
VITE_APP_TEST_ACCOUNT=
VITE_APP_TEST_PASSPORT=
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=staging APP_ENV=staging
VITE_APP_ENV=stage VITE_APP_ENV=stage
......
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=test APP_ENV=test
VITE_APP_ENV=test VITE_APP_ENV=test
...@@ -7,29 +9,29 @@ VITE_DEBUG=true ...@@ -7,29 +9,29 @@ VITE_DEBUG=true
VITE_APP_LOG_ENV=2 VITE_APP_LOG_ENV=2
# ===== 统一业务平台(必填)===== # ===== 统一业务平台(必填)=====
VITE_APP_UNIPLAT= VITE_APP_UNIPLAT=http://hro.test-api.qqxb.jinsehuaqin.com:8800
VITE_APP_UNIPLAT_WEBSOCKET_URI= VITE_APP_UNIPLAT_WEBSOCKET_URI=ws://hro.channel.jinsehuaqin.com:8080/ws
# ===== 账号中心 / 通行证 ===== # ===== 账号中心 / 通行证 =====
VITE_APP_WWW_WORK_APP_URL= VITE_APP_WWW_WORK_APP_URL=http://106.120.107.150:5000
VITE_APP_API_WORK_APP_URL= VITE_APP_API_WORK_APP_URL=http://106.120.107.150:8090
VITE_APP_API_WORK_ORG_URL= VITE_APP_API_WORK_ORG_URL=http://106.120.107.150:7771
VITE_APP_USER_CENTER= VITE_APP_USER_CENTER=http://106.120.107.150:8080
# ===== 客户端凭证(必填,向后端申请)===== # ===== 客户端凭证(必填,向后端申请)=====
VITE_APP_CLIENT_ID= VITE_APP_CLIENT_ID=hrs100_wechat_mini_app
VITE_APP_CLIENT_SECRET= VITE_APP_CLIENT_SECRET=123456
# ===== 支付 ===== # ===== 支付 =====
VITE_APP_PAY_URL= VITE_APP_PAY_URL=https://payment-api.teammix.com
# ===== 静态资源 / H5 ===== # ===== 静态资源 / H5 =====
VITE_APP_H5_URL= VITE_APP_H5_URL=https://static.qinqinxiaobao.com/flb-mp
VITE_APP_LAND_PAGE= VITE_APP_LAND_PAGE=https://static.qinqinxiaobao.com/flb-mp
# ===== 小程序 / 公众号 appid ===== # ===== 小程序 / 公众号 appid =====
VITE_MP_APPID= VITE_MP_APPID=wx71116f4df50d7f93
VITE_MP_ACCOUNT_APPID= VITE_MP_ACCOUNT_APPID=wx67ad4269eb98b531
# ===== 第三方 ===== # ===== 第三方 =====
VITE_MAP_KEY= VITE_MAP_KEY=
......
...@@ -83,11 +83,11 @@ export const config: MpConfig = { ...@@ -83,11 +83,11 @@ export const config: MpConfig = {
h5Url: import.meta.env.VITE_APP_H5_URL || "", h5Url: import.meta.env.VITE_APP_H5_URL || "",
imgBaseUrl: `${import.meta.env.VITE_APP_LAND_PAGE || ""}/img/`, imgBaseUrl: `${import.meta.env.VITE_APP_LAND_PAGE || ""}/img/`,
// TODO: 替换为后端分配的平台入口标识 // TODO: 替换为后端分配的平台入口标识
rootEntrance: "", rootEntrance: "统一业务平台",
domainService: { domainService: {
// TODO: 替换为项目实际的子项目名与服务名 // TODO: 替换为项目实际的子项目名与服务名(当前为测试环境的示例值)
subProjectName: "", subProjectName: "welfare_v2",
serviceName: "", serviceName: "smart_app_api",
}, },
clientId: (import.meta.env.VITE_APP_CLIENT_ID || "") as ClientId, clientId: (import.meta.env.VITE_APP_CLIENT_ID || "") as ClientId,
clientSecret: import.meta.env.VITE_APP_CLIENT_SECRET || "", clientSecret: import.meta.env.VITE_APP_CLIENT_SECRET || "",
...@@ -100,7 +100,7 @@ export const config: MpConfig = { ...@@ -100,7 +100,7 @@ export const config: MpConfig = {
testPassport: import.meta.env.VITE_APP_TEST_PASSPORT || "", testPassport: import.meta.env.VITE_APP_TEST_PASSPORT || "",
shareConfig: { shareConfig: {
// TODO: 替换为项目分享标题与分享图 // TODO: 替换为项目分享标题与分享图
title: "", title: "小程序模板",
img: "", img: "",
path: "/pages/login/index", path: "/pages/login/index",
}, },
......
...@@ -39,12 +39,21 @@ ...@@ -39,12 +39,21 @@
type="text" type="text"
/> />
<image <image
v-if="imageCodeModel" v-if="imageCodeModel?.img && !imageCodeFailed"
:src="imageCodeModel.img" :src="imageCodeModel.img"
mode="aspectFit" mode="aspectFit"
class="code-img" class="code-img"
@click="getVerifyCodeImg" @click="getVerifyCodeImg"
@error="imageCodeFailed = true"
/> />
<!-- 拿不到验证码时给出可点击的占位,而不是留一个破图 -->
<view
v-else
class="code-img code-img--fallback"
@click="getVerifyCodeImg"
>
加载失败,点击重试
</view>
</view> </view>
<view class="form-item form-item-row"> <view class="form-item form-item-row">
<input <input
...@@ -180,9 +189,16 @@ const codeCountdown = ref(60) ...@@ -180,9 +189,16 @@ const codeCountdown = ref(60)
let codeTimer: ReturnType<typeof setInterval> | null = null let codeTimer: ReturnType<typeof setInterval> | null = null
const imageCodeModel = ref<VerifyImageResult | null>(null) const imageCodeModel = ref<VerifyImageResult | null>(null)
const imageCodeFailed = ref(false)
// 同步拼 URL,加载失败由 <image> error 事件兜住
function getVerifyCodeImg() { function getVerifyCodeImg() {
imageCodeFailed.value = false
imageCodeModel.value = getVerifyService().generateImage() imageCodeModel.value = getVerifyService().generateImage()
if (!imageCodeModel.value.img) {
imageCodeFailed.value = true
toastError("获取图形验证码失败,请检查 VITE_APP_UNIPLAT 配置")
}
} }
function startCodeCountdown() { function startCodeCountdown() {
...@@ -398,6 +414,19 @@ onLoad(() => { ...@@ -398,6 +414,19 @@ onLoad(() => {
height: 60rpx; height: 60rpx;
margin-left: 20rpx; margin-left: 20rpx;
flex-shrink: 0; flex-shrink: 0;
&--fallback {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border: 1rpx dashed #ddd;
background-color: #fafafa;
color: #999;
font-size: 20rpx;
line-height: 1.2;
text-align: center;
}
} }
.code-text { .code-text {
......
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=dev APP_ENV=dev
VITE_APP_ENV=dev VITE_APP_ENV=dev
# ===== API 基础地址(必填)===== # ===== API 基础地址(必填)=====
NUXT_PUBLIC_API_BASE_URL= NUXT_PUBLIC_API_BASE_URL=http://hro.test-api.qqxb.jinsehuaqin.com:8800
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=production APP_ENV=production
VITE_APP_ENV=prod VITE_APP_ENV=prod
......
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=staging APP_ENV=staging
VITE_APP_ENV=stage VITE_APP_ENV=stage
......
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=test APP_ENV=test
VITE_APP_ENV=test VITE_APP_ENV=test
# ===== API 基础地址(必填)===== # ===== API 基础地址(必填)=====
NUXT_PUBLIC_API_BASE_URL= NUXT_PUBLIC_API_BASE_URL=http://hro.test-api.qqxb.jinsehuaqin.com:8800
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=dev APP_ENV=dev
VITE_APP_ENV=dev VITE_APP_ENV=dev
...@@ -7,22 +9,22 @@ VITE_DEBUG=true ...@@ -7,22 +9,22 @@ VITE_DEBUG=true
VITE_APP_LOG_ENV=0 VITE_APP_LOG_ENV=0
# ===== 统一业务平台(必填)===== # ===== 统一业务平台(必填)=====
VITE_APP_UNIPLAT= VITE_APP_UNIPLAT=http://hro.test-api.qqxb.jinsehuaqin.com:8800
VITE_APP_UNIPLAT_WEBSOCKET_URI= VITE_APP_UNIPLAT_WEBSOCKET_URI=ws://hro.channel.jinsehuaqin.com:8080/ws
# ===== 账号中心 / 通行证 ===== # ===== 账号中心 / 通行证 =====
VITE_APP_WWW_WORK_APP_URL= VITE_APP_WWW_WORK_APP_URL=http://106.120.107.150:5000
VITE_APP_API_WORK_APP_URL= VITE_APP_API_WORK_APP_URL=http://106.120.107.150:8090
VITE_APP_API_WORK_ORG_URL= VITE_APP_API_WORK_ORG_URL=http://106.120.107.150:7771
VITE_APP_USER_CENTER= VITE_APP_USER_CENTER=http://106.120.107.150:8080
# ===== 客户端凭证(必填,向后端申请)===== # ===== 客户端凭证(必填,向后端申请)=====
VITE_APP_CLIENT_ID= VITE_APP_CLIENT_ID=qqxb_mini_app
VITE_APP_CLIENT_SECRET= VITE_APP_CLIENT_SECRET=123456
# ===== 支付 ===== # ===== 支付 =====
VITE_APP_PAY_URL= VITE_APP_PAY_URL=https://payment-api.teammix.com
# ===== 静态资源 / H5 ===== # ===== 静态资源 / H5 =====
VITE_APP_H5_URL= VITE_APP_H5_URL=https://static.qinqinxiaobao.com/flb-mp
VITE_APP_LAND_PAGE= VITE_APP_LAND_PAGE=https://static.qinqinxiaobao.com/flb-mp
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=production APP_ENV=production
VITE_APP_ENV=prod VITE_APP_ENV=prod
......
# ⚠️ 预发/生产环境的值故意留空——模板工程不携带生产凭证。
# 上线前请填入自己的后端地址与 client id/secret,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=staging APP_ENV=staging
VITE_APP_ENV=stage VITE_APP_ENV=stage
......
# ⚠️ 本文件已预填「测试环境」的可用值,仅为开箱体验用(登录/图形验证码可直接跑通)。
# 派生新项目后请全部替换为自己的后端地址与凭证,详见 ARCHITECTURE.md「环境变量」一节。
# ===== 应用元信息 ===== # ===== 应用元信息 =====
APP_ENV=test APP_ENV=test
VITE_APP_ENV=test VITE_APP_ENV=test
...@@ -7,22 +9,22 @@ VITE_DEBUG=true ...@@ -7,22 +9,22 @@ VITE_DEBUG=true
VITE_APP_LOG_ENV=2 VITE_APP_LOG_ENV=2
# ===== 统一业务平台(必填)===== # ===== 统一业务平台(必填)=====
VITE_APP_UNIPLAT= VITE_APP_UNIPLAT=http://hro.test-api.qqxb.jinsehuaqin.com:8800
VITE_APP_UNIPLAT_WEBSOCKET_URI= VITE_APP_UNIPLAT_WEBSOCKET_URI=ws://hro.channel.jinsehuaqin.com:8080/ws
# ===== 账号中心 / 通行证 ===== # ===== 账号中心 / 通行证 =====
VITE_APP_WWW_WORK_APP_URL= VITE_APP_WWW_WORK_APP_URL=http://106.120.107.150:5000
VITE_APP_API_WORK_APP_URL= VITE_APP_API_WORK_APP_URL=http://106.120.107.150:8090
VITE_APP_API_WORK_ORG_URL= VITE_APP_API_WORK_ORG_URL=http://106.120.107.150:7771
VITE_APP_USER_CENTER= VITE_APP_USER_CENTER=http://106.120.107.150:8080
# ===== 客户端凭证(必填,向后端申请)===== # ===== 客户端凭证(必填,向后端申请)=====
VITE_APP_CLIENT_ID= VITE_APP_CLIENT_ID=qqxb_mini_app
VITE_APP_CLIENT_SECRET= VITE_APP_CLIENT_SECRET=123456
# ===== 支付 ===== # ===== 支付 =====
VITE_APP_PAY_URL= VITE_APP_PAY_URL=https://payment-api.teammix.com
# ===== 静态资源 / H5 ===== # ===== 静态资源 / H5 =====
VITE_APP_H5_URL= VITE_APP_H5_URL=https://static.qinqinxiaobao.com/flb-mp
VITE_APP_LAND_PAGE= VITE_APP_LAND_PAGE=https://static.qinqinxiaobao.com/flb-mp
...@@ -5,6 +5,10 @@ export type { LoginRegisterResult, VerifyImageResult } ...@@ -5,6 +5,10 @@ export type { LoginRegisterResult, VerifyImageResult }
/** /**
* 获取图形验证码 * 获取图形验证码
*
* 注意:这是同步方法,SDK 只负责拼出图片 URL,实际加载由 `<img>` 完成。
* 因此拿不到验证码时不会 reject,需要判断 `img` 是否为空 + 监听 `<img>`
* 的 error 事件。
*/ */
export function getVerifyImage(): VerifyImageResult { export function getVerifyImage(): VerifyImageResult {
return verifyService.generateImage() return verifyService.generateImage()
......
...@@ -18,13 +18,13 @@ export const config: WebConfig = { ...@@ -18,13 +18,13 @@ export const config: WebConfig = {
appName: 'Web 应用', appName: 'Web 应用',
uniplatApi: import.meta.env.VITE_APP_UNIPLAT || '', uniplatApi: import.meta.env.VITE_APP_UNIPLAT || '',
// TODO: 替换为后端分配的平台入口标识 // TODO: 替换为后端分配的平台入口标识
rootEntrance: '', rootEntrance: '统一业务平台',
logEnv: (import.meta.env.VITE_APP_ENV || Environment.Dev) as Environment, logEnv: (import.meta.env.VITE_APP_ENV || Environment.Dev) as Environment,
clientId: (import.meta.env.VITE_APP_CLIENT_ID || '') as ClientId, clientId: (import.meta.env.VITE_APP_CLIENT_ID || '') as ClientId,
domainService: { domainService: {
// TODO: 替换为项目实际的子项目名与服务名 // TODO: 替换为项目实际的子项目名与服务名(当前为测试环境的示例值)
subProjectName: '', subProjectName: 'welfare_v2',
serviceName: '', serviceName: 'smart_app_api',
}, },
passportUrl: import.meta.env.VITE_APP_WWW_WORK_APP_URL || '', passportUrl: import.meta.env.VITE_APP_WWW_WORK_APP_URL || '',
imgBaseUrl: `${import.meta.env.VITE_APP_LAND_PAGE || ''}/img/`, imgBaseUrl: `${import.meta.env.VITE_APP_LAND_PAGE || ''}/img/`,
......
...@@ -38,12 +38,24 @@ ...@@ -38,12 +38,24 @@
placeholder="请输入图形验证码" placeholder="请输入图形验证码"
/> />
<img <img
v-if="verifyImage.img" v-if="verifyImage.img && !verifyImageFailed"
:src="verifyImage.img" :src="verifyImage.img"
class="login__verify-img" class="login__verify-img"
alt="验证码" alt="验证码"
title="点击刷新"
@click="refreshVerifyImage" @click="refreshVerifyImage"
@error="verifyImageFailed = true"
/> />
<!-- 拿不到验证码时给出可点击的占位,而不是留一个破图 -->
<button
v-else
class="login__verify-img login__verify-img--fallback"
type="button"
title="点击重试"
@click="refreshVerifyImage"
>
加载失败<br />点击重试
</button>
</div> </div>
<div class="login__row"> <div class="login__row">
...@@ -92,6 +104,7 @@ ...@@ -92,6 +104,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { VerifyImageResult } from '@common/sdk'
import { SMS_COUNTDOWN } from '@common/utils/constants' import { SMS_COUNTDOWN } from '@common/utils/constants'
import { isPhone } from '@common/utils/regex' import { isPhone } from '@common/utils/regex'
import { ref } from 'vue' import { ref } from 'vue'
...@@ -116,17 +129,18 @@ const passwordForm = ref({ username: '', password: '' }) ...@@ -116,17 +129,18 @@ const passwordForm = ref({ username: '', password: '' })
const smsLoading = ref(false) const smsLoading = ref(false)
const pwdLoading = ref(false) const pwdLoading = ref(false)
const countdown = ref(0) const countdown = ref(0)
const verifyImage = ref({ img: '', seed: '' }) const verifyImage = ref<VerifyImageResult>({ img: '', seed: '' })
const verifyImageFailed = ref(false)
let timer: ReturnType<typeof setInterval> | null = null let timer: ReturnType<typeof setInterval> | null = null
// 获取图形验证码 // 获取图形验证码(同步拼 URL,加载失败由 <img> 的 error 事件兜住)
function refreshVerifyImage() { function refreshVerifyImage() {
try { verifyImageFailed.value = false
verifyImage.value = getVerifyImage() as any verifyImage.value = getVerifyImage()
} if (!verifyImage.value.img) {
catch { verifyImageFailed.value = true
Toast.error('获取图形验证码失败') Toast.error('获取图形验证码失败,请检查 VITE_APP_UNIPLAT 配置')
} }
} }
...@@ -268,10 +282,20 @@ refreshVerifyImage() ...@@ -268,10 +282,20 @@ refreshVerifyImage()
} }
&__verify-img { &__verify-img {
flex: none;
width: 120px; width: 120px;
height: 44px; height: 44px;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
&--fallback {
padding: 0;
border: 1px dashed #ddd;
background: #fafafa;
color: #999;
font-size: 12px;
line-height: 1.3;
}
} }
&__sms-btn { &__sms-btn {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment