Files
qncV4uni-app/src/pages/toolbox/query.vue

1616 lines
43 KiB
Vue
Raw Normal View History

2026-05-16 15:47:07 +08:00
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getToolboxItem } from '@/config/toolboxRegistry'
import { postToolboxQuery } from '@/api/toolbox'
definePage({
style: {
navigationBarTitleText: '工具查询',
navigationStyle: 'default',
navigationBarBackgroundColor: '#ffffff',
navigationBarTextStyle: 'black',
enablePullDownRefresh: false,
},
})
const toolKey = ref('')
const tool = ref<ReturnType<typeof getToolboxItem>>(null)
const form = ref<Record<string, string>>({})
const loading = ref(false)
const result = ref<Record<string, any> | null>(null)
const error = ref('')
2026-05-20 14:48:07 +08:00
// 游戏相关状态
const gameOptions = ref<Array<{ label: string; value: string; isCorrect: boolean }>>([])
const selectedOption = ref<string | null>(null)
const answered = ref(false)
const answeredCorrect = ref(false)
const systemIdiom = ref('') // 系统给出的成语
const chainGameStep = ref(0) // 接龙游戏步骤0=初始1=用户输入
2026-05-16 15:47:07 +08:00
// 选择框相关状态
const popup = ref<any>(null)
const currentField = ref<any>(null)
const pickerValue = ref([0])
const indicatorStyle = `'height: 50px'`
onLoad((query) => {
const key = (query?.key as string) || ''
toolKey.value = key
tool.value = getToolboxItem(key)
2026-05-20 14:48:07 +08:00
console.log('工具配置:', tool.value)
2026-05-16 15:47:07 +08:00
if (!tool.value) {
error.value = '未找到该工具'
}
else {
uni.setNavigationBarTitle({ title: tool.value.name })
// 初始化表单默认值
if (tool.value.fields) {
tool.value.fields.forEach(field => {
2026-05-20 14:48:07 +08:00
// 设置默认值
2026-05-16 15:47:07 +08:00
if (field.default !== undefined && !form.value[field.key]) {
form.value[field.key] = field.default
}
2026-05-20 14:48:07 +08:00
// 对于 select 类型,即使没有值也设置默认值
if (field.type === 'select' && !form.value[field.key] && field.default !== undefined) {
form.value[field.key] = field.default
}
// 如果是日期字段且没有默认值,设置为今天
if (field.type === 'date' && !form.value[field.key]) {
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, '0')
const day = String(today.getDate()).padStart(2, '0')
form.value[field.key] = `${year}-${month}-${day}`
}
2026-05-16 15:47:07 +08:00
})
2026-05-20 14:48:07 +08:00
console.log('初始化后的表单:', JSON.stringify(form.value))
2026-05-16 15:47:07 +08:00
}
// 如果工具标记了自动查询或不需要输入参数,打开即查
if (tool.value.autoQuery || tool.value.fields.length === 0) {
handleQuery()
}
}
})
// 获取选择框当前选中项的索引
function getSelectIndex(field, value) {
if (!field.options || !Array.isArray(field.options)) {
return 0
}
const defaultValue = field.options[0]?.value || ''
const targetValue = value || defaultValue
return field.options.findIndex(opt => opt.value === targetValue)
}
// 获取选择框当前选中项的标签
function getSelectedLabel(field, value) {
if (!field.options || !Array.isArray(field.options) || !value) {
return null
}
const option = field.options.find(opt => opt.value === value)
return option ? option.label : null
}
2026-05-20 14:48:07 +08:00
// 判断字段是否可见
function isFieldVisible(field, formValue) {
if (!tool.value?.fieldVisibility || !field.key) {
return true
}
const visibilityFn = tool.value.fieldVisibility[field.key]
if (visibilityFn && typeof visibilityFn === 'function') {
return visibilityFn(formValue)
}
return true
}
// 获取动态placeholder
function getFieldPlaceholder(field) {
if (!tool.value?.fieldPlaceholder || !field.key) {
return field.placeholder || ''
}
const placeholderFn = tool.value.fieldPlaceholder[field.key]
if (placeholderFn && typeof placeholderFn === 'function') {
return placeholderFn(form.value)
}
return field.placeholder || ''
}
// 处理单选按钮切换
function handleRadioChange(fieldKey: string, value: any) {
form.value[fieldKey] = value
// 清空依赖该字段的其他字段值
if (tool.value?.fieldVisibility) {
Object.keys(tool.value.fieldVisibility).forEach(key => {
const visibilityFn = tool.value.fieldVisibility[key]
if (visibilityFn && !visibilityFn(form.value)) {
form.value[key] = ''
}
})
}
// 清空word字段的值
if (fieldKey === 'type') {
form.value['word'] = ''
}
}
// 获取显示值(支持 transform
function getDisplayValue(key: string, value: any) {
if (!tool.value?.resultLabels)
return value
const labelOrObj = tool.value.resultLabels[key]
if (typeof labelOrObj === 'object' && labelOrObj !== null && labelOrObj.transform) {
return labelOrObj.transform(value)
}
return value
}
// 处理输入框输入,自动截断
function handleInput(fieldKey: string, value: string) {
form.value[fieldKey] = value
// 如果有maxlength限制自动截断
const field = tool.value?.fields?.find(f => f.key === fieldKey)
if (field?.maxlength && value.length > field.maxlength) {
form.value[fieldKey] = value.substring(0, field.maxlength)
}
}
2026-05-16 15:47:07 +08:00
// 显示自定义选择器
function showPicker(field) {
currentField.value = field
const currentValue = form.value[field.key] || field.options[0]?.value || ''
const index = field.options.findIndex(opt => opt.value === currentValue)
pickerValue.value = [index]
popup.value.open()
}
// 关闭选择器
function closePicker() {
popup.value.close()
}
// 确认选择
function confirmPicker() {
if (currentField.value) {
const index = pickerValue.value[0]
form.value[currentField.value.key] = currentField.value.options[index].value
closePicker()
}
}
// 选择器变化处理
function onPickerChange(e) {
pickerValue.value = e.detail.value
}
async function handleQuery() {
if (!tool.value)
return
error.value = ''
2026-05-20 14:48:07 +08:00
answered.value = false
// 成语接龙特殊处理
if (toolKey.value === 'chengyujielong') {
if (!form.value.word) {
error.value = '请输入成语'
return
}
loading.value = true
try {
const res = await postToolboxQuery(toolKey.value, {
word: form.value.word,
userid: '1212' // 固定用户ID
})
if (res.code === 200 && res.data?.result) {
result.value = res.data.result
answered.value = true // 标记已回答
// 如果接龙成功,保存系统返回的成语
if (res.data.result.word) {
systemIdiom.value = res.data.result.word
chainGameStep.value = 1
}
} else {
error.value = res.msg || '接龙失败'
}
} catch {
error.value = '网络错误,请稍后重试'
} finally {
loading.value = false
}
return
}
2026-05-16 15:47:07 +08:00
result.value = null
revealedKeys.value = new Set()
2026-05-20 14:48:07 +08:00
// 调试:打印表单值
console.log('提交的表单数据:', JSON.stringify(form.value))
console.log('验证结果:', tool.value.validate(form.value))
2026-05-16 15:47:07 +08:00
if (!tool.value.validate(form.value)) {
error.value = tool.value.validateMsg
return
}
loading.value = true
try {
const res = await postToolboxQuery(toolKey.value, form.value)
if (res.code === 200 && res.data?.result) {
result.value = res.data.result
2026-05-20 14:48:07 +08:00
// 如果是游戏类工具
if (tool.value?.isGame) {
// 成语填字和诗词填空:准备选项
if (toolKey.value === 'idiom-quiz' || toolKey.value === 'poem-fill') {
prepareGameOptions()
}
2026-05-21 12:00:38 +08:00
// 百科题库:准备选项
if (toolKey.value === 'baiketiku') {
prepareBaiKeOptions()
}
// 垃圾分类问答:准备选项
if (toolKey.value === 'anslajifenlei') {
prepareGarbageOptions()
}
2026-05-20 14:48:07 +08:00
// 成语接龙:记录系统返回的成语
if (toolKey.value === 'chengyujielong') {
if (result.value?.word) {
systemIdiom.value = result.value.word
chainGameStep.value = 1
}
}
}
2026-05-16 15:47:07 +08:00
}
else {
error.value = res.msg || '查询失败'
}
}
catch {
error.value = '网络错误,请稍后重试'
}
finally {
loading.value = false
}
}
const resultEntries = computed(() => {
if (!result.value || !tool.value?.resultLabels)
return []
return Object.entries(tool.value.resultLabels)
.filter(([key]) => result.value![key] !== undefined)
.map(([key, labelOrObj]) => {
const val = result.value![key]
const display = val === '' || val === null ? '无' : val
if (typeof labelOrObj === 'object' && labelOrObj !== null) {
return { key, label: labelOrObj.label, hidden: !!labelOrObj.hidden, value: display }
}
return { key, label: labelOrObj, hidden: false, value: display }
})
})
const revealedKeys = ref<Set<string>>(new Set())
function toggleReveal(key: string) {
if (revealedKeys.value.has(key)) {
revealedKeys.value.delete(key)
}
else {
revealedKeys.value.add(key)
}
}
const resultList = computed(() => {
if (!result.value || tool.value?.resultType !== 'list')
return []
const list = result.value.list
if (!Array.isArray(list))
return []
return list
})
2026-05-18 13:54:57 +08:00
const subResultList = computed(() => {
if (!result.value || !tool.value?.subResultKey || !tool.value?.subResultLabels)
return []
const sub = result.value[tool.value.subResultKey]
if (!Array.isArray(sub))
return []
return sub
})
const subResultLabelEntries = computed(() => {
if (!tool.value?.subResultLabels)
return []
return Object.entries(tool.value.subResultLabels)
})
2026-05-16 15:47:07 +08:00
const listLabelEntries = computed(() => {
if (!tool.value?.resultLabels)
return []
return Object.entries(tool.value.resultLabels)
})
2026-05-20 14:48:07 +08:00
// 游戏工具:准备选项(打乱顺序)
const prepareGameOptions = () => {
if (!result.value || !result.value.correct)
return
const correct = result.value.correct
const options = [
{ label: correct, value: correct, isCorrect: true },
{ label: result.value.wrong_a || '', value: result.value.wrong_a || '', isCorrect: false },
{ label: result.value.wrong_b || '', value: result.value.wrong_b || '', isCorrect: false },
{ label: result.value.wrong_c || '', value: result.value.wrong_c || '', isCorrect: false },
].filter(opt => opt.label) // 过滤空值
// 打乱顺序
for (let i = options.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[options[i], options[j]] = [options[j], options[i]]
}
gameOptions.value = options
selectedOption.value = null
answered.value = false
}
2026-05-21 12:00:38 +08:00
// 百科题库准备选项A/B/C/D 四个选项,标记正确答案)
const prepareBaiKeOptions = () => {
if (!result.value)
return
// answer 可能是选项字母标识(如 "A"),也可能是选项文本内容
const correctAnswer = String(result.value.answer || '').trim()
const optionKeys = ['answerA', 'answerB', 'answerC', 'answerD'] as const
const optionLabels = ['A', 'B', 'C', 'D'] as const
const options = optionKeys
.map((key, i) => {
const val = String(result.value[key] || '').trim()
if (!val) return null
// answer 可能是字母标识 "A"/"B"/...,也可能是选项文本本身
const isCorrect = correctAnswer === optionLabels[i] || correctAnswer === val
return { label: `${optionLabels[i]}. ${val}`, value: val, isCorrect }
})
.filter(Boolean) as Array<{ label: string; value: string; isCorrect: boolean }>
gameOptions.value = options
selectedOption.value = null
answered.value = false
}
// 垃圾分类问答:准备选项(四个分类,标记正确答案)
const prepareGarbageOptions = () => {
if (!result.value)
return
const correctType = result.value.type
const typeNames: Record<number, string> = {
0: '可回收物',
1: '有害垃圾',
2: '厨余垃圾',
3: '其他垃圾',
}
const options = Object.entries(typeNames).map(([typeKey, typeName]) => ({
label: typeName,
value: typeName,
isCorrect: Number(typeKey) === Number(correctType),
}))
// 打乱顺序
for (let i = options.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[options[i], options[j]] = [options[j], options[i]]
}
gameOptions.value = options
selectedOption.value = null
answered.value = false
}
2026-05-20 14:48:07 +08:00
// 点击选项
function handleOptionClick(option: { label: string; value: string; isCorrect: boolean }) {
if (answered.value)
return
selectedOption.value = option.value
answered.value = true
answeredCorrect.value = option.isCorrect
// 如果答对了,显示完整答案和解释
if (option.isCorrect) {
revealedKeys.value.add('full')
revealedKeys.value.add('explan')
}
}
// 快速填入:将系统接龙的成语填入输入框
function handleQuickFill() {
if (result.value?.word) {
form.value.word = result.value.word
systemIdiom.value = result.value.word
// 清空之前的结果
result.value = null
answered.value = false
}
}
// 重新开始(再来一题)
function handleRestart() {
if (toolKey.value === 'chengyujielong') {
// 成语接龙:清空表单,重新开局
form.value.word = ''
systemIdiom.value = ''
chainGameStep.value = 0
answered.value = false
result.value = null
return
}
handleQuery()
}
2026-05-16 15:47:07 +08:00
</script>
<template>
<view class="page-root">
<scroll-view scroll-y class="scrollarea">
<view class="page">
<view v-if="tool" class="card">
<view class="card-desc">
<text class="desc-text">{{ tool.desc }}</text>
</view>
<!-- 动态表单 -->
<view class="form-area">
<view v-for="field in tool.fields" :key="field.key" class="field">
2026-05-20 14:48:07 +08:00
<!-- 检查字段是否可见 -->
<template v-if="isFieldVisible(field, form)">
<text class="field-label">{{ field.label }}</text>
2026-05-16 15:47:07 +08:00
2026-05-20 14:48:07 +08:00
<!-- 按钮切换 (Radio) -->
<view v-if="field.type === 'radio'" class="field-radio-group">
<view
v-for="opt in field.options"
:key="opt.value"
class="radio-item"
:class="{ active: (form[field.key] ?? field.default) === opt.value }"
@tap="handleRadioChange(field.key, opt.value)"
>
{{ opt.label }}
</view>
2026-05-18 13:54:57 +08:00
</view>
2026-05-20 14:48:07 +08:00
<!-- 选择框 -->
<picker
v-else-if="field.type === 'select'"
mode="selector"
:range="field.options.map(opt => opt.label)"
:value="getSelectIndex(field, form[field.key])"
@change="(e: any) => {
const index = e.detail.value
form[field.key] = field.options[index].value
}"
>
<view class="field-input field-picker">
{{ getSelectedLabel(field, form[field.key]) || field.placeholder }}
</view>
</picker>
<!-- 日期选择器 -->
<picker
v-else-if="field.type === 'date'"
mode="date"
:value="form[field.key] || ''"
@change="(e: any) => (form[field.key] = e.detail.value)"
>
<view class="field-input field-picker">
{{ form[field.key] || field.placeholder }}
</view>
</picker>
<!-- 文本域 -->
<textarea
v-else-if="field.type === 'textarea'"
class="field-input field-textarea"
:maxlength="field.maxlength"
:placeholder="field.placeholder"
:value="form[field.key] || ''"
placeholder-class="field-ph"
@input="(e: any) => handleInput(field.key, e.detail.value)"
/>
<!-- 普通输入框 -->
<input
v-else
class="field-input"
:type="field.type"
:maxlength="field.maxlength"
:placeholder="getFieldPlaceholder(field)"
:value="form[field.key] || ''"
placeholder-class="field-ph"
confirm-type="done"
@input="(e: any) => handleInput(field.key, e.detail.value)"
@blur="(e: any) => (form[field.key] = e.detail.value)"
@confirm="(e: any) => (form[field.key] = e.detail.value)"
/>
</template>
2026-05-16 15:47:07 +08:00
</view>
</view>
<!-- 查询按钮 -->
<button
class="query-btn"
:disabled="loading"
@tap="handleQuery"
>
{{ loading ? '查询中...' : '立即查询' }}
</button>
<!-- 错误提示 -->
<view v-if="error" class="msg-area msg-error">
<text class="msg-text">{{ error }}</text>
</view>
2026-05-20 14:48:07 +08:00
<!-- 游戏类工具特殊展示 -->
<view v-if="tool?.isGame && result" class="game-area">
<!-- 成语接龙特殊展示 -->
<template v-if="toolKey === 'chengyujielong'">
<!-- 用户输入的成语 -->
<view v-if="form.word" class="chain-user-input">
<text class="chain-user-label">你的成语</text>
<text class="chain-user-value">{{ form.word }}</text>
</view>
<!-- 系统接龙的结果 -->
<view v-if="result && result.word" class="chain-result">
<view class="chain-success">
<text class="chain-success-icon">🔗</text>
<view class="chain-success-content">
<text class="chain-idiom">{{ result.word }}</text>
<text v-if="result.pinyin" class="chain-pinyin">{{ result.pinyin }}</text>
<text v-if="result.jieshi" class="chain-explain">{{ result.jieshi }}</text>
</view>
</view>
<!-- 快速填入按钮 -->
<button class="chain-quick-btn" @tap="handleQuickFill">
📝 用这个继续接龙
</button>
</view>
<!-- 空状态提示 -->
<view v-if="!result" class="chain-empty">
<text class="chain-empty-text">输入成语后系统会自动接龙</text>
</view>
</template>
<!-- 填字游戏成语填字诗词填空 -->
<template v-else>
<!-- 难度标签 -->
<view v-if="result.diff" class="game-diff">
<text class="game-diff-text">{{ getDisplayValue('diff', result.diff) }}</text>
</view>
<!-- 题目 -->
<view class="game-question">
2026-05-21 12:00:38 +08:00
<text v-if="toolKey === 'anslajifenlei'" class="game-question-text game-question-garbage">
<text class="garbage-name">{{ result.name }}</text>
<text class="garbage-prompt">属于什么垃圾</text>
</text>
<text v-else class="game-question-text">{{ result.title || result.question }}</text>
2026-05-20 14:48:07 +08:00
</view>
<!-- 选项按钮 -->
2026-05-21 12:00:38 +08:00
<view class="game-options" :class="{ 'game-options-col': toolKey === 'anslajifenlei' }">
2026-05-20 14:48:07 +08:00
<view
v-for="(option, index) in gameOptions"
:key="index"
class="game-option"
:class="{
'game-option-selected': selectedOption === option.value,
'game-option-correct': answered && option.isCorrect,
'game-option-wrong': answered && selectedOption === option.value && !option.isCorrect,
2026-05-21 12:00:38 +08:00
'game-option-recyclable': toolKey === 'anslajifenlei' && option.value === '可回收物',
'game-option-hazardous': toolKey === 'anslajifenlei' && option.value === '有害垃圾',
'game-option-kitchen': toolKey === 'anslajifenlei' && option.value === '厨余垃圾',
'game-option-other': toolKey === 'anslajifenlei' && option.value === '其他垃圾',
2026-05-20 14:48:07 +08:00
}"
@tap="handleOptionClick(option)"
>
<text class="game-option-text">{{ option.label }}</text>
</view>
</view>
<!-- 答案和解释 -->
<view v-if="answered" class="game-result">
<!-- 答对提示 -->
<view v-if="answeredCorrect" class="game-status game-status-correct">
<text class="game-status-text">🎉 回答正确</text>
</view>
<!-- 答错提示 -->
<view v-else class="game-status game-status-wrong">
<text class="game-status-text"> 回答错误</text>
</view>
<!-- 成语填字显示完整成语和解释 -->
<template v-if="toolKey === 'idiom-quiz'">
<view class="game-answer">
<text class="game-answer-label">完整成语</text>
<text class="game-answer-value">{{ result.full }}</text>
</view>
<view class="game-explan">
<text class="game-explan-label">成语解释</text>
<text class="game-explan-value">{{ result.explan }}</text>
</view>
</template>
<!-- 诗词填空显示完整诗句和详细信息 -->
<template v-else-if="toolKey === 'poem-fill'">
<view class="game-answer">
<text class="game-answer-label">完整诗句</text>
<text class="game-answer-value">{{ result.full }}</text>
</view>
<view class="game-poem-info">
<view v-if="result.title" class="game-poem-item">
<text class="game-poem-label">诗名</text>
<text class="game-poem-value">{{ result.title }}</text>
</view>
<view v-if="result.author" class="game-poem-item">
<text class="game-poem-label">作者</text>
<text class="game-poem-value">{{ result.author }}</text>
</view>
<view v-if="result.dynasty" class="game-poem-item">
<text class="game-poem-label">朝代</text>
<text class="game-poem-value">{{ result.dynasty }}</text>
</view>
<view v-if="result.category" class="game-poem-item">
<text class="game-poem-label">分类</text>
<text class="game-poem-value">{{ result.category }}</text>
</view>
</view>
<view v-if="result.note" class="game-explan">
<text class="game-explan-label">注释</text>
<text class="game-explan-value">{{ result.note }}</text>
</view>
</template>
2026-05-21 12:00:38 +08:00
<!-- 百科题库显示正确答案和解析 -->
<template v-else-if="toolKey === 'baiketiku'">
<view class="game-answer">
<text class="game-answer-label">正确答案</text>
<text class="game-answer-value">{{ result.answer }}</text>
</view>
<view v-if="result.analytic" class="game-explan">
<text class="game-explan-label">解析</text>
<text class="game-explan-value">{{ result.analytic }}</text>
</view>
</template>
<!-- 垃圾分类问答显示正确分类 -->
<template v-else-if="toolKey === 'anslajifenlei'">
<view class="game-answer">
<text class="game-answer-label">正确分类</text>
<text class="game-answer-value garbage-answer-type">{{ result.type_name }}</text>
</view>
<view v-if="result.explain" class="game-explan">
<text class="game-explan-label">说明</text>
<text class="game-explan-value">{{ result.explain }}</text>
</view>
</template>
2026-05-20 14:48:07 +08:00
</view>
</template>
<!-- 再来一题按钮 -->
<button class="game-restart-btn" @tap="handleRestart">
🔄 再来一题
</button>
</view>
2026-05-16 15:47:07 +08:00
<!-- 结果展示 - 普通键值对 -->
2026-05-20 14:48:07 +08:00
<view v-if="resultEntries.length > 0 && tool?.resultType !== 'list' && !tool?.isGame" class="result-area">
2026-05-16 15:47:07 +08:00
<view class="result-title">查询结果</view>
2026-05-20 14:48:07 +08:00
<!-- 字典类型特殊显示 -->
<view v-if="tool?.resultType === 'dict'" class="dict-result">
<!-- 汉字标题 -->
<view v-if="result.word" class="dict-word">
<text class="dict-word-text">{{ result.word }}</text>
</view>
<!-- 基础信息卡片 -->
<view v-if="result.py || result.pyyb || result.wubi || result.bihua || result.bushou" class="dict-basic-info">
<view v-if="result.py" class="dict-info-item">
<text class="dict-info-label">拼音</text>
<text class="dict-info-value">{{ result.py }}</text>
</view>
<view v-if="result.pyyb" class="dict-info-item">
<text class="dict-info-label">拼音音标</text>
<text class="dict-info-value">{{ result.pyyb }}</text>
</view>
<view v-if="result.wubi" class="dict-info-item">
<text class="dict-info-label">五笔</text>
<text class="dict-info-value">{{ result.wubi }}</text>
</view>
<view v-if="result.bihua" class="dict-info-item">
<text class="dict-info-label">笔画</text>
<text class="dict-info-value">{{ result.bihua }}</text>
</view>
<view v-if="result.bushou" class="dict-info-item">
<text class="dict-info-label">部首</text>
<text class="dict-info-value">{{ result.bushou }}</text>
</view>
<view v-if="result.bishun" class="dict-info-item">
<text class="dict-info-label">笔顺</text>
<text class="dict-info-value">{{ result.bishun }}</text>
</view>
</view>
<!-- 释义卡片 -->
<view v-if="result.content" class="dict-content">
<text class="dict-content-title">释义</text>
<text class="dict-content-text">{{ result.content }}</text>
</view>
<!-- 详细解释卡片 -->
<view v-if="result.explain" class="dict-explain">
<text class="dict-explain-title">详细解释</text>
<text class="dict-explain-text">{{ result.explain }}</text>
</view>
</view>
<!-- 普通列表显示 -->
<view v-else class="result-list">
2026-05-16 15:47:07 +08:00
<view
v-for="item in resultEntries"
:key="item.key"
class="result-row"
>
<text class="result-label">{{ item.label }}</text>
<!-- 隐藏字段点击显示/隐藏 -->
<view v-if="item.hidden" class="result-reveal-wrap">
<view
v-if="!revealedKeys.has(item.key)"
class="result-reveal-btn"
@tap="toggleReveal(item.key)"
>
点击查看
</view>
<template v-else>
<text class="result-value">{{ item.value }}</text>
<text class="result-hide-btn" @tap="toggleReveal(item.key)">收起</text>
</template>
</view>
2026-05-20 14:48:07 +08:00
<!-- 多行文本 -->
<text v-else-if="typeof item.value === 'string' && item.value.length > 50" class="result-value result-multiline">{{ item.value }}</text>
2026-05-16 15:47:07 +08:00
<!-- 普通字段 -->
<text v-else class="result-value">{{ item.value }}</text>
</view>
</view>
</view>
2026-05-18 13:54:57 +08:00
<!-- 结果展示 - 子列表型 (如油价明细) -->
<view v-if="subResultList.length > 0" class="result-area">
<view class="result-title">详细列表</view>
<view class="result-card-list">
<view
v-for="(item, idx) in subResultList"
:key="idx"
class="result-card-item"
>
<view
v-for="([fieldKey, fieldLabel], fIdx) in subResultLabelEntries"
:key="fieldKey"
class="card-item-row"
>
<template v-if="item[fieldKey] !== undefined && item[fieldKey] !== ''">
<text v-if="fIdx === 0" class="card-item-index">{{ idx + 1 }}</text>
<text v-else class="card-item-index-placeholder" />
<text class="card-item-field-label">{{ typeof fieldLabel === 'object' ? fieldLabel.label : fieldLabel }}</text>
<text class="card-item-field-value">{{ item[fieldKey] }}</text>
</template>
</view>
</view>
</view>
</view>
2026-05-16 15:47:07 +08:00
<!-- 结果展示 - 列表型 -->
<view v-if="resultList.length > 0" class="result-area">
<view class="result-title">查询结果</view>
<view class="result-card-list">
<view
v-for="(item, idx) in resultList"
:key="idx"
class="result-card-item"
>
<view
v-for="([fieldKey, fieldLabel], fIdx) in listLabelEntries"
:key="fieldKey"
class="card-item-row"
>
<template v-if="item[fieldKey] !== undefined && item[fieldKey] !== ''">
<text v-if="fIdx === 0" class="card-item-index">{{ idx + 1 }}</text>
<text v-else class="card-item-index-placeholder" />
<text class="card-item-field-label">{{ typeof fieldLabel === 'object' ? fieldLabel.label : fieldLabel }}</text>
2026-05-20 14:48:07 +08:00
<!-- 隐藏字段点击显示/隐藏 -->
<view v-if="typeof fieldLabel === 'object' && fieldLabel.hidden" class="card-item-reveal-wrap">
<view
v-if="!revealedKeys.has(`${idx}-${fieldKey}`)"
class="card-item-reveal-btn"
@tap="toggleReveal(`${idx}-${fieldKey}`)"
>
点击查看
</view>
<template v-else>
<text class="card-item-field-value">{{ item[fieldKey] }}</text>
<text class="card-item-hide-btn" @tap="toggleReveal(`${idx}-${fieldKey}`)">收起</text>
</template>
</view>
<!-- 普通字段 -->
<text v-else class="card-item-field-value">{{ item[fieldKey] }}</text>
2026-05-16 15:47:07 +08:00
</template>
</view>
</view>
</view>
</view>
</view>
<view v-else class="empty">
<text class="empty-text">未找到该工具</text>
</view>
</view>
</scroll-view>
</view>
</template>
<style scoped lang="scss">
.page-root {
height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #f8faff 0%, #f3f5fb 100%);
}
.scrollarea {
flex: 1;
min-height: 0;
height: 0;
}
.page {
padding: 24rpx 24rpx 40rpx;
box-sizing: border-box;
}
.card {
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
border-radius: 24rpx;
padding: 32rpx 28rpx;
border: 1rpx solid #e5e6f0;
box-shadow:
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
}
.card-desc {
margin-bottom: 28rpx;
}
.desc-text {
font-size: 24rpx;
color: #86909c;
line-height: 1.5;
}
.form-area {
margin-bottom: 28rpx;
}
.field {
margin-bottom: 20rpx;
}
.field-label {
display: block;
font-size: 24rpx;
color: #4e5969;
margin-bottom: 8rpx;
}
.field-input {
width: 100%;
height: 80rpx;
background: #f7f8fa;
border: 1rpx solid #e5e6f0;
border-radius: 16rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #1d2129;
box-sizing: border-box;
}
2026-05-18 13:54:57 +08:00
.field-radio-group {
display: flex;
gap: 16rpx;
flex-wrap: wrap;
}
.radio-item {
flex: 1;
min-width: 140rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f7f8fa;
border: 1rpx solid #e5e6f0;
border-radius: 12rpx;
font-size: 26rpx;
color: #4e5969;
transition: all 0.2s;
}
.radio-item.active {
background: #e8f3ff;
border-color: #1768ff;
color: #1768ff;
font-weight: 500;
}
2026-05-16 15:47:07 +08:00
.field-picker {
display: flex;
align-items: center;
color: #1d2129;
}
.field-picker:empty::before {
content: attr(placeholder);
color: #c9cdd4;
}
.field-textarea {
height: 200rpx;
padding: 16rpx 24rpx;
line-height: 1.6;
}
.field-ph {
color: #c9cdd4;
}
.query-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #1768ff, #4e8cff);
color: #ffffff;
font-size: 30rpx;
font-weight: 500;
border-radius: 16rpx;
border: none;
margin-bottom: 20rpx;
}
.query-btn[disabled] {
opacity: 0.6;
}
.msg-area {
padding: 16rpx 20rpx;
border-radius: 12rpx;
margin-bottom: 20rpx;
}
.msg-error {
background: #fff2f0;
border: 1rpx solid #ffccc7;
}
.msg-text {
font-size: 24rpx;
color: #f53f3f;
}
2026-05-20 14:48:07 +08:00
/* 字典结果样式 */
.dict-result {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.dict-word {
background: linear-gradient(135deg, #1768ff, #4e8cff);
padding: 32rpx;
border-radius: 20rpx;
text-align: center;
}
.dict-word-text {
font-size: 72rpx;
font-weight: 600;
color: #ffffff;
}
.dict-basic-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
padding: 0 8rpx;
}
.dict-info-item {
background: #f7f8fa;
padding: 20rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.dict-info-label {
font-size: 24rpx;
color: #86909c;
}
.dict-info-value {
font-size: 30rpx;
font-weight: 500;
color: #1d2129;
}
.dict-content,
.dict-explain {
background: #ffffff;
border: 1rpx solid #e5e6f0;
border-radius: 16rpx;
padding: 24rpx;
}
.dict-content-title,
.dict-explain-title {
font-size: 28rpx;
font-weight: 600;
color: #1d2129;
display: block;
margin-bottom: 16rpx;
}
.dict-content-text,
.dict-explain-text {
font-size: 26rpx;
color: #4e5969;
line-height: 1.8;
white-space: pre-wrap;
word-wrap: break-word;
}
2026-05-16 15:47:07 +08:00
.result-area {
margin-top: 8rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f2f3f5;
}
.result-title {
font-size: 28rpx;
font-weight: 600;
color: #1d2129;
margin-bottom: 16rpx;
}
.result-list {
background: #f7f8fa;
border-radius: 16rpx;
padding: 8rpx 0;
}
.result-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 24rpx;
}
.result-label {
font-size: 26rpx;
color: #86909c;
}
.result-value {
font-size: 26rpx;
color: #1d2129;
font-weight: 500;
flex: 1;
text-align: right;
}
2026-05-20 14:48:07 +08:00
.result-multiline {
text-align: left;
line-height: 1.8;
word-wrap: break-word;
}
2026-05-16 15:47:07 +08:00
.result-reveal-wrap {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12rpx;
}
.result-reveal-btn {
font-size: 24rpx;
color: #ffffff;
background: linear-gradient(135deg, #1768ff, #4e8cff);
padding: 6rpx 24rpx;
border-radius: 24rpx;
}
.result-hide-btn {
font-size: 22rpx;
color: #86909c;
flex-shrink: 0;
}
.empty {
display: flex;
justify-content: center;
align-items: center;
padding: 120rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #86909c;
}
/* 列表型结果卡片 */
.result-card-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.result-card-item {
background: #ffffff;
border: 1rpx solid #e5e6f0;
border-radius: 16rpx;
padding: 20rpx 24rpx;
}
.card-item-row {
display: flex;
align-items: flex-start;
gap: 12rpx;
padding: 4rpx 0;
}
.card-item-index {
flex-shrink: 0;
width: 40rpx;
height: 40rpx;
line-height: 40rpx;
text-align: center;
background: linear-gradient(135deg, #1768ff, #4e8cff);
color: #ffffff;
font-size: 22rpx;
font-weight: 600;
border-radius: 50%;
}
.card-item-index-placeholder {
flex-shrink: 0;
width: 40rpx;
}
.card-item-field-label {
flex-shrink: 0;
font-size: 24rpx;
color: #86909c;
min-width: 80rpx;
}
.card-item-field-value {
flex: 1;
font-size: 26rpx;
color: #1d2129;
line-height: 1.6;
font-weight: 500;
}
2026-05-20 14:48:07 +08:00
/* 列表卡片中的隐藏字段 */
.card-item-reveal-wrap {
flex: 1;
display: flex;
align-items: center;
gap: 12rpx;
}
.card-item-reveal-btn {
padding: 8rpx 24rpx;
font-size: 24rpx;
color: #ffffff;
background: linear-gradient(135deg, #1768ff, #4e8cff);
border-radius: 24rpx;
cursor: pointer;
}
.card-item-hide-btn {
font-size: 22rpx;
color: #86909c;
flex-shrink: 0;
}
2026-05-16 15:47:07 +08:00
/* 自定义选择器样式 */
.picker-container {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
padding: 30rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
height: 88rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.picker-header text {
font-size: 32rpx;
}
.picker-header text:first-child {
color: #606266;
}
.picker-header text:last-child {
color: #1768ff;
}
.picker-view {
width: 100%;
height: 400rpx;
}
.picker-item {
line-height: 100rpx;
text-align: center;
font-size: 28rpx;
color: #333;
}
2026-05-20 14:48:07 +08:00
/* 游戏区域样式 */
.game-area {
margin-top: 28rpx;
padding: 28rpx;
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
border-radius: 20rpx;
border: 2rpx solid #e5e6f0;
}
.game-diff {
margin-bottom: 16rpx;
}
.game-diff-text {
font-size: 22rpx;
color: #86909c;
background: #f7f8fa;
padding: 8rpx 20rpx;
border-radius: 20rpx;
}
.game-question {
text-align: center;
padding: 40rpx 20rpx;
margin-bottom: 32rpx;
}
.game-question-text {
font-size: 48rpx;
font-weight: 600;
color: #1d2129;
line-height: 1.6;
}
.game-options {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
margin-bottom: 32rpx;
}
.game-option {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border: 2rpx solid #e5e6f0;
border-radius: 16rpx;
transition: all 0.2s;
}
.game-option:active {
transform: scale(0.98);
}
.game-option-selected {
border-color: #1768ff;
background: #e8f3ff;
}
.game-option-correct {
border-color: #00b42a;
background: #e8ffeb;
}
.game-option-wrong {
border-color: #f53f3f;
background: #ffe8e8;
}
.game-option-text {
font-size: 32rpx;
font-weight: 600;
color: #1d2129;
}
.game-result {
margin-bottom: 24rpx;
padding: 24rpx;
background: #f7f8fa;
border-radius: 16rpx;
}
.game-status {
text-align: center;
padding: 16rpx;
border-radius: 12rpx;
margin-bottom: 20rpx;
}
.game-status-correct {
background: #e8ffeb;
}
.game-status-wrong {
background: #ffe8e8;
}
.game-status-text {
font-size: 28rpx;
font-weight: 600;
}
.game-answer {
display: flex;
align-items: baseline;
margin-bottom: 16rpx;
}
.game-answer-label {
font-size: 24rpx;
color: #86909c;
}
.game-answer-value {
font-size: 32rpx;
font-weight: 600;
color: #1d2129;
margin-left: 8rpx;
}
.game-explan {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.game-explan-label {
font-size: 24rpx;
color: #86909c;
font-weight: 600;
}
.game-explan-value {
font-size: 26rpx;
color: #4e5969;
line-height: 1.8;
}
/* 诗词信息样式 */
.game-poem-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12rpx;
margin-bottom: 16rpx;
padding: 16rpx;
background: #ffffff;
border-radius: 12rpx;
}
.game-poem-item {
display: flex;
align-items: baseline;
gap: 8rpx;
}
.game-poem-label {
font-size: 22rpx;
color: #86909c;
}
.game-poem-value {
font-size: 24rpx;
font-weight: 500;
color: #1d2129;
}
.game-restart-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #1768ff, #4e8cff);
color: #ffffff;
font-size: 28rpx;
font-weight: 500;
border-radius: 16rpx;
border: none;
margin-top: 16rpx;
}
.game-restart-btn[disabled] {
opacity: 0.6;
}
/* 成语接龙游戏样式 */
.chain-user-input {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 24rpx;
padding: 20rpx;
background: linear-gradient(135deg, #e8f3ff 0%, #ffffff 100%);
border-radius: 16rpx;
}
.chain-user-label {
font-size: 24rpx;
color: #86909c;
}
.chain-user-value {
font-size: 32rpx;
font-weight: 600;
color: #1768ff;
}
.chain-result {
margin-bottom: 24rpx;
}
.chain-success {
display: flex;
align-items: flex-start;
gap: 16rpx;
padding: 24rpx;
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
border: 2rpx solid #e8f3ff;
border-radius: 16rpx;
}
.chain-success-icon {
flex-shrink: 0;
width: 48rpx;
height: 48rpx;
line-height: 48rpx;
text-align: center;
background: linear-gradient(135deg, #00b42a, #36cfc9);
color: #ffffff;
font-size: 28rpx;
font-weight: 600;
border-radius: 50%;
}
.chain-success-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.chain-idiom {
font-size: 36rpx;
font-weight: 600;
color: #1d2129;
}
.chain-pinyin {
font-size: 22rpx;
color: #86909c;
}
.chain-explain {
font-size: 24rpx;
color: #4e5969;
line-height: 1.6;
}
.chain-quick-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #1768ff, #4e8cff);
color: #ffffff;
font-size: 28rpx;
font-weight: 500;
border-radius: 16rpx;
border: none;
margin-top: 16rpx;
}
.chain-empty {
text-align: center;
padding: 40rpx 0;
color: #86909c;
}
.chain-empty-text {
font-size: 24rpx;
}
2026-05-21 12:00:38 +08:00
/* 垃圾分类游戏样式 */
.game-question-garbage {
display: flex;
flex-direction: column;
gap: 16rpx;
align-items: center;
}
.garbage-name {
font-size: 56rpx;
font-weight: 700;
color: #1d2129;
}
.garbage-prompt {
font-size: 30rpx;
color: #86909c;
font-weight: 400;
}
.garbage-answer-type {
color: #00b42a;
}
/* 垃圾分类选项布局 - 单列 */
.game-options-col {
grid-template-columns: 1fr;
}
/* 垃圾分类四个类别的默认色 */
.game-option-recyclable {
border-left: 6rpx solid #1768ff;
}
.game-option-hazardous {
border-left: 6rpx solid #f53f3f;
}
.game-option-kitchen {
border-left: 6rpx solid #00b42a;
}
.game-option-other {
border-left: 6rpx solid #86909c;
}
2026-05-16 15:47:07 +08:00
</style>