From 6faaf5392dadbb20fef2b17cd75afa5beae68a4d Mon Sep 17 00:00:00 2001
From: jokerxue <2509699647@qq.com>
Date: Wed, 25 Mar 2026 11:15:29 +0800
Subject: [PATCH] APP 登录门店对接
---
美国版/Food Labeling Management App UniApp/src/components/LocationPicker.vue | 53 ++++++++++++++++++++++++++++++++++++++++-------------
美国版/Food Labeling Management App UniApp/src/components/SideMenu.vue | 6 ++----
美国版/Food Labeling Management App UniApp/src/env.d.ts | 9 +++++++++
美国版/Food Labeling Management App UniApp/src/locales/en.ts | 4 ++++
美国版/Food Labeling Management App UniApp/src/locales/zh.ts | 4 ++++
美国版/Food Labeling Management App UniApp/src/pages/index/index.vue | 12 ++++++++++++
美国版/Food Labeling Management App UniApp/src/pages/login/login.vue | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
美国版/Food Labeling Management App UniApp/src/pages/more/location.vue | 59 ++++++++++++++++++++++++++++++++++++++++++-----------------
美国版/Food Labeling Management App UniApp/src/pages/more/more.vue | 10 ++++------
美国版/Food Labeling Management App UniApp/src/pages/more/profile.vue | 4 ++--
美国版/Food Labeling Management App UniApp/src/pages/store-select/store-select.vue | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
美国版/Food Labeling Management App UniApp/src/services/usAppAuth.ts | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
美国版/Food Labeling Management App UniApp/src/utils/apiBase.ts | 17 +++++++++++++++++
美国版/Food Labeling Management App UniApp/src/utils/authSession.ts | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
美国版/Food Labeling Management App UniApp/src/utils/stores.ts | 34 ++++++++++++++++++++--------------
美国版/Food Labeling Management App UniApp/vite.config.ts | 6 ++++++
美国版App登录接口说明.md | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
17 files changed, 703 insertions(+), 84 deletions(-)
create mode 100644 美国版/Food Labeling Management App UniApp/src/services/usAppAuth.ts
create mode 100644 美国版/Food Labeling Management App UniApp/src/utils/apiBase.ts
create mode 100644 美国版/Food Labeling Management App UniApp/src/utils/authSession.ts
create mode 100644 美国版App登录接口说明.md
diff --git a/美国版/Food Labeling Management App UniApp/src/components/LocationPicker.vue b/美国版/Food Labeling Management App UniApp/src/components/LocationPicker.vue
index 2d4fb63..aa87715 100644
--- a/美国版/Food Labeling Management App UniApp/src/components/LocationPicker.vue
+++ b/美国版/Food Labeling Management App UniApp/src/components/LocationPicker.vue
@@ -1,7 +1,7 @@
-
- LOC-{{ currentId }}
+
+ {{ displayCode || '—' }}
@@ -25,8 +25,8 @@
- {{ t(s.nameKey) }}
- {{ s.address }}, {{ s.city }}
+ {{ s.locationName }}
+ {{ s.fullAddress }}
@@ -39,30 +39,57 @@
diff --git a/美国版/Food Labeling Management App UniApp/src/pages/more/location.vue b/美国版/Food Labeling Management App UniApp/src/pages/more/location.vue
index 6b7bedb..3e86f88 100644
--- a/美国版/Food Labeling Management App UniApp/src/pages/more/location.vue
+++ b/美国版/Food Labeling Management App UniApp/src/pages/more/location.vue
@@ -23,12 +23,11 @@
{{ t('location.currentStore') }}
- {{ t(currentStore.nameKey) }}
+ {{ currentStore.locationName }}
- {{ currentStore.address }}
- {{ currentStore.city }}
+ {{ currentStore.fullAddress }}
@@ -104,8 +103,8 @@
- {{ t(s.nameKey) }}
- {{ s.address }}, {{ s.city }}
+ {{ s.locationName }}
+ {{ s.fullAddress }}
@@ -130,25 +129,52 @@
diff --git a/美国版/Food Labeling Management App UniApp/src/pages/more/profile.vue b/美国版/Food Labeling Management App UniApp/src/pages/more/profile.vue
index 9361ace..42c09f1 100644
--- a/美国版/Food Labeling Management App UniApp/src/pages/more/profile.vue
+++ b/美国版/Food Labeling Management App UniApp/src/pages/more/profile.vue
@@ -86,8 +86,8 @@ import { getStatusBarHeight } from '../../utils/statusBar'
const { t } = useI18n()
const statusBarHeight = getStatusBarHeight()
const isMenuOpen = ref(false)
-const name = ref(uni.getStorageSync('userName') || 'John Smith')
-const email = ref('john.smith@company.com')
+const name = ref(uni.getStorageSync('userName') || 'Employee')
+const email = ref(uni.getStorageSync('user_email') || '')
const phone = ref('+1 (555) 123-4567')
const employeeId = ref('EMP-2024-001')
diff --git a/美国版/Food Labeling Management App UniApp/src/pages/store-select/store-select.vue b/美国版/Food Labeling Management App UniApp/src/pages/store-select/store-select.vue
index ba1a62f..480dbee 100644
--- a/美国版/Food Labeling Management App UniApp/src/pages/store-select/store-select.vue
+++ b/美国版/Food Labeling Management App UniApp/src/pages/store-select/store-select.vue
@@ -12,6 +12,9 @@
+
+ {{ t('login.noStoresBound') }}
+
- {{ t(store.nameKey) }}
+ {{ store.locationName }}
- {{ store.address }}
+ {{ store.fullAddress }}
- {{ t('location.storeManager') }}: {{ store.manager }}
- {{ t('location.storePhone') }}: {{ store.phone }}
+ {{ store.locationCode }}
- {{ t('common.confirm') }}
+ {{ loading ? '…' : t('common.confirm') }}
@@ -145,6 +178,16 @@ const handleConfirm = () => {
padding-bottom: 24rpx;
}
+.empty-hint {
+ padding: 48rpx 24rpx;
+ text-align: center;
+}
+
+.empty-text {
+ font-size: 28rpx;
+ color: #6b7280;
+}
+
.card {
padding: 32rpx;
background: #ffffff;
@@ -204,6 +247,11 @@ const handleConfirm = () => {
margin-bottom: 6rpx;
}
+.info.muted {
+ color: #9ca3af;
+ font-size: 24rpx;
+}
+
.card-check {
width: 44rpx;
height: 44rpx;
diff --git a/美国版/Food Labeling Management App UniApp/src/services/usAppAuth.ts b/美国版/Food Labeling Management App UniApp/src/services/usAppAuth.ts
new file mode 100644
index 0000000..7b60c6a
--- /dev/null
+++ b/美国版/Food Labeling Management App UniApp/src/services/usAppAuth.ts
@@ -0,0 +1,165 @@
+import { buildApiUrl } from '../utils/apiBase'
+
+/** 与后端 UsAppBoundLocationDto 对齐 */
+export interface UsAppBoundLocationDto {
+ id: string
+ locationCode: string
+ locationName: string
+ fullAddress: string
+ state: boolean
+}
+
+export interface UsAppLoginInput {
+ email: string
+ password: string
+ uuid?: string
+ code?: string
+}
+
+export interface UsAppLoginOutputDto {
+ token: string
+ refreshToken: string
+ locations: UsAppBoundLocationDto[]
+}
+
+function normalizeLocation(raw: Record): UsAppBoundLocationDto {
+ return {
+ id: String(raw.id ?? raw.Id ?? ''),
+ locationCode: String(raw.locationCode ?? raw.LocationCode ?? ''),
+ locationName: String(raw.locationName ?? raw.LocationName ?? ''),
+ fullAddress: String(raw.fullAddress ?? raw.FullAddress ?? ''),
+ state: raw.state !== false && raw.State !== false,
+ }
+}
+
+function normalizeLoginOutput(raw: unknown): UsAppLoginOutputDto {
+ const o = raw as Record
+ const locs = o.locations ?? o.Locations
+ const arr = Array.isArray(locs) ? locs : []
+ return {
+ token: String(o.token ?? o.Token ?? ''),
+ refreshToken: String(o.refreshToken ?? o.RefreshToken ?? ''),
+ locations: arr.map((x) => normalizeLocation(x as Record)),
+ }
+}
+
+function normalizeLocationList(raw: unknown): UsAppBoundLocationDto[] {
+ const arr = Array.isArray(raw) ? raw : []
+ return arr.map((x) => normalizeLocation(x as Record))
+}
+
+/**
+ * 取出真实业务负载:支持 ABP `result`、以及项目统一包装 `{ succeeded, data }`(data 内为 token / 数组等)
+ */
+function unwrap(data: unknown): T {
+ if (data == null || typeof data !== 'object') return data as T
+ const o = data as Record
+ if ('result' in o && o.result !== undefined) {
+ return o.result as T
+ }
+ const payload = o.data ?? o.Data
+ if (payload !== undefined && payload !== null) {
+ return payload as T
+ }
+ return data as T
+}
+
+/** 统一解析后端错误文案(含统一返回体里的 errors、succeeded 等) */
+function parseErrorMessage(data: unknown): string {
+ if (data == null) return 'Request failed'
+ if (typeof data === 'string') return data
+ if (typeof data === 'object') {
+ const o = data as Record
+ const errorsRaw = o.errors ?? o.Errors
+ if (typeof errorsRaw === 'string' && errorsRaw.trim()) return errorsRaw.trim()
+ if (Array.isArray(errorsRaw)) {
+ const parts = errorsRaw.map((x) => String(x)).filter(Boolean)
+ if (parts.length) return parts.join('; ')
+ }
+ const err = o.error as Record | undefined
+ if (err && typeof err.message === 'string') return err.message
+ if (typeof o.message === 'string') return o.message
+ if (typeof o.error_description === 'string') return o.error_description
+ }
+ return 'Request failed'
+}
+
+/** HTTP 200 但业务失败(如 succeeded: false、体内 statusCode 403) */
+function isBusinessFailurePayload(data: unknown): boolean {
+ if (data == null || typeof data !== 'object') return false
+ const o = data as Record
+ if (o.succeeded === false || o.Succeeded === false) return true
+ const inner = o.statusCode ?? o.StatusCode
+ if (typeof inner === 'number' && inner >= 400) return true
+ return false
+}
+
+function request(options: {
+ path: string
+ method: 'GET' | 'POST'
+ data?: unknown
+ auth?: boolean
+}): Promise {
+ const header: Record = {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ }
+ if (options.auth) {
+ const token = uni.getStorageSync('access_token')
+ if (token) header.Authorization = `Bearer ${token}`
+ }
+
+ return new Promise((resolve, reject) => {
+ uni.request({
+ url: buildApiUrl(options.path),
+ method: options.method,
+ data: options.data,
+ header,
+ success: (res) => {
+ const status = res.statusCode ?? 0
+ if (status >= 400) {
+ reject(new Error(parseErrorMessage(res.data)))
+ return
+ }
+ if (isBusinessFailurePayload(res.data)) {
+ reject(new Error(parseErrorMessage(res.data)))
+ return
+ }
+ try {
+ const body = unwrap(res.data as unknown)
+ resolve(body)
+ } catch {
+ reject(new Error('Invalid response'))
+ }
+ },
+ fail: (err) => {
+ reject(new Error(err.errMsg || 'Network error'))
+ },
+ })
+ })
+}
+
+/** POST /api/app/us-app-auth/login */
+export async function usAppLogin(input: UsAppLoginInput): Promise {
+ const raw = await request({
+ path: '/api/app/us-app-auth/login',
+ method: 'POST',
+ data: {
+ email: input.email.trim(),
+ password: input.password,
+ ...(input.uuid ? { uuid: input.uuid } : {}),
+ ...(input.code != null ? { code: input.code } : {}),
+ },
+ })
+ return normalizeLoginOutput(raw)
+}
+
+/** GET /api/app/us-app-auth/my-locations */
+export async function usAppFetchMyLocations(): Promise {
+ const raw = await request({
+ path: '/api/app/us-app-auth/my-locations',
+ method: 'GET',
+ auth: true,
+ })
+ return normalizeLocationList(raw)
+}
diff --git a/美国版/Food Labeling Management App UniApp/src/utils/apiBase.ts b/美国版/Food Labeling Management App UniApp/src/utils/apiBase.ts
new file mode 100644
index 0000000..df5d3e0
--- /dev/null
+++ b/美国版/Food Labeling Management App UniApp/src/utils/apiBase.ts
@@ -0,0 +1,17 @@
+/**
+ * 美国版后端 API 根地址(不含末尾 /)。
+ * - H5 开发:可在 .env.development 留空,配合 vite proxy 走同源 /api
+ * - App / 生产:在 .env 中设置 VITE_US_API_BASE,例如 http://192.168.1.10:19001
+ */
+export function getApiBaseUrl(): string {
+ const fromEnv = (import.meta.env.VITE_US_API_BASE as string | undefined)?.trim()
+ if (fromEnv) return fromEnv.replace(/\/$/, '')
+ if (import.meta.env.DEV && typeof window !== 'undefined') return ''
+ return 'http://flus-test.3ffoodsafety.com'
+}
+
+export function buildApiUrl(path: string): string {
+ const base = getApiBaseUrl()
+ const p = path.startsWith('/') ? path : `/${path}`
+ return base ? `${base}${p}` : p
+}
diff --git a/美国版/Food Labeling Management App UniApp/src/utils/authSession.ts b/美国版/Food Labeling Management App UniApp/src/utils/authSession.ts
new file mode 100644
index 0000000..99a43cf
--- /dev/null
+++ b/美国版/Food Labeling Management App UniApp/src/utils/authSession.ts
@@ -0,0 +1,83 @@
+import type { UsAppBoundLocationDto } from '../services/usAppAuth'
+
+const KEY_ACCESS = 'access_token'
+const KEY_REFRESH = 'refresh_token'
+const KEY_LOCATIONS = 'bound_locations_json'
+const KEY_LOGGED = 'isLoggedIn'
+const KEY_USER = 'userName'
+const KEY_EMAIL = 'user_email'
+const KEY_REMEMBER = 'login_remember_me'
+const KEY_SAVED_EMAIL = 'login_saved_email'
+const KEY_SAVED_PASSWORD = 'login_saved_password'
+
+export function saveAuthSession(payload: {
+ token: string
+ refreshToken: string
+ locations: UsAppBoundLocationDto[]
+ /** 展示用:无姓名接口时用邮箱本地部分或全文 */
+ displayName: string
+ email: string
+}): void {
+ uni.setStorageSync(KEY_ACCESS, payload.token)
+ uni.setStorageSync(KEY_REFRESH, payload.refreshToken)
+ uni.setStorageSync(KEY_LOCATIONS, JSON.stringify(payload.locations ?? []))
+ uni.setStorageSync(KEY_LOGGED, 'true')
+ uni.setStorageSync(KEY_USER, payload.displayName)
+ uni.setStorageSync(KEY_EMAIL, payload.email)
+}
+
+export function setBoundLocations(locations: UsAppBoundLocationDto[]): void {
+ uni.setStorageSync(KEY_LOCATIONS, JSON.stringify(locations ?? []))
+}
+
+export function getBoundLocations(): UsAppBoundLocationDto[] {
+ try {
+ const raw = uni.getStorageSync(KEY_LOCATIONS)
+ if (!raw || typeof raw !== 'string') return []
+ const parsed = JSON.parse(raw) as unknown
+ if (!Array.isArray(parsed)) return []
+ return parsed as UsAppBoundLocationDto[]
+ } catch {
+ return []
+ }
+}
+
+export function clearAuthSession(): void {
+ uni.removeStorageSync(KEY_ACCESS)
+ uni.removeStorageSync(KEY_REFRESH)
+ uni.removeStorageSync(KEY_LOCATIONS)
+ uni.removeStorageSync(KEY_LOGGED)
+ uni.removeStorageSync(KEY_USER)
+ uni.removeStorageSync(KEY_EMAIL)
+ uni.removeStorageSync('storeId')
+ uni.removeStorageSync('storeName')
+ uni.removeStorageSync('storeLocationCode')
+}
+
+export function isLoggedIn(): boolean {
+ return !!uni.getStorageSync(KEY_ACCESS) && uni.getStorageSync(KEY_LOGGED) === 'true'
+}
+
+export function getAccessToken(): string {
+ return uni.getStorageSync(KEY_ACCESS) || ''
+}
+
+export function saveRememberPreference(remember: boolean, email: string, password: string): void {
+ uni.setStorageSync(KEY_REMEMBER, remember ? '1' : '0')
+ if (remember) {
+ uni.setStorageSync(KEY_SAVED_EMAIL, email)
+ uni.setStorageSync(KEY_SAVED_PASSWORD, password)
+ } else {
+ uni.removeStorageSync(KEY_SAVED_EMAIL)
+ uni.removeStorageSync(KEY_SAVED_PASSWORD)
+ }
+}
+
+export function loadSavedCredentials(): { email: string; password: string; remember: boolean } {
+ const remember = uni.getStorageSync(KEY_REMEMBER) === '1'
+ return {
+ remember,
+ email: remember ? (uni.getStorageSync(KEY_SAVED_EMAIL) || '') : '',
+ password: remember ? (uni.getStorageSync(KEY_SAVED_PASSWORD) || '') : '',
+ }
+}
diff --git a/美国版/Food Labeling Management App UniApp/src/utils/stores.ts b/美国版/Food Labeling Management App UniApp/src/utils/stores.ts
index 3cc4907..d0ab4a8 100644
--- a/美国版/Food Labeling Management App UniApp/src/utils/stores.ts
+++ b/美国版/Food Labeling Management App UniApp/src/utils/stores.ts
@@ -1,22 +1,28 @@
-export interface StoreInfo {
- id: string
- nameKey: string
- address: string
- city: string
-}
+import type { UsAppBoundLocationDto } from '../services/usAppAuth'
+import { getBoundLocations } from './authSession'
+
+export type StoreInfo = UsAppBoundLocationDto
-export const storeList: StoreInfo[] = [
- { id: '1', nameKey: 'login.store1', address: '123 Main St', city: 'New York, NY 10001' },
- { id: '2', nameKey: 'login.store2', address: '456 Oak Ave', city: 'Brooklyn, NY 11201' },
- { id: '3', nameKey: 'login.store3', address: '789 Pine Rd', city: 'Queens, NY 11354' },
- { id: '4', nameKey: 'login.store4', address: '321 Elm St', city: 'Manhattan, NY 10002' },
-]
+export { getBoundLocations } from './authSession'
export function getCurrentStoreId(): string {
- return uni.getStorageSync('storeId') || '1'
+ return uni.getStorageSync('storeId') || ''
}
-export function switchStore(id: string, storeName: string) {
+export function switchStore(id: string, storeName: string, locationCode?: string) {
uni.setStorageSync('storeId', id)
uni.setStorageSync('storeName', storeName)
+ if (locationCode != null && locationCode !== '') {
+ uni.setStorageSync('storeLocationCode', locationCode)
+ }
+}
+
+/** 顶部药丸展示用,如 LOC-1 */
+export function getCurrentLocationCode(): string {
+ const saved = uni.getStorageSync('storeLocationCode')
+ if (saved) return saved
+ const id = getCurrentStoreId()
+ if (!id) return ''
+ const row = getBoundLocations().find((l) => l.id === id)
+ return row?.locationCode || ''
}
diff --git a/美国版/Food Labeling Management App UniApp/vite.config.ts b/美国版/Food Labeling Management App UniApp/vite.config.ts
index ce0c914..dbd56c0 100644
--- a/美国版/Food Labeling Management App UniApp/vite.config.ts
+++ b/美国版/Food Labeling Management App UniApp/vite.config.ts
@@ -8,5 +8,11 @@ export default defineConfig({
plugins: [uni()],
server: {
open: "/",
+ proxy: {
+ "/api": {
+ target: "http://flus-test.3ffoodsafety.com",
+ changeOrigin: true,
+ },
+ },
},
});
diff --git a/美国版App登录接口说明.md b/美国版App登录接口说明.md
new file mode 100644
index 0000000..5733cf5
--- /dev/null
+++ b/美国版App登录接口说明.md
@@ -0,0 +1,158 @@
+# 美国版 App 登录接口说明
+
+## 概述
+
+美国版移动端认证由 `food-labeling-us` 模块的 **`UsAppAuthAppService`** 提供,采用 ABP 约定式动态 API。宿主统一前缀为 **`/api/app`**,建议以 Swagger 为准核对路径(本地示例:`http://localhost:19001/swagger`,搜索 `UsAppAuth`)。
+
+| 说明 | 内容 |
+|------|------|
+| 账号标识 | 使用 **`User.Email`**(邮箱)登录,邮箱比对**忽略大小写** |
+| 密码 | 与 Web 共用 `User` 表,校验方式与 RBAC **`AccountManager`** 一致(盐值 + `MD5Helper.SHA2Encode`) |
+| 验证码 | 当配置 **`Rbac:EnableCaptcha`** 为 `true` 时,需先拉取图形验证码,本接口入参传 `uuid`、`code`;未开启时可传空或不传 |
+
+---
+
+## 接口 1:App 登录
+
+签发 **Access Token**、**Refresh Token**,并返回当前用户在 **`userlocation`** 中绑定的门店列表(关联 **`location`** 表详情)。
+
+### HTTP
+
+- **方法**:`POST`
+- **路径**:`/api/app/us-app-auth/login`
+- **Content-Type**:`application/json`
+- **鉴权**:无需登录(匿名)
+
+### 请求体参数(UsAppLoginInputVo)
+
+| 参数名(JSON) | 类型 | 必填 | 说明 |
+|----------------|------|------|------|
+| `email` | string | 是 | 登录邮箱,对应数据库 `User.Email` |
+| `password` | string | 是 | 明文密码 |
+| `uuid` | string | 条件 | 图形验证码 UUID;**开启验证码时必填** |
+| `code` | string | 条件 | 图形验证码;**开启验证码时必填** |
+
+### 传参示例(请求 Body)
+
+未开启图形验证码时:
+
+```json
+{
+ "email": "admin@example.com",
+ "password": "123456"
+}
+```
+
+开启图形验证码时(需与系统验证码接口返回的 `uuid`、用户输入的验证码一致):
+
+```json
+{
+ "email": "test@example.com",
+ "password": "您的密码",
+ "uuid": "验证码接口返回的 uuid",
+ "code": "用户看到的验证码"
+}
+```
+
+### 响应体(UsAppLoginOutputDto)
+
+| 字段(JSON) | 类型 | 说明 |
+|--------------|------|------|
+| `token` | string | 访问令牌(Bearer),后续业务接口放在 Header `Authorization: Bearer {token}` |
+| `refreshToken` | string | 刷新令牌(与系统账号体系一致,用于刷新 access token,具体用法与 Web 一致) |
+| `locations` | array | 绑定门店列表,元素见下表 |
+
+#### `locations[]` 元素(UsAppBoundLocationDto)
+
+| 字段(JSON) | 类型 | 说明 |
+|--------------|------|------|
+| `id` | string | 门店主键(Guid 字符串) |
+| `locationCode` | string | 业务编码,如 LOC-1 |
+| `locationName` | string | 门店名称 |
+| `fullAddress` | string | 拼接后的完整地址(街道、城市、州、邮编等;无数据时可能为 `"无"`) |
+| `state` | bool | 门店是否启用 |
+
+### 响应示例
+
+```json
+{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "locations": [
+ {
+ "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f",
+ "locationCode": "LOC-1",
+ "locationName": "Downtown Kitchen",
+ "fullAddress": "123 Main St, New York, NY 10001",
+ "state": true
+ }
+ ]
+}
+```
+
+### 常见错误提示(业务异常文案)
+
+- 邮箱或密码为空:`请输入合理数据!`
+- 邮箱在库中不存在(未删除且启用用户中无匹配邮箱):`登录失败!邮箱不存在!`
+- 密码错误:`登录失败!用户名或密码错误!`(与 `UserConst.Login_Error` 一致)
+- 验证码错误(开启验证码时):`验证码错误`
+
+---
+
+## 接口 2:获取当前账号绑定门店
+
+无需重新登录即可刷新 **`userlocation`** 绑定门店列表(例如切换门店前先同步列表)。
+
+### HTTP
+
+- **方法**:`GET`
+- **路径**:`/api/app/us-app-auth/my-locations`
+- **鉴权**:需要登录,请求头携带 **`Authorization: Bearer {token}`**(使用接口 1 返回的 `token`)
+
+### 请求参数
+
+无 Query / Body 参数;用户身份由 JWT 解析。
+
+### 传参示例
+
+```http
+GET /api/app/us-app-auth/my-locations HTTP/1.1
+Host: localhost:19001
+Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
+```
+
+若前端统一约定 GET 使用 `data` 封装,可自行在客户端组装;本接口服务端**不读取额外 Query 参数**。
+
+### 响应体
+
+与登录接口中 **`locations`** 相同:**`UsAppBoundLocationDto[]`**(数组)。
+
+### 响应示例
+
+```json
+[
+ {
+ "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f",
+ "locationCode": "LOC-1",
+ "locationName": "Downtown Kitchen",
+ "fullAddress": "123 Main St, New York, NY 10001",
+ "state": true
+ }
+]
+```
+
+### 常见错误
+
+- 未登录或 Token 无效:按网关/ABP 返回 401 及统一错误体
+- 无用户上下文:`用户未登录`
+
+---
+
+## 与其他登录方式的区别
+
+| 场景 | 说明 |
+|------|------|
+| Web 管理端 | 仍使用 RBAC **`AccountService.PostLoginAsync`**,一般为人 **`userName`** + 密码 |
+| 美国版 App | **仅**本模块 **`/api/app/us-app-auth/login`** 使用 **邮箱 + 密码** |
+
+两者共用同一 `User` 表与 JWT 体系;App 端需保证账号已维护 **`Email`** 字段,否则无法通过邮箱登录。
--
libgit2 0.21.4