fix(h5-partner): resolve merge conflicts for auth and sub-accounts
Merge partner session model (AuthGate, ensureSession, WeChat OAuth in Context) with sub-account routing, admin partner-account tree CRUD, upload lock, and partner staff flows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { PartnerMe } from '@dukang/shared-types';
|
||||
import type { PartnerMe, PartnerStaffRole } from '@dukang/shared-types';
|
||||
import { isOnAppPath, toAppPath } from '@dukang/weixin-sdk';
|
||||
import { showPartnerToast } from './toast';
|
||||
|
||||
@@ -22,7 +22,9 @@ const AUTH_RECOVERY_EXEMPT_PATHS = [
|
||||
'/partner/auth/login/wechat',
|
||||
];
|
||||
|
||||
export type PartnerSessionProfile = Pick<PartnerMe, 'id' | 'name' | 'phone' | 'companyName' | 'isPrimary'>;
|
||||
export type PartnerSessionProfile = Pick<PartnerMe, 'id' | 'name' | 'phone' | 'companyName' | 'isPrimary'> & {
|
||||
staffRole?: PartnerStaffRole;
|
||||
};
|
||||
|
||||
export type PartnerSessionPayload = {
|
||||
accessToken: string;
|
||||
@@ -31,7 +33,7 @@ export type PartnerSessionPayload = {
|
||||
};
|
||||
|
||||
export type ApiRequestOptions = RequestInit & {
|
||||
/** 为 true 时不弹出 toast(由调用方自行展示) */
|
||||
/** 保留字段,兼容旧调用;错误提示由页面自行处理 */
|
||||
silent?: boolean;
|
||||
};
|
||||
|
||||
@@ -101,6 +103,7 @@ function profileFromMe(me: PartnerMe): PartnerSessionProfile {
|
||||
phone: me.phone,
|
||||
companyName: me.companyName,
|
||||
isPrimary: me.isPrimary,
|
||||
staffRole: me.staffRole ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type {
|
||||
CreatePartnerStaffRequest,
|
||||
PartnerStaffItem,
|
||||
UpdatePartnerStaffRequest,
|
||||
import {
|
||||
PartnerStaffRole,
|
||||
type CreatePartnerStaffRequest,
|
||||
type PartnerStaffItem,
|
||||
type UpdatePartnerStaffRequest,
|
||||
} from '@dukang/shared-types';
|
||||
import { request } from './api';
|
||||
|
||||
@@ -12,7 +13,11 @@ export function listPartnerStaff() {
|
||||
export function createPartnerStaff(body: CreatePartnerStaffRequest) {
|
||||
return request<PartnerStaffItem>('PARTNER_H5', '/partner/staff', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
body: JSON.stringify({
|
||||
name: body.name,
|
||||
phone: body.phone,
|
||||
staffRole: PartnerStaffRole.INTERNAL,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,10 +33,3 @@ export function deletePartnerStaff(id: string) {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
export function sendStaffAddSms(phone: string) {
|
||||
return request('PARTNER_H5', '/partner/auth/sms/send', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ phone, scene: 'PARTNER_STAFF_ADD' }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export const defaultStoreForm = (): StoreDraftForm => ({
|
||||
bankBranch: '',
|
||||
});
|
||||
|
||||
function normalizeStringArray(raw: unknown, length: number): string[] {
|
||||
export function normalizeStringArray(raw: unknown, length: number): string[] {
|
||||
if (!Array.isArray(raw)) return Array.from({ length }, () => '');
|
||||
const items = raw.map((item) => String(item ?? ''));
|
||||
while (items.length < length) items.push('');
|
||||
@@ -138,6 +138,12 @@ export function validateStoreStep2(
|
||||
return null;
|
||||
}
|
||||
|
||||
export function patchEnvPhotoAt(urls: string[], index: number, url: string): string[] {
|
||||
const envPhotoUrls = normalizeStringArray(urls, 3);
|
||||
envPhotoUrls[index] = url;
|
||||
return envPhotoUrls;
|
||||
}
|
||||
|
||||
export function validateStoreStep3(
|
||||
form: Pick<StoreDraftForm, 'bankAccountName' | 'bankAccountNo' | 'bankBranch'>,
|
||||
): string | null {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/** 串行化上传,避免微信 JSSDK 并发选图/读图导致卡死 */
|
||||
let uploadChain: Promise<unknown> = Promise.resolve();
|
||||
|
||||
export function enqueueUpload<T>(task: () => Promise<T>): Promise<T> {
|
||||
const next = uploadChain.then(task, task);
|
||||
uploadChain = next.then(
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
return next;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiBase, request } from './api';
|
||||
import { showPartnerToast } from './toast';
|
||||
|
||||
const UPLOAD_TIMEOUT_MS = 120_000;
|
||||
|
||||
export type OssMediaType = 'IMAGE' | 'VIDEO' | 'FILE';
|
||||
|
||||
@@ -10,8 +11,7 @@ export type UploadFileResult = {
|
||||
mock: boolean;
|
||||
};
|
||||
|
||||
/** 经 API 服务端转存 OSS,避免浏览器直传跨域 */
|
||||
export async function uploadFileToOss(
|
||||
async function uploadFileToOssInner(
|
||||
file: File,
|
||||
options: { bizType: string; mediaType?: OssMediaType },
|
||||
): Promise<UploadFileResult> {
|
||||
@@ -25,29 +25,48 @@ export async function uploadFileToOss(
|
||||
const headers: Record<string, string> = { 'X-Client-App': 'PARTNER_H5' };
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(`${apiBase}/common/resources/upload`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.code === 401) {
|
||||
localStorage.removeItem('accessToken');
|
||||
showPartnerToast('未登录', 'error');
|
||||
throw new Error('未登录');
|
||||
}
|
||||
if (json.code !== 0) {
|
||||
showPartnerToast(json.message || '上传失败', 'error');
|
||||
throw new Error(json.message || '上传失败');
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const timeoutId = window.setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);
|
||||
|
||||
const data = json.data as UploadFileResult;
|
||||
return {
|
||||
url: data.url,
|
||||
ossKey: data.ossKey,
|
||||
bucket: data.bucket,
|
||||
mock: data.mock,
|
||||
};
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/common/resources/upload`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
signal: controller.signal,
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.code === 401) {
|
||||
localStorage.removeItem('accessToken');
|
||||
throw new Error('未登录');
|
||||
}
|
||||
if (json.code !== 0) {
|
||||
throw new Error(json.message || '上传失败');
|
||||
}
|
||||
|
||||
const data = json.data as UploadFileResult;
|
||||
return {
|
||||
url: data.url,
|
||||
ossKey: data.ossKey,
|
||||
bucket: data.bucket,
|
||||
mock: data.mock,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === 'AbortError') {
|
||||
throw new Error('上传超时,请检查网络后重试');
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
/** 经 API 服务端转存 OSS,避免浏览器直传跨域 */
|
||||
export function uploadFileToOss(
|
||||
file: File,
|
||||
options: { bizType: string; mediaType?: OssMediaType },
|
||||
): Promise<UploadFileResult> {
|
||||
return uploadFileToOssInner(file, options);
|
||||
}
|
||||
|
||||
export type OpenCityOption = {
|
||||
|
||||
@@ -107,6 +107,6 @@ export async function processPartnerWechatOAuthCallback(): Promise<PartnerSessio
|
||||
}
|
||||
|
||||
/** @deprecated 使用 handlePartnerWechatLoginResult */
|
||||
export function savePartnerWechatAuth(result: WechatLoginResult): boolean {
|
||||
return !!handlePartnerWechatLoginResult(result);
|
||||
export function savePartnerWechatAuth(result: WechatLoginResult): PartnerSessionPayload | null {
|
||||
return handlePartnerWechatLoginResult(result);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user