feat(miniprogram): OSS直链上传+资讯互动+详情页重设计

- API 基址支持 TARO_APP_API_BASE 覆盖;头像/入驻资料上传带 dir 业务目录(OSS 路径规范)
- 资讯详情:真实点赞(计数)/微信分享(openType=share+useShareAppMessage)/留言;封面 3.35:1 裁切;顶部色条修复
- 发布者徽章(官方/园区/OPC 三款渐变胶囊);头像有则显示无则不留位
- 动作按钮纯图标化并入留言标题行;留言区胶囊输入+渐变发送键
This commit is contained in:
Pine
2026-08-28 17:20:10 +08:00
parent d23b6976eb
commit c6def26b52
14 changed files with 68 additions and 30 deletions
+4 -1
View File
@@ -11,7 +11,10 @@ const config = {
alias: { alias: {
'@': path.resolve(__dirname, '..', 'src') '@': path.resolve(__dirname, '..', 'src')
}, },
defineConstants: {}, defineConstants: {
// 构建时可覆盖 API 基址:TARO_APP_API_BASE=http://192.168.1.3:8090 yarn dev:weapp
'process.env.TARO_APP_API_BASE': JSON.stringify(process.env.TARO_APP_API_BASE || ''),
},
copy: { patterns: [], options: {} }, copy: { patterns: [], options: {} },
framework: 'react', framework: 'react',
compiler: { type: 'webpack5', prebundle: { enable: false } }, compiler: { type: 'webpack5', prebundle: { enable: false } },
+2 -2
View File
@@ -1,7 +1,7 @@
export default { export default {
pages: [ pages: [
'pages/booking/index',
'pages/index/index', 'pages/index/index',
'pages/booking/index',
'pages/test/index', 'pages/test/index',
'pages/policy/index', 'pages/policy/index',
'pages/plan/index', 'pages/plan/index',
@@ -36,9 +36,9 @@ export default {
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
borderStyle: 'white', borderStyle: 'white',
list: [ list: [
{ pagePath: 'pages/index/index', text: '首页' },
{ pagePath: 'pages/booking/index', text: '活动' }, { pagePath: 'pages/booking/index', text: '活动' },
{ pagePath: 'pages/test/index', text: '测评' }, { pagePath: 'pages/test/index', text: '测评' },
{ pagePath: 'pages/index/index', text: '首页' },
{ pagePath: 'pages/content/index', text: '资讯' }, { pagePath: 'pages/content/index', text: '资讯' },
{ pagePath: 'pages/mine/index', text: '我的' } { pagePath: 'pages/mine/index', text: '我的' }
] ]
+17 -1
View File
@@ -116,7 +116,9 @@ page { -ms-overflow-style: none; scrollbar-width: none; }
} }
.btn-ghost { .btn-ghost {
background: rgba(255, 255, 255, 0.7); background: rgba(255, 255, 255, 0.55);
backdrop-filter: blur(20px) saturate(160%);
-webkit-backdrop-filter: blur(20px) saturate(160%);
color: #5a6aa3; color: #5a6aa3;
border: 1px solid rgba(99, 132, 255, 0.22); border: 1px solid rgba(99, 132, 255, 0.22);
border-radius: 999px; border-radius: 999px;
@@ -130,6 +132,15 @@ page { -ms-overflow-style: none; scrollbar-width: none; }
border: none; border: none;
} }
/* 禁用态:微信原生 button[disabled] 浅底浅字会盖掉单类样式(特异度更高),这里显式压回可读配色 */
.btn-main[disabled], .btn-main.button-disabled,
.btn-ghost[disabled], .btn-ghost.button-disabled {
background: rgba(120, 140, 220, 0.14);
color: #8b97c4;
border: 1px solid rgba(99, 132, 255, 0.12);
box-shadow: none;
}
.section-head { .section-head {
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;
@@ -378,3 +389,8 @@ page { -ms-overflow-style: none; scrollbar-width: none; }
border-color: rgba(99, 132, 255, 0.16) !important; border-color: rgba(99, 132, 255, 0.16) !important;
color: #3a4672 !important; color: #3a4672 !important;
} }
/* 发布者徽章图标(官方/园区/OPC) */
.ic-shield { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M12 3l8 3v5c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z'/%3E%3Cpath d='M9 12l2 2 4-4'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M12 3l8 3v5c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z'/%3E%3Cpath d='M9 12l2 2 4-4'/%3E%3C/svg%3E"); }
.ic-landmark { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M3 10l9-6 9 6'/%3E%3Cpath d='M5 10v9M9.5 10v9M14.5 10v9M19 10v9M3 20h18'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M3 10l9-6 9 6'/%3E%3Cpath d='M5 10v9M9.5 10v9M14.5 10v9M19 10v9M3 20h18'/%3E%3C/svg%3E"); }
.ic-users { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Ccircle cx='9' cy='8' r='3.5'/%3E%3Cpath d='M2.5 20a6.5 6.5 0 0 1 13 0'/%3E%3Cpath d='M15.5 5a3.5 3.5 0 0 1 0 6.6'/%3E%3Cpath d='M17.5 14.4a6.5 6.5 0 0 1 4 5.6'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Ccircle cx='9' cy='8' r='3.5'/%3E%3Cpath d='M2.5 20a6.5 6.5 0 0 1 13 0'/%3E%3Cpath d='M15.5 5a3.5 3.5 0 0 1 0 6.6'/%3E%3Cpath d='M17.5 14.4a6.5 6.5 0 0 1 4 5.6'/%3E%3C/svg%3E"); }
@@ -1,7 +1,8 @@
.sk { .sk {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
background: #1d1d24; /* 亮色玻璃底:白色半透明 + 流光(旧暗色 #1d1d24 在浅色页面上呈黑块,已废弃) */
background: rgba(255, 255, 255, 0.6);
border-radius: 8rpx; border-radius: 8rpx;
} }
.sk::after { .sk::after {
@@ -9,7 +10,7 @@
position: absolute; position: absolute;
top: 0; left: 0; right: 0; bottom: 0; top: 0; left: 0; right: 0; bottom: 0;
transform: translateX(-100%); transform: translateX(-100%);
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.06), transparent); background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.65), transparent);
animation: sk-shimmer 1.4s ease-in-out infinite; animation: sk-shimmer 1.4s ease-in-out infinite;
} }
.sk-round { border-radius: 50%; } .sk-round { border-radius: 50%; }
+1 -1
View File
@@ -129,7 +129,7 @@ export default function Booking() {
))} ))}
{loaded && filtered.map((e) => renderCard(e))} {loaded && filtered.map((e) => renderCard(e))}
</View> </View>
{loaded && filtered.length === 0 && !err && <Text className="muted">暂无该类型活动切换筛选看看</Text>} {loaded && filtered.length === 0 && !err && <Text className="muted bk-empty">暂无该类型活动切换筛选看看</Text>}
{err && <Text className="err-text">{err}</Text>} {err && <Text className="err-text">{err}</Text>}
</View> </View>
); );
+11 -4
View File
@@ -74,12 +74,12 @@
} }
.cell-tags { position: absolute; top: 14rpx; left: 14rpx; display: flex; gap: 8rpx; } .cell-tags { position: absolute; top: 14rpx; left: 14rpx; display: flex; gap: 8rpx; }
.ev-tag { .ev-tag {
font-size: 20rpx; font-weight: 700; color: #000; background: rgba(255,255,255,0.55); backdrop-filter: blur(20px) saturate(160%); -webkit-backdrop-filter: blur(20px) saturate(160%); font-size: 20rpx; font-weight: 700; color: #23315c; background: rgba(255,255,255,0.92);
border-radius: 999px; padding: 3rpx 14rpx; border-radius: 999px; padding: 3rpx 14rpx;
} }
.ev-tag.alt { background: rgba(91,140,255,0.92); color: #23315c; } .ev-tag.alt { background: #5b8cff; color: #fff; }
.ev-tag.mode { background: rgba(0,0,0,0.5); color: #23315c; } .ev-tag.mode { background: rgba(20,28,54,0.78); color: #fff; }
.ev-tag.mode.online { background: rgba(140,200,255,0.7); color: #0a0d22; } .ev-tag.mode.online { background: rgba(140,200,255,0.92); color: #0a1a3a; }
.cell-body { padding: 18rpx 18rpx 20rpx; } .cell-body { padding: 18rpx 18rpx 20rpx; }
.cell-name { .cell-name {
@@ -124,3 +124,10 @@
.mk-count-cell .num { font-family: 'BubbledotICG', monospace; font-size: 56rpx; color: #23315c; line-height: 1; } .mk-count-cell .num { font-family: 'BubbledotICG', monospace; font-size: 56rpx; color: #23315c; line-height: 1; }
.mk-count-cell .unit { font-size: 22rpx; color: #4a5580; } .mk-count-cell .unit { font-size: 22rpx; color: #4a5580; }
.mk-count-name { display: block; position: relative; z-index: 2; font-size: 30rpx; font-weight: 600; color: #23315c; } .mk-count-name { display: block; position: relative; z-index: 2; font-size: 30rpx; font-weight: 600; color: #23315c; }
/* 空状态提示:垂直留白 + 居中 */
.bk-empty {
display: block;
text-align: center;
padding: 120rpx 40rpx;
}
+5 -3
View File
@@ -5,6 +5,7 @@ import { useTabIndex } from '@/utils/tab';
import { isAuthed, getUser, logout, wxLogin, phoneLogin, bindPhone, sendCode, wxPhoneBind, updateProfile, getMe } from '@/utils/auth'; import { isAuthed, getUser, logout, wxLogin, phoneLogin, bindPhone, sendCode, wxPhoneBind, updateProfile, getMe } from '@/utils/auth';
import { getMyBookings, checkIn, parseIso, BK_STATUS_OPTIONS, BK_TOPIC_OPTIONS, BK_SOURCE_OPTIONS } from '@/utils/booking'; import { getMyBookings, checkIn, parseIso, BK_STATUS_OPTIONS, BK_TOPIC_OPTIONS, BK_SOURCE_OPTIONS } from '@/utils/booking';
import { getMyAdmission } from '@/utils/parks'; import { getMyAdmission } from '@/utils/parks';
import { API_BASE } from '@/utils/api';
import Skeleton from '@/components/skeleton'; import Skeleton from '@/components/skeleton';
import CountdownBox from '@/components/CountdownBox'; import CountdownBox from '@/components/CountdownBox';
import './index.scss'; import './index.scss';
@@ -231,9 +232,10 @@ export default function Mine() {
try { try {
const token = Taro.getStorageSync('pine_token') || ''; const token = Taro.getStorageSync('pine_token') || '';
const up = await Taro.uploadFile({ const up = await Taro.uploadFile({
url: 'https://opc.pinesound.cn/api/upload', url: `${API_BASE}/api/upload`,
filePath: tempPath, filePath: tempPath,
name: 'file', name: 'file',
formData: { dir: 'avatar' },
header: { Authorization: `Bearer ${token}` } header: { Authorization: `Bearer ${token}` }
}); });
const data = JSON.parse(up.data || '{}'); const data = JSON.parse(up.data || '{}');
@@ -451,7 +453,7 @@ export default function Mine() {
</View> </View>
<View className="card mk-act"> <View className="card mk-act">
<Button className="btn-main mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/scan/index' })}>扫码接单</Button> <Button className="btn-main mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/scan/index' })}>扫码接单</Button>
<Text className="mk-hint">任务广场请到 Web 端或桌面端查看</Text> <Text className="muted mk-center-hint">任务广场请到 Web 端或桌面端查看</Text>
</View> </View>
{/* 我的园区:未入驻 → 申请入驻 + OPC认证;已入驻 → 园区信息 + OPC认证 + 转园 */} {/* 我的园区:未入驻 → 申请入驻 + OPC认证;已入驻 → 园区信息 + OPC认证 + 转园 */}
@@ -466,7 +468,7 @@ export default function Mine() {
if (!admitted) { if (!admitted) {
return ( return (
<View className="card mk-actions"> <View className="card mk-actions">
<Text className="mk-hint">暂未入驻园区可申请入驻并完成 OPC 认证</Text> <Text className="muted mk-center-hint">暂未入驻园区可申请入驻并完成 OPC 认证</Text>
<View className="mk-btnrow"> <View className="mk-btnrow">
<Button className="btn-main mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/park-apply/index' })}>申请入驻</Button> <Button className="btn-main mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/park-apply/index' })}>申请入驻</Button>
<Button className="btn-ghost mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/cert-apply/index' })}>OPC 认证</Button> <Button className="btn-ghost mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/cert-apply/index' })}>OPC 认证</Button>
+7
View File
@@ -403,3 +403,10 @@
.mk-btnrow { display: flex; gap: 16rpx; margin-top: 18rpx; flex-wrap: wrap; } .mk-btnrow { display: flex; gap: 16rpx; margin-top: 18rpx; flex-wrap: wrap; }
.mk-hint { margin-top: 12rpx; font-size: 24rpx; color: #8a8a93; display: block; line-height: 1.5; } .mk-hint { margin-top: 12rpx; font-size: 24rpx; color: #8a8a93; display: block; line-height: 1.5; }
.mk-hint:first-child { margin-top: 0; } .mk-hint:first-child { margin-top: 0; }
/* 提示文案对齐「还没有报名记录。」样式:muted 尺寸 + 卡内居中 */
.mk-center-hint {
display: block;
text-align: center;
padding: 16rpx 0;
}
+3 -3
View File
@@ -126,9 +126,9 @@ export default function ScanLogin() {
</> </>
) : ( ) : (
<> <>
<Text className="sl-title">确认扫码操作</Text> <Text className="sl-title">确认登录</Text>
<Text className="sl-desc">确认使用当前微信账号在你操作的端口完成登录 / 绑定</Text> <Text className="sl-desc">确认使用当前账号完成登录或绑定</Text>
{scene && <Text className="sl-scene"> #{scene.slice(0, 12)}</Text>} {scene && <Text className="sl-scene">确认是您正在操作</Text>}
</> </>
)} )}
</View> </View>
+2 -2
View File
@@ -108,7 +108,7 @@ export default function Test() {
<Text className="ot-en">Start</Text> <Text className="ot-en">Start</Text>
</View> </View>
<Text className="ot-intro"> <Text className="ot-intro">
一个人也可以是一家公司测评会回答四件事我适不适合做 OPC · 该选什么赛道 · 适合什么角色 · 怎么和 AI 协作完成约 510 分钟结果即时生成 一个人也可以是一家公司510 分钟看清你的 OPC 赛道角色与 AI 协作方式
</Text> </Text>
<View className="ot-version-grid"> <View className="ot-version-grid">
<View className={`ot-vcard${version === 'quick' ? ' sel' : ''}`} onClick={() => setVersion('quick')}> <View className={`ot-vcard${version === 'quick' ? ' sel' : ''}`} onClick={() => setVersion('quick')}>
@@ -161,7 +161,7 @@ export default function Test() {
<View className="quiz-body"> <View className="quiz-body">
<View className="ot-qcard one"> <View className="ot-qcard one">
<View className="ot-qnum"> <View className="ot-qnum">
<Text> {q.id} </Text> <Text> {current + 1} </Text>
{ans && <Text className="ot-check">已答</Text>} {ans && <Text className="ot-check">已答</Text>}
</View> </View>
<Text className="ot-qtext">{q.question}</Text> <Text className="ot-qtext">{q.question}</Text>
+8 -7
View File
@@ -48,9 +48,9 @@
/* ---------- 欢迎 ---------- */ /* ---------- 欢迎 ---------- */
.ot-intro { .ot-intro {
display: block; display: block;
font-size: 28rpx; font-size: 24rpx; /* 辅助说明降一档,减少视觉比重 */
color: #4a5580; color: #8a94b8;
line-height: 1.75; line-height: 1.7;
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
@@ -321,7 +321,7 @@
.ot-code { .ot-code {
font-family: 'BubbledotICG', monospace; font-family: 'BubbledotICG', monospace;
font-size: 60rpx; font-size: 60rpx;
color: #23315c; color: #dfe6ff; /* 深色 hero 卡上提亮,保证对比度 */
letter-spacing: 4rpx; letter-spacing: 4rpx;
line-height: 1; line-height: 1;
} }
@@ -396,9 +396,10 @@
.ot-weak { .ot-weak {
font-size: 24rpx; font-size: 24rpx;
color: #f5d9a0; color: #8a5a00; /* 浅黄底上换深琥珀,保证对比度 */
background: rgba(245, 217, 160, 0.08); font-weight: 600;
border: 1rpx dashed rgba(245, 217, 160, 0.4); background: rgba(245, 200, 90, 0.28);
border: 1rpx dashed rgba(190, 140, 30, 0.55);
border-radius: 12px; border-radius: 12px;
padding: 16rpx 22rpx; padding: 16rpx 22rpx;
margin-bottom: 8rpx; margin-bottom: 8rpx;
+1 -1
View File
@@ -4,7 +4,7 @@ import { cached } from './cache';
// API 基址 // API 基址
// - 开发:指向本地 web 后端(project.config.json 需 urlCheck:false // - 开发:指向本地 web 后端(project.config.json 需 urlCheck:false
// - 生产:真实备案 HTTPS 域名(小程序要求) // - 生产:真实备案 HTTPS 域名(小程序要求)
const API_BASE = 'https://opc.pinesound.cn'; // 云超服 FastAPI 后端(备案 HTTPS 域名) export const API_BASE = process.env.TARO_APP_API_BASE || 'https://opc.pinesound.cn'; // 云超服 FastAPI 后端(备案 HTTPS 域名;开发可用 TARO_APP_API_BASE 覆盖
/** /**
* 统一请求封装(对齐 web 端 fetch 契约) * 统一请求封装(对齐 web 端 fetch 契约)
+2 -2
View File
@@ -9,8 +9,8 @@ export const MK_HERO = {
headline: ['ONE PERSON', 'DESIGNED TO EVOLVE'], headline: ['ONE PERSON', 'DESIGNED TO EVOLVE'],
subhead: subhead:
'我们落地在昆明市大学生创业园。每周有免费公益课和线下沙龙,讲透「一个人 + AI 怎么开一家公司」。先来听一场,交个朋友。', '我们落地在昆明市大学生创业园。每周有免费公益课和线下沙龙,讲透「一个人 + AI 怎么开一家公司」。先来听一场,交个朋友。',
ctaPrimary: { label: '预约公益课 / 沙龙', tab: '/pages/booking/index' }, ctaPrimary: { label: '预约', tab: '/pages/booking/index' },
ctaSecondary: { label: '先做 OPC 测评', tab: '/pages/test/index' } ctaSecondary: { label: '测评', tab: '/pages/test/index' }
}; };
export const MK_TRUST = [ export const MK_TRUST = [
+2 -1
View File
@@ -1,7 +1,7 @@
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import { request } from './api'; import { request } from './api';
const API_BASE = 'https://opc.pinesound.cn'; import { API_BASE } from './api';
/** 平台可见园区列表(选择园区用,公开) */ /** 平台可见园区列表(选择园区用,公开) */
export async function getParks() { export async function getParks() {
@@ -29,6 +29,7 @@ export async function uploadParkDoc(filePath, name = 'file') {
url: `${API_BASE}/api/upload`, url: `${API_BASE}/api/upload`,
filePath, filePath,
name, name,
formData: { dir: 'park-admission' },
header: { Authorization: `Bearer ${token}` }, header: { Authorization: `Bearer ${token}` },
}); });
const data = JSON.parse(up.data || '{}'); const data = JSON.parse(up.data || '{}');