feat: update JSON logs and documentation for new test and policy entries

- Added new entries to planLogs.json and policyLogs.json to reflect recent updates.
- Introduced a new test entry in tests.json to enhance testing capabilities.
- Updated documentation to reflect architectural changes regarding OPC testing logic, emphasizing backend authority and migration plans for the web version.
This commit is contained in:
2026-08-21 16:20:29 +08:00
parent 291f322929
commit 487fb84aac
43 changed files with 19440 additions and 25587 deletions
+3
View File
@@ -12,6 +12,9 @@ wheels/
# Virtual environments
.venv
# Taro / SWC 编译缓存
**/.swc/
# Runtime data: JSON "database" files are generated on first boot from
# data/seed/. They behave like a local database and should not be committed.
# The seed files under data/seed/ ARE committed.
@@ -1,3 +1,5 @@
const path = require('path');
const config = {
projectName: 'miniprogram',
date: '2026-8-20',
@@ -6,6 +8,9 @@ const config = {
sourceRoot: 'src',
outputRoot: 'dist',
plugins: [],
alias: {
'@': path.resolve(__dirname, '..', 'src')
},
defineConstants: {},
copy: { patterns: [], options: {} },
framework: 'react',
@@ -14,7 +19,8 @@ const config = {
mini: {
postcss: {
pxtransform: { enable: true, config: {} },
url: { enable: true, config: { limit: 1024 } },
// limit 调高:内嵌像素字体等小体积资源为 base64,避免运行时路径解析问题
url: { enable: true, config: { limit: 300000 } },
cssModules: { enable: false }
}
},
File diff suppressed because it is too large Load Diff
@@ -12,5 +12,19 @@ export default {
navigationBarTitleText: 'OPC 一人公司',
navigationBarTextStyle: 'white',
backgroundColor: '#000000'
},
tabBar: {
custom: true,
color: '#8a8a93',
selectedColor: '#ffd28a',
backgroundColor: '#000000',
borderStyle: 'black',
list: [
{ pagePath: 'pages/booking/index', text: '报名' },
{ pagePath: 'pages/test/index', text: '测评' },
{ pagePath: 'pages/index/index', text: '首页' },
{ pagePath: 'pages/policy/index', text: '政策' },
{ pagePath: 'pages/plan/index', text: '流程' }
]
}
};
@@ -1,8 +1,24 @@
import { Component } from 'react';
import { View, Canvas } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { startStarfield } from './utils/starfield';
import './app.scss';
class App extends Component {
componentDidMount() {}
componentDidMount() {
// 旧版 CanvasContext 绘制星空(避开 type="2d" 节点在部分基础库的渲染层崩溃)。
// 尺寸取窗口逻辑尺寸;try/catch + CSS 星空兜底:画布失败绝不黑屏。
Taro.nextTick(() => {
try {
const info = Taro.getWindowInfo ? Taro.getWindowInfo() : Taro.getSystemInfoSync();
const cw = info.windowWidth || 375;
const ch = info.windowHeight || 667;
this.stopStarfield = startStarfield('starfield', cw, ch);
} catch (e) {
console.warn('starfield init failed:', e);
}
});
}
componentDidShow() {}
@@ -10,8 +26,21 @@ class App extends Component {
componentDidCatchError() {}
componentWillUnmount() {
if (this.stopStarfield) this.stopStarfield();
}
render() {
return this.props.children;
return (
<View className="app">
{/* 星空背景(全局,所有页面共用)——旧版 canvas,canvas-id 定位 */}
<View className="starfield" aria-hidden="true">
<Canvas canvasId="starfield" id="starfield" className="starfield__canvas" />
<View className="starfield__shade" />
</View>
{this.props.children}
</View>
);
}
}
@@ -1,9 +1,97 @@
/* 像素字体(web 端 BubbledotICG-FinePos 同款,CC BY 4.0)——资源内联为 base64 */
@font-face {
font-family: 'BubbledotICG';
src: url('./assets/fonts/BubbledotICG-FinePos.ttf') format('truetype');
font-display: swap;
}
page {
background: #000000;
color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", sans-serif;
}
/* 内容层:置于星空之上,底部留白避开固定 tabBar(含首页上凸按钮)。
用 @supports 把「基础值」与「安全区增强」分成两条独立规则:
压缩器无法合并去重,即使 env() 在个别基础库不可用,180rpx 基础值仍生效,内容绝不被遮蔽 */
.app {
position: relative;
z-index: 1;
min-height: 100vh;
padding-bottom: 180rpx;
}
@supports (padding-bottom: env(safe-area-inset-bottom)) {
.app {
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
}
}
/* 星空背景:全屏固定于内容之下。
含两层兜底:深空渐变 + CSS 星点(即使动画画布失败,也有星夜底,绝不黑屏) */
.starfield {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
overflow: hidden;
background:
/* CSS 静态星点(小尺寸 + repeat 平铺) */
radial-gradient(2rpx 2rpx at 25% 25%, rgba(255, 255, 255, 0.9), transparent 70%),
radial-gradient(2rpx 2rpx at 65% 55%, rgba(255, 255, 255, 0.7), transparent 70%),
radial-gradient(3rpx 3rpx at 85% 30%, rgba(255, 220, 170, 0.85), transparent 70%),
radial-gradient(2rpx 2rpx at 15% 75%, rgba(190, 225, 255, 0.85), transparent 70%),
radial-gradient(2rpx 2rpx at 45% 15%, rgba(255, 255, 255, 0.6), transparent 70%),
/* 星云色晕(整屏单张) */
radial-gradient(1200rpx 900rpx at 78% 18%, rgba(56, 92, 150, 0.20), transparent 60%),
radial-gradient(900rpx 700rpx at 12% 88%, rgba(150, 90, 60, 0.14), transparent 60%),
radial-gradient(600rpx 600rpx at 50% 40%, rgba(40, 50, 80, 0.25), transparent 70%),
/* 深空底色 */
#06070a;
background-size:
260rpx 260rpx,
320rpx 320rpx,
400rpx 400rpx,
340rpx 340rpx,
440rpx 440rpx,
100% 100%,
100% 100%,
100% 100%,
auto;
background-repeat: repeat;
animation: starsTwinkle 5s ease-in-out infinite alternate;
}
@keyframes starsTwinkle {
from {
opacity: 0.85;
}
to {
opacity: 1;
}
}
.starfield__canvas {
width: 100%;
height: 100%;
display: block;
}
/* 顶部压暗 + 底部微光,保证内容可读、导航区通透 */
.starfield__shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
linear-gradient(to bottom, rgba(0, 0, 0, 0.30), transparent 32%),
radial-gradient(120% 90% at 50% 115%, rgba(255, 210, 138, 0.05), transparent 55%);
pointer-events: none;
}
/* 通用卡片 */
.card {
background: #0c0c0e;
@@ -66,3 +154,195 @@ page {
color: #8e8e8e;
font-size: 24px;
}
/* ============================================================
单题一屏 · 共享答题组件(OPC 测评 / 政策测评共用)
============================================================ */
.ot-quiz-head {
padding-top: 8px;
}
.ot-quiz-top {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.ot-quiz-title {
font-size: 30px;
font-weight: 700;
color: #fff;
}
.ot-quiz-tag {
margin-left: 14px;
font-size: 20px;
font-weight: 600;
color: #000;
background: #fff;
border-radius: 999px;
padding: 4px 16px;
vertical-align: middle;
}
.ot-progress-text {
font-size: 24px;
color: #8e8e8e;
}
.ot-progress {
height: 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
margin: 20px 0;
overflow: hidden;
}
.ot-progress-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, #ffd28a, #ff8f6b);
transition: width 0.3s ease;
}
.ot-part {
display: block;
font-size: 24px;
color: #ffd28a;
margin-bottom: 8px;
}
/* 单题卡片下方的操作提示 */
.ot-hint {
display: block;
margin-top: 16px;
font-size: 22px;
color: #8e8e8e;
text-align: center;
}
.ot-qcard {
background: #0c0c0e;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
padding: 28px 30px;
margin-bottom: 20px;
}
.ot-qcard.answered {
border-color: rgba(255, 210, 138, 0.5);
}
/* 一题一屏:卡片更突出,题目/选项更大 */
.ot-qcard.one {
margin-top: 8px;
padding: 44px 32px;
}
.ot-qcard.one .ot-qtext {
font-size: 36px;
line-height: 1.5;
margin-bottom: 36px;
}
.ot-qcard.one .ot-opt {
padding: 26px 26px;
}
.ot-qnum {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 22px;
color: #6f6f6f;
margin-bottom: 12px;
}
.ot-check {
color: #ffd28a;
font-size: 26px;
}
.ot-qtext {
display: block;
font-size: 30px;
font-weight: 600;
color: #fff;
line-height: 1.5;
margin-bottom: 20px;
}
.ot-opts {
display: flex;
flex-direction: column;
gap: 12px;
}
.ot-opt {
display: flex;
align-items: flex-start;
gap: 12px;
background: #141417;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 14px;
padding: 20px 22px;
}
.ot-opt.sel {
border-color: #ffd28a;
background: rgba(255, 210, 138, 0.08);
}
.opt-key {
font-family: 'BubbledotICG', monospace;
color: #8e8e8e;
font-size: 26px;
flex-shrink: 0;
}
.ot-opt.sel .opt-key {
color: #ffd28a;
}
.opt-text {
font-size: 27px;
color: #c4c2c3;
line-height: 1.55;
flex: 1;
}
/* 底部导航(上一题 / 进度计数) */
.ot-nav-one {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin: 28px 0 8px;
}
.ot-nav-btn {
flex-shrink: 0;
line-height: 2.4;
padding: 0 28px;
}
.ot-nav-info {
font-family: 'BubbledotICG', monospace;
font-size: 26px;
color: #8e8e8e;
letter-spacing: 2px;
}
/* 单选无右侧按钮时,占位保持计数居中 */
.ot-nav-empty {
width: 110px;
flex-shrink: 0;
}
.ot-loading {
display: block;
text-align: center;
font-size: 26px;
color: #ffd28a;
margin-top: 24px;
}
@@ -0,0 +1,3 @@
export default {
component: true
};
@@ -0,0 +1,105 @@
import { Component } from 'react';
import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro';
import './index.scss';
/* 与 app.config.js tabBar.list 保持一致;首页(index)居中并做特殊凸起按钮 */
const LIST = [
{ pagePath: '/pages/booking/index', text: '报名', icon: 'book' },
{ pagePath: '/pages/test/index', text: '测评', icon: 'compass' },
{ pagePath: '/pages/index/index', text: '首页', icon: 'star', center: true },
{ pagePath: '/pages/policy/index', text: '政策', icon: 'shield' },
{ pagePath: '/pages/plan/index', text: '流程', icon: 'route' }
];
export default class CustomTabBar extends Component {
state = {
selected: 2 // 默认首页(居中)
};
switchTab = (item, index) => {
if (index === this.state.selected) return;
this.setState({ selected: index });
Taro.switchTab({ url: item.pagePath });
};
renderIcon(icon, active) {
switch (icon) {
case 'star':
return (
<View className={`tab-icon tab-icon--star${active ? ' on' : ''}`}>
<View className="ic-star" />
</View>
);
case 'book':
return (
<View className="tab-icon tab-icon--book">
<View className="ic-bar ic-book-h" />
<View className="ic-bar ic-book-v" />
</View>
);
case 'compass':
return (
<View className="tab-icon tab-icon--compass">
<View className="ic-compass-ring" />
<View className="ic-compass-dot" />
</View>
);
case 'shield':
return (
<View className="tab-icon tab-icon--shield">
<View className="ic-doc" />
<View className="ic-line ic-line-1" />
<View className="ic-line ic-line-2" />
</View>
);
case 'route':
default:
return (
<View className="tab-icon tab-icon--route">
<View className="ic-pole" />
<View className="ic-flag" />
</View>
);
}
}
render() {
const { selected } = this.state;
return (
<View className="tab-bar">
<View className="tab-bar__inner">
{LIST.map((item, index) => {
const active = index === selected;
if (item.center) {
/* 首页:居中凸起 + 异形辉光底座 */
return (
<View
key={item.pagePath}
className={`tab-item tab-item--home${active ? ' tab-item--active' : ''}`}
onClick={() => this.switchTab(item, index)}
>
<View className="home-blob" />
<View className="home-inner">
<View className="home-icon">{this.renderIcon(item.icon, active)}</View>
<Text className="home-text">{item.text}</Text>
</View>
</View>
);
}
return (
<View
key={item.pagePath}
className={`tab-item${active ? ' tab-item--active' : ''}`}
onClick={() => this.switchTab(item, index)}
>
<View className="tab-item__icon">{this.renderIcon(item.icon)}</View>
<Text className="tab-item__text">{item.text}</Text>
</View>
);
})}
</View>
</View>
);
}
}
@@ -0,0 +1,269 @@
/* 自定义底部导航 —— 星夜主题玻璃拟态;首页居中凸起异形按钮 */
.tab-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background: linear-gradient(to top, rgba(8, 9, 12, 0.97), rgba(10, 11, 16, 0.88));
box-shadow: 0 -6rpx 30rpx rgba(0, 0, 0, 0.35);
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
/* 顶部细光缝(玻璃高光) */
.tab-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1rpx;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.14), transparent);
}
.tab-bar__inner {
display: flex;
height: 108rpx;
}
/* ---------- 常规项 ---------- */
.tab-item {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6rpx;
color: #8a8a93;
transition: color 0.2s ease;
}
.tab-item--active {
color: #ffd28a;
}
.tab-item--active::before {
content: '';
position: absolute;
top: -1rpx;
left: 50%;
transform: translateX(-50%);
width: 64rpx;
height: 2rpx;
border-radius: 2rpx;
background: linear-gradient(90deg, transparent, #ffd28a, transparent);
opacity: 0.9;
}
.tab-item__icon {
height: 46rpx;
display: flex;
align-items: center;
justify-content: center;
}
.tab-item--active .tab-item__icon {
filter: drop-shadow(0 2rpx 10rpx rgba(255, 210, 138, 0.55));
}
.tab-item--active .tab-icon {
transform: translateY(-2rpx);
}
.tab-item__text {
font-size: 20rpx;
line-height: 1;
letter-spacing: 1rpx;
}
/* ---------- 居中首页:凸起 + 异形辉光底座 ---------- */
.tab-item--home {
margin-top: -30rpx;
z-index: 2;
justify-content: flex-start;
color: rgba(255, 210, 138, 0.55);
}
.home-blob {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 112rpx;
height: 108rpx;
/* 上圆下收的异形(水滴/倒角蛋形) */
border-radius: 52% 52% 44% 44% / 58% 58% 42% 42%;
background: linear-gradient(180deg, #2c2640, #15121e 72%);
border: 2rpx solid rgba(255, 210, 138, 0.4);
box-shadow:
0 8rpx 26rpx rgba(0, 0, 0, 0.5),
inset 0 0 22rpx rgba(255, 210, 138, 0.1);
}
.home-inner {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
padding-top: 14rpx;
}
.home-icon {
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.home-text {
font-size: 20rpx;
line-height: 1;
letter-spacing: 2rpx;
}
/* 激活:底座点亮 + 星星辉光 */
.tab-item--home.tab-item--active {
color: #ffd28a;
}
.tab-item--home.tab-item--active .home-blob {
border-color: rgba(255, 210, 138, 0.9);
box-shadow:
0 0 36rpx rgba(255, 210, 138, 0.45),
0 8rpx 26rpx rgba(0, 0, 0, 0.5),
inset 0 0 28rpx rgba(255, 210, 138, 0.22);
}
.tab-item--home.tab-item--active .ic-star {
filter: drop-shadow(0 0 10rpx rgba(255, 210, 138, 0.9));
transform: scale(1.12);
}
/* ---------- 图标(CSS 线性绘制,随 currentColor 变色) ---------- */
.tab-icon {
position: relative;
width: 46rpx;
height: 46rpx;
transition: transform 0.2s ease;
}
/* 首页:五角星(品牌星夜意象) */
.ic-star {
position: absolute;
top: 4rpx;
left: 6rpx;
width: 34rpx;
height: 34rpx;
background: currentColor;
clip-path: polygon(
50% 0%,
61% 35%,
98% 35%,
68% 57%,
79% 91%,
50% 70%,
21% 91%,
32% 57%,
2% 35%,
39% 35%
);
transition: transform 0.2s ease;
}
/* 报名:加号(新增/预约) */
.ic-bar {
position: absolute;
border-radius: 3rpx;
background: currentColor;
}
.ic-book-h {
top: 20rpx;
left: 5rpx;
width: 36rpx;
height: 5rpx;
}
.ic-book-v {
top: 5rpx;
left: 20rpx;
width: 5rpx;
height: 36rpx;
}
/* 测评:靶心 */
.ic-compass-ring {
position: absolute;
top: 4rpx;
left: 4rpx;
width: 38rpx;
height: 38rpx;
border: 3rpx solid currentColor;
border-radius: 50%;
box-sizing: border-box;
}
.ic-compass-dot {
position: absolute;
top: 19rpx;
left: 19rpx;
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: currentColor;
}
/* 政策:文档 */
.ic-doc {
position: absolute;
top: 3rpx;
left: 10rpx;
width: 26rpx;
height: 38rpx;
border: 3rpx solid currentColor;
border-radius: 5rpx;
box-sizing: border-box;
}
.ic-line {
position: absolute;
left: 14rpx;
height: 3rpx;
border-radius: 2rpx;
background: currentColor;
}
.ic-line-1 {
top: 14rpx;
width: 18rpx;
}
.ic-line-2 {
top: 24rpx;
width: 13rpx;
}
/* 流程:旗(启动) */
.ic-pole {
position: absolute;
top: 2rpx;
left: 22rpx;
width: 3rpx;
height: 40rpx;
border-radius: 2rpx;
background: currentColor;
}
.ic-flag {
position: absolute;
top: 3rpx;
left: 24rpx;
width: 18rpx;
height: 12rpx;
border-radius: 0 4rpx 4rpx 0;
background: currentColor;
}
@@ -2,9 +2,11 @@ import { View, Text, Input, Picker, Button } from '@tarojs/components';
import { useState } from 'react';
import { sendCode } from '@/utils/auth';
import { submitBooking, BK_STATUS_OPTIONS, BK_WANT_OPTIONS, BK_TOPIC_OPTIONS, BK_SOURCE_OPTIONS } from '@/utils/booking';
import { useTabIndex } from '@/utils/tab';
import './index.scss';
export default function Booking() {
useTabIndex(0);
const [phone, setPhone] = useState('');
const [code, setCode] = useState('');
const [name, setName] = useState('');
@@ -1,34 +1,236 @@
import { View, Text } from '@tarojs/components';
import { View, Text, Button } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
import { useTabIndex } from '@/utils/tab';
import { request } from '@/utils/api';
import {
MK_HERO, MK_TRUST, MK_PRINCIPLE, MK_PRODUCTS,
MK_CORE, MK_OPENING, MK_ASSESS, MK_SIGNUP
} from '@/utils/home';
import './index.scss';
const ENTRIES = [
{ url: '/pages/booking/index', title: '预约公益课 / 沙龙', desc: '手机号 + 验证码报名,未注册自动注册' },
{ url: '/pages/test/index', title: 'OPC 创业测评', desc: '30/60 题测出你的赛道、角色与 AI 协作方式' },
{ url: '/pages/policy/index', title: '政策测评', desc: '测出适配政策、补贴与贷款参考' },
{ url: '/pages/plan/index', title: '创业启动流程', desc: '入驻园区 → 注册 → 刻章 → 税务 → 开户 → 三方协议' }
];
/* ---------- 当期排期:倒计时 + 当期公益课 / 沙龙 ---------- */
const fmtEvt = (iso) => {
const d = new Date(iso);
const p = (n) => String(n).padStart(2, '0');
const week = ['日', '一', '二', '三', '四', '五', '六'][d.getDay()];
return `${d.getMonth() + 1}月${d.getDate()}日 周${week} ${p(d.getHours())}:${p(d.getMinutes())}`;
};
export default function Index() {
const go = (url) => () => Taro.navigateTo({ url });
function CountdownBox({ target }) {
const [now, setNow] = useState(Date.now());
useEffect(() => {
const t = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(t);
}, []);
const diff = Math.max(0, new Date(target).getTime() - now);
const d = Math.floor(diff / 86400000);
const h = Math.floor((diff % 86400000) / 3600000);
const m = Math.floor((diff % 3600000) / 60000);
const s = Math.floor((diff % 60000) / 1000);
const pad = (n) => String(n).padStart(2, '0');
return (
<View className="index">
<View className="hero">
<Text className="badge">一个人,也可以是一家公司</Text>
<Text className="headline">{'ONE PERSON\nDESIGNED TO EVOLVE'}</Text>
<Text className="sub">免费公益课 + 沙龙,讲透「一个人 + AI 怎么开一家公司」。先来听一场,不卖课。</Text>
<View className="mk-count-nums">
<View className="mk-count-cell"><Text className="num">{d}</Text><Text className="unit">天</Text></View>
<View className="mk-count-cell"><Text className="num">{pad(h)}</Text><Text className="unit">时</Text></View>
<View className="mk-count-cell"><Text className="num">{pad(m)}</Text><Text className="unit">分</Text></View>
<View className="mk-count-cell"><Text className="num">{pad(s)}</Text><Text className="unit">秒</Text></View>
</View>
);
}
function EventCard({ e, tag, hot }) {
const go = () => Taro.switchTab({ url: '/pages/booking/index' });
return (
<View className={`mk-product${hot ? ' hot' : ''}`} onClick={go}>
<Text className={`mk-tag${hot ? '' : ' alt'}`}>{tag}</Text>
<Text className="mk-pname">{e.title}</Text>
<Text className="mk-pprice">{fmtEvt(e.startAt)} <Text className="mk-pprice-em">{e.location}</Text></Text>
<Text className="mk-pdesc">{e.desc || e.subtitle}</Text>
<View className="mk-pcta">
<Button className={`btn ${hot ? 'btn-cta' : 'btn-white'}`}>预约报名</Button>
</View>
</View>
);
}
/* ---------- 首页宣传落地页 ---------- */
export default function Index() {
useTabIndex(2);
const [evt, setEvt] = useState(null);
useEffect(() => {
request('GET', '/api/events?current=1')
.then((d) => setEvt(d || null))
.catch(() => setEvt(null));
}, []);
const goTab = (url) => () => Taro.switchTab({ url });
const next = evt && evt.next;
return (
<View className="home">
{/* 1 · Hero */}
<View className="mk-hero">
<Text className="mk-badge">{MK_HERO.badge}</Text>
<View className="mk-headline">
{MK_HERO.headline.map((l) => <Text className="mk-headline-line" key={l}>{l}</Text>)}
</View>
<Text className="mk-subhead">{MK_HERO.subhead}</Text>
<View className="mk-cta">
<Button className="btn btn-cta" onClick={goTab(MK_HERO.ctaPrimary.tab)}>{MK_HERO.ctaPrimary.label} →</Button>
<Button className="btn btn-ghost" onClick={goTab(MK_HERO.ctaSecondary.tab)}>{MK_HERO.ctaSecondary.label}</Button>
</View>
</View>
<View className="cards">
{ENTRIES.map((e) => (
<View key={e.url} className="card entry" onClick={go(e.url)}>
<View className="card-title">{e.title}</View>
<View className="card-desc">{e.desc}</View>
{/* 2 · 信任条 */}
<View className="mk-trust">
{MK_TRUST.map((s, i) => (
<View className="mk-stat" key={i}>
<Text className="mk-stat-glyph">{s.glyph}</Text>
<Text className="mk-stat-value">{s.value}{s.suffix}</Text>
<Text className="mk-stat-label">{s.label}</Text>
</View>
))}
</View>
<View className="foot">政策数据以官方最新文件为准</View>
{/* 3 · 痛点理念对比 */}
<View className="mk-section">
<View className="mk-section-head">
<Text className="mk-no">01</Text>
<Text className="mk-title">{MK_PRINCIPLE.title}</Text>
<Text className="mk-en">{MK_PRINCIPLE.en}</Text>
</View>
<View className="mk-compare">
<View className="mk-cmp old">
<Text className="mk-cmp-title">{MK_PRINCIPLE.oldTitle}</Text>
{MK_PRINCIPLE.oldPoints.map((p, i) => <Text className="mk-cmp-li no" key={i}>{p}</Text>)}
</View>
<View className="mk-cmp new">
<Text className="mk-cmp-title">{MK_PRINCIPLE.newTitle}</Text>
{MK_PRINCIPLE.newPoints.map((p, i) => <Text className="mk-cmp-li yes" key={i}>{p}</Text>)}
</View>
</View>
<Text className="mk-quote">—— {MK_PRINCIPLE.quote}</Text>
</View>
{/* 4 · 三档产品 */}
<View className="mk-section">
<View className="mk-section-head">
<Text className="mk-no">02</Text>
<Text className="mk-title">公益课 & 沙龙 · 先来认识我们</Text>
<Text className="mk-en">Events</Text>
</View>
<View className="mk-products">
{MK_PRODUCTS.map((p) => (
<View className={`mk-product${p.highlight ? ' hot' : ''}`} key={p.name} onClick={goTab(p.tab)}>
<Text className={`mk-tag${p.highlight ? '' : ' alt'}`}>{p.tag}</Text>
<Text className="mk-pname">{p.name}</Text>
<Text className="mk-pprice">{p.price} <Text className="mk-pprice-em">{p.period}</Text></Text>
<Text className="mk-pdesc">{p.desc}</Text>
<View className="mk-pdel">{p.deliverables.map((d, i) => <Text className="mk-pdel-li" key={i}>{d}</Text>)}</View>
<View className="mk-pcta"><Button className={`btn ${p.highlight ? 'btn-cta' : 'btn-white'}`}>{p.cta}</Button></View>
</View>
))}
</View>
</View>
{/* 5 · 三件套 */}
<View className="mk-section">
<View className="mk-section-head">
<Text className="mk-no">03</Text>
<Text className="mk-title">{MK_CORE.title}</Text>
<Text className="mk-en">{MK_CORE.en}</Text>
</View>
<View className="mk-core">
{MK_CORE.items.map((it) => (
<View className="mk-core-item" key={it.step}>
<Text className="mk-core-step">{it.step}</Text>
<Text className="mk-core-title">{it.title}</Text>
<Text className="mk-core-desc">{it.desc}</Text>
<Text className="mk-core-out">{it.out}</Text>
</View>
))}
</View>
<Text className="mk-quote">—— {MK_CORE.quote}</Text>
</View>
{/* 6 · 当期排期(倒计时;无后端时降级静态排期) */}
<View className="mk-section">
{next ? (
<View>
<View className="mk-section-head">
<Text className="mk-no">04</Text>
<Text className="mk-title">近期场次 · 免费报名</Text>
<Text className="mk-en">Upcoming</Text>
</View>
<View className="mk-count">
<Text className="mk-count-lbl">距下一场开始</Text>
<CountdownBox target={next.startAt} />
<Text className="mk-count-name">{next.title}</Text>
</View>
<View className="mk-products">
{evt.free && <EventCard e={evt.free} tag="公益课" hot />}
{evt.salon && <EventCard e={evt.salon} tag="沙龙" />}
</View>
<Text className="mk-note">{MK_OPENING.note}</Text>
</View>
) : (
<View>
<View className="mk-section-head">
<Text className="mk-no">04</Text>
<Text className="mk-title">公益课 & 沙龙 · 每周滚动</Text>
<Text className="mk-en">Rolling events</Text>
</View>
<View className="mk-opening">
{MK_OPENING.rows.map((r) => (
<View className="mk-row" key={r.item}>
<Text className={`mk-tag${r.tag === '公益' ? '' : ' alt'}`}>{r.tag}</Text>
<View className="mk-row-body">
<Text className="mk-row-item">{r.item}</Text>
<Text className="mk-row-desc">{r.desc}</Text>
</View>
<Text className="mk-row-arrow">→</Text>
</View>
))}
</View>
<View className="mk-open-cta">
<Button className="btn btn-ghost" onClick={goTab('/pages/booking/index')}>立即预约 →</Button>
</View>
</View>
)}
</View>
{/* 7 · OPC 测评入口 */}
<View className="mk-section">
<View className="mk-assess">
<View className="assess-txt">
<Text className="assess-badge">{MK_ASSESS.badge}</Text>
<Text className="assess-title">{MK_ASSESS.title}</Text>
<Text className="assess-desc">{MK_ASSESS.desc}</Text>
</View>
<View className="assess-cta">
<Button className="btn btn-cta" onClick={goTab('/pages/test/index')}>开始 OPC 测评 →</Button>
</View>
</View>
</View>
{/* 8 · 报名 CTA */}
<View className="mk-section">
<View className="mk-signup">
<Text className="mk-signup-title">{MK_SIGNUP.title}</Text>
<Text className="mk-signup-desc">{MK_SIGNUP.desc}</Text>
<View className="mk-cta">
<Button className="btn btn-cta" onClick={goTab('/pages/booking/index')}>{MK_HERO.ctaPrimary.label}</Button>
<Button className="btn btn-ghost" onClick={goTab('/pages/test/index')}>{MK_HERO.ctaSecondary.label}</Button>
</View>
</View>
</View>
{/* 页脚 */}
<View className="mk-foot">
<Text className="mk-foot-line">政策数据以官方最新文件为准</Text>
<Text className="mk-foot-line">像素字体 BubbledotICG-FinePos · CC BY 4.0</Text>
</View>
</View>
);
}
@@ -1,11 +1,584 @@
.index { padding: 40px 32px; }
/* 首页营销落地页 · 小程序端(对齐 web home.css,暗色 + 白色按钮 + 像素字体)
底部留白交给全局 .app(含 tabBar 上凸按钮),这里只做左右内边距 */
.home {
padding: 0 32rpx;
}
.hero { display: flex; flex-direction: column; margin-bottom: 48px; }
.badge { font-size: 24px; color: #c8c8c8; letter-spacing: 1px; }
.headline { font-size: 56px; font-weight: 700; line-height: 1.1; margin: 24px 0; white-space: pre-line; }
.sub { font-size: 28px; color: #c4c2c3; line-height: 1.7; }
.mk-section {
margin-top: 64rpx;
}
.cards { display: flex; flex-direction: column; }
.entry { border-left: 6px solid #ffffff; }
/* ---------- Hero ---------- */
.mk-hero {
min-height: 70vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 96rpx 0 48rpx;
}
.foot { margin-top: 56px; font-size: 24px; color: #6f6f6f; text-align: center; }
.mk-badge {
display: inline-flex;
border: 1rpx solid rgba(255, 255, 255, 0.22);
color: #c8c8c8;
background: rgba(40, 40, 42, 0.6);
border-radius: 999px;
padding: 12rpx 32rpx;
font-size: 24rpx;
letter-spacing: 1rpx;
margin-bottom: 40rpx;
animation: mkReveal 0.85s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: 0.1s;
}
.mk-headline {
color: #fff;
font-family: 'BubbledotICG', monospace;
font-size: 64rpx;
line-height: 1.12;
letter-spacing: 2rpx;
animation: mkReveal 0.85s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: 0.18s;
}
.mk-headline-line {
display: block;
}
.mk-subhead {
max-width: 640rpx;
margin-top: 36rpx;
font-size: 28rpx;
color: #c4c2c3;
line-height: 1.75;
animation: mkReveal 0.85s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: 0.3s;
}
.mk-cta {
display: flex;
gap: 20rpx;
flex-wrap: wrap;
justify-content: center;
margin-top: 48rpx;
animation: mkReveal 0.85s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: 0.4s;
}
@keyframes mkReveal {
from {
opacity: 0;
transform: translateY(22rpx) scale(0.98);
}
to {
opacity: 1;
transform: none;
}
}
/* ---------- 信任条 ---------- */
.mk-trust {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
margin-top: 8rpx;
}
.mk-stat {
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
padding: 28rpx 20rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.mk-stat-glyph {
font-family: 'BubbledotICG', monospace;
font-size: 28rpx;
color: #6f6f6f;
}
.mk-stat-value {
font-family: 'BubbledotICG', monospace;
font-size: 44rpx;
color: #fff;
line-height: 1;
}
.mk-stat-label {
font-size: 22rpx;
color: #8e8e8e;
}
/* ---------- 区块头 ---------- */
.mk-section-head {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8rpx;
margin-bottom: 28rpx;
}
.mk-no {
font-family: 'BubbledotICG', monospace;
color: #6f6f6f;
font-size: 26rpx;
letter-spacing: 2rpx;
}
.mk-title {
font-size: 38rpx;
font-weight: 700;
color: #fff;
line-height: 1.3;
letter-spacing: -0.5rpx;
}
.mk-en {
font-family: 'BubbledotICG', monospace;
color: #5b5b5b;
font-size: 24rpx;
letter-spacing: 2rpx;
}
/* ---------- 痛点理念对比 ---------- */
.mk-compare {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.mk-cmp {
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
padding: 32rpx;
}
.mk-cmp.new {
border-color: rgba(255, 255, 255, 0.45);
background: #111113;
}
.mk-cmp-title {
display: block;
font-size: 30rpx;
font-weight: 600;
margin-bottom: 20rpx;
color: #8e8e8e;
}
.mk-cmp.new .mk-cmp-title {
color: #fff;
}
.mk-cmp-li {
display: block;
position: relative;
font-size: 26rpx;
color: #a9a9a9;
line-height: 1.6;
margin-top: 16rpx;
padding-left: 36rpx;
}
.mk-cmp.new .mk-cmp-li {
color: #cfcfcf;
}
.mk-cmp-li::before {
content: '✕';
position: absolute;
left: 0;
color: #8e8e8e;
font-size: 24rpx;
}
.mk-cmp-li.yes::before {
content: '✓';
color: #fff;
}
.mk-quote {
display: block;
margin-top: 32rpx;
text-align: center;
font-size: 30rpx;
color: #fff;
font-weight: 500;
letter-spacing: 1rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.14);
padding-top: 32rpx;
}
/* ---------- 三档产品 ---------- */
.mk-products {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.mk-product {
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
padding: 36rpx;
display: flex;
flex-direction: column;
}
.mk-product.hot {
border-color: rgba(255, 255, 255, 0.55);
box-shadow: 0 0 0 1rpx #fff;
background: #111113;
}
.mk-tag {
display: inline-block;
align-self: flex-start;
font-size: 22rpx;
font-weight: 700;
color: #000;
background: #fff;
border-radius: 999px;
padding: 6rpx 22rpx;
margin-bottom: 24rpx;
}
.mk-tag.alt {
background: #323234;
color: #c8c8c8;
}
.mk-pname {
font-size: 36rpx;
font-weight: 700;
color: #fff;
}
.mk-pprice {
font-size: 30rpx;
color: #fff;
font-weight: 600;
margin-top: 8rpx;
}
.mk-pprice-em {
font-style: normal;
font-size: 24rpx;
color: #8e8e8e;
font-weight: 500;
}
.mk-pdesc {
font-size: 26rpx;
color: #a9a9a9;
line-height: 1.65;
margin: 20rpx 0;
}
.mk-pdel {
display: flex;
flex-direction: column;
gap: 10rpx;
margin-bottom: 28rpx;
}
.mk-pdel-li {
position: relative;
font-size: 26rpx;
color: #c4c2c3;
line-height: 1.7;
padding-left: 32rpx;
}
.mk-pdel-li::before {
content: '';
position: absolute;
left: 4rpx;
top: 18rpx;
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #fff;
}
.mk-pcta {
margin-top: auto;
}
/* ---------- 三件套 ---------- */
.mk-core {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.mk-core-item {
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 14px;
padding: 32rpx;
}
.mk-core-step {
display: block;
font-family: 'BubbledotICG', monospace;
color: #6f6f6f;
font-size: 26rpx;
letter-spacing: 2rpx;
}
.mk-core-title {
display: block;
font-size: 32rpx;
font-weight: 600;
margin: 14rpx 0;
}
.mk-core-desc {
display: block;
font-size: 26rpx;
color: #8e8e8e;
line-height: 1.6;
}
.mk-core-out {
display: block;
font-size: 26rpx;
color: #c4c2c3;
margin-top: 18rpx;
padding-top: 18rpx;
border-top: 1rpx dashed rgba(255, 255, 255, 0.14);
}
/* ---------- 倒计时 ---------- */
.mk-count {
background: linear-gradient(135deg, #161618, #28282a);
border: 1rpx solid rgba(255, 255, 255, 0.22);
border-radius: 20px;
padding: 44rpx 36rpx;
text-align: center;
margin-bottom: 28rpx;
position: relative;
overflow: hidden;
}
.mk-count-lbl {
display: block;
position: relative;
font-size: 24rpx;
letter-spacing: 4rpx;
color: #8e8e8e;
}
.mk-count-nums {
position: relative;
display: flex;
justify-content: center;
gap: 24rpx;
margin: 28rpx 0 18rpx;
}
.mk-count-cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
}
.mk-count-cell .num {
font-family: 'BubbledotICG', monospace;
font-size: 56rpx;
color: #fff;
line-height: 1;
}
.mk-count-cell .unit {
font-size: 22rpx;
color: #c4c2c3;
}
.mk-count-name {
display: block;
position: relative;
font-size: 30rpx;
font-weight: 600;
color: #fff;
}
.mk-note {
display: block;
text-align: center;
font-size: 24rpx;
color: #6f6f6f;
margin-top: 24rpx;
}
/* ---------- 降级静态排期 ---------- */
.mk-opening {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.mk-row {
display: flex;
align-items: center;
gap: 24rpx;
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 14px;
padding: 28rpx 32rpx;
}
.mk-row .mk-tag {
margin: 0;
flex-shrink: 0;
}
.mk-row-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.mk-row-item {
font-size: 30rpx;
font-weight: 600;
color: #fff;
}
.mk-row-desc {
font-size: 24rpx;
color: #8e8e8e;
}
.mk-row-arrow {
color: #6f6f6f;
flex-shrink: 0;
}
.mk-open-cta {
margin-top: 28rpx;
text-align: center;
}
/* ---------- OPC 测评入口 ---------- */
.mk-assess {
background: linear-gradient(135deg, #161618, #28282a);
border: 1rpx solid rgba(255, 255, 255, 0.22);
border-radius: 20px;
padding: 48rpx 40rpx;
display: flex;
flex-direction: column;
gap: 32rpx;
text-align: center;
}
.assess-badge {
display: block;
font-size: 22rpx;
font-weight: 700;
letter-spacing: 4rpx;
color: #8e8e8e;
}
.assess-title {
display: block;
font-size: 40rpx;
font-weight: 700;
color: #fff;
margin: 12rpx 0;
letter-spacing: -0.5rpx;
}
.assess-desc {
display: block;
font-size: 26rpx;
color: #c4c2c3;
line-height: 1.7;
}
.assess-cta {
display: flex;
justify-content: center;
}
/* ---------- 报名 CTA ---------- */
.mk-signup {
text-align: center;
padding: 20rpx 0 6rpx;
}
.mk-signup-title {
display: block;
font-size: 40rpx;
font-weight: 700;
color: #fff;
letter-spacing: -0.5rpx;
line-height: 1.35;
}
.mk-signup-desc {
display: block;
max-width: 640rpx;
margin: 20rpx auto 0;
font-size: 27rpx;
color: #c4c2c3;
line-height: 1.7;
}
/* ---------- 按钮 ---------- */
.btn {
border: none;
border-radius: 999px;
font-weight: 600;
font-size: 28rpx;
padding: 0 40rpx;
line-height: 2.7;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn::after {
border: none;
}
.btn-cta {
background: #ffffff;
color: #000000;
}
.btn-ghost {
background: transparent;
color: #e8e8e8;
border: 1rpx solid rgba(255, 255, 255, 0.3);
}
.btn-white {
background: #ffffff;
color: #000000;
}
/* ---------- 页脚 ---------- */
.mk-foot {
margin-top: 72rpx;
padding-top: 32rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.mk-foot-line {
font-size: 22rpx;
color: #6f6f6f;
}
@@ -3,9 +3,11 @@ import { useState } from 'react';
import { SP_REGIONS, SP_STATUS, buildPlan } from '@/utils/startPlan';
import { getUser } from '@/utils/auth';
import { request } from '@/utils/api';
import { useTabIndex } from '@/utils/tab';
import './index.scss';
export default function Plan() {
useTabIndex(4);
const [region, setRegion] = useState('昆明');
const [status, setStatus] = useState('none');
const [needPark, setNeedPark] = useState(true);
@@ -4,33 +4,51 @@ import { useState } from 'react';
import { PT_QUESTIONS, computePolicy } from '@/utils/policyTest';
import { getUser } from '@/utils/auth';
import { request } from '@/utils/api';
import { useTabIndex } from '@/utils/tab';
import './index.scss';
const QUESTIONS = PT_QUESTIONS;
export default function Policy() {
useTabIndex(3);
const [ans, setAns] = useState({});
const [current, setCurrent] = useState(0); // 当前题下标(一题一屏)
const [result, setResult] = useState(null);
const toggle = (qid, value, multi) => {
const toggle = (qid, value) => {
setAns((a) => {
if (multi) {
const arr = a[qid] || [];
return { ...a, [qid]: arr.includes(value) ? arr.filter((x) => x !== value) : [...arr, value] };
}
return { ...a, [qid]: value };
});
};
const run = () => {
if (!ans.status || !ans.region || !ans.entity || !ans.capital || !(ans.industry && ans.industry.length)) {
/* 单选:选择后自动进入下一题(最后一题直接生成结果);多选走 toggle + 下一题按钮 */
const choose = (qid, value, multi) => {
if (multi) {
toggle(qid, value);
return;
}
const next = { ...ans, [qid]: value };
setAns(next);
if (current < QUESTIONS.length - 1) {
setCurrent(current + 1);
} else {
generate(next);
}
};
const generate = (finalAns) => {
const a = finalAns || ans;
if (!a.status || !a.region || !a.entity || !a.capital || !(a.industry && a.industry.length)) {
Taro.showToast({ title: '请完成所有题目(创业方向至少选一项)', icon: 'none' });
return;
}
const r = computePolicy(ans);
const r = computePolicy(a);
setResult(r);
const u = getUser();
request('POST', '/api/policy-logs', {
username: u ? u.username : '',
answers: ans,
answers: a,
policiesCount: r.policies.length,
subsidiesCount: r.subsidies.length,
loansCount: r.loans.length,
@@ -38,7 +56,11 @@ export default function Policy() {
}).catch(() => {});
};
const reset = () => { setAns({}); setResult(null); };
const reset = () => {
setAns({});
setCurrent(0);
setResult(null);
};
if (result) {
return (
@@ -78,23 +100,63 @@ export default function Policy() {
);
}
/* 一题一屏 */
const q = QUESTIONS[current];
const answered = Object.keys(ans).length;
const pct = QUESTIONS.length ? Math.round((answered / QUESTIONS.length) * 100) : 0;
const multiCount = q.multi ? (ans[q.id] || []).length : (ans[q.id] ? 1 : 0);
const isLast = current === QUESTIONS.length - 1;
const nextBtn = () => {
if (multiCount === 0) {
Taro.showToast({ title: q.multi ? '请至少选择一项' : '请选择一个选项', icon: 'none' });
return;
}
if (isLast) generate();
else setCurrent(current + 1);
};
return (
<View className="policy">
<Text className="muted">选择你的身份、行业、地区与资金情况,匹配适配的政策、补贴与贷款参考。</Text>
{PT_QUESTIONS.map((q) => (
<View className="card" key={q.id}>
<View className="card-title">{q.label}{q.multi ? '(可多选)' : ''}</View>
<View className="opts">
<View className="ot-quiz-head">
<View className="ot-quiz-top">
<Text className="ot-quiz-title">政策适配测评<Text className="ot-quiz-tag">5 题</Text></Text>
<Text className="ot-progress-text">{current + 1} / {QUESTIONS.length} 题</Text>
</View>
<View className="ot-progress"><View className="ot-progress-fill" style={{ width: pct + '%' }} /></View>
</View>
<View className="ot-qcard one">
<View className="ot-qnum">
<Text>第 {current + 1} 题</Text>
{multiCount > 0 && <Text className="ot-check">✓ {q.multi ? `已选 ${multiCount}` : '已答'}</Text>}
</View>
<Text className="ot-qtext">{q.label}{q.multi ? '(可多选)' : ''}</Text>
<View className="ot-opts">
{q.options.map((o) => {
const sel = q.multi ? (ans[q.id] || []).includes(o.value) : ans[q.id] === o.value;
return (
<View key={o.value} className={`opt${sel ? ' sel' : ''}`} onClick={() => toggle(q.id, o.value, q.multi)}>{o.label}</View>
<View key={o.value} className={`ot-opt${sel ? ' sel' : ''}`} onClick={() => choose(q.id, o.value, q.multi)}>
<Text className="opt-key">·</Text>
<Text className="opt-text">{o.label}</Text>
</View>
);
})}
</View>
<Text className="ot-hint">
{q.multi ? '可多选,点「下一题」继续' : multiCount > 0 ? '选择其他选项将直接进入下一题' : '选择一个选项后自动进入下一题'}
</Text>
</View>
<View className="ot-nav-one">
<Button className="btn-ghost ot-nav-btn" disabled={current === 0} onClick={() => setCurrent(current - 1)}>◀ 上一题</Button>
<Text className="ot-nav-info">{current + 1} / {QUESTIONS.length}</Text>
{q.multi ? (
<Button className="btn-main ot-nav-btn" onClick={nextBtn}>{isLast ? '生成测评结果' : '下一题 →'}</Button>
) : (
<View className="ot-nav-empty" />
)}
</View>
))}
<Button className="btn-main" onClick={run}>生成测评结果</Button>
</View>
);
}
@@ -1,20 +1,304 @@
import { View, Text, Button } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState } from 'react';
import { useTabIndex } from '@/utils/tab';
import { request } from '@/utils/api';
import { getUser } from '@/utils/auth';
import './index.scss';
const adaptColor = (s) => (s >= 78 ? '#a8f0c8' : s >= 55 ? '#f5d9a0' : '#f0b8b8');
export default function Test() {
useTabIndex(1);
const [stage, setStage] = useState('welcome'); // welcome | quiz | result
const [version, setVersion] = useState('full');
const [questions, setQuestions] = useState([]);
const [sections, setSections] = useState({});
const [axisNames, setAxisNames] = useState({});
const [answers, setAnswers] = useState({});
const [current, setCurrent] = useState(0); // 当前题下标(一题一屏)
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [err, setErr] = useState('');
const start = async () => {
setErr('');
setLoading(true);
try {
const d = await request('GET', `/api/tests/opc/questions?version=${version}`);
setQuestions(d.questions || []);
setSections(d.sections || {});
setAxisNames(d.axisNames || {});
setAnswers({});
setCurrent(0);
setStage('quiz');
} catch (e) {
setErr((e && e.message) || '题目加载失败,请检查网络后重试');
} finally {
setLoading(false);
}
};
/* 选择答案:记录 + 自动进入下一题;最后一题则自动提交 */
const select = (id, choice) => {
const next = { ...answers, [id]: choice };
setAnswers(next);
if (current < questions.length - 1) {
setCurrent(current + 1);
} else {
submit(next);
}
};
const submit = async (finalAnswers) => {
setErr('');
setLoading(true);
try {
const d = await request('POST', '/api/tests/opc/calculate', { answers: finalAnswers || answers, version });
const r = d.result;
setResult(r);
setStage('result');
// 上报测评记录(登录用户关联 username;失败不影响展示)
const u = getUser();
request('POST', '/api/tests', {
typeCode: r.typeCode,
persona: (r.persona && r.persona.name) || '',
adaptIndex: r.adaptIndex,
adaptLevel: (r.adaptLevel && r.adaptLevel.level) || '',
tracks: (r.tracks || []).map((t) => t.code),
version,
username: u ? u.username : ''
}).catch(() => {});
} catch (e) {
setErr((e && e.message) || '计算失败,请稍后重试');
} finally {
setLoading(false);
}
};
const reset = () => {
setResult(null);
setAnswers({});
setCurrent(0);
setErr('');
setStage('welcome');
};
/* ================= 欢迎 ================= */
if (stage === 'welcome') {
return (
<View className="test">
<Text className="section-head">OPC 创业测评</Text>
<Text className="muted">一个人,也可以是一家公司。30/60 题测出你的赛道、角色与 AI 协作方式。</Text>
<View className="card">
<View className="card-title">测评功能建设中</View>
<View className="card-desc">完整版测评(60 题 + 完整画像报告)正在迁移中,敬请期待。可以先做政策测评,或预约公益课到现场体验。</View>
<View className="ot">
<View className="ot-head">
<Text className="ot-no">00</Text>
<Text className="ot-title">开始测评</Text>
<Text className="ot-en">Start</Text>
</View>
<Button className="btn-main" onClick={() => Taro.navigateTo({ url: '/pages/policy/index' })}>先做政策测评</Button>
<Button className="btn-ghost" style={{ marginTop: 24 }} onClick={() => Taro.navigateTo({ url: '/pages/booking/index' })}>预约公益课 / 沙龙</Button>
<Text className="ot-intro">
一个人,也可以是一家公司。测评会回答四件事:我适不适合做 OPC · 该选什么赛道 · 适合什么角色 · 怎么和 AI 协作。完成约 5–10 分钟,结果即时生成。
</Text>
<View className="ot-version-grid">
<View className={`ot-vcard${version === 'quick' ? ' sel' : ''}`} onClick={() => setVersion('quick')}>
<Text className="ot-vname">快速版</Text>
<Text className="ot-vmeta">30 题 · 约 5 分钟 · 方向性参考</Text>
<Text className="ot-vdesc">适合公益课现场 / 快速自测。结论更轻,建议随后做完整版。</Text>
</View>
<View className={`ot-vcard${version === 'full' ? ' sel' : ''}`} onClick={() => setVersion('full')}>
<Text className="ot-vname">完整版</Text>
<Text className="ot-vmeta">60 题 · 约 10 分钟 · 诊断级信度</Text>
<Text className="ot-vdesc">每维度 2–3 题冗余,结论更稳。适合深度自测与报名前诊断。</Text>
</View>
</View>
<View className="ot-features">
<Text className="ot-feature">· 适配指数:我适不适合一个人做 OPC</Text>
<Text className="ot-feature">· 推荐赛道:文旅 / 内容IP / 咨询 / 电商 / 跨境 / 本地</Text>
<Text className="ot-feature">· 主辅角色 + 工具模式:我在 OPC 里干什么</Text>
<Text className="ot-feature">· 4 字母类型码 + 人设名,可分享可传播</Text>
</View>
<Button className="btn-main ot-start" loading={loading} onClick={start}>{loading ? '加载题目中…' : '开始测评 →'}</Button>
{err && <Text className="err-text">{err}</Text>}
</View>
);
}
/* ================= 答题(一题一屏,选择后自动进入下一题) ================= */
if (stage === 'quiz') {
const q = questions[current];
const answered = Object.keys(answers).length;
const pct = questions.length ? Math.round((answered / questions.length) * 100) : 0;
const curPart = q ? (sections[q.part] || '') : '';
const ans = q ? answers[q.id] : undefined;
if (!q) return null;
return (
<View className="ot">
<View className="ot-quiz-head">
<View className="ot-quiz-top">
<Text className="ot-quiz-title">OPC 创业基因测评<Text className="ot-quiz-tag">{version === 'quick' ? '快速版' : '完整版'}</Text></Text>
<Text className="ot-progress-text">{current + 1} / {questions.length} 题</Text>
</View>
<View className="ot-progress"><View className="ot-progress-fill" style={{ width: pct + '%' }} /></View>
{curPart && <Text className="ot-part">{curPart}</Text>}
</View>
<View className="ot-qcard one">
<View className="ot-qnum">
<Text>第 {q.id} 题</Text>
{ans && <Text className="ot-check">✓ 已答</Text>}
</View>
<Text className="ot-qtext">{q.question}</Text>
<View className="ot-opts">
{['A', 'B'].map((k) => (
<View className={`ot-opt${ans === k ? ' sel' : ''}`} key={k} onClick={() => select(q.id, k)}>
<Text className="opt-key">{k}.</Text>
<Text className="opt-text">{q[k]}</Text>
</View>
))}
</View>
<Text className="ot-hint">{ans ? '选择其他选项将直接进入下一题' : '选择一个选项后自动进入下一题'}</Text>
</View>
<View className="ot-nav-one">
<Button className="btn-ghost ot-nav-btn" disabled={current === 0 || loading} onClick={() => setCurrent(current - 1)}>◀ 上一题</Button>
<Text className="ot-nav-info">{current + 1} / {questions.length}</Text>
</View>
{loading && <Text className="ot-loading">正在计算你的 OPC 画像…</Text>}
{err && <Text className="err-text">{err}</Text>}
</View>
);
}
/* ================= 报告 ================= */
if (stage === 'result' && result) {
const r = result;
const persona = r.persona || {};
const level = r.adaptLevel || {};
return (
<View className="ot">
{/* 头部:类型码 + 人设 + 适配指数 */}
<View className="ot-head">
<Text className="ot-no">02</Text>
<Text className="ot-title">你的 OPC 画像</Text>
<Text className="ot-en">Report</Text>
</View>
<View className="ot-result-hero">
<View className="ot-hero-left">
<Text className="ot-code">{r.typeCode}</Text>
<Text className="ot-persona">{persona.name}</Text>
<Text className="ot-tagline">{persona.tagline}</Text>
<Text className="ot-openpath">{persona.openPath}</Text>
</View>
<View className="ot-hero-right">
<View className="ot-ring" style={{ '--p': r.adaptIndex + '%' }}>
<View className="ot-ring-inner"><Text className="ot-ring-num">{r.adaptIndex}</Text></View>
</View>
<Text className="ot-ring-label">OPC 适配指数 · {level.label}</Text>
</View>
</View>
<View className="ot-verdict"><Text>{level.verdict}</Text></View>
{r.weakestDims.length > 0 && (
<View className="ot-weak">优先补齐:{r.weakestDims.map((d) => `${d.label}(${d.score})`).join('、')}</View>
)}
{/* 适配 6 维 */}
<View className="ot-head">
<Text className="ot-no">03</Text>
<Text className="ot-title">适配 6 维</Text>
<Text className="ot-en">Fitness</Text>
</View>
{[...r.adaptDims].sort((a, b) => a.code.localeCompare(b.code)).map((d) => (
<View className="ot-dim" key={d.code}>
<Text className="ot-dim-label">{d.label}</Text>
<View className="ot-dim-bar">
<View className="ot-dim-fill" style={{ width: d.score + '%', background: adaptColor(d.score) }}>
<Text className="ot-dim-num">{d.score}</Text>
</View>
</View>
</View>
))}
{/* 特质 4 轴 */}
<View className="ot-head">
<Text className="ot-no">04</Text>
<Text className="ot-title">特质轴</Text>
<Text className="ot-en">{`Your Type · ${r.typeCode}`}</Text>
</View>
{r.axesDetail.map((ax) => (
<View className="ot-axis" key={ax.pair}>
<View className="ot-axis-head">
<Text className="ot-axis-name">{axisNames[ax.pair] || ax.pair}</Text>
<Text className="ot-axis-win">{ax.winner}</Text>
<Text className="ot-axis-pct">{ax.leftPct} / {ax.rightPct}</Text>
</View>
<View className="ot-axis-bar">
<View className="ot-axis-fill" style={{ width: ax.leftPct + '%' }} />
</View>
</View>
))}
{/* 推荐赛道 */}
<View className="ot-head">
<Text className="ot-no">05</Text>
<Text className="ot-title">推荐赛道</Text>
<Text className="ot-en">Tracks</Text>
</View>
{r.tracks.map((t) => (
<View className="ot-card" key={t.code}>
<View className="ot-card-head"><Text className="ot-card-name">{t.name || t.code}</Text><Text className="ot-badge">匹配 {t.pct}%</Text></View>
<Text className="ot-desc">{t.desc}</Text>
<Text className="ot-line"><Text className="b">为什么适合你:</Text>{t.why}</Text>
<Text className="ot-line"><Text className="b">起步第一小步:</Text>{t.firstStep}</Text>
<Text className="ot-line muted"><Text className="b">对应课程:</Text>{t.courseRef}</Text>
</View>
))}
{/* 适合角色 */}
<View className="ot-head">
<Text className="ot-no">06</Text>
<Text className="ot-title">适合角色</Text>
<Text className="ot-en">Role</Text>
</View>
{r.roles.map((ro, i) => (
<View className="ot-card" key={ro.code}>
<View className="ot-card-head"><Text className="ot-card-name">{ro.name || ro.code}</Text><Text className={`ot-badge${i === 0 ? '' : ' alt'}`}>{i === 0 ? '主角色' : '辅助角色'} · {ro.pct}%</Text></View>
<Text className="ot-line"><Text className="b">职责:</Text>{ro.duty}</Text>
<Text className="ot-line"><Text className="b">你的特质证据:</Text>{ro.evidence}</Text>
<Text className="ot-line"><Text className="b">分工建议:</Text>{ro.split}</Text>
</View>
))}
{/* 工具模式 */}
<View className="ot-head">
<Text className="ot-no">07</Text>
<Text className="ot-title">工具模式</Text>
<Text className="ot-en">AI Mode</Text>
</View>
{r.tools.map((t, i) => (
<View className="ot-card" key={t.code}>
<View className="ot-card-head"><Text className="ot-card-name">{t.name || t.code}</Text><Text className={`ot-badge${i === 0 ? '' : ' alt'}`}>{i === 0 ? '首选' : '备选'} · {t.pct}%</Text></View>
<Text className="ot-desc">{t.desc}</Text>
<Text className="ot-line"><Text className="b">人机分工:</Text>{t.division}</Text>
<Text className="ot-line"><Text className="b">智能体配置:</Text>{(t.agents || []).join(';')}</Text>
</View>
))}
{/* 下一步行动 */}
<View className="ot-head">
<Text className="ot-no">08</Text>
<Text className="ot-title">下一步行动</Text>
<Text className="ot-en">Next</Text>
</View>
<View className="ot-card">
{(level.advice || []).map((a, i) => <Text className="ot-step" key={i}>· {a}</Text>)}
{level.nextStep && <Text className="ot-step next"><Text className="b">{level.nextStep}</Text></Text>}
</View>
<Text className="ot-note">注意:测评参考 MBTI 的自评逻辑开发,测量的是「偏好」而非「能力」,结果仅供参考,不构成任何职业或投资决策的硬性依据。{r.version === 'quick' ? ' 本结果为快速版(30 题),仅供方向参考;如需更准画像建议完成完整版。' : ''}</Text>
<Button className="btn-main ot-restart" onClick={reset}>↻ 重新测评</Button>
</View>
);
}
return null;
}
@@ -1 +1,570 @@
.test { padding: 40px 32px; }
/* OPC 创业基因测评 · 小程序端(后端驱动) */
.ot {
padding: 0 32rpx;
}
/* ---------- 区块头 ---------- */
.ot-head {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6rpx;
margin: 48rpx 0 24rpx;
}
.ot-no {
font-family: 'BubbledotICG', monospace;
color: #6f6f6f;
font-size: 26rpx;
letter-spacing: 2rpx;
}
.ot-title {
font-size: 38rpx;
font-weight: 700;
color: #fff;
letter-spacing: -0.5rpx;
}
.ot-en {
font-family: 'BubbledotICG', monospace;
color: #5b5b5b;
font-size: 24rpx;
letter-spacing: 2rpx;
}
/* ---------- 欢迎 ---------- */
.ot-intro {
display: block;
font-size: 28rpx;
color: #c4c2c3;
line-height: 1.75;
margin-bottom: 24rpx;
}
.ot-version-grid {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 20rpx;
}
.ot-vcard {
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
padding: 28rpx 32rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.ot-vcard.sel {
border-color: #ffd28a;
box-shadow: 0 0 0 1rpx rgba(255, 210, 138, 0.6), 0 0 24rpx rgba(255, 210, 138, 0.15);
}
.ot-vname {
font-size: 34rpx;
font-weight: 700;
color: #fff;
}
.ot-vcard.sel .ot-vname {
color: #ffd28a;
}
.ot-vmeta {
font-size: 24rpx;
color: #8e8e8e;
}
.ot-vdesc {
font-size: 25rpx;
color: #a9a9a9;
line-height: 1.6;
}
.ot-features {
display: flex;
flex-direction: column;
gap: 10rpx;
margin-bottom: 32rpx;
}
.ot-feature {
font-size: 26rpx;
color: #a9a9a9;
line-height: 1.5;
}
.ot-start {
margin-top: 8rpx;
}
/* ---------- 答题 ---------- */
.ot-quiz-head {
padding-top: 8rpx;
}
.ot-quiz-top {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.ot-quiz-title {
font-size: 30rpx;
font-weight: 700;
color: #fff;
}
.ot-quiz-tag {
margin-left: 14rpx;
font-size: 20rpx;
font-weight: 600;
color: #000;
background: #fff;
border-radius: 999px;
padding: 4rpx 16rpx;
vertical-align: middle;
}
.ot-progress-text {
font-size: 24rpx;
color: #8e8e8e;
}
.ot-progress {
height: 10rpx;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
margin: 20rpx 0;
overflow: hidden;
}
.ot-progress-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, #ffd28a, #ff8f6b);
transition: width 0.3s ease;
}
.ot-part {
display: block;
font-size: 24rpx;
color: #ffd28a;
margin-bottom: 8rpx;
}
/* 单题卡片下方的操作提示 */
.ot-hint {
display: block;
margin-top: 16rpx;
font-size: 22rpx;
color: #8e8e8e;
text-align: center;
}
.ot-qcard {
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
padding: 28rpx 30rpx;
margin-bottom: 20rpx;
}
.ot-qcard.answered {
border-color: rgba(255, 210, 138, 0.5);
}
/* 一题一屏:卡片更突出,题目/选项更大 */
.ot-qcard.one {
margin-top: 8rpx;
padding: 44rpx 32rpx;
}
.ot-qcard.one .ot-qtext {
font-size: 36rpx;
line-height: 1.5;
margin-bottom: 36rpx;
}
.ot-qcard.one .ot-opt {
padding: 26rpx 26rpx;
}
.ot-qnum {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 22rpx;
color: #6f6f6f;
margin-bottom: 12rpx;
}
.ot-check {
color: #ffd28a;
font-size: 26rpx;
}
.ot-qtext {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #fff;
line-height: 1.5;
margin-bottom: 20rpx;
}
.ot-opts {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.ot-opt {
display: flex;
align-items: flex-start;
gap: 12rpx;
background: #141417;
border: 1rpx solid rgba(255, 255, 255, 0.12);
border-radius: 14px;
padding: 20rpx 22rpx;
}
.ot-opt.sel {
border-color: #ffd28a;
background: rgba(255, 210, 138, 0.08);
}
.opt-key {
font-family: 'BubbledotICG', monospace;
color: #8e8e8e;
font-size: 26rpx;
flex-shrink: 0;
}
.ot-opt.sel .opt-key {
color: #ffd28a;
}
.opt-text {
font-size: 27rpx;
color: #c4c2c3;
line-height: 1.55;
flex: 1;
}
/* 底部导航(上一题 / 进度计数) */
.ot-nav-one {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
margin: 28rpx 0 8rpx;
}
.ot-nav-btn {
flex-shrink: 0;
line-height: 2.4;
padding: 0 28rpx;
}
.ot-nav-info {
font-family: 'BubbledotICG', monospace;
font-size: 26rpx;
color: #8e8e8e;
letter-spacing: 2rpx;
}
.ot-loading {
display: block;
text-align: center;
font-size: 26rpx;
color: #ffd28a;
margin-top: 24rpx;
}
/* ---------- 报告 ---------- */
.ot-result-hero {
background: linear-gradient(135deg, #161618, #221d2e);
border: 1rpx solid rgba(255, 255, 255, 0.18);
border-radius: 20px;
padding: 36rpx 32rpx;
display: flex;
align-items: center;
gap: 28rpx;
margin-bottom: 16rpx;
}
.ot-hero-left {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
min-width: 0;
}
.ot-code {
font-family: 'BubbledotICG', monospace;
font-size: 60rpx;
color: #fff;
letter-spacing: 4rpx;
line-height: 1;
}
.ot-persona {
font-size: 34rpx;
font-weight: 700;
color: #ffd28a;
}
.ot-tagline {
font-size: 24rpx;
color: #a9a9a9;
}
.ot-openpath {
font-size: 24rpx;
color: #c4c2c3;
line-height: 1.5;
}
.ot-hero-right {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
}
.ot-ring {
width: 168rpx;
height: 168rpx;
border-radius: 50%;
background: conic-gradient(#ffd28a var(--p, 0%), rgba(255, 255, 255, 0.12) var(--p, 0%));
display: flex;
align-items: center;
justify-content: center;
}
.ot-ring-inner {
width: 132rpx;
height: 132rpx;
border-radius: 50%;
background: #0c0c0e;
display: flex;
align-items: center;
justify-content: center;
}
.ot-ring-num {
font-family: 'BubbledotICG', monospace;
font-size: 56rpx;
color: #fff;
}
.ot-ring-label {
font-size: 22rpx;
color: #c4c2c3;
text-align: center;
}
.ot-verdict {
background: rgba(255, 255, 255, 0.05);
border: 1rpx solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
padding: 24rpx 28rpx;
font-size: 28rpx;
color: #fff;
line-height: 1.6;
margin-bottom: 12rpx;
}
.ot-weak {
font-size: 24rpx;
color: #f5d9a0;
background: rgba(245, 217, 160, 0.08);
border: 1rpx dashed rgba(245, 217, 160, 0.4);
border-radius: 12px;
padding: 16rpx 22rpx;
margin-bottom: 8rpx;
}
/* 适配 6 维 */
.ot-dim {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 16rpx;
}
.ot-dim-label {
width: 140rpx;
flex-shrink: 0;
font-size: 26rpx;
color: #c4c2c3;
}
.ot-dim-bar {
flex: 1;
height: 26rpx;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.ot-dim-fill {
height: 100%;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 10rpx;
min-width: 60rpx;
}
.ot-dim-num {
font-family: 'BubbledotICG', monospace;
font-size: 20rpx;
color: #000;
font-weight: 700;
}
/* 特质 4 轴 */
.ot-axis {
margin-bottom: 18rpx;
}
.ot-axis-head {
display: flex;
align-items: center;
gap: 14rpx;
margin-bottom: 10rpx;
}
.ot-axis-name {
font-size: 26rpx;
color: #c4c2c3;
}
.ot-axis-win {
font-family: 'BubbledotICG', monospace;
font-size: 26rpx;
color: #ffd28a;
}
.ot-axis-pct {
margin-left: auto;
font-family: 'BubbledotICG', monospace;
font-size: 22rpx;
color: #8e8e8e;
}
.ot-axis-bar {
height: 16rpx;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
overflow: hidden;
}
.ot-axis-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, #ffd28a, #ff8f6b);
}
/* 卡片(赛道 / 角色 / 工具) */
.ot-card {
background: #0c0c0e;
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
padding: 28rpx 30rpx;
margin-bottom: 16rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.ot-card-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4rpx;
}
.ot-card-name {
font-size: 32rpx;
font-weight: 700;
color: #fff;
}
.ot-badge {
font-size: 20rpx;
font-weight: 700;
color: #000;
background: #fff;
border-radius: 999px;
padding: 4rpx 16rpx;
flex-shrink: 0;
}
.ot-badge.alt {
background: #323234;
color: #c8c8c8;
}
.ot-desc {
font-size: 26rpx;
color: #a9a9a9;
line-height: 1.6;
}
.ot-line {
font-size: 25rpx;
color: #c4c2c3;
line-height: 1.6;
}
.ot-line.muted {
color: #8e8e8e;
}
.ot-line .b {
color: #ffd28a;
font-weight: 600;
}
/* 下一步行动 */
.ot-step {
display: block;
font-size: 26rpx;
color: #c4c2c3;
line-height: 1.65;
}
.ot-step.next {
color: #fff;
}
/* 免责声明 + 重测 */
.ot-note {
display: block;
font-size: 22rpx;
color: #6f6f6f;
line-height: 1.6;
margin: 24rpx 0;
}
.ot-restart {
margin-top: 8rpx;
}
.err-text {
display: block;
margin-top: 20rpx;
font-size: 26rpx;
color: #f0b8b8;
text-align: center;
}
@@ -21,6 +21,7 @@ export function request(method, path, data = {}, withAuth = false) {
method,
data,
header,
timeout: 10000, // 后端未启动时快速失败,避免界面一直"加载中"
success: (res) => {
const d = res.data || {};
if (res.statusCode >= 200 && res.statusCode < 300 && d.ok !== false) {
@@ -0,0 +1,106 @@
/**
* home.js — 首页宣传页内容数据
* 与 web 端 website/src/data/home.js 保持同步(口径对齐营销总纲:
* 对外红线——公域只邀请到公益课/沙龙 + OPC 测评;付费课价格/交付物/效果卡数字不在首页出现)
*/
export const MK_HERO = {
badge: '一个人,也可以是一家公司',
headline: ['ONE PERSON', 'DESIGNED TO EVOLVE'],
subhead:
'我们落地在昆明市大学生创业园。每周有免费公益课和线下沙龙,讲透「一个人 + AI 怎么开一家公司」。先来听一场,交个朋友,不卖课。',
ctaPrimary: { label: '预约公益课 / 沙龙', tab: '/pages/booking/index' },
ctaSecondary: { label: '先做 OPC 测评', tab: '/pages/test/index' }
};
export const MK_TRUST = [
{ glyph: '①', value: '1', suffix: ' 场/周', label: '公益课 · 全年滚动' },
{ glyph: '②', value: '36', suffix: ' 期', label: '公益课主题库' },
{ glyph: '③', value: '4', suffix: ' 方向', label: '沙龙小场深聊' },
{ glyph: '④', value: '200', suffix: '+', label: '园区入驻创业者' }
];
export const MK_PRINCIPLE = {
title: '别再教 OPC,直接做 OPC',
en: 'From teaching to delivering',
oldTitle: '课程逻辑(旧)',
oldPoints: [
'多周教学,学员边学边做作业',
'赛道卡、六维等靠学员自填自评',
'交付的是「知识」'
],
newTitle: '服务逻辑(现在)',
newPoints: [
'先来公益课 / 沙龙认识我们',
'需要时,我们把方案做给你',
'交付「方案 + 智能体 + 陪跑」'
],
quote: '课程卖的是「知识」,服务交付的是「结果」。'
};
export const MK_PRODUCTS = [
{
tag: '公益',
name: '公益课',
price: '免费',
period: '每周 1 场 · 主题轮换',
desc: '一场分享,带走一个值得做的方向——先交个朋友,不卖课。',
deliverables: ['60–90 分钟主题认知', '免费 OPC 测评入口', '扫码预约,无门槛'],
cta: '报名公益课',
tab: '/pages/booking/index',
highlight: false
},
{
tag: '沙龙',
name: '交流沙龙',
price: '免费',
period: '每月 2–4 场 · 12–30 人',
desc: '小场深度聊:主题深谈 + AI 现场演示 + 云南本地人脉面对面。',
deliverables: ['小场深度交流', 'AI 工具 / 智能体现场演示', '本地资源对接'],
cta: '预约沙龙',
tab: '/pages/booking/index',
highlight: true
},
{
tag: '测评',
name: 'OPC 创业基因测评',
price: '免费',
period: '5 分钟 · 随时可测',
desc: '测出你适不适合做 OPC、该选什么赛道、适合什么角色、怎么和 AI 协作。',
deliverables: ['适配指数 + 6 维分析', '推荐赛道 TOP2–3', '4 字母类型码 + 人设名'],
cta: '开始测评',
tab: '/pages/test/index',
highlight: false
}
];
export const MK_CORE = {
title: '我们怎么帮你:认识 → 方向 → 陪跑',
en: 'Know · Point · Run',
quote: '你先来听一场、做一次测评,认识我们;需要时,剩下的交给我们和 AI 团队。',
items: [
{ step: '01', title: '认识', desc: '公益课 / 沙龙里看清「一个人 + AI 开公司」是怎么回事,做一次测评找到自己的倾向。', out: '得到:一个值得做的方向' },
{ step: '02', title: '方向', desc: '需要时,我们把你的资源、能力梳理成一条可执行的路(体系设计)。', out: '得到:一条可执行的路' },
{ step: '03', title: '陪跑', desc: '30 天每周复盘 + 答疑,陪你一起推进,落地到结果。', out: '得到:落地进展 / 结果' }
]
};
export const MK_OPENING = {
note: '免费公益课每周 1 场、沙龙每月 2–4 场,主题见公众号 / 社群每周预告。',
rows: [
{ tag: '公益', item: '公益课 · 每周 1 场', desc: '每周四 20:00 直播 / 每月 ≥1 场线下' },
{ tag: '沙龙', item: '交流沙龙 · 每月 2–4 场', desc: '昆明 / 大理 · 12–30 人小场深聊' },
{ tag: '测评', item: 'OPC 创业测评 · 随时可测', desc: '5 分钟测出你的赛道 / 角色 / AI 协作方式' }
]
};
export const MK_ASSESS = {
badge: '先测一测',
title: '你适合做什么样的 OPC?',
desc: '5 分钟做一次 OPC 创业基因测评:测出适配指数、推荐赛道、适合角色与 AI 协作方式——先了解自己,再来公益课 / 沙龙深聊落地。'
};
export const MK_SIGNUP = {
title: '下一步:从一次测评或一场公益课开始',
desc: '先花 5 分钟做 OPC 测评,或预约一场免费公益课 / 沙龙。预约方式:公众号菜单栏「报名」或现场扫码。'
};
@@ -0,0 +1,216 @@
/**
* starfield.js — 星空背景(旧版 CanvasContext)
*
* 对齐 web 端 ParticleField(WebGL 粒子)的视觉:
* - 星河:对角细带,中心暖金 / 边缘冷蓝
* - 散布星点:白 / 青 / 暖
* - 光斑 bokeh:大而柔的辉光(两圈低透明度矩形叠加模拟柔光)
* - 星云光晕:暖金核心 + 冷蓝臂 + 深处紫(createCircularGradient 预生成,逐帧复用)
* - 闪烁 + 缓慢自转;30fps(setTimeout)节流
*
* 注意:不用 <Canvas type="2d"> 的 node API —— 部分基础库(3.17.x 灰度)创建
* type="2d" canvas 节点会在渲染层抛 "Cannot read properties of undefined
* (reading 'writeOnly')" 导致黑屏。改用稳定可靠的旧版 canvas-id +
* Taro.createCanvasContext 桥接路径。
*
* 绘制坐标按 canvas 元素 CSS 尺寸(= 屏幕逻辑尺寸),由 createCanvasContext 托管。
*/
import Taro from '@tarojs/taro';
/* 调色板:0-5 常规星,6-8 光斑(低透明度大光斑) */
const PALETTE = [
{ base: 'rgba(255,237,184,0.30)', bright: 'rgba(255,246,216,0.55)' }, // 0 暖金核心
{ base: 'rgba(255,210,138,0.26)', bright: 'rgba(255,226,170,0.50)' }, // 1 暖金
{ base: 'rgba(153,209,255,0.28)', bright: 'rgba(190,225,255,0.52)' }, // 2 冷蓝臂
{ base: 'rgba(120,200,255,0.24)', bright: 'rgba(160,220,255,0.48)' }, // 3 冷青
{ base: 'rgba(242,247,255,0.28)', bright: 'rgba(255,255,255,0.52)' }, // 4 白
{ base: 'rgba(255,232,205,0.24)', bright: 'rgba(255,240,220,0.48)' }, // 5 暖
{ base: 'rgba(255,200,130,0.10)', bright: 'rgba(255,220,170,0.18)' }, // 6 光斑暖
{ base: 'rgba(120,170,255,0.10)', bright: 'rgba(160,200,255,0.18)' }, // 7 光斑冷
{ base: 'rgba(200,240,255,0.10)', bright: 'rgba(230,250,255,0.18)' } // 8 光斑青
];
function mkStar(bx, by, bz, size, palette, base) {
return {
bx, by, bz,
size,
palette,
base,
ph: Math.random() * Math.PI * 2,
freq: 1.2 + Math.random() * 2.4 // 闪烁频率
};
}
/* 生成星空:星河 + 散布星点 + 光斑 */
function buildStars(cw, ch) {
const B = Math.max(cw, ch) * 1.1;
const BX = B, BY = B, BZ = B * 0.9;
// 旧版画布桥接开销较高,粒子数适当控制:~1500-2600
const total = Math.min(2600, Math.max(1500, Math.floor((cw * ch) / 300)));
const bokeh = Math.min(50, Math.floor(total * 0.025));
const stars = [];
const gauss = () => (Math.random() + Math.random() + Math.random() - 1.5);
// 星河:对角细带,中心暖金 / 边缘冷蓝
const gal = Math.floor(total * 0.6);
for (let i = 0; i < gal; i++) {
const x = (Math.random() * 2 - 1) * BX;
const spread = Math.random() < 0.55 ? 0.045 : 0.09;
const y = x * 0.5 + gauss() * BY * spread;
const z = gauss() * BZ * 0.5;
const dev = Math.min(1, Math.abs(y - x * 0.5) / (BY * 0.16));
const warm = Math.random() < 1 - dev * 0.55;
const pal = warm ? (dev < 0.35 ? 0 : 1) : (dev < 0.5 ? 3 : 2);
const size = warm ? 1.1 + Math.random() * 1.7 : 0.9 + Math.random() * 1.3;
stars.push(mkStar(x, y, z, size, pal, 1.0));
}
// 散布星点:白 / 青 / 暖
const sc = total - gal;
for (let i = 0; i < sc; i++) {
const x = (Math.random() * 2 - 1) * BX;
const y = (Math.random() * 2 - 1) * BY;
const z = (Math.random() * 2 - 1) * BZ;
const c = Math.random();
const pal = c < 0.55 ? 4 : c < 0.8 ? 3 : 5;
stars.push(mkStar(x, y, z, 0.8 + Math.random() * 1.2, pal, 0.9));
}
// 光斑 bokeh
for (let i = 0; i < bokeh; i++) {
const x = (Math.random() * 2 - 1) * BX;
const y = (Math.random() * 2 - 1) * BY;
const z = (Math.random() * 2 - 1) * BZ;
const c = Math.random();
const pal = c < 0.4 ? 6 : c < 0.7 ? 7 : 8;
const s = mkStar(x, y, z, 5 + Math.random() * 9, pal, 0.5);
s.soft = true;
stars.push(s);
}
return stars;
}
/**
* 获取画布上下文。
* 注意:Taro 3.6 未把 createCanvasContext 加入 API 白名单(@tarojs/shared/dist/native-apis.js),
* 故优先使用原生 wx.createCanvasContext(旧版桥接路径,稳定可靠)。
*/
function getCanvasContext(canvasId) {
if (typeof wx !== 'undefined' && typeof wx.createCanvasContext === 'function') {
return wx.createCanvasContext(canvasId);
}
if (typeof Taro.createCanvasContext === 'function') {
return Taro.createCanvasContext(canvasId);
}
return null;
}
/**
* 启动星空动画(旧版 CanvasContext)。
* @param {string} canvasId canvas 组件的 canvas-id
* @param {number} cw canvas 逻辑宽度(px)
* @param {number} ch canvas 逻辑高度(px)
* @returns {Function} 停止函数
*/
export function startStarfield(canvasId, cw, ch) {
const ctx = getCanvasContext(canvasId);
if (!ctx) return () => {}; // 拿不到上下文时静默降级(CSS 星空兜底仍在)
const stars = buildStars(cw, ch);
const F = 300; // 焦距
const Z0 = 60, Z1 = 900;
const cx = cw / 2, cy = ch / 2;
const M = Math.max(cw, ch);
// 星云光晕:createCircularGradient 预生成,逐帧复用
const mkGrad = (x, y, r, c) => {
const g = ctx.createCircularGradient(x, y, r);
g.addColorStop(0, c);
g.addColorStop(1, 'rgba(0,0,0,0)');
return g;
};
const nebula = [
{ x: cw * 0.30, y: ch * 0.28, r: M * 0.55, c: 'rgba(255,210,138,0.10)' }, // 暖金核心
{ x: cw * 0.74, y: ch * 0.60, r: M * 0.60, c: 'rgba(96,140,220,0.11)' }, // 冷蓝臂
{ x: cw * 0.16, y: ch * 0.82, r: M * 0.46, c: 'rgba(120,200,255,0.07)' }, // 冷青
{ x: cw * 0.52, y: ch * 0.46, r: M * 0.85, c: 'rgba(70,60,120,0.07)' } // 深处紫
].map((n) => ({ x: n.x, y: n.y, r: n.r, g: mkGrad(n.x, n.y, n.r, n.c) }));
let frameCount = 0;
let timer = null;
let running = true;
const frame = () => {
frameCount++;
const t = frameCount; // 30fps:t 按渲染帧增长
const ay = Math.sin(t * 0.0011) * 0.6;
const ax = Math.sin(t * 0.0007) * 0.14;
const cY = Math.cos(ay), sY = Math.sin(ay);
const cX = Math.cos(ax), sX = Math.sin(ax);
ctx.clearRect(0, 0, cw, ch);
// 星云光晕
for (let i = 0; i < nebula.length; i++) {
ctx.setFillStyle(nebula[i].g);
ctx.fillRect(0, 0, cw, ch);
}
// 按调色板分组(twinkle → base/bright),减少 setFillStyle 桥接次数
const groups = [];
for (let g = 0; g < 18; g++) groups[g] = [];
const soft = [];
for (let i = 0; i < stars.length; i++) {
const p = stars[i];
// Ry 后 Rx 的 3D 投影
let x = p.bx * cY + p.bz * sY;
let z = -p.bx * sY + p.bz * cY;
let y = p.by * cX - z * sX;
z = p.by * sX + z * cX;
if (z < 40) continue;
const sc = F / z;
const sx = cx + x * sc;
const sy = cy + y * sc;
if (sx < -40 || sx > cw + 40 || sy < -40 || sy > ch + 40) continue;
const depth = (Z1 - z) / (Z1 - Z0);
const size = p.size * (0.5 + depth * 1.4);
if (p.soft) {
soft.push([sx, sy, size, p.palette]);
} else {
const bright = Math.sin(t * p.freq + p.ph) > 0.4;
groups[p.palette * 2 + (bright ? 1 : 0)].push([sx, sy, size]);
}
}
for (let g = 0; g < 18; g++) {
const arr = groups[g];
if (arr.length === 0) continue;
const col = g % 2 === 1 ? PALETTE[g >> 1].bright : PALETTE[g >> 1].base;
ctx.setFillStyle(col);
for (let k = 0; k < arr.length; k++) {
const s = arr[k];
ctx.fillRect(s[0] - s[2] / 2, s[1] - s[2] / 2, s[2], s[2]);
}
}
// 光斑:外圈低透明度 + 内圈高透明度 → 柔光
for (let k = 0; k < soft.length; k++) {
const s = soft[k];
const pal = PALETTE[s[3]];
ctx.setFillStyle(pal.base);
ctx.fillRect(s[0] - s[2] * 1.6, s[1] - s[2] * 1.6, s[2] * 3.2, s[2] * 3.2);
ctx.setFillStyle(pal.bright);
ctx.fillRect(s[0] - s[2] * 0.7, s[1] - s[2] * 0.7, s[2] * 1.4, s[2] * 1.4);
}
ctx.draw(false, () => {});
if (running) timer = setTimeout(frame, 33);
};
frame();
return () => {
running = false;
if (timer) clearTimeout(timer);
};
}
@@ -0,0 +1,41 @@
/**
* tab.js — 自定义 tabBar 选中态同步
*
* 微信自定义 tabBar 时,每个 tab 页各自持有一个 custom-tab-bar 实例,
* 必须在页面显示时主动把选中下标同步给它。
*
* 注意:Taro.getTabBar(pageCtx) 需要传入「当前微信页面实例」
* (其内部调用 pageCtx.getTabBar().$taroInstances),无参调用会返回 undefined。
*/
import { useDidShow } from '@tarojs/taro';
import Taro from '@tarojs/taro';
function getTabBarInstance() {
const inst = Taro.getCurrentInstance();
const page = inst && inst.page;
if (page && typeof page.getTabBar === 'function') {
const bar = Taro.getTabBar(page);
return bar && (bar.then ? bar.then((b) => b) : bar);
}
return null;
}
export function useTabIndex(index) {
useDidShow(() => {
let tries = 0;
const sync = () => {
const bar = getTabBarInstance();
if (bar) {
if (bar.then) bar.then((b) => b && b.setState && b.setState({ selected: index }));
else if (bar.setState) bar.setState({ selected: index });
return;
}
// 首个 tab 页首次渲染时实例可能尚未就绪,重试几次
if (tries < 4) {
tries++;
Taro.nextTick(sync);
}
};
sync();
});
}
@@ -1,46 +0,0 @@
{
"hash": "c23389a2",
"configHash": "6f747217",
"lockfileHash": "49760828",
"browserHash": "b9e5cd06",
"optimized": {
"react": {
"src": "../../react/index.js",
"file": "react.js",
"fileHash": "abb3988d",
"needsInterop": true
},
"react-dom": {
"src": "../../react-dom/index.js",
"file": "react-dom.js",
"fileHash": "5ba65a6d",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "7a907891",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "d208ed98",
"needsInterop": true
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "473f97bb",
"needsInterop": true
}
},
"chunks": {
"chunk-373CG7ZK": {
"file": "chunk-373CG7ZK.js"
},
"chunk-REFQX4J5": {
"file": "chunk-REFQX4J5.js"
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
{
"type": "module"
}
@@ -1,6 +0,0 @@
import {
require_react_dom
} from "./chunk-373CG7ZK.js";
import "./chunk-REFQX4J5.js";
export default require_react_dom();
//# sourceMappingURL=react-dom.js.map
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
@@ -1,38 +0,0 @@
import {
require_react_dom
} from "./chunk-373CG7ZK.js";
import {
__commonJS
} from "./chunk-REFQX4J5.js";
// node_modules/react-dom/client.js
var require_client = __commonJS({
"node_modules/react-dom/client.js"(exports) {
var m = require_react_dom();
if (false) {
exports.createRoot = m.createRoot;
exports.hydrateRoot = m.hydrateRoot;
} else {
i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
exports.createRoot = function(c, o) {
i.usingClientEntryPoint = true;
try {
return m.createRoot(c, o);
} finally {
i.usingClientEntryPoint = false;
}
};
exports.hydrateRoot = function(c, h, o) {
i.usingClientEntryPoint = true;
try {
return m.hydrateRoot(c, h, o);
} finally {
i.usingClientEntryPoint = false;
}
};
}
var i;
}
});
export default require_client();
//# sourceMappingURL=react-dom_client.js.map
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../react-dom/client.js"],
"sourcesContent": ["'use strict';\n\nvar m = require('react-dom');\nif (process.env.NODE_ENV === 'production') {\n exports.createRoot = m.createRoot;\n exports.hydrateRoot = m.hydrateRoot;\n} else {\n var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n exports.createRoot = function(c, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.createRoot(c, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n exports.hydrateRoot = function(c, h, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.hydrateRoot(c, h, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n}\n"],
"mappings": ";;;;;;;;AAAA;AAAA;AAEA,QAAI,IAAI;AACR,QAAI,OAAuC;AACzC,cAAQ,aAAa,EAAE;AACvB,cAAQ,cAAc,EAAE;AAAA,IAC1B,OAAO;AACD,UAAI,EAAE;AACV,cAAQ,aAAa,SAAS,GAAG,GAAG;AAClC,UAAE,wBAAwB;AAC1B,YAAI;AACF,iBAAO,EAAE,WAAW,GAAG,CAAC;AAAA,QAC1B,UAAE;AACA,YAAE,wBAAwB;AAAA,QAC5B;AAAA,MACF;AACA,cAAQ,cAAc,SAAS,GAAG,GAAG,GAAG;AACtC,UAAE,wBAAwB;AAC1B,YAAI;AACF,iBAAO,EAAE,YAAY,GAAG,GAAG,CAAC;AAAA,QAC9B,UAAE;AACA,YAAE,wBAAwB;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAjBM;AAAA;AAAA;",
"names": []
}
@@ -1,5 +0,0 @@
import {
require_react
} from "./chunk-REFQX4J5.js";
export default require_react();
//# sourceMappingURL=react.js.map
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
@@ -1,911 +0,0 @@
import {
__commonJS,
require_react
} from "./chunk-REFQX4J5.js";
// node_modules/react/cjs/react-jsx-dev-runtime.development.js
var require_react_jsx_dev_runtime_development = __commonJS({
"node_modules/react/cjs/react-jsx-dev-runtime.development.js"(exports) {
"use strict";
if (true) {
(function() {
"use strict";
var React = require_react();
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
var REACT_MEMO_TYPE = Symbol.for("react.memo");
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
var FAUX_ITERATOR_SYMBOL = "@@iterator";
function getIteratorFn(maybeIterable) {
if (maybeIterable === null || typeof maybeIterable !== "object") {
return null;
}
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
if (typeof maybeIterator === "function") {
return maybeIterator;
}
return null;
}
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function error(format) {
{
{
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
printWarning("error", format, args);
}
}
}
function printWarning(level, format, args) {
{
var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame;
var stack = ReactDebugCurrentFrame2.getStackAddendum();
if (stack !== "") {
format += "%s";
args = args.concat([stack]);
}
var argsWithFormat = args.map(function(item) {
return String(item);
});
argsWithFormat.unshift("Warning: " + format);
Function.prototype.apply.call(console[level], console, argsWithFormat);
}
}
var enableScopeAPI = false;
var enableCacheElement = false;
var enableTransitionTracing = false;
var enableLegacyHidden = false;
var enableDebugTracing = false;
var REACT_MODULE_REFERENCE;
{
REACT_MODULE_REFERENCE = Symbol.for("react.module.reference");
}
function isValidElementType(type) {
if (typeof type === "string" || typeof type === "function") {
return true;
}
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) {
return true;
}
if (typeof type === "object" && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) {
return true;
}
}
return false;
}
function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
if (displayName) {
return displayName;
}
var functionName = innerType.displayName || innerType.name || "";
return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName;
}
function getContextName(type) {
return type.displayName || "Context";
}
function getComponentNameFromType(type) {
if (type == null) {
return null;
}
{
if (typeof type.tag === "number") {
error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.");
}
}
if (typeof type === "function") {
return type.displayName || type.name || null;
}
if (typeof type === "string") {
return type;
}
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_CONTEXT_TYPE:
var context = type;
return getContextName(context) + ".Consumer";
case REACT_PROVIDER_TYPE:
var provider = type;
return getContextName(provider._context) + ".Provider";
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
case REACT_MEMO_TYPE:
var outerName = type.displayName || null;
if (outerName !== null) {
return outerName;
}
return getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return getComponentNameFromType(init(payload));
} catch (x) {
return null;
}
}
}
}
return null;
}
var assign = Object.assign;
var disabledDepth = 0;
var prevLog;
var prevInfo;
var prevWarn;
var prevError;
var prevGroup;
var prevGroupCollapsed;
var prevGroupEnd;
function disabledLog() {
}
disabledLog.__reactDisabledLog = true;
function disableLogs() {
{
if (disabledDepth === 0) {
prevLog = console.log;
prevInfo = console.info;
prevWarn = console.warn;
prevError = console.error;
prevGroup = console.group;
prevGroupCollapsed = console.groupCollapsed;
prevGroupEnd = console.groupEnd;
var props = {
configurable: true,
enumerable: true,
value: disabledLog,
writable: true
};
Object.defineProperties(console, {
info: props,
log: props,
warn: props,
error: props,
group: props,
groupCollapsed: props,
groupEnd: props
});
}
disabledDepth++;
}
}
function reenableLogs() {
{
disabledDepth--;
if (disabledDepth === 0) {
var props = {
configurable: true,
enumerable: true,
writable: true
};
Object.defineProperties(console, {
log: assign({}, props, {
value: prevLog
}),
info: assign({}, props, {
value: prevInfo
}),
warn: assign({}, props, {
value: prevWarn
}),
error: assign({}, props, {
value: prevError
}),
group: assign({}, props, {
value: prevGroup
}),
groupCollapsed: assign({}, props, {
value: prevGroupCollapsed
}),
groupEnd: assign({}, props, {
value: prevGroupEnd
})
});
}
if (disabledDepth < 0) {
error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
}
}
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var prefix;
function describeBuiltInComponentFrame(name, source, ownerFn) {
{
if (prefix === void 0) {
try {
throw Error();
} catch (x) {
var match = x.stack.trim().match(/\n( *(at )?)/);
prefix = match && match[1] || "";
}
}
return "\n" + prefix + name;
}
}
var reentry = false;
var componentFrameCache;
{
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
componentFrameCache = new PossiblyWeakMap();
}
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) {
return "";
}
{
var frame = componentFrameCache.get(fn);
if (frame !== void 0) {
return frame;
}
}
var control;
reentry = true;
var previousPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = void 0;
var previousDispatcher;
{
previousDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = null;
disableLogs();
}
try {
if (construct) {
var Fake = function() {
throw Error();
};
Object.defineProperty(Fake.prototype, "props", {
set: function() {
throw Error();
}
});
if (typeof Reflect === "object" && Reflect.construct) {
try {
Reflect.construct(Fake, []);
} catch (x) {
control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x) {
control = x;
}
fn.call(Fake.prototype);
}
} else {
try {
throw Error();
} catch (x) {
control = x;
}
fn();
}
} catch (sample) {
if (sample && control && typeof sample.stack === "string") {
var sampleLines = sample.stack.split("\n");
var controlLines = control.stack.split("\n");
var s = sampleLines.length - 1;
var c = controlLines.length - 1;
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
c--;
}
for (; s >= 1 && c >= 0; s--, c--) {
if (sampleLines[s] !== controlLines[c]) {
if (s !== 1 || c !== 1) {
do {
s--;
c--;
if (c < 0 || sampleLines[s] !== controlLines[c]) {
var _frame = "\n" + sampleLines[s].replace(" at new ", " at ");
if (fn.displayName && _frame.includes("<anonymous>")) {
_frame = _frame.replace("<anonymous>", fn.displayName);
}
{
if (typeof fn === "function") {
componentFrameCache.set(fn, _frame);
}
}
return _frame;
}
} while (s >= 1 && c >= 0);
}
break;
}
}
}
} finally {
reentry = false;
{
ReactCurrentDispatcher.current = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
}
var name = fn ? fn.displayName || fn.name : "";
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : "";
{
if (typeof fn === "function") {
componentFrameCache.set(fn, syntheticFrame);
}
}
return syntheticFrame;
}
function describeFunctionComponentFrame(fn, source, ownerFn) {
{
return describeNativeComponentFrame(fn, false);
}
}
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
if (type == null) {
return "";
}
if (typeof type === "function") {
{
return describeNativeComponentFrame(type, shouldConstruct(type));
}
}
if (typeof type === "string") {
return describeBuiltInComponentFrame(type);
}
switch (type) {
case REACT_SUSPENSE_TYPE:
return describeBuiltInComponentFrame("Suspense");
case REACT_SUSPENSE_LIST_TYPE:
return describeBuiltInComponentFrame("SuspenseList");
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return describeFunctionComponentFrame(type.render);
case REACT_MEMO_TYPE:
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
} catch (x) {
}
}
}
}
return "";
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var loggedTypeFailures = {};
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame.setExtraStackFrame(null);
}
}
}
function checkPropTypes(typeSpecs, values, location, componentName, element) {
{
var has = Function.call.bind(hasOwnProperty);
for (var typeSpecName in typeSpecs) {
if (has(typeSpecs, typeSpecName)) {
var error$1 = void 0;
try {
if (typeof typeSpecs[typeSpecName] !== "function") {
var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
err.name = "Invariant Violation";
throw err;
}
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
} catch (ex) {
error$1 = ex;
}
if (error$1 && !(error$1 instanceof Error)) {
setCurrentlyValidatingElement(element);
error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1);
setCurrentlyValidatingElement(null);
}
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
loggedTypeFailures[error$1.message] = true;
setCurrentlyValidatingElement(element);
error("Failed %s type: %s", location, error$1.message);
setCurrentlyValidatingElement(null);
}
}
}
}
}
var isArrayImpl = Array.isArray;
function isArray(a) {
return isArrayImpl(a);
}
function typeName(value) {
{
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
return type;
}
}
function willCoercionThrow(value) {
{
try {
testStringCoercion(value);
return false;
} catch (e) {
return true;
}
}
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
{
if (willCoercionThrow(value)) {
error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value));
return testStringCoercion(value);
}
}
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
var RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true
};
var specialPropKeyWarningShown;
var specialPropRefWarningShown;
var didWarnAboutStringRefs;
{
didWarnAboutStringRefs = {};
}
function hasValidRef(config) {
{
if (hasOwnProperty.call(config, "ref")) {
var getter = Object.getOwnPropertyDescriptor(config, "ref").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== void 0;
}
function hasValidKey(config) {
{
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== void 0;
}
function warnIfStringRefCannotBeAutoConverted(config, self) {
{
if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
if (!didWarnAboutStringRefs[componentName]) {
error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
didWarnAboutStringRefs[componentName] = true;
}
}
}
}
function defineKeyPropWarningGetter(props, displayName) {
{
var warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
}
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function() {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type,
key,
ref,
props,
// Record the component responsible for creating this element.
_owner: owner
};
{
element._store = {};
Object.defineProperty(element._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: false
});
Object.defineProperty(element, "_self", {
configurable: false,
enumerable: false,
writable: false,
value: self
});
Object.defineProperty(element, "_source", {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
function jsxDEV(type, config, maybeKey, source, self) {
{
var propName;
var props = {};
var key = null;
var ref = null;
if (maybeKey !== void 0) {
{
checkKeyStringCoercion(maybeKey);
}
key = "" + maybeKey;
}
if (hasValidKey(config)) {
{
checkKeyStringCoercion(config.key);
}
key = "" + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config, self);
}
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === void 0) {
props[propName] = defaultProps[propName];
}
}
}
if (key || ref) {
var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
}
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement$1(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
}
}
}
var propTypesMisspellWarningShown;
{
propTypesMisspellWarningShown = false;
}
function isValidElement(object) {
{
return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
}
function getDeclarationErrorAddendum() {
{
if (ReactCurrentOwner$1.current) {
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
if (name) {
return "\n\nCheck the render method of `" + name + "`.";
}
}
return "";
}
}
function getSourceInfoErrorAddendum(source) {
{
if (source !== void 0) {
var fileName = source.fileName.replace(/^.*[\\\/]/, "");
var lineNumber = source.lineNumber;
return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
}
return "";
}
}
var ownerHasKeyUseWarning = {};
function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
if (!info) {
var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name;
if (parentName) {
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
}
}
return info;
}
}
function validateExplicitKey(element, parentType) {
{
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
var childOwner = "";
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
}
setCurrentlyValidatingElement$1(element);
error('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
setCurrentlyValidatingElement$1(null);
}
}
function validateChildKeys(node, parentType) {
{
if (typeof node !== "object") {
return;
}
if (isArray(node)) {
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
if (node._store) {
node._store.validated = true;
}
} else if (node) {
var iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === "function") {
if (iteratorFn !== node.entries) {
var iterator = iteratorFn.call(node);
var step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
}
function validatePropTypes(element) {
{
var type = element.type;
if (type === null || type === void 0 || typeof type === "string") {
return;
}
var propTypes;
if (typeof type === "function") {
propTypes = type.propTypes;
} else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
var name = getComponentNameFromType(type);
checkPropTypes(propTypes, element.props, "prop", name, element);
} else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
var _name = getComponentNameFromType(type);
error("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown");
}
if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) {
error("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
}
}
}
function validateFragmentProps(fragment) {
{
var keys = Object.keys(fragment.props);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key !== "children" && key !== "key") {
setCurrentlyValidatingElement$1(fragment);
error("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key);
setCurrentlyValidatingElement$1(null);
break;
}
}
if (fragment.ref !== null) {
setCurrentlyValidatingElement$1(fragment);
error("Invalid attribute `ref` supplied to `React.Fragment`.");
setCurrentlyValidatingElement$1(null);
}
}
}
var didWarnAboutKeySpread = {};
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
{
var validType = isValidElementType(type);
if (!validType) {
var info = "";
if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
}
var sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
var typeString;
if (type === null) {
typeString = "null";
} else if (isArray(type)) {
typeString = "array";
} else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />";
info = " Did you accidentally export a JSX literal instead of a component?";
} else {
typeString = typeof type;
}
error("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info);
}
var element = jsxDEV(type, props, key, source, self);
if (element == null) {
return element;
}
if (validType) {
var children = props.children;
if (children !== void 0) {
if (isStaticChildren) {
if (isArray(children)) {
for (var i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
}
} else {
validateChildKeys(children, type);
}
}
}
{
if (hasOwnProperty.call(props, "key")) {
var componentName = getComponentNameFromType(type);
var keys = Object.keys(props).filter(function(k) {
return k !== "key";
});
var beforeExample = keys.length > 0 ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
var afterExample = keys.length > 0 ? "{" + keys.join(": ..., ") + ": ...}" : "{}";
error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
}
var jsxDEV$1 = jsxWithValidation;
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.jsxDEV = jsxDEV$1;
})();
}
}
});
// node_modules/react/jsx-dev-runtime.js
var require_jsx_dev_runtime = __commonJS({
"node_modules/react/jsx-dev-runtime.js"(exports, module) {
if (false) {
module.exports = null;
} else {
module.exports = require_react_jsx_dev_runtime_development();
}
}
});
export default require_jsx_dev_runtime();
/*! Bundled license information:
react/cjs/react-jsx-dev-runtime.development.js:
(**
* @license React
* react-jsx-dev-runtime.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=react_jsx-dev-runtime.js.map
File diff suppressed because one or more lines are too long
@@ -1,923 +0,0 @@
import {
__commonJS,
require_react
} from "./chunk-REFQX4J5.js";
// node_modules/react/cjs/react-jsx-runtime.development.js
var require_react_jsx_runtime_development = __commonJS({
"node_modules/react/cjs/react-jsx-runtime.development.js"(exports) {
"use strict";
if (true) {
(function() {
"use strict";
var React = require_react();
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
var REACT_MEMO_TYPE = Symbol.for("react.memo");
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
var FAUX_ITERATOR_SYMBOL = "@@iterator";
function getIteratorFn(maybeIterable) {
if (maybeIterable === null || typeof maybeIterable !== "object") {
return null;
}
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
if (typeof maybeIterator === "function") {
return maybeIterator;
}
return null;
}
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function error(format) {
{
{
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
printWarning("error", format, args);
}
}
}
function printWarning(level, format, args) {
{
var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame;
var stack = ReactDebugCurrentFrame2.getStackAddendum();
if (stack !== "") {
format += "%s";
args = args.concat([stack]);
}
var argsWithFormat = args.map(function(item) {
return String(item);
});
argsWithFormat.unshift("Warning: " + format);
Function.prototype.apply.call(console[level], console, argsWithFormat);
}
}
var enableScopeAPI = false;
var enableCacheElement = false;
var enableTransitionTracing = false;
var enableLegacyHidden = false;
var enableDebugTracing = false;
var REACT_MODULE_REFERENCE;
{
REACT_MODULE_REFERENCE = Symbol.for("react.module.reference");
}
function isValidElementType(type) {
if (typeof type === "string" || typeof type === "function") {
return true;
}
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) {
return true;
}
if (typeof type === "object" && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) {
return true;
}
}
return false;
}
function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
if (displayName) {
return displayName;
}
var functionName = innerType.displayName || innerType.name || "";
return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName;
}
function getContextName(type) {
return type.displayName || "Context";
}
function getComponentNameFromType(type) {
if (type == null) {
return null;
}
{
if (typeof type.tag === "number") {
error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.");
}
}
if (typeof type === "function") {
return type.displayName || type.name || null;
}
if (typeof type === "string") {
return type;
}
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_CONTEXT_TYPE:
var context = type;
return getContextName(context) + ".Consumer";
case REACT_PROVIDER_TYPE:
var provider = type;
return getContextName(provider._context) + ".Provider";
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
case REACT_MEMO_TYPE:
var outerName = type.displayName || null;
if (outerName !== null) {
return outerName;
}
return getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return getComponentNameFromType(init(payload));
} catch (x) {
return null;
}
}
}
}
return null;
}
var assign = Object.assign;
var disabledDepth = 0;
var prevLog;
var prevInfo;
var prevWarn;
var prevError;
var prevGroup;
var prevGroupCollapsed;
var prevGroupEnd;
function disabledLog() {
}
disabledLog.__reactDisabledLog = true;
function disableLogs() {
{
if (disabledDepth === 0) {
prevLog = console.log;
prevInfo = console.info;
prevWarn = console.warn;
prevError = console.error;
prevGroup = console.group;
prevGroupCollapsed = console.groupCollapsed;
prevGroupEnd = console.groupEnd;
var props = {
configurable: true,
enumerable: true,
value: disabledLog,
writable: true
};
Object.defineProperties(console, {
info: props,
log: props,
warn: props,
error: props,
group: props,
groupCollapsed: props,
groupEnd: props
});
}
disabledDepth++;
}
}
function reenableLogs() {
{
disabledDepth--;
if (disabledDepth === 0) {
var props = {
configurable: true,
enumerable: true,
writable: true
};
Object.defineProperties(console, {
log: assign({}, props, {
value: prevLog
}),
info: assign({}, props, {
value: prevInfo
}),
warn: assign({}, props, {
value: prevWarn
}),
error: assign({}, props, {
value: prevError
}),
group: assign({}, props, {
value: prevGroup
}),
groupCollapsed: assign({}, props, {
value: prevGroupCollapsed
}),
groupEnd: assign({}, props, {
value: prevGroupEnd
})
});
}
if (disabledDepth < 0) {
error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
}
}
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var prefix;
function describeBuiltInComponentFrame(name, source, ownerFn) {
{
if (prefix === void 0) {
try {
throw Error();
} catch (x) {
var match = x.stack.trim().match(/\n( *(at )?)/);
prefix = match && match[1] || "";
}
}
return "\n" + prefix + name;
}
}
var reentry = false;
var componentFrameCache;
{
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
componentFrameCache = new PossiblyWeakMap();
}
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) {
return "";
}
{
var frame = componentFrameCache.get(fn);
if (frame !== void 0) {
return frame;
}
}
var control;
reentry = true;
var previousPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = void 0;
var previousDispatcher;
{
previousDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = null;
disableLogs();
}
try {
if (construct) {
var Fake = function() {
throw Error();
};
Object.defineProperty(Fake.prototype, "props", {
set: function() {
throw Error();
}
});
if (typeof Reflect === "object" && Reflect.construct) {
try {
Reflect.construct(Fake, []);
} catch (x) {
control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x) {
control = x;
}
fn.call(Fake.prototype);
}
} else {
try {
throw Error();
} catch (x) {
control = x;
}
fn();
}
} catch (sample) {
if (sample && control && typeof sample.stack === "string") {
var sampleLines = sample.stack.split("\n");
var controlLines = control.stack.split("\n");
var s = sampleLines.length - 1;
var c = controlLines.length - 1;
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
c--;
}
for (; s >= 1 && c >= 0; s--, c--) {
if (sampleLines[s] !== controlLines[c]) {
if (s !== 1 || c !== 1) {
do {
s--;
c--;
if (c < 0 || sampleLines[s] !== controlLines[c]) {
var _frame = "\n" + sampleLines[s].replace(" at new ", " at ");
if (fn.displayName && _frame.includes("<anonymous>")) {
_frame = _frame.replace("<anonymous>", fn.displayName);
}
{
if (typeof fn === "function") {
componentFrameCache.set(fn, _frame);
}
}
return _frame;
}
} while (s >= 1 && c >= 0);
}
break;
}
}
}
} finally {
reentry = false;
{
ReactCurrentDispatcher.current = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
}
var name = fn ? fn.displayName || fn.name : "";
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : "";
{
if (typeof fn === "function") {
componentFrameCache.set(fn, syntheticFrame);
}
}
return syntheticFrame;
}
function describeFunctionComponentFrame(fn, source, ownerFn) {
{
return describeNativeComponentFrame(fn, false);
}
}
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
if (type == null) {
return "";
}
if (typeof type === "function") {
{
return describeNativeComponentFrame(type, shouldConstruct(type));
}
}
if (typeof type === "string") {
return describeBuiltInComponentFrame(type);
}
switch (type) {
case REACT_SUSPENSE_TYPE:
return describeBuiltInComponentFrame("Suspense");
case REACT_SUSPENSE_LIST_TYPE:
return describeBuiltInComponentFrame("SuspenseList");
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return describeFunctionComponentFrame(type.render);
case REACT_MEMO_TYPE:
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
} catch (x) {
}
}
}
}
return "";
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var loggedTypeFailures = {};
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame.setExtraStackFrame(null);
}
}
}
function checkPropTypes(typeSpecs, values, location, componentName, element) {
{
var has = Function.call.bind(hasOwnProperty);
for (var typeSpecName in typeSpecs) {
if (has(typeSpecs, typeSpecName)) {
var error$1 = void 0;
try {
if (typeof typeSpecs[typeSpecName] !== "function") {
var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
err.name = "Invariant Violation";
throw err;
}
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
} catch (ex) {
error$1 = ex;
}
if (error$1 && !(error$1 instanceof Error)) {
setCurrentlyValidatingElement(element);
error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1);
setCurrentlyValidatingElement(null);
}
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
loggedTypeFailures[error$1.message] = true;
setCurrentlyValidatingElement(element);
error("Failed %s type: %s", location, error$1.message);
setCurrentlyValidatingElement(null);
}
}
}
}
}
var isArrayImpl = Array.isArray;
function isArray(a) {
return isArrayImpl(a);
}
function typeName(value) {
{
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
return type;
}
}
function willCoercionThrow(value) {
{
try {
testStringCoercion(value);
return false;
} catch (e) {
return true;
}
}
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
{
if (willCoercionThrow(value)) {
error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value));
return testStringCoercion(value);
}
}
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
var RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true
};
var specialPropKeyWarningShown;
var specialPropRefWarningShown;
var didWarnAboutStringRefs;
{
didWarnAboutStringRefs = {};
}
function hasValidRef(config) {
{
if (hasOwnProperty.call(config, "ref")) {
var getter = Object.getOwnPropertyDescriptor(config, "ref").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== void 0;
}
function hasValidKey(config) {
{
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== void 0;
}
function warnIfStringRefCannotBeAutoConverted(config, self) {
{
if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
if (!didWarnAboutStringRefs[componentName]) {
error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
didWarnAboutStringRefs[componentName] = true;
}
}
}
}
function defineKeyPropWarningGetter(props, displayName) {
{
var warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
}
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function() {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type,
key,
ref,
props,
// Record the component responsible for creating this element.
_owner: owner
};
{
element._store = {};
Object.defineProperty(element._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: false
});
Object.defineProperty(element, "_self", {
configurable: false,
enumerable: false,
writable: false,
value: self
});
Object.defineProperty(element, "_source", {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
function jsxDEV(type, config, maybeKey, source, self) {
{
var propName;
var props = {};
var key = null;
var ref = null;
if (maybeKey !== void 0) {
{
checkKeyStringCoercion(maybeKey);
}
key = "" + maybeKey;
}
if (hasValidKey(config)) {
{
checkKeyStringCoercion(config.key);
}
key = "" + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config, self);
}
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === void 0) {
props[propName] = defaultProps[propName];
}
}
}
if (key || ref) {
var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
}
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement$1(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
}
}
}
var propTypesMisspellWarningShown;
{
propTypesMisspellWarningShown = false;
}
function isValidElement(object) {
{
return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
}
function getDeclarationErrorAddendum() {
{
if (ReactCurrentOwner$1.current) {
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
if (name) {
return "\n\nCheck the render method of `" + name + "`.";
}
}
return "";
}
}
function getSourceInfoErrorAddendum(source) {
{
if (source !== void 0) {
var fileName = source.fileName.replace(/^.*[\\\/]/, "");
var lineNumber = source.lineNumber;
return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
}
return "";
}
}
var ownerHasKeyUseWarning = {};
function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
if (!info) {
var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name;
if (parentName) {
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
}
}
return info;
}
}
function validateExplicitKey(element, parentType) {
{
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
var childOwner = "";
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
}
setCurrentlyValidatingElement$1(element);
error('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
setCurrentlyValidatingElement$1(null);
}
}
function validateChildKeys(node, parentType) {
{
if (typeof node !== "object") {
return;
}
if (isArray(node)) {
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
if (node._store) {
node._store.validated = true;
}
} else if (node) {
var iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === "function") {
if (iteratorFn !== node.entries) {
var iterator = iteratorFn.call(node);
var step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
}
function validatePropTypes(element) {
{
var type = element.type;
if (type === null || type === void 0 || typeof type === "string") {
return;
}
var propTypes;
if (typeof type === "function") {
propTypes = type.propTypes;
} else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
var name = getComponentNameFromType(type);
checkPropTypes(propTypes, element.props, "prop", name, element);
} else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
var _name = getComponentNameFromType(type);
error("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown");
}
if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) {
error("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
}
}
}
function validateFragmentProps(fragment) {
{
var keys = Object.keys(fragment.props);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key !== "children" && key !== "key") {
setCurrentlyValidatingElement$1(fragment);
error("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key);
setCurrentlyValidatingElement$1(null);
break;
}
}
if (fragment.ref !== null) {
setCurrentlyValidatingElement$1(fragment);
error("Invalid attribute `ref` supplied to `React.Fragment`.");
setCurrentlyValidatingElement$1(null);
}
}
}
var didWarnAboutKeySpread = {};
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
{
var validType = isValidElementType(type);
if (!validType) {
var info = "";
if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
}
var sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
var typeString;
if (type === null) {
typeString = "null";
} else if (isArray(type)) {
typeString = "array";
} else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />";
info = " Did you accidentally export a JSX literal instead of a component?";
} else {
typeString = typeof type;
}
error("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info);
}
var element = jsxDEV(type, props, key, source, self);
if (element == null) {
return element;
}
if (validType) {
var children = props.children;
if (children !== void 0) {
if (isStaticChildren) {
if (isArray(children)) {
for (var i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
}
} else {
validateChildKeys(children, type);
}
}
}
{
if (hasOwnProperty.call(props, "key")) {
var componentName = getComponentNameFromType(type);
var keys = Object.keys(props).filter(function(k) {
return k !== "key";
});
var beforeExample = keys.length > 0 ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
var afterExample = keys.length > 0 ? "{" + keys.join(": ..., ") + ": ...}" : "{}";
error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
}
function jsxWithValidationStatic(type, props, key) {
{
return jsxWithValidation(type, props, key, true);
}
}
function jsxWithValidationDynamic(type, props, key) {
{
return jsxWithValidation(type, props, key, false);
}
}
var jsx = jsxWithValidationDynamic;
var jsxs = jsxWithValidationStatic;
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.jsx = jsx;
exports.jsxs = jsxs;
})();
}
}
});
// node_modules/react/jsx-runtime.js
var require_jsx_runtime = __commonJS({
"node_modules/react/jsx-runtime.js"(exports, module) {
if (false) {
module.exports = null;
} else {
module.exports = require_react_jsx_runtime_development();
}
}
});
export default require_jsx_runtime();
/*! Bundled license information:
react/cjs/react-jsx-runtime.development.js:
(**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=react_jsx-runtime.js.map
File diff suppressed because one or more lines are too long
@@ -24,6 +24,7 @@
import { loadAll, add, update, remove, seedIfEmpty } from './store.mjs';
import { createHash } from 'node:crypto';
import { currentQuestions, calculate, expandResult, SECTIONS, AXIS_NAMES, ADAPT_LABELS, PROFILES } from './opcTest.js';
// 模拟账号(写死;迁移后由真实用户库替代)
const CREDENTIALS = { username: 'pine', password: '123456' };
@@ -219,6 +220,38 @@ export async function mockApi(req, res, next) {
return json(res, 200, { ok: true, username: payload.username });
}
/* ---------------- OPC 测评:题目下发 + 计分(测评逻辑与评语全在后端) ---------------- */
if (resName === 'tests' && sub === 'opc') {
// GET /api/tests/opc/questions?version=quick|full —— 下发题目与渲染元数据
if (rest[2] === 'questions' && method === 'GET') {
const q = new URLSearchParams((req.url || '').split('?')[1] || '');
const version = q.get('version') === 'quick' ? 'quick' : 'full';
return json(res, 200, {
ok: true,
version,
questions: currentQuestions(version).map((x) => ({ id: x.id, part: x.part, question: x.question, A: x.A, B: x.B })),
sections: SECTIONS,
axisNames: AXIS_NAMES,
dimLabels: ADAPT_LABELS,
disclaimer: PROFILES.disclaimer,
quickNote: PROFILES.quickNote
});
}
// POST /api/tests/opc/calculate { answers, version } —— 后端计分 + 返回完整报告/评语
if (rest[2] === 'calculate' && method === 'POST') {
const body = (await readBody(req)) || {};
const version = body.version === 'quick' ? 'quick' : 'full';
const answers = body.answers && typeof body.answers === 'object' ? body.answers : {};
try {
const result = expandResult(calculate(answers, version));
return json(res, 200, { ok: true, result });
} catch (e) {
return json(res, 400, { ok: false, error: '测评计算失败,请稍后重试' });
}
}
return json(res, 404, { ok: false, error: 'Not Found' });
}
/* ---------------- 公开:提交预约 / 报名(报名即注册/登录) ---------------- */
if (resName === 'bookings' && method === 'POST' && !id) {
const body = (await readBody(req)) || {};
@@ -76,3 +76,17 @@ export function calculate(answers, version = 'full') {
return { version, answeredCount: Object.keys(answers).filter((id) => ids.includes(Number(id))).length, typeCode, persona, adaptIndex, adaptLevel, adaptDims, weakestDims, axesDetail, tracks, roles, tools };
}
/**
* 展开报告:把 tracks / roles / tools 从「code + 票数」补全为包含全部评语/描述的完整对象,
* 这样客户端渲染报告时无需再持有 PROFILES 本地副本(评语全部来自后端)。
*/
export function expandResult(r) {
const find = (list, code) => list.find((x) => x.code === code) || {};
return {
...r,
tracks: (r.tracks || []).map((t) => ({ ...t, ...find(PROFILES.tracks, t.code) })),
roles: (r.roles || []).map((x) => ({ ...x, ...find(PROFILES.roles, x.code) })),
tools: (r.tools || []).map((x) => ({ ...x, ...find(PROFILES.toolModes, x.code) }))
};
}
@@ -8,5 +8,15 @@
"needPark": false,
"hasStaff": true,
"stepsCount": 6
},
{
"id": "LMT2OHYUQF",
"createdAt": "2026-08-21T08:19:39.314Z",
"username": "",
"region": "昆明",
"status": "none",
"needPark": true,
"hasStaff": false,
"stepsCount": 6
}
]
@@ -16,5 +16,23 @@
"subsidiesCount": 4,
"loansCount": 2,
"summary": "综合你的身份、行业、地区与资金情况,为你匹配了 3 项适配政策、4 项补贴线索、2 项贷款参考。"
},
{
"id": "PMT2OHOWEI",
"createdAt": "2026-08-21T08:19:26.414Z",
"username": "",
"answers": {
"status": "unemployed",
"industry": [
"tourism"
],
"region": "other",
"entity": "none",
"capital": "lowcost"
},
"policiesCount": 3,
"subsidiesCount": 3,
"loansCount": 2,
"summary": "综合你的身份、行业、地区与资金情况,为你匹配了 3 项适配政策、3 项补贴线索、2 项贷款参考。"
}
]
@@ -1 +1,17 @@
[]
[
{
"id": "TMT2OH152X",
"createdAt": "2026-08-21T08:18:55.622Z",
"username": "",
"typeCode": "EVRP",
"persona": "资源整合者",
"adaptIndex": 67,
"adaptLevel": "mid",
"tracks": [
"CONTENT",
"CROSS",
"DIGITAL"
],
"version": "quick"
}
]
@@ -22,14 +22,16 @@
> 以下模块为**纯 JS 逻辑**,web 与小程序各自维护一份,**任何改动两端必须同步**(禁止只改一端)。
| 共享模块 | web 位置 | 小程序位置 | 内容 |
|---------|---------|-----------|------|
| OPC 测评题库 + 计分 | `website/src/data/opcTest.js` | `miniprogram/src/utils/opcTest.js` | 30/60 题、6 维计分、类型码、赛道/角色/工具映射、报告 |
| 共享模块 | 位置(单一数据源) | 使用端 | 内容 |
|---------|-------------------|--------|------|
| OPC 测评题库 + 计分 + 评语 | **后端** `website/server/opcTest.js`(题目/计分/评语唯一权威) | web(待迁)、小程序通过 `GET /api/tests/opc/questions` + `POST /api/tests/opc/calculate` 取题目与完整报告 | 30/60 题、6 维计分、类型码、赛道/角色/工具映射、16 人设评语、报告 |
| 政策测评题库 + 匹配 | `website/src/data/policyTest.js` | `miniprogram/src/utils/policyTest.js` | 5 题、computePolicy(政策/补贴/贷款匹配) |
| 启动流程步骤库 + 生成 | `website/src/data/startPlan.js` | `miniprogram/src/utils/startPlan.js` | 地区/状态/园区/社保 → buildPlan 步骤 |
| 报名字段契约 | `website/src/data/booking.js` | `miniprogram/src/utils/booking.js` | phone/code/name/status/want/topics/question/source |
| 政策数据(知识库) | `知识库/02_云南OPC政策.md` | 小程序引用同一数据源 | 政策/补贴/贷款「以官方最新为准」 |
> ★ 架构变更登记(2026-08):**OPC 测评逻辑移至后端**。`server/opcTest.js` 为唯一权威(题目/计分/评语),小程序已改为后端驱动并删除本地 `opcTest.js`;**web 端 `src/data/opcTest.js` 尚未迁移,待办**——迁移后删除 web 本地副本,两端统一走后端。
---
## 三、API 契约(前后端一致 ★)
@@ -42,6 +44,8 @@
| `POST /api/auth/phone-login` | 手机号+验证码登录/注册 | `{phone, code}` → `{token, username, name, isNew}` |
| `POST /api/bookings` | 报名(即注册/登录) | `{phone, code, name, ...}` → `{id, token, isNew}` |
| `POST /api/tests` | 上报测评 | `{typeCode, ...}` → `{id}` |
| `GET /api/tests/opc/questions?version=quick\|full` | 下发测评题目 + 章节/轴元数据 | → `{ok, version, questions, sections, axisNames, dimLabels, disclaimer, quickNote}` |
| `POST /api/tests/opc/calculate` | 提交答案,后端计分 + 返回完整报告/评语 | `{answers, version}` → `{ok, result}`(result 含 typeCode/persona/adaptIndex/adaptLevel/6维/4轴/赛道/角色/工具/下一步) |
| `POST /api/policy-logs` / `POST /api/plan-logs` | 上报政策/流程 | `{username, answers/...}` → `{id}` |
| `GET /api/events?current=1` | 当期排期 | → `{next, free, salon}` |