forked from wangziqi/gongxue-base
feat: scaffold taro h5 frontends
This commit is contained in:
3
apps/taro/src/pages/bootstrap/index.config.ts
Normal file
3
apps/taro/src/pages/bootstrap/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '启动',
|
||||
});
|
||||
48
apps/taro/src/pages/bootstrap/index.css
Normal file
48
apps/taro/src/pages/bootstrap/index.css
Normal file
@@ -0,0 +1,48 @@
|
||||
.bootstrap-page {
|
||||
min-height: 100vh;
|
||||
padding: 56px 28px;
|
||||
background: #101827;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.bootstrap-shell {
|
||||
display: flex;
|
||||
min-height: calc(100vh - 112px);
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
color: #80b7ff;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.title {
|
||||
max-width: 620px;
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #cbd5e1;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #fecaca;
|
||||
font-size: 24px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
width: 240px;
|
||||
height: 76px;
|
||||
border-radius: 8px;
|
||||
background: #2f7cf6;
|
||||
color: #fff;
|
||||
font-size: 28px;
|
||||
line-height: 76px;
|
||||
}
|
||||
51
apps/taro/src/pages/bootstrap/index.tsx
Normal file
51
apps/taro/src/pages/bootstrap/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { Button, Text, View } from '@tarojs/components';
|
||||
import { appEnv, assertFrontendSecretsAreAbsent } 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;
|
||||
return '';
|
||||
}
|
||||
|
||||
function landingPath() {
|
||||
if (appEnv.portal === 'tenant-admin') return '/pages/tenant-admin/workbench/index';
|
||||
if (appEnv.portal === 'platform-admin') return '/pages/platform-admin/workbench/index';
|
||||
return '/pages/student/home/index';
|
||||
}
|
||||
|
||||
export default function BootstrapPage() {
|
||||
const [status, setStatus] = useState('正在解析租户');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
assertFrontendSecretsAreAbsent();
|
||||
resolveTenant({ host: hostFromRuntime() })
|
||||
.then(() => {
|
||||
setStatus('租户解析完成');
|
||||
Taro.redirectTo({ url: landingPath() });
|
||||
})
|
||||
.catch((nextError: Error) => {
|
||||
setError(nextError.message);
|
||||
setStatus('租户解析失败');
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className='bootstrap-page'>
|
||||
<View className='bootstrap-shell'>
|
||||
<Text className='eyebrow'>Tiku SaaS</Text>
|
||||
<Text className='title'>正在进入题库系统</Text>
|
||||
<Text className='status'>{status}</Text>
|
||||
{error ? <Text className='error'>{error}</Text> : null}
|
||||
{error ? (
|
||||
<Button className='primary-button' onClick={() => Taro.reLaunch({ url: '/pages/bootstrap/index' })}>
|
||||
重新尝试
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '平台后台',
|
||||
});
|
||||
53
apps/taro/src/pages/platform-admin/workbench/index.css
Normal file
53
apps/taro/src/pages/platform-admin/workbench/index.css
Normal file
@@ -0,0 +1,53 @@
|
||||
.platform-page {
|
||||
min-height: 100vh;
|
||||
padding: 28px;
|
||||
background: #f7f9fc;
|
||||
}
|
||||
|
||||
.platform-header {
|
||||
padding-bottom: 28px;
|
||||
border-bottom: 1px solid #d8e0ec;
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
display: block;
|
||||
color: #101827;
|
||||
font-size: 38px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.platform-subtitle {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
color: #64748b;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.platform-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.platform-tile {
|
||||
min-height: 128px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tile-title {
|
||||
display: block;
|
||||
color: #172033;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.tile-status {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
color: #64748b;
|
||||
font-size: 22px;
|
||||
}
|
||||
29
apps/taro/src/pages/platform-admin/workbench/index.tsx
Normal file
29
apps/taro/src/pages/platform-admin/workbench/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Text, View } from '@tarojs/components';
|
||||
import { apiRequest } from '@/services/api';
|
||||
import './index.css';
|
||||
|
||||
export default function PlatformWorkbenchPage() {
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
apiRequest('/api/platform-admin/overview').then(() => setReady(true)).catch(() => setReady(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className='platform-page'>
|
||||
<View className='platform-header'>
|
||||
<Text className='platform-title'>SaaS 平台后台</Text>
|
||||
<Text className='platform-subtitle'>租户、套餐、账单、公共题库授权和平台审计入口</Text>
|
||||
</View>
|
||||
<View className='platform-grid'>
|
||||
{['租户管理', 'SaaS 套餐', '服务费账单', '公共题库', '用量记录', '安全审计'].map(name => (
|
||||
<View className='platform-tile' key={name}>
|
||||
<Text className='tile-title'>{name}</Text>
|
||||
<Text className='tile-status'>{ready ? '接口已连接' : '需要平台管理员登录'}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
3
apps/taro/src/pages/student/home/index.config.ts
Normal file
3
apps/taro/src/pages/student/home/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '学习首页',
|
||||
});
|
||||
98
apps/taro/src/pages/student/home/index.css
Normal file
98
apps/taro/src/pages/student/home/index.css
Normal file
@@ -0,0 +1,98 @@
|
||||
.student-page {
|
||||
min-height: 100vh;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: block;
|
||||
color: #14213d;
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.subline {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
color: #64748b;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.ghost-button {
|
||||
width: 132px;
|
||||
height: 64px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #1e3a8a;
|
||||
font-size: 24px;
|
||||
line-height: 64px;
|
||||
}
|
||||
|
||||
.hero-band {
|
||||
margin-top: 36px;
|
||||
padding: 34px 30px;
|
||||
border-left: 8px solid #2f7cf6;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
display: block;
|
||||
margin-top: 14px;
|
||||
color: #475569;
|
||||
font-size: 25px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
margin-bottom: 18px;
|
||||
color: #172033;
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.entry-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.entry-tile {
|
||||
min-height: 126px;
|
||||
padding: 22px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.entry-name {
|
||||
display: block;
|
||||
color: #111827;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.entry-type {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
color: #64748b;
|
||||
font-size: 22px;
|
||||
}
|
||||
54
apps/taro/src/pages/student/home/index.tsx
Normal file
54
apps/taro/src/pages/student/home/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Text, View } from '@tarojs/components';
|
||||
import { getTenantContext } from '@/services/api';
|
||||
import { loadStudentDashboard, type DashboardSnapshot } from '@/services/catalog';
|
||||
import { loadCurrentUser } from '@/services/auth';
|
||||
import './index.css';
|
||||
|
||||
export default function StudentHomePage() {
|
||||
const tenant = getTenantContext();
|
||||
const [snapshot, setSnapshot] = useState<DashboardSnapshot | null>(null);
|
||||
const [userName, setUserName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadStudentDashboard().then(setSnapshot).catch(() => setSnapshot({ entries: [], banners: [], announcements: [], profile: null }));
|
||||
loadCurrentUser().then(payload => setUserName(payload.user?.name || payload.item?.name || '')).catch(() => setUserName(''));
|
||||
}, []);
|
||||
|
||||
const entryNames = useMemo(() => (snapshot?.entries || []).slice(0, 6), [snapshot]);
|
||||
const brandName = tenant?.branding.brandName || tenant?.branding.shortName || '工学题库';
|
||||
|
||||
return (
|
||||
<View className='student-page'>
|
||||
<View className='topbar'>
|
||||
<View>
|
||||
<Text className='brand'>{brandName}</Text>
|
||||
<Text className='subline'>{userName ? `${userName},继续学习` : '题库、手册、资料和会员统一入口'}</Text>
|
||||
</View>
|
||||
<Button className='ghost-button'>会员</Button>
|
||||
</View>
|
||||
|
||||
<View className='hero-band'>
|
||||
<Text className='hero-title'>今日学习</Text>
|
||||
<Text className='hero-copy'>后端已接入多租户题库、练习快照、错题、收藏、单词和知识手册接口。</Text>
|
||||
</View>
|
||||
|
||||
<View className='section'>
|
||||
<Text className='section-title'>学习入口</Text>
|
||||
<View className='entry-grid'>
|
||||
{entryNames.length ? entryNames.map(item => (
|
||||
<View className='entry-tile' key={item.id}>
|
||||
<Text className='entry-name'>{item.name}</Text>
|
||||
<Text className='entry-type'>{item.entryType || 'content'}</Text>
|
||||
</View>
|
||||
)) : ['刷题', '背单词', '知识手册', '分数线', '资料下载', '会员中心'].map(name => (
|
||||
<View className='entry-tile' key={name}>
|
||||
<Text className='entry-name'>{name}</Text>
|
||||
<Text className='entry-type'>待联调</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '租户后台',
|
||||
});
|
||||
72
apps/taro/src/pages/tenant-admin/workbench/index.css
Normal file
72
apps/taro/src/pages/tenant-admin/workbench/index.css
Normal file
@@ -0,0 +1,72 @@
|
||||
.admin-page {
|
||||
min-height: 100vh;
|
||||
padding: 28px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
padding-bottom: 26px;
|
||||
border-bottom: 1px solid #dbe3ef;
|
||||
}
|
||||
|
||||
.admin-title {
|
||||
display: block;
|
||||
color: #111827;
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.admin-subtitle {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
color: #64748b;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.metric-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
min-height: 116px;
|
||||
padding: 18px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
display: block;
|
||||
color: #64748b;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
color: #0f172a;
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.module-list {
|
||||
margin-top: 28px;
|
||||
border-top: 1px solid #dbe3ef;
|
||||
}
|
||||
|
||||
.module-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid #dbe3ef;
|
||||
color: #172033;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.module-status {
|
||||
color: #64748b;
|
||||
font-size: 24px;
|
||||
}
|
||||
46
apps/taro/src/pages/tenant-admin/workbench/index.tsx
Normal file
46
apps/taro/src/pages/tenant-admin/workbench/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Text, View } from '@tarojs/components';
|
||||
import { apiRequest, getTenantContext } from '@/services/api';
|
||||
import './index.css';
|
||||
|
||||
interface Dashboard {
|
||||
cards?: Record<string, unknown>;
|
||||
trends?: unknown[];
|
||||
recentActivities?: unknown[];
|
||||
}
|
||||
|
||||
export default function TenantWorkbenchPage() {
|
||||
const tenant = getTenantContext();
|
||||
const [dashboard, setDashboard] = useState<Dashboard | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
apiRequest<{ item?: Dashboard }>('/api/tenant-admin/dashboard', { query: { timeRange: '30d' } })
|
||||
.then(payload => setDashboard(payload.item || null))
|
||||
.catch(() => setDashboard(null));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className='admin-page'>
|
||||
<View className='admin-header'>
|
||||
<Text className='admin-title'>{tenant?.branding.brandName || '租户运营后台'}</Text>
|
||||
<Text className='admin-subtitle'>品牌、题库、学生、订单、营销和数据看板的统一工作台</Text>
|
||||
</View>
|
||||
<View className='metric-row'>
|
||||
{['收益', '注册', '答题', '激活码'].map(label => (
|
||||
<View className='metric' key={label}>
|
||||
<Text className='metric-label'>{label}</Text>
|
||||
<Text className='metric-value'>{dashboard ? '已接入' : '待登录'}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View className='module-list'>
|
||||
{['内容管理', '导入复检', '学生运营', '销售分佣', 'CRM 队列', '品牌域名'].map(name => (
|
||||
<View className='module-row' key={name}>
|
||||
<Text>{name}</Text>
|
||||
<Text className='module-status'>按权限显示</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user