Files
qncV4uni-app/src/pages/toolbox/query.vue
2026-05-21 12:00:38 +08:00

1616 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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('')
// 游戏相关状态
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=用户输入
// 选择框相关状态
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)
console.log('工具配置:', tool.value)
if (!tool.value) {
error.value = '未找到该工具'
}
else {
uni.setNavigationBarTitle({ title: tool.value.name })
// 初始化表单默认值
if (tool.value.fields) {
tool.value.fields.forEach(field => {
// 设置默认值
if (field.default !== undefined && !form.value[field.key]) {
form.value[field.key] = field.default
}
// 对于 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}`
}
})
console.log('初始化后的表单:', JSON.stringify(form.value))
}
// 如果工具标记了自动查询或不需要输入参数,打开即查
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
}
// 判断字段是否可见
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)
}
}
// 显示自定义选择器
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 = ''
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
}
result.value = null
revealedKeys.value = new Set()
// 调试:打印表单值
console.log('提交的表单数据:', JSON.stringify(form.value))
console.log('验证结果:', tool.value.validate(form.value))
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
// 如果是游戏类工具
if (tool.value?.isGame) {
// 成语填字和诗词填空:准备选项
if (toolKey.value === 'idiom-quiz' || toolKey.value === 'poem-fill') {
prepareGameOptions()
}
// 百科题库:准备选项
if (toolKey.value === 'baiketiku') {
prepareBaiKeOptions()
}
// 垃圾分类问答:准备选项
if (toolKey.value === 'anslajifenlei') {
prepareGarbageOptions()
}
// 成语接龙:记录系统返回的成语
if (toolKey.value === 'chengyujielong') {
if (result.value?.word) {
systemIdiom.value = result.value.word
chainGameStep.value = 1
}
}
}
}
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
})
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)
})
const listLabelEntries = computed(() => {
if (!tool.value?.resultLabels)
return []
return Object.entries(tool.value.resultLabels)
})
// 游戏工具:准备选项(打乱顺序)
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
}
// 百科题库准备选项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
}
// 点击选项
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()
}
</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">
<!-- 检查字段是否可见 -->
<template v-if="isFieldVisible(field, form)">
<text class="field-label">{{ field.label }}</text>
<!-- 按钮切换 (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>
</view>
<!-- 选择框 -->
<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>
</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>
<!-- 游戏类工具特殊展示 -->
<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">
<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>
</view>
<!-- 选项按钮 -->
<view class="game-options" :class="{ 'game-options-col': toolKey === 'anslajifenlei' }">
<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,
'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 === '其他垃圾',
}"
@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>
<!-- 百科题库显示正确答案和解析 -->
<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>
</view>
</template>
<!-- 再来一题按钮 -->
<button class="game-restart-btn" @tap="handleRestart">
🔄 再来一题
</button>
</view>
<!-- 结果展示 - 普通键值对 -->
<view v-if="resultEntries.length > 0 && tool?.resultType !== 'list' && !tool?.isGame" class="result-area">
<view class="result-title">查询结果</view>
<!-- 字典类型特殊显示 -->
<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">
<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>
<!-- 多行文本 -->
<text v-else-if="typeof item.value === 'string' && item.value.length > 50" class="result-value result-multiline">{{ item.value }}</text>
<!-- 普通字段 -->
<text v-else class="result-value">{{ item.value }}</text>
</view>
</view>
</view>
<!-- 结果展示 - 子列表型 (如油价明细) -->
<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>
<!-- 结果展示 - 列表型 -->
<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>
<!-- 隐藏字段点击显示/隐藏 -->
<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>
</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;
}
.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;
}
.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;
}
/* 字典结果样式 */
.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;
}
.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;
}
.result-multiline {
text-align: left;
line-height: 1.8;
word-wrap: break-word;
}
.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;
}
/* 列表卡片中的隐藏字段 */
.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;
}
/* 自定义选择器样式 */
.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;
}
/* 游戏区域样式 */
.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;
}
/* 垃圾分类游戏样式 */
.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;
}
</style>