Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d005a0bf3c | ||
|
|
03fe4ddf9d | ||
|
|
cfe4c82c90 | ||
|
|
c3ceb0dd0f | ||
|
|
9add23e019 | ||
|
|
2199887c07 | ||
|
|
56dbd7f115 | ||
|
|
d10f213573 | ||
|
|
a968945057 | ||
|
|
971a627288 | ||
|
|
c37d5abac4 | ||
|
|
ebc463864b | ||
|
|
ab140ab05e | ||
|
|
894f52e875 | ||
|
|
a133d5d85d | ||
|
|
2c238d6599 | ||
|
|
6ce58bcd85 | ||
|
|
1ca87f8d72 | ||
|
|
5bf6ea01f6 | ||
|
|
58a7197d3e | ||
|
|
90be4bf60b | ||
|
|
1c7b22a72f | ||
|
|
16d301f302 | ||
|
|
00ce6d6875 | ||
|
|
bdd8e69ec8 | ||
|
|
8bbd6f7885 | ||
|
|
245c52075d | ||
|
|
c836084fd3 | ||
|
|
a010483bdc | ||
|
|
e20374e5d8 | ||
|
|
037e5ba450 | ||
|
|
d5b0ab4709 | ||
|
|
079e50006d | ||
|
|
2c0b9c5afa | ||
|
|
dd28bba354 | ||
|
|
a797743aa4 | ||
|
|
abb4aced1c | ||
|
|
4ee8a8e98b | ||
|
|
7ddb4882e3 | ||
|
|
c2b7018a22 | ||
|
|
57a7c415a1 | ||
|
|
582c7cf0c6 | ||
|
|
2425fb60d0 | ||
|
|
22a371a733 | ||
|
|
81d6a38e26 | ||
|
|
64d760af1a | ||
|
|
9ba53b6145 | ||
|
|
30cf33c546 | ||
|
|
e1fe818dce | ||
|
|
31312ae975 |
@@ -22,5 +22,3 @@ unpackage/dist
|
||||
*.sw?
|
||||
|
||||
TUICallKit
|
||||
# 本工程的通话源码复用仓库内 ../wx/TUICallKit;保留相对软链接以便干净克隆后可直接构建。
|
||||
!TUICallKit
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../wx/TUICallKit
|
||||
@@ -63,7 +63,7 @@
|
||||
"requiredBackgroundModes" : [ "audio" ],
|
||||
"plugins" : {
|
||||
"WechatSI" : {
|
||||
"version" : "0.3.9",
|
||||
"version" : "0.3.5",
|
||||
"provider" : "wx069ba97219f66d99"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
"mp-weixin": {
|
||||
"usingPlugins": {
|
||||
"WechatSI": {
|
||||
"version": "0.3.9",
|
||||
"version": "0.3.5",
|
||||
"provider": "wx069ba97219f66d99"
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@
|
||||
"mp-weixin": {
|
||||
"usingPlugins": {
|
||||
"WechatSI": {
|
||||
"version": "0.3.9",
|
||||
"version": "0.3.5",
|
||||
"provider": "wx069ba97219f66d99"
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,7 @@
|
||||
"mp-weixin": {
|
||||
"usingPlugins": {
|
||||
"WechatSI": {
|
||||
"version": "0.3.9",
|
||||
"version": "0.3.5",
|
||||
"provider": "wx069ba97219f66d99"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
## 已接入能力
|
||||
|
||||
- 复用主小程序 `token` 与微信小程序登录,不创建第二套游戏账号。
|
||||
- 按周一日期自动分配最多 7 人的混合同行组,前期不区分男女。
|
||||
- 本周已经进入旧男女组的账号,下次读取榜单时保留成绩并自动迁入混合组。
|
||||
- 按周一日期、性别自动分配最多 7 人的同行组。
|
||||
- 首次入组使用数据库分配锁,多个用户同时进入也不会重复分组或超过 7 人。
|
||||
- 以真实平台昵称、头像、认糖数和本周最高分排序。
|
||||
- 每局用 `session_key` 上报绝对进度,断网重试不会重复加分。
|
||||
@@ -34,7 +33,7 @@
|
||||
|
||||
## 上线前检查
|
||||
|
||||
- 用不同账号进入,确认按进入顺序加入同一个最多 7 人的混合同行组。
|
||||
- 用男女各两个测试账号进入,确认被分入对应性别组。
|
||||
- 同一局重复提交相同 `session_key`,确认周认糖数不重复增加。
|
||||
- 断网完成几次消除,再联网打开榜单,确认成绩补传。
|
||||
- 分享给另一个微信账号,确认能直接进入游戏且链接中没有用户 ID。
|
||||
|
||||
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 55 KiB |
@@ -1,577 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// 识糖小课堂棋盘压力测试:复刻生产版的 5x4 棋盘、掉落、洗牌和渐进换组规则。
|
||||
// 运行:node tongji/endless-game/build/stress-game.mjs --runs 10000
|
||||
|
||||
const ROWS = 5
|
||||
const COLS = 4
|
||||
const HIGH_CAP = 3
|
||||
const MAX_CASCADE_WAVES = 3
|
||||
const DANGER_DROP_CHANCE = 0.1
|
||||
const COLUMN_GROUP_BIAS = 0.72
|
||||
|
||||
const FOOD_GROUPS = [
|
||||
{ title: '中式早餐', safe: ['egg', 'cucumber', 'corn', 'grainMantou'], danger: 'congee' },
|
||||
{ title: '家常粥餐', safe: ['celery', 'mushroom', 'chicken', 'taro'], danger: 'centuryCongee' },
|
||||
{ title: '粗粮早餐', safe: ['broccoli', 'milk', 'brownRice', 'potato'], danger: 'milletCongee' },
|
||||
{ title: '水果饮品', safe: ['apple', 'strawberry', 'banana', 'mango'], danger: 'orangeJuice' },
|
||||
{ title: '日常饮品', safe: ['milk', 'tea', 'blackCoffee', 'coffee'], danger: 'soda' },
|
||||
{ title: '下午茶甜饮', safe: ['milk', 'tea', 'apple', 'strawberry'], danger: 'milkTea' },
|
||||
{ title: '南方汤粉', safe: ['pepper', 'greenBean', 'shrimp', 'udon'], danger: 'riceNoodleSoup' },
|
||||
{ title: '北方面食', safe: ['onion', 'celery', 'corn', 'grainMantou'], danger: 'noodleSoup' },
|
||||
{ title: '家常餐桌', safe: ['tofu', 'spinach', 'carrot', 'fish'], danger: 'redBeanCongee' }
|
||||
]
|
||||
|
||||
const HIGH_KEYS = new Set(FOOD_GROUPS.map(group => group.danger))
|
||||
const SAFE_KEYS = new Set(FOOD_GROUPS.flatMap(group => group.safe))
|
||||
|
||||
function readNumberArg(name, fallback) {
|
||||
const index = process.argv.indexOf(name)
|
||||
if (index < 0) return fallback
|
||||
const value = Number(process.argv[index + 1])
|
||||
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback
|
||||
}
|
||||
|
||||
const RUNS = readNumberArg('--runs', 10000)
|
||||
const TURNS = readNumberArg('--turns', 80)
|
||||
const BASE_SEED = readNumberArg('--seed', 20260722)
|
||||
|
||||
function createRng(seed) {
|
||||
let state = seed >>> 0 || 1
|
||||
return () => {
|
||||
state ^= state << 13
|
||||
state ^= state >>> 17
|
||||
state ^= state << 5
|
||||
return (state >>> 0) / 4294967296
|
||||
}
|
||||
}
|
||||
|
||||
function cloneBoard(source) {
|
||||
return source.map(tile => tile ? { ...tile } : null)
|
||||
}
|
||||
|
||||
function tile(key) {
|
||||
return { key }
|
||||
}
|
||||
|
||||
function isAdjacent(a, b) {
|
||||
const ar = Math.floor(a / COLS)
|
||||
const ac = a % COLS
|
||||
const br = Math.floor(b / COLS)
|
||||
const bc = b % COLS
|
||||
return Math.abs(ar - br) + Math.abs(ac - bc) === 1
|
||||
}
|
||||
|
||||
function swap(source, a, b) {
|
||||
const next = cloneBoard(source)
|
||||
;[next[a], next[b]] = [next[b], next[a]]
|
||||
return next
|
||||
}
|
||||
|
||||
function shuffle(source, rng) {
|
||||
const result = cloneBoard(source)
|
||||
for (let i = result.length - 1; i > 0; i -= 1) {
|
||||
const j = Math.floor(rng() * (i + 1))
|
||||
;[result[i], result[j]] = [result[j], result[i]]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function findMatches(source) {
|
||||
const groups = []
|
||||
for (let row = 0; row < ROWS; row += 1) {
|
||||
let start = 0
|
||||
while (start < COLS) {
|
||||
let end = start + 1
|
||||
while (end < COLS && source[row * COLS + end]?.key === source[row * COLS + start]?.key) end += 1
|
||||
if (source[row * COLS + start] && end - start >= 3) {
|
||||
groups.push({ direction: 'row', indices: Array.from({ length: end - start }, (_, n) => row * COLS + start + n) })
|
||||
}
|
||||
start = end
|
||||
}
|
||||
}
|
||||
for (let col = 0; col < COLS; col += 1) {
|
||||
let start = 0
|
||||
while (start < ROWS) {
|
||||
let end = start + 1
|
||||
while (end < ROWS && source[end * COLS + col]?.key === source[start * COLS + col]?.key) end += 1
|
||||
if (source[start * COLS + col] && end - start >= 3) {
|
||||
groups.push({ direction: 'col', indices: Array.from({ length: end - start }, (_, n) => (start + n) * COLS + col) })
|
||||
}
|
||||
start = end
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
function enumerateMatchingMoves(source) {
|
||||
const moves = []
|
||||
for (let i = 0; i < source.length; i += 1) {
|
||||
const candidates = []
|
||||
if (i % COLS < COLS - 1) candidates.push(i + 1)
|
||||
if (i + COLS < source.length) candidates.push(i + COLS)
|
||||
for (const j of candidates) {
|
||||
const copy = swap(source, i, j)
|
||||
const groups = findMatches(copy)
|
||||
if (!groups.length) continue
|
||||
const indices = new Set(groups.flatMap(group => group.indices))
|
||||
const hitsHigh = [...indices].some(index => HIGH_KEYS.has(copy[index]?.key))
|
||||
moves.push({ from: i, to: j, hitsHigh, groups })
|
||||
}
|
||||
}
|
||||
return moves
|
||||
}
|
||||
|
||||
function safeMoves(source) {
|
||||
return enumerateMatchingMoves(source).filter(move => !move.hitsHigh)
|
||||
}
|
||||
|
||||
function wouldCreateMatchAt(source, index, key) {
|
||||
const row = Math.floor(index / COLS)
|
||||
const col = index % COLS
|
||||
const keyAt = target => target === index ? key : source[target]?.key
|
||||
let horizontal = 1
|
||||
for (let c = col - 1; c >= 0 && keyAt(row * COLS + c) === key; c -= 1) horizontal += 1
|
||||
for (let c = col + 1; c < COLS && keyAt(row * COLS + c) === key; c += 1) horizontal += 1
|
||||
if (horizontal >= 3) return true
|
||||
let vertical = 1
|
||||
for (let r = row - 1; r >= 0 && keyAt(r * COLS + col) === key; r -= 1) vertical += 1
|
||||
for (let r = row + 1; r < ROWS && keyAt(r * COLS + col) === key; r += 1) vertical += 1
|
||||
return vertical >= 3
|
||||
}
|
||||
|
||||
function openingKeys(state) {
|
||||
const [a, b, c, d] = state.activeSafeKeys
|
||||
const danger = FOOD_GROUPS[state.themeIndex].danger
|
||||
return [
|
||||
a, b, c, d,
|
||||
b, c, d, a,
|
||||
c, d, a, danger,
|
||||
d, a, danger, b,
|
||||
a, b, c, d
|
||||
]
|
||||
}
|
||||
|
||||
function forcedPlayableKeys(state) {
|
||||
const [a, b, c, d] = state.activeSafeKeys
|
||||
const danger = FOOD_GROUPS[state.themeIndex].danger
|
||||
return [
|
||||
d, a, danger, c,
|
||||
d, c, b, danger,
|
||||
c, a, b, a,
|
||||
d, d, a, a,
|
||||
b, d, c, b
|
||||
]
|
||||
}
|
||||
|
||||
function buildPlayableBoard(state, keys = openingKeys(state)) {
|
||||
let candidate = keys.map(tile)
|
||||
for (let attempt = 0; attempt < 240; attempt += 1) {
|
||||
if (!findMatches(candidate).length && safeMoves(candidate).length) return candidate
|
||||
candidate = shuffle(candidate, state.rng)
|
||||
}
|
||||
state.metrics.guaranteedFallbacks += 1
|
||||
return forcedPlayableKeys(state).map(tile)
|
||||
}
|
||||
|
||||
function makeStablePlayableBoard(state, source) {
|
||||
const tiles = source.filter(Boolean).map(item => ({ ...item }))
|
||||
if (tiles.length === ROWS * COLS) {
|
||||
for (let attempt = 0; attempt < 300; attempt += 1) {
|
||||
const candidate = shuffle(tiles, state.rng)
|
||||
if (!findMatches(candidate).length && safeMoves(candidate).length) return candidate
|
||||
}
|
||||
}
|
||||
state.metrics.stableFallbacks += 1
|
||||
return buildPlayableBoard(state)
|
||||
}
|
||||
|
||||
function queueSafeGroupTransition(state, keys) {
|
||||
const target = [...keys]
|
||||
const last = state.queuedSafeGroups[state.queuedSafeGroups.length - 1]
|
||||
if (!last || !last.every((key, index) => key === target[index])) {
|
||||
state.queuedSafeGroups.push(target)
|
||||
}
|
||||
startNextTransition(state)
|
||||
}
|
||||
|
||||
function startNextTransition(state) {
|
||||
if (state.transition) return false
|
||||
while (state.queuedSafeGroups.length) {
|
||||
const target = state.queuedSafeGroups[0]
|
||||
const slot = target.findIndex((key, index) => state.activeSafeKeys[index] !== key)
|
||||
if (slot < 0) {
|
||||
state.queuedSafeGroups.shift()
|
||||
continue
|
||||
}
|
||||
const from = state.activeSafeKeys[slot]
|
||||
const to = target[slot]
|
||||
state.activeSafeKeys[slot] = to
|
||||
state.transition = { from, to, slot, age: 0 }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function finishTransitionIfReady(state) {
|
||||
let changed = false
|
||||
while (state.transition) {
|
||||
const { from, to } = state.transition
|
||||
const remaining = state.board.filter(item => item?.key === from).length
|
||||
if (remaining > 2) break
|
||||
if (remaining) {
|
||||
state.board = state.board.map(item => item?.key === from ? { ...item, key: to } : item)
|
||||
state.metrics.tailConversions += remaining
|
||||
}
|
||||
state.transition = null
|
||||
changed = true
|
||||
startNextTransition(state)
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
function prepareTransitionForReshuffle(state) {
|
||||
const activeSet = new Set(state.activeSafeKeys)
|
||||
const counts = Object.fromEntries(state.activeSafeKeys.map(key => [key, 0]))
|
||||
state.board.forEach(item => {
|
||||
if (item && activeSet.has(item.key)) counts[item.key] += 1
|
||||
})
|
||||
let imported = 0
|
||||
const next = state.board.map(item => {
|
||||
if (!item || item.key === 'camel' || HIGH_KEYS.has(item.key)) return item ? { ...item } : item
|
||||
if (state.transition && item.key === state.transition.from) {
|
||||
imported += 1
|
||||
counts[state.transition.to] += 1
|
||||
return { ...item, key: state.transition.to }
|
||||
}
|
||||
if (activeSet.has(item.key)) return { ...item }
|
||||
const key = [...state.activeSafeKeys].sort((a, b) => counts[a] - counts[b])[0]
|
||||
counts[key] += 1
|
||||
imported += 1
|
||||
return { ...item, key }
|
||||
})
|
||||
state.board = next
|
||||
state.metrics.reshuffleImports += imported
|
||||
}
|
||||
|
||||
function randomFoodKey(state, snapshot, col) {
|
||||
const highs = snapshot.filter(item => item && HIGH_KEYS.has(item.key)).length
|
||||
if (highs < HIGH_CAP && state.rng() < DANGER_DROP_CHANCE) {
|
||||
return FOOD_GROUPS[state.themeIndex].danger
|
||||
}
|
||||
if (state.rng() < COLUMN_GROUP_BIAS) return state.activeSafeKeys[col % state.activeSafeKeys.length]
|
||||
return state.activeSafeKeys[Math.floor(state.rng() * state.activeSafeKeys.length)]
|
||||
}
|
||||
|
||||
function pickDropKey(state, snapshot, index, col) {
|
||||
for (let attempt = 0; attempt < 16; attempt += 1) {
|
||||
const key = randomFoodKey(state, snapshot, col)
|
||||
if (!wouldCreateMatchAt(snapshot, index, key)) return key
|
||||
}
|
||||
const fallback = [...state.activeSafeKeys, FOOD_GROUPS[state.themeIndex].danger]
|
||||
.find(key => !wouldCreateMatchAt(snapshot, index, key))
|
||||
return fallback || state.activeSafeKeys[(index + col) % state.activeSafeKeys.length]
|
||||
}
|
||||
|
||||
function collapseAndFill(state) {
|
||||
const next = Array(ROWS * COLS).fill(null)
|
||||
for (let col = 0; col < COLS; col += 1) {
|
||||
const existing = []
|
||||
for (let row = ROWS - 1; row >= 0; row -= 1) {
|
||||
const item = state.board[row * COLS + col]
|
||||
if (item) existing.push(item)
|
||||
}
|
||||
existing.forEach((item, offset) => {
|
||||
next[(ROWS - 1 - offset) * COLS + col] = item
|
||||
})
|
||||
}
|
||||
for (let row = 0; row < ROWS; row += 1) {
|
||||
for (let col = 0; col < COLS; col += 1) {
|
||||
const index = row * COLS + col
|
||||
if (!next[index]) next[index] = tile(pickDropKey(state, next, index, col))
|
||||
}
|
||||
}
|
||||
state.board = next
|
||||
}
|
||||
|
||||
function reshuffle(state) {
|
||||
prepareTransitionForReshuffle(state)
|
||||
let candidate = null
|
||||
for (let attempt = 0; attempt < 240; attempt += 1) {
|
||||
const shuffled = shuffle(state.board, state.rng)
|
||||
if (findMatches(shuffled).length || !safeMoves(shuffled).length) continue
|
||||
candidate = shuffled
|
||||
break
|
||||
}
|
||||
state.board = candidate || buildPlayableBoard(state, forcedPlayableKeys(state))
|
||||
finishTransitionIfReady(state)
|
||||
state.metrics.reshuffles += 1
|
||||
}
|
||||
|
||||
function advanceTheme(state) {
|
||||
const previous = FOOD_GROUPS[state.themeIndex]
|
||||
state.themeIndex = (state.themeIndex + 1) % FOOD_GROUPS.length
|
||||
const next = FOOD_GROUPS[state.themeIndex]
|
||||
state.board = state.board.map(item => item?.key === previous.danger ? { ...item, key: next.danger } : item)
|
||||
queueSafeGroupTransition(state, next.safe)
|
||||
state.metrics.themeChanges += 1
|
||||
}
|
||||
|
||||
function resolveMatches(state, initialGroups) {
|
||||
let groups = initialGroups
|
||||
let wave = 0
|
||||
let camelMatch = 0
|
||||
while (groups.length && wave < MAX_CASCADE_WAVES) {
|
||||
wave += 1
|
||||
const remove = new Set(groups.flatMap(group => group.indices))
|
||||
for (const group of groups) {
|
||||
if (group.indices.every(index => state.board[index]?.key === 'camel')) {
|
||||
camelMatch = Math.max(camelMatch, group.indices.length)
|
||||
}
|
||||
}
|
||||
remove.forEach(index => { state.board[index] = null })
|
||||
collapseAndFill(state)
|
||||
finishTransitionIfReady(state)
|
||||
groups = findMatches(state.board)
|
||||
const autoHigh = groups.some(group => group.indices.some(index => HIGH_KEYS.has(state.board[index]?.key)))
|
||||
if (autoHigh) {
|
||||
state.metrics.automaticHighAvoided += 1
|
||||
state.board = makeStablePlayableBoard(state, state.board)
|
||||
groups = []
|
||||
} else if (wave >= MAX_CASCADE_WAVES && groups.length) {
|
||||
state.metrics.cascadeCaps += 1
|
||||
state.board = makeStablePlayableBoard(state, state.board)
|
||||
groups = []
|
||||
}
|
||||
}
|
||||
state.metrics.maxCascade = Math.max(state.metrics.maxCascade, wave)
|
||||
if (camelMatch >= 3) advanceTheme(state)
|
||||
}
|
||||
|
||||
function useCamelTool(state) {
|
||||
const danger = FOOD_GROUPS[state.themeIndex].danger
|
||||
const targets = state.board
|
||||
.map((item, index) => item?.key === danger ? index : -1)
|
||||
.filter(index => index >= 0)
|
||||
if (!targets.length) return false
|
||||
const index = targets[Math.floor(state.rng() * targets.length)]
|
||||
state.board[index] = tile('camel')
|
||||
state.metrics.toolUses += 1
|
||||
const groups = findMatches(state.board)
|
||||
if (groups.length) resolveMatches(state, groups)
|
||||
return true
|
||||
}
|
||||
|
||||
function recordFailure(state, type, detail = '') {
|
||||
state.metrics.failures[type] = (state.metrics.failures[type] || 0) + 1
|
||||
if (state.metrics.samples.length < 20) {
|
||||
state.metrics.samples.push({ run: state.run, turn: state.turn, type, detail, board: state.board.map(item => item?.key || '-') })
|
||||
}
|
||||
}
|
||||
|
||||
function validateState(state, expectStable = true) {
|
||||
if (state.board.length !== ROWS * COLS || state.board.some(item => !item?.key)) {
|
||||
recordFailure(state, 'invalid_board_size')
|
||||
return false
|
||||
}
|
||||
const currentDanger = FOOD_GROUPS[state.themeIndex].danger
|
||||
const highKeys = state.board.filter(item => HIGH_KEYS.has(item.key)).map(item => item.key)
|
||||
if (highKeys.some(key => key !== currentDanger)) recordFailure(state, 'wrong_theme_danger', highKeys.join(','))
|
||||
if (highKeys.length > HIGH_CAP) recordFailure(state, 'danger_cap_exceeded', String(highKeys.length))
|
||||
|
||||
const allowed = new Set([...state.activeSafeKeys, 'camel'])
|
||||
if (state.transition) allowed.add(state.transition.from)
|
||||
const stale = state.board.filter(item => !HIGH_KEYS.has(item.key) && !allowed.has(item.key)).map(item => item.key)
|
||||
if (stale.length) recordFailure(state, 'stale_food', stale.join(','))
|
||||
|
||||
const normalTypes = new Set(state.board.filter(item => SAFE_KEYS.has(item.key)).map(item => item.key))
|
||||
if (normalTypes.size > 5) recordFailure(state, 'too_many_normal_types', String(normalTypes.size))
|
||||
if (new Set(state.activeSafeKeys).size !== 4) recordFailure(state, 'duplicate_active_food')
|
||||
if (state.transition) {
|
||||
const remaining = state.board.filter(item => item.key === state.transition.from).length
|
||||
if (remaining <= 2) recordFailure(state, 'orphan_transition_tail', `${state.transition.from}:${remaining}`)
|
||||
}
|
||||
if (expectStable && findMatches(state.board).length) recordFailure(state, 'unresolved_match')
|
||||
return true
|
||||
}
|
||||
|
||||
function createMetrics() {
|
||||
return {
|
||||
runs: RUNS,
|
||||
turnsPerRun: TURNS,
|
||||
totalTurns: 0,
|
||||
validMoves: 0,
|
||||
reshuffles: 0,
|
||||
reshuffleImports: 0,
|
||||
guaranteedFallbacks: 0,
|
||||
stableFallbacks: 0,
|
||||
themeChanges: 0,
|
||||
toolUses: 0,
|
||||
tailConversions: 0,
|
||||
automaticHighAvoided: 0,
|
||||
cascadeCaps: 0,
|
||||
maxCascade: 0,
|
||||
unsafeHighMoveProbes: 0,
|
||||
maxTransitionAge: 0,
|
||||
maxQueuedThemes: 0,
|
||||
transitionDrainTurns: 0,
|
||||
unfinishedTransitions: 0,
|
||||
failures: {},
|
||||
samples: []
|
||||
}
|
||||
}
|
||||
|
||||
function runTargetedTailTests(metrics) {
|
||||
for (let groupIndex = 0; groupIndex < FOOD_GROUPS.length; groupIndex += 1) {
|
||||
const nextIndex = (groupIndex + 1) % FOOD_GROUPS.length
|
||||
const previousGroup = FOOD_GROUPS[groupIndex]
|
||||
const nextGroup = FOOD_GROUPS[nextIndex]
|
||||
// 相邻主题允许在相同槽位保留同一种食物。定向测试应选择真正退出下一组的食物,
|
||||
// 不能固定测试第 0 槽,否则“牛奶→牛奶”会被误报为尾巴未转换。
|
||||
const slot = previousGroup.safe.findIndex(key => !nextGroup.safe.includes(key))
|
||||
if (slot < 0) continue
|
||||
for (const remaining of [0, 1, 2]) {
|
||||
const state = {
|
||||
run: -1,
|
||||
turn: remaining,
|
||||
rng: createRng(BASE_SEED + groupIndex * 17 + remaining),
|
||||
metrics,
|
||||
themeIndex: nextIndex,
|
||||
activeSafeKeys: [...nextGroup.safe],
|
||||
transition: {
|
||||
from: previousGroup.safe[slot],
|
||||
to: nextGroup.safe[slot],
|
||||
slot,
|
||||
age: 0
|
||||
},
|
||||
queuedSafeGroups: [[...nextGroup.safe]],
|
||||
board: []
|
||||
}
|
||||
const fill = nextGroup.safe
|
||||
state.board = Array.from({ length: ROWS * COLS }, (_, index) => tile(fill[index % fill.length]))
|
||||
for (let index = 0; index < remaining; index += 1) state.board[index] = tile(state.transition.from)
|
||||
finishTransitionIfReady(state)
|
||||
if (state.board.some(item => item.key === state.transition?.from || item.key === previousGroup.safe[slot])) {
|
||||
recordFailure(state, 'targeted_tail_not_converted', `${groupIndex}:${remaining}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const metrics = createMetrics()
|
||||
runTargetedTailTests(metrics)
|
||||
|
||||
for (let run = 0; run < RUNS; run += 1) {
|
||||
const state = {
|
||||
run,
|
||||
turn: 0,
|
||||
rng: createRng(BASE_SEED + run * 2654435761),
|
||||
metrics,
|
||||
themeIndex: run % FOOD_GROUPS.length,
|
||||
activeSafeKeys: [...FOOD_GROUPS[run % FOOD_GROUPS.length].safe],
|
||||
transition: null,
|
||||
queuedSafeGroups: [],
|
||||
board: []
|
||||
}
|
||||
state.board = buildPlayableBoard(state)
|
||||
validateState(state)
|
||||
|
||||
let toolsUsed = 0
|
||||
for (let turn = 0; turn < TURNS; turn += 1) {
|
||||
state.turn = turn
|
||||
metrics.totalTurns += 1
|
||||
if (state.transition) {
|
||||
state.transition.age += 1
|
||||
metrics.maxTransitionAge = Math.max(metrics.maxTransitionAge, state.transition.age)
|
||||
}
|
||||
metrics.maxQueuedThemes = Math.max(metrics.maxQueuedThemes, state.queuedSafeGroups.length)
|
||||
|
||||
// 模拟玩家积攒到驼乳后替换高糖,但每局限制5次,避免测试人为填满棋盘。
|
||||
if (toolsUsed < 5 && turn > 0 && turn % 13 === 0 && useCamelTool(state)) {
|
||||
toolsUsed += 1
|
||||
finishTransitionIfReady(state)
|
||||
if (findMatches(state.board).length) state.board = makeStablePlayableBoard(state, state.board)
|
||||
}
|
||||
|
||||
let moves = safeMoves(state.board)
|
||||
if (!moves.length) {
|
||||
reshuffle(state)
|
||||
moves = safeMoves(state.board)
|
||||
}
|
||||
if (!moves.length) {
|
||||
recordFailure(state, 'dead_board_after_reshuffle')
|
||||
break
|
||||
}
|
||||
|
||||
// 额外探测“主动三消高糖会结束”的判定,不改变主模拟棋盘。
|
||||
const unsafe = enumerateMatchingMoves(state.board).find(move => move.hitsHigh)
|
||||
if (unsafe) {
|
||||
const probe = swap(state.board, unsafe.from, unsafe.to)
|
||||
const direct = findMatches(probe)
|
||||
if (!direct.some(group => group.indices.some(index => HIGH_KEYS.has(probe[index]?.key)))) {
|
||||
recordFailure(state, 'unsafe_high_probe_missed')
|
||||
}
|
||||
metrics.unsafeHighMoveProbes += 1
|
||||
}
|
||||
|
||||
const move = moves[Math.floor(state.rng() * moves.length)]
|
||||
state.board = swap(state.board, move.from, move.to)
|
||||
const groups = findMatches(state.board)
|
||||
if (!groups.length) {
|
||||
recordFailure(state, 'safe_move_without_match')
|
||||
break
|
||||
}
|
||||
if (groups.some(group => group.indices.some(index => HIGH_KEYS.has(state.board[index]?.key)))) {
|
||||
recordFailure(state, 'safe_move_hit_high')
|
||||
break
|
||||
}
|
||||
resolveMatches(state, groups)
|
||||
metrics.validMoves += 1
|
||||
|
||||
// 用较快节奏覆盖连续换组和排队逻辑,强度高于正常玩家。
|
||||
if ((turn + 1) % 16 === 0 && turn + 1 < TURNS) advanceTheme(state)
|
||||
finishTransitionIfReady(state)
|
||||
if (findMatches(state.board).length) state.board = makeStablePlayableBoard(state, state.board)
|
||||
if (!safeMoves(state.board).length) reshuffle(state)
|
||||
validateState(state)
|
||||
}
|
||||
|
||||
// 停止新增主题后继续正常消除,验证排队中的旧食物能否全部退出,避免队列永久积压。
|
||||
let drainTurn = 0
|
||||
while ((state.transition || state.queuedSafeGroups.length) && drainTurn < 80) {
|
||||
state.turn = TURNS + drainTurn
|
||||
if (state.transition) {
|
||||
state.transition.age += 1
|
||||
metrics.maxTransitionAge = Math.max(metrics.maxTransitionAge, state.transition.age)
|
||||
}
|
||||
let moves = safeMoves(state.board)
|
||||
if (!moves.length) {
|
||||
reshuffle(state)
|
||||
moves = safeMoves(state.board)
|
||||
}
|
||||
if (!moves.length) {
|
||||
recordFailure(state, 'dead_board_while_draining')
|
||||
break
|
||||
}
|
||||
const move = moves[Math.floor(state.rng() * moves.length)]
|
||||
state.board = swap(state.board, move.from, move.to)
|
||||
resolveMatches(state, findMatches(state.board))
|
||||
metrics.validMoves += 1
|
||||
finishTransitionIfReady(state)
|
||||
if (findMatches(state.board).length) state.board = makeStablePlayableBoard(state, state.board)
|
||||
if (!safeMoves(state.board).length) reshuffle(state)
|
||||
validateState(state)
|
||||
drainTurn += 1
|
||||
}
|
||||
metrics.transitionDrainTurns += drainTurn
|
||||
if (state.transition || state.queuedSafeGroups.length) metrics.unfinishedTransitions += 1
|
||||
}
|
||||
|
||||
const failureCount = Object.values(metrics.failures).reduce((sum, count) => sum + count, 0)
|
||||
const result = {
|
||||
ok: failureCount === 0,
|
||||
...metrics,
|
||||
failureCount
|
||||
}
|
||||
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
||||
if (!result.ok) process.exitCode = 1
|
||||
@@ -1,32 +0,0 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import type { Plugin } from 'vite'
|
||||
|
||||
const FOOD_ICON_FILES = [
|
||||
'tofu.jpg',
|
||||
'spinach.jpg',
|
||||
'carrot.jpg',
|
||||
'fish.jpg',
|
||||
'redBeanCongee.jpg',
|
||||
'milkTea.jpg'
|
||||
]
|
||||
|
||||
/**
|
||||
* Keep endless-game food icons inside the tongji subpackage.
|
||||
* Importing them from Vue would make Vite emit them into the main-package assets folder.
|
||||
*/
|
||||
export function tongjiFoodAssetsPlugin(): Plugin {
|
||||
return {
|
||||
name: 'tongji-food-assets',
|
||||
apply: 'build',
|
||||
generateBundle() {
|
||||
FOOD_ICON_FILES.forEach((fileName) => {
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: `tongji/endless-game/assets/food/${fileName}`,
|
||||
source: readFileSync(resolve(process.cwd(), 'tongji/endless-game/assets/food', fileName))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ export function useGamePlatform(proxy, ensureLoggedIn) {
|
||||
const connected = ref(false)
|
||||
const loading = ref(false)
|
||||
const syncStatus = ref('idle')
|
||||
const lastError = ref('')
|
||||
const leaderboard = ref({ ...EMPTY_BOARD })
|
||||
const confirmedSessionLearned = ref(0)
|
||||
|
||||
@@ -74,19 +73,17 @@ export function useGamePlatform(proxy, ensureLoggedIn) {
|
||||
}
|
||||
connected.value = true
|
||||
syncStatus.value = 'synced'
|
||||
lastError.value = ''
|
||||
}
|
||||
|
||||
async function refreshLeaderboard() {
|
||||
const res = await api({
|
||||
url: '/api/tcm/gameWeeklyLeaderboard',
|
||||
method: 'GET',
|
||||
data: { session_key: sessionKey }
|
||||
method: 'GET'
|
||||
})
|
||||
if (res?.code !== 1 || !res.data) {
|
||||
throw new Error(res?.msg || '同行榜加载失败')
|
||||
}
|
||||
applyLeaderboard(res.data, sessionKey)
|
||||
applyLeaderboard(res.data)
|
||||
return res.data
|
||||
}
|
||||
|
||||
@@ -115,10 +112,9 @@ export function useGamePlatform(proxy, ensureLoggedIn) {
|
||||
await refreshLeaderboard()
|
||||
if (queuedPayloads.length) flushProgress()
|
||||
return true
|
||||
} catch (error) {
|
||||
} catch (_) {
|
||||
connected.value = false
|
||||
syncStatus.value = 'offline'
|
||||
lastError.value = String(error?.message || '平台连接失败,请点击重试')
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -128,21 +124,14 @@ export function useGamePlatform(proxy, ensureLoggedIn) {
|
||||
return connectPromise
|
||||
}
|
||||
|
||||
function beginSession(existingSessionKey = '') {
|
||||
function beginSession() {
|
||||
if (syncTimer) clearTimeout(syncTimer)
|
||||
const restoredKey = String(existingSessionKey || '').trim()
|
||||
sessionKey = /^[A-Za-z0-9_-]{16,64}$/.test(restoredKey)
|
||||
? restoredKey
|
||||
: createSessionKey()
|
||||
sessionKey = createSessionKey()
|
||||
confirmedSessionLearned.value = 0
|
||||
syncStatus.value = queuedPayloads.length ? 'pending' : (connected.value ? 'synced' : 'offline')
|
||||
return sessionKey
|
||||
}
|
||||
|
||||
function getSessionKey() {
|
||||
return sessionKey
|
||||
}
|
||||
|
||||
function queueProgress(learnedCount, score, ended = false) {
|
||||
const nextPayload = {
|
||||
session_key: sessionKey,
|
||||
@@ -202,10 +191,9 @@ export function useGamePlatform(proxy, ensureLoggedIn) {
|
||||
|| Number(item.ended) > Number(payload.ended)
|
||||
))
|
||||
persistPendingPayloads()
|
||||
} catch (error) {
|
||||
} catch (_) {
|
||||
connected.value = false
|
||||
syncStatus.value = 'offline'
|
||||
lastError.value = String(error?.message || '成绩暂未保存,联网后会自动重试')
|
||||
// 队首保留原绝对值,下次连接或打开榜单时安全重试。
|
||||
persistPendingPayloads()
|
||||
} finally {
|
||||
@@ -249,12 +237,10 @@ export function useGamePlatform(proxy, ensureLoggedIn) {
|
||||
connected,
|
||||
loading,
|
||||
syncStatus,
|
||||
lastError,
|
||||
leaderboard,
|
||||
confirmedSessionLearned,
|
||||
connect,
|
||||
beginSession,
|
||||
getSessionKey,
|
||||
queueProgress,
|
||||
flushProgress,
|
||||
syncAndRefresh,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# 识糖小课堂 3D 食物图标候选稿 v1
|
||||
|
||||
- 共 42 个食物图标,文件名与 `FOODS` 中的英文键名一致。
|
||||
- 单图尺寸:320 × 320,JPEG。
|
||||
- 风格:中式、圆润 3D、暖色奶油背景、大轮廓、适老化高辨识度。
|
||||
- 糖级、边框和角标不画入图标,由游戏界面控制,方便医生调整分类。
|
||||
- `milkTea.jpg` 是唯一自带角色表情的高糖小反派候选。
|
||||
- 驼乳属于产品道具,不计入 42 个普通食物图标,本轮未替换现有产品罐素材。
|
||||
- 本目录当前仅作设计候选,没有接入正式游戏,也不会进入微信小程序发布包。
|
||||
|
||||
目录说明:
|
||||
|
||||
- `icons/`:可直接用于游戏的独立图标。
|
||||
- `sheets/`:11 张分组预览母版,方便整体审美检查。
|
||||
- `manifest.json`:食物中文名、代码键名与文件路径清单。
|
||||
|
||||
医学提示:图标仅表达食物外观,不代表最终糖级结论;糖级和健康说明仍需甄养堂医生审核。
|
||||
|
Before Width: | Height: | Size: 286 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"style": "chinese-rounded-3d-elderly-friendly",
|
||||
"iconSize": {
|
||||
"width": 320,
|
||||
"height": 320,
|
||||
"format": "jpg"
|
||||
},
|
||||
"items": [
|
||||
{ "key": "corn", "name": "玉米", "file": "icons/corn.jpg" },
|
||||
{ "key": "egg", "name": "鸡蛋", "file": "icons/egg.jpg" },
|
||||
{ "key": "tomato", "name": "番茄", "file": "icons/tomato.jpg" },
|
||||
{ "key": "potato", "name": "红薯", "file": "icons/potato.jpg" },
|
||||
{ "key": "cucumber", "name": "黄瓜", "file": "icons/cucumber.jpg" },
|
||||
{ "key": "lettuce", "name": "生菜", "file": "icons/lettuce.jpg" },
|
||||
{ "key": "grainMantou", "name": "杂粮馒头", "file": "icons/grainMantou.jpg" },
|
||||
{ "key": "wonton", "name": "馄饨", "file": "icons/wonton.jpg" },
|
||||
{ "key": "celery", "name": "芹菜", "file": "icons/celery.jpg" },
|
||||
{ "key": "mushroom", "name": "香菇", "file": "icons/mushroom.jpg" },
|
||||
{ "key": "chicken", "name": "鸡肉", "file": "icons/chicken.jpg" },
|
||||
{ "key": "taro", "name": "芋头", "file": "icons/taro.jpg" },
|
||||
{ "key": "broccoli", "name": "西兰花", "file": "icons/broccoli.jpg" },
|
||||
{ "key": "milk", "name": "牛奶", "file": "icons/milk.jpg" },
|
||||
{ "key": "brownRice", "name": "糙米饭", "file": "icons/brownRice.jpg" },
|
||||
{ "key": "apple", "name": "苹果", "file": "icons/apple.jpg" },
|
||||
{ "key": "strawberry", "name": "草莓", "file": "icons/strawberry.jpg" },
|
||||
{ "key": "banana", "name": "香蕉", "file": "icons/banana.jpg" },
|
||||
{ "key": "mango", "name": "芒果", "file": "icons/mango.jpg" },
|
||||
{ "key": "tea", "name": "红茶", "file": "icons/tea.jpg" },
|
||||
{ "key": "blackCoffee", "name": "黑咖啡", "file": "icons/blackCoffee.jpg" },
|
||||
{ "key": "coffee", "name": "咖啡", "file": "icons/coffee.jpg" },
|
||||
{ "key": "pineapple", "name": "菠萝", "file": "icons/pineapple.jpg" },
|
||||
{ "key": "pepper", "name": "青椒", "file": "icons/pepper.jpg" },
|
||||
{ "key": "greenBean", "name": "四季豆", "file": "icons/greenBean.jpg" },
|
||||
{ "key": "shrimp", "name": "虾仁", "file": "icons/shrimp.jpg" },
|
||||
{ "key": "udon", "name": "乌冬面", "file": "icons/udon.jpg" },
|
||||
{ "key": "onion", "name": "洋葱", "file": "icons/onion.jpg" },
|
||||
{ "key": "congee", "name": "白米粥", "file": "icons/congee.jpg" },
|
||||
{ "key": "eightCongee", "name": "八宝粥", "file": "icons/eightCongee.jpg" },
|
||||
{ "key": "centuryCongee", "name": "皮蛋瘦肉粥", "file": "icons/centuryCongee.jpg" },
|
||||
{ "key": "milletCongee", "name": "小米粥", "file": "icons/milletCongee.jpg" },
|
||||
{ "key": "orangeJuice", "name": "柳橙汁", "file": "icons/orangeJuice.jpg" },
|
||||
{ "key": "soda", "name": "含糖汽水", "file": "icons/soda.jpg" },
|
||||
{ "key": "riceNoodleSoup", "name": "汤粉", "file": "icons/riceNoodleSoup.jpg" },
|
||||
{ "key": "noodleSoup", "name": "汤面", "file": "icons/noodleSoup.jpg" },
|
||||
{ "key": "milkTea", "name": "含糖奶茶", "file": "icons/milkTea.jpg" },
|
||||
{ "key": "tofu", "name": "豆腐", "file": "icons/tofu.jpg" },
|
||||
{ "key": "spinach", "name": "菠菜", "file": "icons/spinach.jpg" },
|
||||
{ "key": "carrot", "name": "胡萝卜", "file": "icons/carrot.jpg" },
|
||||
{ "key": "fish", "name": "鱼", "file": "icons/fish.jpg" },
|
||||
{ "key": "redBeanCongee", "name": "软烂红豆粥", "file": "icons/redBeanCongee.jpg" }
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 110 KiB |
@@ -46,44 +46,7 @@
|
||||
.eg-nav-actions { display: flex; width: 156rpx; flex-shrink: 0; justify-content: flex-end; gap: 10rpx; }
|
||||
.eg-nav-btn--small { width: 68rpx; height: 68rpx; border-radius: 22rpx; }
|
||||
|
||||
.eg-title-wrap { position: relative; display: flex; min-width: 0; flex-direction: column; align-items: center; }
|
||||
.eg-title-wrap > .eg-title,
|
||||
.eg-title-wrap > .eg-subtitle { transition: opacity .08s ease; }
|
||||
.eg-title-wrap.is-noticing > .eg-title,
|
||||
.eg-title-wrap.is-noticing > .eg-subtitle { opacity: .16; }
|
||||
.eg-nav-notice {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 5rpx 8rpx;
|
||||
border: 1rpx solid rgba(21,94,75,.09);
|
||||
border-radius: 16rpx;
|
||||
color: rgba(38,86,72,.82);
|
||||
background: rgba(255,255,255,.58);
|
||||
box-shadow: 0 4rpx 12rpx rgba(13,64,51,.06);
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: egNavNotice 1.05s ease-out both;
|
||||
pointer-events: none;
|
||||
}
|
||||
.eg-nav-notice-title,
|
||||
.eg-nav-notice-detail {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
line-height: 1.15;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.eg-nav-notice-title { font-size: 23rpx; font-weight: 800; }
|
||||
.eg-nav-notice-detail { margin-top: 2rpx; color: rgba(154,106,42,.78); font-size: 20rpx; font-weight: 700; }
|
||||
.eg-title-wrap { display: flex; min-width: 0; flex-direction: column; align-items: center; }
|
||||
.eg-title, .eg-subtitle { overflow: hidden; max-width: 100%; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.eg-title { color: #155e4b; font-size: 40rpx; font-weight: 800; }
|
||||
.eg-subtitle { margin-top: 2rpx; color: #648278; font-size: 24rpx; }
|
||||
@@ -154,20 +117,12 @@
|
||||
background: rgba(255,255,255,.92);
|
||||
box-shadow: 0 18rpx 44rpx rgba(29, 78, 59, .12);
|
||||
}
|
||||
.eg-board-shell.is-flowing {
|
||||
border-color: rgba(245, 158, 11, .72);
|
||||
box-shadow: 0 0 0 5rpx rgba(255, 221, 89, .2), 0 18rpx 46rpx rgba(234, 88, 12, .2);
|
||||
animation: egFlowShell 1.05s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.eg-board-top { display: flex; align-items: center; justify-content: space-between; margin: 0 4rpx 14rpx; }
|
||||
.eg-board-bonuses { display: flex; align-items: center; gap: 8rpx; }
|
||||
.eg-combo { padding: 8rpx 16rpx; border-radius: 999rpx; color: #45685d; background: #e8f3ee; font-size: 25rpx; font-weight: 700; }
|
||||
.eg-combo.is-hot { color: #fff; background: linear-gradient(135deg, #f59e0b, #ea580c); }
|
||||
.eg-double-state { padding: 8rpx 14rpx; border: 2rpx solid rgba(255,255,255,.9); border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #7c3aed, #d946ef); box-shadow: 0 4rpx 12rpx rgba(124,58,237,.24); font-size: 23rpx; font-weight: 900; white-space: nowrap; }
|
||||
.eg-flow-state { display: flex; align-items: center; gap: 8rpx; padding: 8rpx 14rpx; border: 3rpx solid #fff3a3; border-radius: 999rpx; color: #fff; background: linear-gradient(135deg, #f97316, #dc2626); box-shadow: 0 5rpx 16rpx rgba(220,38,38,.3); font-size: 25rpx; font-weight: 1000; white-space: nowrap; animation: egFlowBadge .62s ease-in-out infinite alternate; }
|
||||
.eg-flow-state > text:last-child { min-width: 62rpx; color: #fff7a8; text-align: right; }
|
||||
.eg-flow-state.is-paused { border-color: #d9f6ff; background: linear-gradient(135deg, #1687b4, #3155a6); box-shadow: 0 5rpx 16rpx rgba(34,109,166,.28); animation: none; }
|
||||
.eg-risk { display: flex; align-items: center; gap: 10rpx; color: #8d512c; font-size: 24rpx; }
|
||||
.eg-risk-dots { display: flex; gap: 5rpx; }
|
||||
.eg-risk-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background: #ead8c9; }
|
||||
@@ -212,15 +167,7 @@
|
||||
.eg-cell.is-level-high { background: #fff0e4; }
|
||||
.eg-cell.is-milk { background: #edf7ff; }
|
||||
.eg-cell.is-high { border-color: #ef7b45; }
|
||||
.eg-cell.is-villain {
|
||||
background: radial-gradient(circle at 50% 38%, #fff7e8 0%, #ffe9d8 70%, #ffddc6 100%);
|
||||
box-shadow: inset 0 -5rpx 0 rgba(154,57,25,.08), 0 6rpx 15rpx rgba(191,72,31,.16);
|
||||
}
|
||||
.eg-cell.is-villain .eg-food-image { animation: egVillainPeek 2.2s ease-in-out infinite; }
|
||||
.eg-cell.is-villain.is-clearing .eg-food-image,
|
||||
.eg-cell.is-villain.is-dragging .eg-food-image { animation: none; }
|
||||
.eg-food-image { width: 132rpx; height: 92rpx; margin-top: 7rpx; border-radius: 17rpx; filter: saturate(1.06) contrast(1.03); }
|
||||
.eg-food-image.is-large-square { width: 136rpx; height: 102rpx; margin-top: 2rpx; filter: saturate(1.1) contrast(1.06); }
|
||||
.eg-food-name { margin-top: 8rpx; color: #203d34; font-size: 29rpx; font-weight: 900; letter-spacing: 1rpx; line-height: 1.1; }
|
||||
.eg-sugar-tag { position: absolute; top: 5rpx; right: 5rpx; min-width: 52rpx; padding: 4rpx 10rpx; border: 2rpx solid #fff; border-radius: 999rpx; color: #fff; box-shadow: 0 3rpx 8rpx rgba(25,55,45,.22); font-size: 23rpx; font-weight: 900; line-height: 1.25; text-align: center; }
|
||||
.eg-sugar-tag.is-low { color: #316a54; border-color: rgba(255,255,255,.82); background: #dcefe6; box-shadow: 0 2rpx 5rpx rgba(32,101,75,.1); }
|
||||
@@ -390,9 +337,6 @@
|
||||
.eg-weekly-list { display: flex; flex-direction: column; gap: 7rpx; margin-top: 15rpx; }
|
||||
.eg-weekly-row { display: flex; min-height: 68rpx; align-items: center; padding: 7rpx 14rpx; border: 2rpx solid transparent; border-radius: 19rpx; color: #345c50; background: rgba(255,255,255,.72); }
|
||||
.eg-weekly-row.is-me { border-color: #f2bd48; color: #174f3e; background: linear-gradient(90deg, #fff2bb, #fff9e3); box-shadow: 0 5rpx 14rpx rgba(153,102,17,.12); transform: scale(1.015); }
|
||||
.eg-weekly-row.is-waiting { color: #8a9b94; border-style: dashed; border-color: #dbe8e2; background: rgba(247,251,249,.72); }
|
||||
.eg-weekly-row.is-waiting .eg-weekly-avatar { color: #6e887e; border-color: #dce9e3; background: #edf4f1; box-shadow: none; }
|
||||
.eg-weekly-row.is-waiting .eg-weekly-count { color: #9baba4; }
|
||||
.eg-weekly-place { width: 43rpx; color: #6b7d76; font-size: 27rpx; font-weight: 1000; text-align: center; }
|
||||
.eg-weekly-row:nth-child(1) .eg-weekly-place { color: #dd7b17; font-size: 31rpx; }
|
||||
.eg-weekly-avatar { display: flex; width: 52rpx; height: 52rpx; flex: 0 0 52rpx; align-items: center; justify-content: center; margin-left: 6rpx; border: 3rpx solid rgba(255,255,255,.9); border-radius: 50%; color: #fff; background: #5d9b84; box-shadow: 0 3rpx 9rpx rgba(30,78,61,.16); font-size: 24rpx; font-weight: 1000; }
|
||||
@@ -406,9 +350,6 @@
|
||||
.eg-weekly-name { flex: 1; margin-left: 13rpx; overflow: hidden; font-size: 27rpx; font-weight: 850; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.eg-weekly-me { margin-right: 8rpx; padding: 3rpx 9rpx; border-radius: 999rpx; color: #fff; background: #dd6b27; font-size: 18rpx; font-weight: 900; }
|
||||
.eg-weekly-count { min-width: 70rpx; color: #315b4d; font-size: 29rpx; font-weight: 1000; text-align: right; }
|
||||
.eg-weekly-connection { display: flex; align-items: center; justify-content: space-between; gap: 12rpx; margin-top: 13rpx; padding: 12rpx 15rpx; border: 2rpx solid #e4d7b2; border-radius: 18rpx; color: #6b6655; background: #fff9e8; font-size: 21rpx; font-weight: 700; }
|
||||
.eg-weekly-connection > text:first-child { flex: 1; }
|
||||
.eg-weekly-retry { flex-shrink: 0; padding: 6rpx 12rpx; border-radius: 999rpx; color: #fff; background: #23765c; font-weight: 900; }
|
||||
.eg-cheer-card { margin-top: 16rpx; padding: 15rpx 17rpx 14rpx; border: 3rpx solid #f0d38d; border-radius: 23rpx; background: linear-gradient(135deg, #fff9d9, #fff1c2); }
|
||||
.eg-cheer-card.is-family { border-color: #d9c8ef; background: linear-gradient(135deg, #f8f2ff, #efe7ff); }
|
||||
.eg-cheer-card.is-bright { border-color: #f4c77b; background: linear-gradient(135deg, #fff8dc, #ffeec7); }
|
||||
@@ -476,13 +417,6 @@
|
||||
100% { opacity: 0; transform: scale(.32) rotate(7deg); }
|
||||
}
|
||||
|
||||
@keyframes egVillainPeek {
|
||||
0%, 72%, 100% { transform: translateY(0) rotate(0); filter: saturate(1.08) contrast(1.05); }
|
||||
80% { transform: translateY(-3rpx) rotate(-1.5deg); filter: saturate(1.15) contrast(1.08); }
|
||||
88% { transform: translateY(0) rotate(1.5deg); }
|
||||
95% { transform: translateY(-2rpx) rotate(0); }
|
||||
}
|
||||
|
||||
@keyframes egTaskRewardFlight {
|
||||
0% { opacity: 0; transform: translate3d(-20rpx, 0, 0) scale(.62) rotate(-8deg); }
|
||||
16% { opacity: 1; transform: translate3d(0, -24rpx, 0) scale(1.1) rotate(3deg); }
|
||||
@@ -490,13 +424,6 @@
|
||||
100% { opacity: 0; transform: translate3d(350rpx, 1205rpx, 0) scale(.3) rotate(2deg); }
|
||||
}
|
||||
|
||||
@keyframes egNavNotice {
|
||||
0% { opacity: 0; transform: translate(-50%, -42%); }
|
||||
12% { opacity: .9; transform: translate(-50%, -50%); }
|
||||
42% { opacity: .82; transform: translate(-50%, -50%); }
|
||||
100% { opacity: 0; transform: translate(-50%, -58%); }
|
||||
}
|
||||
|
||||
@keyframes egRewardGlow {
|
||||
from { opacity: .4; transform: scale(.86); }
|
||||
to { opacity: .9; transform: scale(1.12); }
|
||||
@@ -626,16 +553,6 @@
|
||||
100% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes egFlowShell {
|
||||
from { filter: saturate(1); }
|
||||
to { filter: saturate(1.08); }
|
||||
}
|
||||
|
||||
@keyframes egFlowBadge {
|
||||
from { transform: scale(.98); filter: brightness(.96); }
|
||||
to { transform: scale(1.035); filter: brightness(1.12); }
|
||||
}
|
||||
|
||||
@keyframes egStageIn {
|
||||
0% { opacity: 0; transform: scale(.7) translateY(34rpx); }
|
||||
18% { opacity: 1; transform: scale(1.06) translateY(0); }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import uni from '@dcloudio/vite-plugin-uni'
|
||||
import Optimization from '@uni-ku/bundle-optimizer'
|
||||
import { tongjiFoodAssetsPlugin } from './tongji/endless-game/build/tongjiFoodAssetsPlugin'
|
||||
|
||||
// uni-app 工程根目录就是源码目录(HBuilderX 兼容)
|
||||
// 编译产物默认输出到 ./dist/<mode>/<platform>
|
||||
@@ -17,7 +16,6 @@ export default defineConfig({
|
||||
dts: false,
|
||||
logger: false,
|
||||
}),
|
||||
tongjiFoodAssetsPlugin(),
|
||||
],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./common/vendor.js");exports.createApp=e.createApp;
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"pages": [
|
||||
"tongji/pages/weekly",
|
||||
"pages/user/user",
|
||||
"pages/user/userinfo",
|
||||
"pages/Card/Card",
|
||||
"pages/Card/edit_card",
|
||||
"pages/Card/contact",
|
||||
"tongji/pages/index",
|
||||
"tongji/pages/more",
|
||||
"tongji/endless-game/index"
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "training",
|
||||
"pages": [
|
||||
"pages/grip-ring",
|
||||
"pages/pilates-ring",
|
||||
"pages/foot-pedal",
|
||||
"pages/dumbbell",
|
||||
"pages/metronome"
|
||||
]
|
||||
}
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "甄养堂血糖管理",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"lazyCodeLoading": "requiredComponents",
|
||||
"requiredBackgroundModes": [
|
||||
"audio"
|
||||
],
|
||||
"plugins": {
|
||||
"WechatSI": {
|
||||
"version": "0.3.5",
|
||||
"provider": "wx069ba97219f66d99"
|
||||
}
|
||||
},
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
page{background-color:#f4fbf4;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}
|
||||
page::after{position:fixed;content:'';left:-1000px;top:-1000px;-webkit-animation:shadow-preload .1s;-webkit-animation-delay:3s;animation:shadow-preload .1s;animation-delay:3s}@-webkit-keyframes shadow-preload{0%{background-image:url(https://cdn1.dcloud.net.cn/5645394f5230704a4d44456c4a5864344e7a6c694f574577596d5a695a6d553359324a6a5a413d3d/img/shadow-grey.png)}100%{background-image:url(https://cdn1.dcloud.net.cn/5645394f5230704a4d44456c4a5864344e7a6c694f574577596d5a695a6d553359324a6a5a413d3d/img/shadow-grey.png)}}@keyframes shadow-preload{0%{background-image:url(https://cdn1.dcloud.net.cn/5645394f5230704a4d44456c4a5864344e7a6c694f574577596d5a695a6d553359324a6a5a413d3d/img/shadow-grey.png)}100%{background-image:url(https://cdn1.dcloud.net.cn/5645394f5230704a4d44456c4a5864344e7a6c694f574577596d5a695a6d553359324a6a5a413d3d/img/shadow-grey.png)}}page{--status-bar-height:25px;--top-window-height:0px;--window-top:0px;--window-bottom:0px;--window-left:0px;--window-right:0px;--window-magin:0px}[data-c-h="true"]{display: none !important;}
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";const e=require("../../common/vendor.js"),t="#8a8a8a",c="#204e2b",r={__name:"tab-bar-dock",props:{active:{type:Number,default:0}},setup(r){const a=r;function o(){1!==a.active&&e.index.redirectTo({url:"/pages/user/user"})}function s(){0!==a.active&&e.index.redirectTo({url:"/tongji/pages/weekly"})}return(a,i)=>({a:0===r.active?1:"",b:0===r.active?c:t,c:e.o(s),d:1===r.active?"/static/user/user_no.png":"/static/user/user.png",e:1===r.active?c:t,f:e.o(o)})}},a=e._export_sfc(r,[["__scopeId","data-v-6c5d8930"]]);wx.createComponent(a);
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<view class="tab-dock data-v-6c5d8930"><view class="tab-dock__inner data-v-6c5d8930"><view class="tab-dock__item tab-dock__item--center data-v-6c5d8930" bindtap="{{c}}"><view class="{{['tab-dock__fab', 'data-v-6c5d8930', a && 'tab-dock__fab--active']}}"><text class="tab-dock__fab-text data-v-6c5d8930">糖</text></view><text class="tab-dock__label tab-dock__label--center data-v-6c5d8930" style="{{'color:' + b}}">血糖</text></view><view class="tab-dock__item data-v-6c5d8930" bindtap="{{f}}"><image class="tab-dock__icon data-v-6c5d8930" src="{{d}}" mode="aspectFit"/><text class="tab-dock__label data-v-6c5d8930" style="{{'color:' + e}}">我的</text></view></view><view class="tab-dock__safe data-v-6c5d8930"/></view>
|
||||
@@ -0,0 +1 @@
|
||||
.tab-dock.data-v-6c5d8930{position:fixed;left:0;right:0;bottom:0;z-index:999;background:#fff;border-top:1rpx solid rgba(0,0,0,.06);box-shadow:0 -6rpx 20rpx rgba(0,0,0,.04)}.tab-dock__inner.data-v-6c5d8930{display:flex;align-items:flex-end;height:100rpx}.tab-dock__item.data-v-6c5d8930{flex:1;height:100rpx;display:flex;flex-direction:column;align-items:center;justify-content:center}.tab-dock__item--center.data-v-6c5d8930{justify-content:flex-end;padding-bottom:6rpx}.tab-dock__icon.data-v-6c5d8930{width:46rpx;height:46rpx}.tab-dock__label.data-v-6c5d8930{margin-top:6rpx;font-size:22rpx;line-height:1.2}.tab-dock__label--center.data-v-6c5d8930{margin-top:8rpx}.tab-dock__fab.data-v-6c5d8930{width:96rpx;height:96rpx;margin-top:-48rpx;border-radius:50%;background:linear-gradient(180deg,#2d7340,#204e2b);border:6rpx solid #ffffff;box-shadow:0 10rpx 24rpx rgba(32,78,43,.35);display:flex;align-items:center;justify-content:center}.tab-dock__fab--active.data-v-6c5d8930{transform:scale(1.04);box-shadow:0 12rpx 28rpx rgba(32,78,43,.42)}.tab-dock__fab-text.data-v-6c5d8930{color:#fff;font-size:38rpx;font-weight:700;line-height:1}.tab-dock__safe.data-v-6c5d8930{height:env(safe-area-inset-bottom);background:#fff}
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";const e=require("../../common/vendor.js"),n=e.defineComponent({__name:"index",props:{bottom:{default:200},inline:{type:Boolean,default:!1},tone:{default:"default"}},setup(n){const i=[{url:"/training/pages/dumbbell",title:"哑铃",sub:"力量塑形",icon:"dumbbell",iconClass:"menu-icon--dumbbell"},{url:"/training/pages/foot-pedal",title:"脚蹬器",sub:"下肢有氧",icon:"pedal",iconClass:"menu-icon--pedal"},{url:"/training/pages/pilates-ring",title:"瑜伽环",sub:"核心塑形",icon:"pilates",iconClass:"menu-icon--pilates"},{url:"/training/pages/grip-ring",title:"握力环",sub:"握力训练",icon:"grip",iconClass:"menu-icon--grip"},{url:"/training/pages/metronome",title:"耗糖节拍器",sub:"健走配速",icon:"metro",iconClass:"menu-icon--metro",featured:!0}],o=n,l=e.computed(()=>{if("weekly"!==o.tone)return i;const e=i.find(e=>"metro"===e.icon),n=i.filter(e=>"metro"!==e.icon);return e?[e,...n]:i}),t=e.ref(!0),a=e.ref(!1),r=()=>{a.value=!a.value},u=n=>{a.value=!1,e.index.navigateTo({url:n})};return(n,i)=>e.e({a:t.value&&o.inline},t.value&&o.inline?e.e({b:"weekly"===o.tone},(o.tone,{}),{c:e.f(l.value,(n,i,o)=>e.e({a:n.featured},(n.featured,{}),{b:"dumbbell"===n.icon},("dumbbell"===n.icon||"pedal"===n.icon||"pilates"===n.icon||"grip"===n.icon||n.icon,{}),{c:"pedal"===n.icon,d:"pilates"===n.icon,e:"grip"===n.icon,f:"metro"===n.icon,g:e.n(n.iconClass),h:e.t(n.title),i:e.t(n.sub),j:n.url,k:e.o(e=>u(n.url),n.url)})),d:"weekly"===o.tone},(o.tone,{}),{e:"weekly"===o.tone?1:""}):t.value?e.e({g:a.value},a.value?{h:e.o(e=>u("/training/pages/dumbbell")),i:e.o(e=>u("/training/pages/foot-pedal")),j:e.o(e=>u("/training/pages/pilates-ring")),k:e.o(e=>u("/training/pages/grip-ring")),l:e.o(e=>u("/training/pages/metronome")),m:e.o(()=>{})}:{},{n:!a.value},(a.value,{}),{o:!a.value},(a.value,{}),{p:a.value},(a.value,{}),{q:a.value?1:"",r:e.o(r),s:o.bottom+"rpx"}):{},{f:t.value})}}),i=e._export_sfc(n,[["__scopeId","data-v-2ae07c93"]]);wx.createComponent(i);
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";const e=require("../../common/vendor.js");if(!Array){e.resolveComponent("uni-icons")()}Math;const t={__name:"Card",setup(t){const{proxy:n}=e.getCurrentInstance(),a=e.ref([]),i=e.ref(!1);e.onMounted(()=>{o()}),e.onShow(()=>{o()});const o=async()=>{var t;i.value=!0;try{if(!e.index.getStorageSync("token"))return void e.index.showToast({title:"请先登录",icon:"none"});const i=e.index.getStorageSync("userData"),o=null==(t=null==i?void 0:i.diagnosis)?void 0:t.patient_id;if(!o)return;const d=await n.apiUrl({url:"/api/tcm/getCardList",method:"GET",data:{patient_id:o}});1===d.code?a.value=d.data||[]:e.index.showToast({title:d.msg||"获取失败",icon:"none"})}catch(o){console.error("加载就诊卡列表失败:",o)}finally{i.value=!1}},d=t=>{e.index.navigateTo({url:`/pages/Card/edit_card?id=${t.id}`})},s=()=>{e.index.getStorageSync("token")?e.index.navigateTo({url:"/pages/Card/edit_card?add=1"}):e.index.showToast({title:"请先登录",icon:"none"})},r=e=>1===e?"status-confirmed":"status-pending",c={first_visit:"初诊",follow_up:"复诊",consultation:"会诊"},l={qi_deficiency:"气虚",blood_deficiency:"血虚",yin_deficiency:"阴虚",yang_deficiency:"阳虚",qi_stagnation:"气滞",blood_stasis:"血瘀",phlegm_dampness:"痰湿",damp_heat:"湿热",cold_dampness:"寒湿",wind_cold:"风寒",wind_heat:"风热"},g=e=>l[e]||e||"-",_=e=>{if(!e)return"-";const t=new Date(1e3*e);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`};return(t,n)=>e.e({a:e.f(a.value,(t,n,a)=>{return{a:e.t(t.id),b:e.t(t.status_desc||(o=t.status,1===o?"启用":"禁用")),c:e.n(r(t.status)),d:e.t(t.patient_name||"-"),e:e.t(t.gender_desc||"-"),f:e.t(t.age),g:e.t((i=t.diagnosis_type,c[i]||i||"-")),h:e.t(g(t.syndrome_type)),i:e.t(t.diagnosis_date_text||_(t.diagnosis_date)),j:e.t(t.create_time||"-"),k:e.o(n=>(t=>{const n=`/tongji/pages/index?diagnosis_id=${t.id}&patient_id=${t.patient_id||""}&patient_name=${encodeURIComponent(t.patient_name||"")}&age=${t.age||""}&gender=${t.gender||""}`;e.index.navigateTo({url:n})})(t),t.id),l:e.o(e=>d(t),t.id),m:t.id,n:e.o(e=>d(t),t.id)};var i,o}),b:!i.value&&0===a.value.length},i.value||0!==a.value.length?{}:{c:e.p({type:"wallet",size:"80",color:"#1890ff"}),d:e.o(s)},{e:i.value},(i.value,{}))}},n=e._export_sfc(t,[["__scopeId","data-v-341940bb"]]);t.__runtimeHooks=2,wx.createPage(n);
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "就诊卡",
|
||||
"usingComponents": {
|
||||
"uni-icons": "../../uni_modules/uni-icons/components/uni-icons/uni-icons"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<view class="card-container data-v-341940bb"><view class="card-list data-v-341940bb"><view wx:for="{{a}}" wx:for-item="card" wx:key="m" class="card-item data-v-341940bb" bindtap="{{card.n}}"><view class="card-header data-v-341940bb"><view class="card-id data-v-341940bb"><text class="label data-v-341940bb">诊单编号:</text><text class="value data-v-341940bb">{{card.a}}</text></view><view class="{{['data-v-341940bb', 'status-badge', card.c]}}">{{card.b}}</view></view><view class="card-info data-v-341940bb"><view class="info-row data-v-341940bb"><text class="info-label data-v-341940bb">患者姓名:</text><text class="info-value data-v-341940bb">{{card.d}}</text></view><view class="info-row data-v-341940bb"><text class="info-label data-v-341940bb">性别年龄:</text><text class="info-value data-v-341940bb">{{card.e}} / {{card.f}}岁</text></view><view class="info-row data-v-341940bb"><text class="info-label data-v-341940bb">诊断类型:</text><text class="info-value data-v-341940bb">{{card.g}}</text></view><view class="info-row data-v-341940bb"><text class="info-label data-v-341940bb">证型:</text><text class="info-value data-v-341940bb">{{card.h}}</text></view></view><view class="card-footer data-v-341940bb"><view class="time-info data-v-341940bb"><text class="time-label data-v-341940bb">诊断日期:</text><text class="time-value data-v-341940bb">{{card.i}}</text></view><view class="time-info data-v-341940bb"><text class="time-label data-v-341940bb">创建时间:</text><text class="time-value data-v-341940bb">{{card.j}}</text></view></view><view class="card-actions data-v-341940bb"><view class="card-action-btn primary data-v-341940bb" catchtap="{{card.k}}"><text class="card-action-icon data-v-341940bb">📈</text><text class="card-action-text data-v-341940bb">日常记录</text></view><view class="card-action-btn data-v-341940bb" catchtap="{{card.l}}"><text class="card-action-icon data-v-341940bb">📝</text><text class="card-action-text data-v-341940bb">查看 / 编辑</text></view></view></view><view wx:if="{{b}}" class="empty-state data-v-341940bb"><uni-icons wx:if="{{c}}" class="data-v-341940bb" u-i="341940bb-0" bind:__l="__l" u-p="{{c}}"></uni-icons><text class="empty-text data-v-341940bb">暂无就诊卡</text><view class="add-card-btn data-v-341940bb" bindtap="{{d}}">新建就诊卡</view></view><view wx:if="{{e}}" class="loading-state data-v-341940bb"><text class="loading-text data-v-341940bb">加载中...</text></view></view></view>
|
||||
@@ -0,0 +1 @@
|
||||
.card-container.data-v-341940bb{min-height:100vh;background:linear-gradient(180deg,#e8f0fa,#fff);padding-bottom:56rpx}.header.data-v-341940bb{background:#fff;padding:48rpx 40rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,.06)}.header .header-title.data-v-341940bb{font-size:48rpx;font-weight:700;color:#1890ff}.card-list.data-v-341940bb{padding:40rpx}.card-item.data-v-341940bb{background:#fff;border-radius:28rpx;padding:44rpx;margin-bottom:36rpx;box-shadow:0 4rpx 20rpx rgba(0,0,0,.08);transition:all .3s;min-height:200rpx}.card-item.data-v-341940bb:active{transform:scale(.98);box-shadow:0 2rpx 12rpx rgba(0,0,0,.1)}.card-header.data-v-341940bb{display:flex;justify-content:space-between;align-items:center;margin-bottom:32rpx;padding-bottom:28rpx;border-bottom:4rpx solid #e8eaed}.card-id .label.data-v-341940bb{font-size:34rpx;color:#555}.card-id .value.data-v-341940bb{font-size:40rpx;font-weight:700;color:#1a1a1a}.status-badge.data-v-341940bb{padding:12rpx 28rpx;border-radius:28rpx;font-size:32rpx;font-weight:500}.status-badge.status-confirmed.data-v-341940bb{background:#f6ffed;color:#204e2b;border:4rpx solid #204e2b}.status-badge.status-pending.data-v-341940bb{background:#fff7e6;color:#fa8c16;border:4rpx solid #ffd591}.card-info.data-v-341940bb{margin-bottom:28rpx}.info-row.data-v-341940bb{display:flex;align-items:center;margin-bottom:24rpx}.info-row .info-label.data-v-341940bb{font-size:34rpx;color:#555;min-width:200rpx}.info-row .info-value.data-v-341940bb{font-size:36rpx;color:#1a1a1a;font-weight:500}.card-footer.data-v-341940bb{padding-top:28rpx;border-top:4rpx solid #e8eaed}.card-actions.data-v-341940bb{display:flex;gap:20rpx;margin-top:28rpx;padding-top:28rpx;border-top:4rpx solid #e8eaed}.card-action-btn.data-v-341940bb{flex:1;display:flex;align-items:center;justify-content:center;gap:12rpx;padding:24rpx 12rpx;background:#f6faff;border-radius:20rpx;border:2rpx solid #d6e6fb;min-height:80rpx;transition:all .2s}.card-action-btn.data-v-341940bb:active{transform:scale(.98);opacity:.85}.card-action-btn.primary.data-v-341940bb{background:linear-gradient(135deg,#1890ff,#0ea5a4);border-color:transparent}.card-action-btn.primary .card-action-icon.data-v-341940bb,.card-action-btn.primary .card-action-text.data-v-341940bb{color:#fff}.card-action-icon.data-v-341940bb{font-size:36rpx;color:#1890ff}.card-action-text.data-v-341940bb{font-size:30rpx;color:#1890ff;font-weight:500}.time-info.data-v-341940bb{display:flex;align-items:center;margin-bottom:20rpx}.time-info .time-label.data-v-341940bb{font-size:30rpx;color:#555;min-width:180rpx}.time-info .time-value.data-v-341940bb{font-size:32rpx;color:#555}.view-count.data-v-341940bb{display:flex;align-items:center;margin-top:12rpx}.view-count .count-icon.data-v-341940bb{font-size:34rpx;margin-right:12rpx}.view-count .count-text.data-v-341940bb{font-size:30rpx;color:#555}.empty-state.data-v-341940bb{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:160rpx 0}.empty-state .empty-icon.data-v-341940bb{font-size:140rpx;margin-bottom:40rpx}.empty-state .empty-text.data-v-341940bb{font-size:40rpx;color:#555;margin-bottom:56rpx}.add-card-btn.data-v-341940bb{padding:36rpx 88rpx;background:#1890ff;color:#fff;font-size:40rpx;font-weight:500;border-radius:56rpx;min-height:100rpx}.loading-state.data-v-341940bb{display:flex;justify-content:center;padding:120rpx 0}.loading-state .loading-text.data-v-341940bb{font-size:36rpx;color:#555}
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";const e=require("../../common/vendor.js"),t={__name:"contact",setup(t){const{proxy:a}=e.getCurrentInstance(),n=e.ref({}),o=e.ref(!1),r=e.ref("");e.onMounted(()=>{c()});const c=async()=>{o.value=!0;try{const t=getCurrentPages(),o=t[t.length-1];r.value=o.options.type;const c=await a.apiUrl({url:"/api/index/policy",method:"GET",data:{type:r.value}});n.value=c.data,e.index.setNavigationBarTitle({title:c.data.title})}catch(t){console.error("加载就诊卡列表失败:",t)}finally{o.value=!1}};return(t,a)=>({a:e.t(n.value.title),b:n.value.content})}},a=e._export_sfc(t,[["__scopeId","data-v-5e5077b6"]]);wx.createPage(a);
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "服务",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<view class="card-container data-v-5e5077b6"><view class="header data-v-5e5077b6"><text class="header-title data-v-5e5077b6">{{a}}</text></view><view class="card-list data-v-5e5077b6"><rich-text class="data-v-5e5077b6" nodes="{{b}}"/></view></view>
|
||||
@@ -0,0 +1 @@
|
||||
.card-container.data-v-5e5077b6{min-height:100vh;background:linear-gradient(180deg,#f0f7ff,#fff);padding-bottom:40rpx}.header.data-v-5e5077b6{background:#fff;padding:40rpx 30rpx;box-shadow:0 2rpx 8rpx rgba(0,0,0,.05)}.header .header-title.data-v-5e5077b6{font-size:40rpx;font-weight:700;color:#1890ff}.card-list.data-v-5e5077b6{padding:30rpx}.card-item.data-v-5e5077b6{background:#fff;border-radius:20rpx;padding:30rpx;margin-bottom:24rpx;box-shadow:0 4rpx 12rpx rgba(0,0,0,.08);transition:all .3s}.card-item.data-v-5e5077b6:active{transform:scale(.98);box-shadow:0 2rpx 8rpx rgba(0,0,0,.1)}.card-header.data-v-5e5077b6{display:flex;justify-content:space-between;align-items:center;margin-bottom:24rpx;padding-bottom:20rpx;border-bottom:2rpx solid #f0f0f0}.card-id .label.data-v-5e5077b6{font-size:28rpx;color:#666}.card-id .value.data-v-5e5077b6{font-size:32rpx;font-weight:700;color:#333}.status-badge.data-v-5e5077b6{padding:8rpx 20rpx;border-radius:20rpx;font-size:24rpx;font-weight:500}.status-badge.status-confirmed.data-v-5e5077b6{background:#f6ffed;color:#52c41a;border:2rpx solid #b7eb8f}.status-badge.status-pending.data-v-5e5077b6{background:#fff7e6;color:#fa8c16;border:2rpx solid #ffd591}.card-info.data-v-5e5077b6{margin-bottom:20rpx}.info-row.data-v-5e5077b6{display:flex;align-items:center;margin-bottom:16rpx}.info-row .info-label.data-v-5e5077b6{font-size:28rpx;color:#666;min-width:160rpx}.info-row .info-value.data-v-5e5077b6{font-size:30rpx;color:#333;font-weight:500}.card-footer.data-v-5e5077b6{padding-top:20rpx;border-top:2rpx solid #f0f0f0}.time-info.data-v-5e5077b6{display:flex;align-items:center;margin-bottom:12rpx}.time-info .time-label.data-v-5e5077b6{font-size:24rpx;color:#999;min-width:140rpx}.time-info .time-value.data-v-5e5077b6{font-size:26rpx;color:#666}.view-count.data-v-5e5077b6{display:flex;align-items:center;margin-top:8rpx}.view-count .count-icon.data-v-5e5077b6{font-size:28rpx;margin-right:8rpx}.view-count .count-text.data-v-5e5077b6{font-size:24rpx;color:#999}.empty-state.data-v-5e5077b6{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:120rpx 0}.empty-state .empty-icon.data-v-5e5077b6{font-size:120rpx;margin-bottom:30rpx}.empty-state .empty-text.data-v-5e5077b6{font-size:32rpx;color:#999}.loading-state.data-v-5e5077b6{display:flex;justify-content:center;padding:80rpx 0}.loading-state .loading-text.data-v-5e5077b6{font-size:28rpx;color:#999}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "修改就诊卡",
|
||||
"usingComponents": {
|
||||
"uni-icons": "../../uni_modules/uni-icons/components/uni-icons/uni-icons"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";const e=require("../../common/vendor.js"),a={name:"profileB",components:{DevTrainingEntry:()=>"../../components/dev-training-entry/index.js",TabBarDock:()=>"../../components/app-tab-bar/tab-bar-dock.js"},data:()=>({userInfo:{avatar:"",nickname:"",userId:"",balance:"0",points:"0",coupons:"5"},orderTypes:[{name:"待付款",image:"/static/images/my/payment.png",path:"/pages/order/list?type=1",count:2},{name:"待发货",image:"/static/images/my/delivery.png",path:"/pages/order/list?type=2",count:1},{name:"待收货",image:"/static/images/my/shipping.png",path:"/pages/order/list?type=3",count:0},{name:"待评价",image:"/static/images/my/review.png",path:"/pages/order/list?type=4",count:3},{name:"退换/售后",image:"/static/images/my/after-sale.png",path:"/pages/order/list?type=5",count:0}],allServices:[{name:"就诊卡",icon:"folder-add",path:"/pages/Card/Card",class:"bg-cyan"},{name:"设置",icon:"gear",path:"/pages/user/userinfo",class:"bg-gray"}]}),onShow(){this.userInfo=e.index.getStorageSync("userData")},methods:{navigateTo(a){e.index.navigateTo({url:a})},showQRCode(){}}};if(!Array){(e.resolveComponent("uni-icons")+e.resolveComponent("dev-training-entry")+e.resolveComponent("TabBarDock"))()}Math;const n=e._export_sfc(a,[["render",function(a,n,t,o,s,r){return{a:s.userInfo.avatar,b:e.t(s.userInfo.nickname||"未登录"),c:e.t(s.userInfo.balance||0),d:e.t(s.userInfo.points||0),e:e.t(s.userInfo.coupons||0),f:e.o(e=>r.navigateTo("/pages/user/userinfo")),g:e.f(s.allServices,(a,n,t)=>e.e({a:"285a6397-0-"+t,b:e.p({type:a.icon,size:"28",color:"#fff"}),c:e.n(a.class),d:e.t(a.name),e:a.desc},a.desc?{f:e.t(a.desc)}:{},{g:"285a6397-1-"+t,h:n,i:e.o(e=>r.navigateTo(a.path),n)})),h:e.p({type:"right",size:"20",color:"#666"}),i:e.p({bottom:360}),j:e.p({active:1})}}],["__scopeId","data-v-285a6397"]]);a.__runtimeHooks=2,wx.createPage(n);
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的",
|
||||
"backgroundColor": "#e8eaed",
|
||||
"backgroundColorTop": "#e8eaed",
|
||||
"usingComponents": {
|
||||
"dev-training-entry": "../../components/dev-training-entry/index",
|
||||
"tab-bar-dock": "../../components/app-tab-bar/tab-bar-dock",
|
||||
"uni-icons": "../../uni_modules/uni-icons/components/uni-icons/uni-icons"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<view class="mine-page data-v-285a6397"><view class="user-card data-v-285a6397" bindtap="{{f}}"><view class="user-header data-v-285a6397"><image class="avatar data-v-285a6397" src="{{a}}" mode="aspectFill"/><view class="user-info data-v-285a6397"><text class="nickname data-v-285a6397">{{b}}</text></view></view><view class="stat-row data-v-285a6397"><view class="stat-item data-v-285a6397"><text class="value data-v-285a6397">{{c}}</text><text class="label data-v-285a6397">钱包(元)</text></view><view class="stat-item data-v-285a6397"><text class="value data-v-285a6397">{{d}}</text><text class="label data-v-285a6397">积分</text></view><view class="stat-item data-v-285a6397"><text class="value data-v-285a6397">{{e}}</text><text class="label data-v-285a6397">优惠券</text></view></view></view><view class="service-list data-v-285a6397"><view wx:for="{{g}}" wx:for-item="item" wx:key="h" class="service-item data-v-285a6397" bindtap="{{item.i}}"><view class="left data-v-285a6397"><view class="{{['service-icon', 'data-v-285a6397', item.c]}}"><uni-icons wx:if="{{item.b}}" class="data-v-285a6397" u-i="{{item.a}}" bind:__l="__l" u-p="{{item.b}}"></uni-icons></view><text class="service-name data-v-285a6397">{{item.d}}</text></view><view class="right data-v-285a6397"><text wx:if="{{item.e}}" class="desc data-v-285a6397">{{item.f}}</text><uni-icons wx:if="{{h}}" class="data-v-285a6397" u-i="{{item.g}}" bind:__l="__l" u-p="{{h}}"></uni-icons></view></view></view><dev-training-entry wx:if="{{i}}" class="data-v-285a6397" u-i="285a6397-2" bind:__l="__l" u-p="{{i}}"/><tab-bar-dock wx:if="{{j}}" class="data-v-285a6397" u-i="285a6397-3" bind:__l="__l" u-p="{{j}}"/></view>
|
||||