test: add taro h5 interaction smoke

This commit is contained in:
Codex
2026-07-01 02:37:43 +08:00
parent c3e1f27a0a
commit ad1c69037b
20 changed files with 1052 additions and 36 deletions

View File

@@ -43,7 +43,7 @@ export default {
options: {},
},
h5: {
publicPath: './',
publicPath: '/',
staticDirectory: 'static',
output: {
filename: 'js/[name].[contenthash:8].js',

View File

@@ -25,6 +25,15 @@ declare const process: {
env: Record<string, string | undefined>;
};
type ProcessLike = {
env?: Record<string, string | undefined>;
};
function envValue(key: string) {
const runtimeProcess = typeof process === 'undefined' ? undefined : (process as ProcessLike);
return runtimeProcess?.env?.[key];
}
const forbiddenFrontendKeys = [
'SUPABASE_SERVICE_ROLE_KEY',
'SUPABASE_SECRET_KEY',
@@ -72,11 +81,11 @@ function assertNoForbiddenKeys(input: Record<string, unknown>, source: string) {
}
export const appEnv: AppEnv = {
portal: (process.env.TARO_APP_PORTAL || 'student') as Portal,
apiBaseUrl: process.env.TARO_APP_API_BASE_URL || 'http://127.0.0.1:8787',
supabaseUrl: process.env.TARO_APP_SUPABASE_URL || '',
supabasePublishableKey: process.env.TARO_APP_SUPABASE_PUBLISHABLE_KEY || '',
tenantCode: process.env.TARO_APP_TENANT_CODE || '',
portal: (envValue('TARO_APP_PORTAL') || 'student') as Portal,
apiBaseUrl: envValue('TARO_APP_API_BASE_URL') || 'http://127.0.0.1:8787',
supabaseUrl: envValue('TARO_APP_SUPABASE_URL') || '',
supabasePublishableKey: envValue('TARO_APP_SUPABASE_PUBLISHABLE_KEY') || '',
tenantCode: envValue('TARO_APP_TENANT_CODE') || '',
};
let runtimeConfigPromise: Promise<AppEnv> | null = null;
@@ -103,7 +112,7 @@ export function applyRuntimeConfig(input: RuntimeConfigInput, source = 'runtime
}
export async function loadRuntimeConfig() {
if (process.env.TARO_ENV !== 'h5' || typeof window === 'undefined' || typeof window.fetch !== 'function') {
if (!isH5Runtime() || typeof window === 'undefined' || typeof window.fetch !== 'function') {
return appEnv;
}
@@ -138,8 +147,20 @@ export function ensureRuntimeConfigLoaded() {
}
export function assertFrontendSecretsAreAbsent() {
const leaked = forbiddenFrontendKeys.filter(key => process.env[key]);
const leaked = forbiddenFrontendKeys.filter(key => envValue(key));
if (leaked.length) {
throw new Error(`Forbidden secret env in Taro build: ${leaked.join(', ')}`);
}
}
export function taroRuntimeEnv() {
return envValue('TARO_ENV') || '';
}
export function isH5Runtime() {
return taroRuntimeEnv() === 'h5' || (typeof window !== 'undefined' && typeof document !== 'undefined');
}
export function isWeappRuntime() {
return taroRuntimeEnv() === 'weapp';
}

View File

@@ -1,12 +1,12 @@
import { useEffect, useState } from 'react';
import Taro from '@tarojs/taro';
import { Button, Text, View } from '@tarojs/components';
import { appEnv, assertFrontendSecretsAreAbsent, ensureRuntimeConfigLoaded } from '@/env';
import { appEnv, assertFrontendSecretsAreAbsent, ensureRuntimeConfigLoaded, isH5Runtime } from '@/env';
import { resolveTenant } from '@/services/api';
import './index.css';
function hostFromRuntime() {
if (process.env.TARO_ENV === 'h5' && typeof window !== 'undefined') return window.location.host;
if (isH5Runtime() && typeof window !== 'undefined') return window.location.host;
return '';
}

View File

@@ -7,6 +7,7 @@ import {
loadSchoolRecommendationReports,
type SchoolRecommendationReport,
} from '@/services/ai';
import { isH5Runtime } from '@/env';
import { loadProfile, type StudentProfile } from '@/services/profile';
import '../student.css';
@@ -25,7 +26,7 @@ function recommendationRows(report: SchoolRecommendationReport | null) {
}
function saveExportFile(fileName: string, content: string, mimeType: string) {
if (process.env.TARO_ENV === 'h5' && typeof window !== 'undefined' && typeof document !== 'undefined') {
if (isH5Runtime() && typeof window !== 'undefined' && typeof document !== 'undefined') {
const blob = new Blob([content], { type: mimeType });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
@@ -94,7 +95,7 @@ export default function StudentAiSchoolPage() {
const payload = await exportSchoolRecommendationReport(current.id, format);
if (!payload.item?.contentText) throw new Error('后端未返回报告内容');
saveExportFile(payload.item.fileName, payload.item.contentText, payload.item.mimeType);
if (process.env.TARO_ENV === 'h5') Taro.showToast({ title: '报告已导出', icon: 'success' });
if (isH5Runtime()) Taro.showToast({ title: '报告已导出', icon: 'success' });
} catch (nextError) {
setError(nextError instanceof Error ? nextError.message : '导出失败');
} finally {

View File

@@ -9,6 +9,7 @@ import {
type ContentAsset,
type SignedAssetLink,
} from '@/services/catalog';
import { isH5Runtime } from '@/env';
import '../student.css';
type PreviewState = {
@@ -20,7 +21,7 @@ type PreviewState = {
function openUrl(url?: string, copiedText = '签名链接已复制,请在浏览器中打开') {
if (!url) return;
if (process.env.TARO_ENV === 'h5' && typeof window !== 'undefined') {
if (isH5Runtime() && typeof window !== 'undefined') {
window.open(url, '_blank', 'noopener,noreferrer');
return;
}
@@ -129,10 +130,10 @@ export default function StudentAssetsPage() {
<Text className='row-meta'> URL </Text>
</View>
) : null}
{previewState.kind === 'preview' && process.env.TARO_ENV === 'h5' ? (
{previewState.kind === 'preview' && isH5Runtime() ? (
<WebView className='asset-preview-iframe' src={previewState.link.url || ''} />
) : null}
{previewState.kind === 'preview' && process.env.TARO_ENV !== 'h5' ? (
{previewState.kind === 'preview' && !isH5Runtime() ? (
<View className='asset-download-confirm'>
<Text className='row-main'></Text>
<Text className='row-meta'>使</Text>

View File

@@ -12,6 +12,7 @@ import {
type PaymentCreateResult,
type SvipPlan,
} from '@/services/commerce';
import { isH5Runtime, isWeappRuntime } from '@/env';
import { loadProfile, type StudentProfile } from '@/services/profile';
import '../student.css';
@@ -56,7 +57,7 @@ function buildPaymentUrl(result?: PaymentCreateResult) {
function tryOpenPaymentUrl(url: string) {
if (!url) return false;
if (process.env.TARO_ENV === 'h5' && typeof window !== 'undefined') {
if (isH5Runtime() && typeof window !== 'undefined') {
window.location.href = url;
return true;
}
@@ -167,8 +168,8 @@ export default function StudentCheckoutPage() {
const paymentPayload = await createPayment({
orderNo: nextOrder.orderNo,
provider,
returnUrl: process.env.TARO_ENV === 'h5' && typeof window !== 'undefined' ? window.location.href : undefined,
quitUrl: process.env.TARO_ENV === 'h5' && typeof window !== 'undefined' ? window.location.href : undefined,
returnUrl: isH5Runtime() && typeof window !== 'undefined' ? window.location.href : undefined,
quitUrl: isH5Runtime() && typeof window !== 'undefined' ? window.location.href : undefined,
});
setPayment(paymentPayload.item || null);
setMessage(provider === 'manual' ? '已生成线下支付记录,请联系教务或客服确认。' : '支付参数已生成,请继续完成支付。');
@@ -181,7 +182,7 @@ export default function StudentCheckoutPage() {
async function handleOpenPayment() {
if (!payment) return;
if (payment.provider === 'wechat_pay' && process.env.TARO_ENV === 'weapp') {
if (payment.provider === 'wechat_pay' && isWeappRuntime()) {
const paramsForWeapp = payment.paymentParams || {};
Taro.requestPayment(paramsForWeapp as unknown as Taro.requestPayment.Option)
.then(() => order?.orderNo ? pollStatus(order.orderNo) : undefined)

View File

@@ -10,6 +10,7 @@ import {
type PaymentCreateResult,
} from '@/services/commerce';
import { getTenantContext } from '@/services/api';
import { isH5Runtime } from '@/env';
import '../student.css';
function centsToYuan(value?: number | null) {
@@ -94,13 +95,13 @@ export default function StudentOrderDetailPage() {
const payload = await createPayment({
orderNo,
provider: provider || detail?.payProvider || status?.payProvider || 'alipay',
returnUrl: process.env.TARO_ENV === 'h5' && typeof window !== 'undefined' ? window.location.href : undefined,
quitUrl: process.env.TARO_ENV === 'h5' && typeof window !== 'undefined' ? window.location.href : undefined,
returnUrl: isH5Runtime() && typeof window !== 'undefined' ? window.location.href : undefined,
quitUrl: isH5Runtime() && typeof window !== 'undefined' ? window.location.href : undefined,
});
const nextPayment = payload.item || null;
setPayment(nextPayment);
const url = paymentUrl(nextPayment);
if (url && process.env.TARO_ENV === 'h5' && typeof window !== 'undefined') {
if (url && isH5Runtime() && typeof window !== 'undefined') {
window.location.href = url;
} else if (url) {
await Taro.setClipboardData({ data: url });

View File

@@ -34,6 +34,7 @@ import {
type PublicQuestionBankItem,
type TenantContentNotificationItem,
} from '@/services/tenantAdmin';
import { isH5Runtime } from '@/env';
import '../admin.css';
function displayBankName(item: PublicQuestionBankItem) {
@@ -74,7 +75,7 @@ function readLocalFileAsText(file: File) {
function openH5FilePicker(format: ImportSourceFormat) {
return new Promise<{ fileName: string; text?: string; fileBase64?: string }>((resolve, reject) => {
if (process.env.TARO_ENV !== 'h5' || typeof document === 'undefined') {
if (!isH5Runtime() || typeof document === 'undefined') {
reject(new Error('当前端暂未接入文件选择,请粘贴 JSON/CSV 内容后预览导入。'));
return;
}
@@ -111,7 +112,7 @@ function base64ToUint8Array(base64: string) {
}
function downloadTemplateFile(template: ImportTemplateItem) {
if (process.env.TARO_ENV !== 'h5' || typeof document === 'undefined') {
if (!isH5Runtime() || typeof document === 'undefined') {
throw new Error('当前端暂不支持直接下载模板,请先使用模板预览。');
}
if (!template.contentBase64) throw new Error('模板内容为空,请重新加载模板。');

View File

@@ -1,9 +1,11 @@
import { isH5Runtime } from '../env';
export type ApiAuthMode = 'auto' | 'none' | 'supabase' | 'legacy';
export type SupabaseAccessTokenProvider = () => Promise<string | null>;
async function defaultH5SupabaseAccessTokenProvider() {
if (process.env.TARO_ENV !== 'h5') return null;
if (!isH5Runtime()) return null;
const { getSupabaseAccessToken } = await import('./supabase');
return getSupabaseAccessToken();
}

View File

@@ -1,4 +1,5 @@
import Taro from '@tarojs/taro';
import { isH5Runtime } from '@/env';
export type AccentType = 'us' | 'uk';
@@ -62,7 +63,7 @@ export async function playWordPronunciation(word: string, accent: AccentType = '
const url = pronunciationUrl(text, accent);
if (process.env.TARO_ENV === 'h5' && typeof Audio !== 'undefined') {
if (isH5Runtime() && typeof Audio !== 'undefined') {
try {
const audio = new Audio(url);
audio.preload = 'auto';