feat: 调整逻辑和修改样式

share-code
xjs 2026-06-04 10:46:54 +08:00
parent b7b3baf17b
commit 2902d26d02
28 changed files with 1316 additions and 389 deletions

18
env/.env vendored
View File

@ -3,8 +3,22 @@ VITE_APP_PORT = 9000
VITE_UNI_APPID = 'H57F2ACE4'
# VITE_WX_APPID = 'wxc48ad15d58a3e417' 六纬中考通
# VITE_WX_APPID = 'wx4b925e36c17dd54a' 六纬裂变
VITE_WX_APPID = 'wxc48ad15d58a3e417'
# VITE_WX_APPID = 'wx4b925e36c17dd54a' 智能中专学校的
# wx487ac749c0e10be5 深泉外国语的
VITE_WX_APPID = 'wx487ac749c0e10be5'
# VITE_WX_VIDEO_ID= 'sphju9MCfZetYHP' 智能中专学校的
# VITE_WX_VIDEO_ID= 'spht0MPpWjxEKb4' 深泉外国语
# spheuUaMulnlpHH 深泉外国语学院 认证的
VITE_WX_VIDEO_ID= 'spht0MPpWjxEKb4'
# 微信小程序 AI
# 智能中专学校的
# VITE_CLOUD_ENV_ID= 'cloud1-d3g5q6bq61a240786',
# VITE_CLOUD_BOT_ID= 'agent-wxai-4gl75um61026324f',
# 深泉外国语学院的
VITE_CLOUD_ENV_ID='cloud1-d9g4odcaqf6a2114f'
VITE_CLOUD_BOT_ID='agent-wxai-9gi3rts96061202f'
# h5部署网站的base配置到 manifest.config.ts 里的 h5.router.base
# https://uniapp.dcloud.net.cn/collocation/manifest.html#h5-router

2
src/env.d.ts vendored
View File

@ -15,6 +15,8 @@ interface ImportMetaEnv {
readonly VITE_SERVER_PORT: string
/** 后台接口地址 */
readonly VITE_SERVER_BASEURL: string
/** 微信视频号 ID */
readonly VITE_WX_VIDEO_ID: string
/** H5是否需要代理 */
readonly VITE_APP_PROXY_ENABLE: 'true' | 'false'
/** H5是否需要代理需要的话有个前缀 */

View File

@ -8,12 +8,13 @@ import {
generateMessageId,
} from '../components/agent-config'
import { fetchRecommendQuestions, streamMessage } from '../components/agent-service'
import AgentHeader from '../components/AgentHeader.vue'
import AgentInput from '../components/AgentInput.vue'
import AgentMessage from '../components/AgentMessage.vue'
definePage({
style: {
navigationBarTitleText: 'AI 助手',
navigationBarTitleText: 'AI助手',
},
})
@ -28,6 +29,8 @@ const aiStore = useAiStore()
const paging = ref<any>(null)
const messages = ref<AiMessage[]>([])
const loading = ref(false)
const initQuestionCapsules = computed(() => agentConfig.initQuestions.slice(0, 3))
const shouldShowInitQuestionCapsules = computed(() => aiStore.messages.length === 0 && initQuestionCapsules.value.length > 0)
// controller cancelled
let cancelFlag = false
@ -43,17 +46,18 @@ onLoad(() => {
// #endif
aiStore.ensureThreadId()
})
// z-paging store
// 0 =
function queryList() {
if (aiStore.messages.length === 0) {
const welcomeMsg = agentConfig.welcomeMsg?.trim()
if (aiStore.messages.length === 0 && welcomeMsg) {
aiStore.addMessage({
id: generateMessageId(),
role: 'assistant',
content: agentConfig.welcomeMsg,
content: welcomeMsg,
createdAt: Date.now(),
})
}
@ -194,19 +198,6 @@ function handleClear() {
<template>
<view class="h-screen w-full flex flex-col bg-[#F4F6FA]">
<!-- 顶部 -->
<view class="flex items-center justify-between bg-white px-[24rpx] py-[20rpx] shadow-[0_2rpx_8rpx_rgba(0,0,0,0.03)]">
<view class="text-[30rpx] text-[#1F2329] font-500">
{{ agentConfig.botName }}
</view>
<view
class="rounded-[24rpx] bg-[#F5F6F8] px-[20rpx] py-[8rpx] text-[24rpx] text-[#666] active:opacity-60"
@click="handleClear"
>
清空对话
</view>
</view>
<!-- 消息列表聊天模式 -->
<view class="min-h-0 flex-1">
<z-paging
@ -224,22 +215,11 @@ function handleClear() {
@query="queryList"
>
<!-- 首屏推荐问题 -->
<template v-if="messages.length <= 1 && agentConfig.initQuestions.length" #top>
<view class="flex flex-col gap-[16rpx] px-[24rpx] py-[20rpx]">
<view class="text-[24rpx] text-[#999] leading-[1.4]">
你可以试试这样问
</view>
<view class="flex flex-wrap gap-[16rpx]">
<view
v-for="q in agentConfig.initQuestions"
:key="q"
class="border-1 border-[#1580FF] rounded-[24rpx] border-solid bg-white px-[20rpx] py-[10rpx] text-[24rpx] text-[#1580FF] active:opacity-60"
@click="handleClickQuestion(q)"
>
{{ q }}
</view>
</view>
</view>
<template v-if="shouldShowInitQuestionCapsules" #top>
<agent-header
:questions="initQuestionCapsules"
@pick="handleClickQuestion"
/>
</template>
<template #default>
@ -254,6 +234,8 @@ function handleClear() {
</template>
<template #bottom>
<view>
<view class="text-[24rpx] text-[#999] text-center my-[16rpx]">AI建议仅供参考请以官方政策为准</view>
<agent-input
:placeholder="agentConfig.placeholder"
:loading="loading"
@ -261,6 +243,10 @@ function handleClear() {
@stop="handleStop"
@clear="handleClear"
/>
</view>
</template>
<template #empty>
<view />
</template>
</z-paging>
</view>

View File

@ -0,0 +1,53 @@
<script lang="ts" setup>
import {defaultAgentConfig} from "./agent-config"
const props = withDefaults(defineProps<{
questions?: string[]
}>(), {
questions: () => [],
})
const emit = defineEmits<{
(event: 'pick', question: string): void
}>()
function handlePick(question: string) {
emit('pick', question)
}
</script>
<template>
<view class="mt-[110rpx] box-border w-full px-[34rpx]">
<view
class="relative z-0 mb-[-108rpx] h-[210rpx] border-[2rpx] border-white rounded-[100rpx_26rpx_26rpx_26rpx] border-solid bg-[#B0E4FF] bg-opacity-80">
<view
class="agent-hello__glow absolute bottom-[46rpx] left-[12rpx] z-[2] h-[64rpx] w-[266rpx] rounded-[40rpx] blur-[20rpx]" />
<image class="absolute bottom-[70rpx] h-[246rpx] w-[240rpx]"
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/ai_zanyong.png" mode="aspectFit" />
<view class="mr-[28rpx] mt-[30rpx] text-right text-[32rpx] text-[#000] font-500 leading-[1.4]">
你好我是你的{{ defaultAgentConfig.botName }}
</view>
</view>
<view
class="relative z-[1] box-border border-[2rpx] border-white rounded-[26rpx] border-solid bg-white bg-opacity-70 px-[34rpx] pb-[36rpx] pt-[32rpx] backdrop-blur-[20rpx]">
<view class="text-[28rpx] text-[#1F2329] leading-[1.5]">
你好我可以帮你解读济南中考政策分数线和志愿填报问题
</view>
</view>
<view v-if="props.questions.length" class="mt-[28rpx] flex flex-wrap gap-[16rpx]">
<view v-for="item in props.questions" :key="item"
class="border-[1rpx] border-[#E8E8E8] rounded-full border-solid bg-white px-[32rpx] py-[16rpx] text-[30rpx] text-[#000] active:opacity-60"
@click="handlePick(item)">
{{ item }}
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.agent-hello__glow {
background: radial-gradient(ellipse at top, #1580ff, #fff);
}
</style>

View File

@ -47,59 +47,52 @@ function handleClickClear() {
<view class="flex flex-col bg-white pb-safe">
<!-- 输入区 -->
<view class="flex items-center gap-[16rpx] px-[24rpx] py-[18rpx]">
<view class="min-h-[72rpx] flex flex-1 items-center rounded-[40rpx] bg-[#F5F6F8] px-[24rpx] py-[12rpx]">
<textarea
v-model="inputValue"
class="max-h-[200rpx] w-full bg-transparent text-[28rpx] text-[#1F2329]"
:placeholder="props.placeholder || '输入你的问题'"
placeholder-style="color:#A6AFBD;font-size:28rpx;"
auto-height
:show-confirm-bar="false"
:adjust-position="true"
cursor-spacing="20"
confirm-type="send"
@confirm="handleSend"
@focus="showActions = false"
<view class="flex flex-1 items-center rounded-full bg-[#F5F6F8] p-[16rpx]">
<textarea v-model="inputValue" class="max-h-[200rpx] w-full bg-transparent text-[28rpx] text-[#1F2329] ml-[14rpx]"
:placeholder="props.placeholder || '有中考问题,直接问我!'" placeholder-style="color:#A6AFBD;font-size:28rpx;"
auto-height :show-confirm-bar="false" :adjust-position="true" :cursor-spacing="20" confirm-type="send"
@confirm="handleSend" @focus="showActions = false" />
<!-- 发送 / 停止 / 展开更多 -->
<view v-if="!props.loading && !inputValue.trim()"
class="h-[52rpx] w-[52rpx] flex items-center justify-center rounded-full "
:class="showActions ? 'rotate-45' : ''" @click="toggleActions">
<image
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/ai_tianjia.png"
mode="scaleToFill"
class="w-[52rpx] h-[52rpx]"
/>
</view>
<view v-else-if="!props.loading"
class="h-[52rpx] w-[52rpx] flex items-center justify-center rounded-[36rpx] "
@click="handleSend">
<image
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/ai_fasong.png"
mode="scaleToFill"
class="h-[52rpx] w-[52rpx]"
/>
</view>
<view v-else
class="h-[52rpx] w-[52rpx] flex items-center justify-center"
@click="emit('stop')">
<image
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/ai_zanting.png"
mode="scaleToFill"
class="h-[52rpx] w-[52rpx]"
/>
</view>
</view>
<!-- 发送 / 停止 / 展开更多 -->
<view
v-if="!props.loading && !inputValue.trim()"
class="h-[72rpx] w-[72rpx] flex items-center justify-center rounded-full bg-[#F5F6F8] text-[40rpx] text-[#666] transition active:opacity-60"
:class="showActions ? 'rotate-45' : ''"
@click="toggleActions"
>
+
</view>
<view
v-else-if="!props.loading"
class="h-[72rpx] w-[120rpx] flex items-center justify-center rounded-[36rpx] bg-[#1580FF] text-[28rpx] text-white font-500 transition active:opacity-80"
@click="handleSend"
>
发送
</view>
<view
v-else
class="h-[72rpx] w-[120rpx] flex items-center justify-center border-1 border-[#1580FF] rounded-[36rpx] border-solid bg-white text-[28rpx] text-[#1580FF] font-500 active:opacity-80"
@click="emit('stop')"
>
停止
</view>
</view>
<!-- 展开的操作面板 -->
<view
v-if="showActions"
class="grid grid-cols-4 gap-[16rpx] px-[24rpx] pb-[24rpx] pt-[12rpx]"
>
<view
class="flex flex-col items-center gap-[8rpx] rounded-[16rpx] bg-[#F5F6F8] py-[24rpx] active:opacity-60"
@click="handleClickClear"
>
<view class="h-[64rpx] w-[64rpx] flex items-center justify-center rounded-full bg-[#FFE4E1] text-[32rpx] text-[#EB5241]">
</view>
<view v-if="showActions" class="flex gap-[16rpx] px-[24rpx] pb-[24rpx] pt-[12rpx]">
<view class="flex items-center justify-center p-[20rpx] bg-[#F5F6F8] active:opacity-60"
@click="handleClickClear">
<image
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/ai_qingkong.png"
mode="scaleToFill"
class="h-[52rpx] w-[52rpx]"
/>
</view>
</view>
</view>

View File

@ -13,32 +13,22 @@ defineEmits<{
</script>
<template>
<view
class="cell-flip flex px-[24rpx] py-[16rpx]"
:class="props.message.role === 'user' ? 'justify-end' : 'justify-start'"
>
<view class="cell-flip flex px-[24rpx] py-[16rpx]"
:class="props.message.role === 'user' ? 'justify-end' : 'justify-start'">
<!-- AI 头像 -->
<view
v-if="props.message.role !== 'user'"
class="mr-[16rpx] h-[64rpx] w-[64rpx] flex shrink-0 items-center justify-center rounded-full bg-[#1580FF] text-[24rpx] text-white"
>
<!-- <view v-if="props.message.role !== 'user'"
class="mr-[16rpx] h-[64rpx] w-[64rpx] flex shrink-0 items-center justify-center rounded-full bg-[#1580FF] text-[24rpx] text-white">
AI
</view>
</view> -->
<view class="max-w-[78%] flex flex-col gap-[8rpx]">
<view
class="break-all rounded-[16rpx] px-[24rpx] py-[18rpx] text-[28rpx] leading-[1.6]"
:class="props.message.role === 'user'
<view class="max-w-[90%] flex flex-col gap-[8rpx]">
<view class="break-all rounded-[16rpx] px-[24rpx] py-[18rpx] text-[28rpx] leading-[1.6]" :class="props.message.role === 'user'
? 'bg-[#1580FF] text-white rounded-tr-[4rpx]'
: 'rounded-tl-[4rpx] bg-white text-[#1F2329] shadow-[0_2rpx_12rpx_rgba(0,0,0,0.04)]'"
>
: 'rounded-tl-[4rpx] bg-white text-[#1F2329] shadow-[0_2rpx_12rpx_rgba(0,0,0,0.04)]'">
<!-- AI 回复用 markdown 渲染仅微信小程序 -->
<!-- #ifdef MP-WEIXIN -->
<markdown-preview
v-if="props.message.role !== 'user'"
:markdown="props.message.content || (props.message.pending ? '' : '')"
:font-size="28"
/>
<markdown-preview v-if="props.message.role !== 'user'"
:markdown="props.message.content || (props.message.pending ? '' : '')" :font-size="28" />
<text v-else class="select-text">
{{ props.message.content }}
</text>
@ -56,37 +46,28 @@ defineEmits<{
</text>
</view>
<view
v-if="props.message.error"
class="self-start text-[24rpx] text-[#EB5241] leading-[1.4] active:opacity-60"
@click="$emit('retry', props.message.id)"
>
<view v-if="props.message.error" class="self-start text-[24rpx] text-[#EB5241] leading-[1.4] active:opacity-60"
@click="$emit('retry', props.message.id)">
生成失败点击重试
</view>
<!-- 追问建议 AI 消息且生成结束后展示 -->
<view
v-if="props.message.role === 'assistant' && !props.message.pending && props.message.recommendQuestions && props.message.recommendQuestions.length"
class="mt-[8rpx] flex flex-col gap-[12rpx]"
>
<view
v-for="q in props.message.recommendQuestions"
:key="q"
class="mt-[8rpx] flex flex-col gap-[12rpx]">
<view v-for="q in props.message.recommendQuestions" :key="q"
class="self-start border-1 border-[#1580FF] rounded-[24rpx] border-solid bg-white px-[20rpx] py-[10rpx] text-[24rpx] text-[#1580FF] active:opacity-60"
@click="$emit('pickRecommend', q)"
>
@click="$emit('pickRecommend', q)">
{{ q }}
</view>
</view>
</view>
<!-- 用户头像 -->
<view
v-if="props.message.role === 'user'"
class="ml-[16rpx] h-[64rpx] w-[64rpx] flex shrink-0 items-center justify-center rounded-full bg-[#FFA94D] text-[28rpx] text-white"
>
<!-- <view v-if="props.message.role === 'user'"
class="ml-[16rpx] h-[64rpx] w-[64rpx] flex shrink-0 items-center justify-center rounded-full bg-[#FFA94D] text-[28rpx] text-white">
U
</view>
</view> -->
</view>
</template>
@ -101,11 +82,13 @@ defineEmits<{
}
@keyframes pulse {
0%,
80%,
100% {
opacity: 0.2;
}
40% {
opacity: 1;
}

View File

@ -10,8 +10,8 @@ export interface AgentConfig {
cloudEnvId: string
/** Agent ID对应 wx.cloud.extend.AI.bot.sendMessage 的 botIdchatMode=bot 必填) */
botId: string
/** 首屏欢迎语 */
welcomeMsg: string
/** 首屏欢迎语;不设置或留空时不插入欢迎消息 */
welcomeMsg?: string
/** 输入框 placeholder */
placeholder: string
/** 显示在 AI 头像位置的图标 / 文字 */
@ -31,15 +31,14 @@ export interface ModelConfig {
export const defaultAgentConfig: AgentConfig = {
chatMode: 'bot',
cloudEnvId: 'cloud1-d3g5q6bq61a240786',
botId: 'agent-wxai-4gl75um61026324f',
welcomeMsg: '你好,我是 AI 助手,有什么可以帮你?',
placeholder: '输入你的问题',
botName: 'AI 助手',
cloudEnvId: import.meta.env.VITE_CLOUD_ENV_ID,
botId: import.meta.env.VITE_CLOUD_BOT_ID,
placeholder: '有中考问题,直接问我!',
botName: '中考小助手',
initQuestions: [
'帮我介绍一下志愿填报流程',
'中考分数线怎么查?',
'推荐几所适合的高中',
'济南500分还能上哪些高中',
'第二批志愿怎么填最稳',
'指标生和统招生区别',
],
}
@ -64,7 +63,6 @@ export const defaultModelAgentConfig: AgentConfig = {
...defaultAgentConfig,
chatMode: 'bot',
botName: 'AI 模型助手',
welcomeMsg: '你好,我是 AI 模型助手,可以直接用大模型和你对话。',
}
export function generateMessageId() {

View File

@ -51,9 +51,15 @@ export interface SendMessageOptions {
export async function streamMessage(options: SendMessageOptions): Promise<string> {
if (options.chatMode === 'model') {
return streamModel(options)
}else{
return streamBot(options)
}
return streamBot(options)
}
function isValidHistoryMessage(message: AiMessage) {
return !message.error
&& (message.role === 'user' || message.role === 'assistant')
&& message.content.trim().length > 0
}
// ---------- bot 模式wx.cloud.extend.AI.bot.sendMessage ----------
@ -67,7 +73,7 @@ async function streamBot(options: SendMessageOptions): Promise<string> {
const messages = [
...history
.filter(m => !m.error && (m.role === 'user' || m.role === 'assistant'))
.filter(isValidHistoryMessage)
.map(m => ({ id: m.id, role: m.role, content: m.content })),
{
id: generateMessageId(),
@ -133,7 +139,7 @@ async function streamModel(options: SendMessageOptions): Promise<string> {
const messages = [
...history
.filter(m => !m.error && (m.role === 'user' || m.role === 'assistant'))
.filter(isValidHistoryMessage)
.map(m => ({ role: m.role, content: m.content })),
{ role: 'user', content: prompt },
]
@ -141,7 +147,7 @@ async function streamModel(options: SendMessageOptions): Promise<string> {
// #ifdef MP-WEIXIN
const ai = wx.cloud.extend.AI
const aiModel = ai.createModel(modelProvider)
console.log("aimodel创建",aiModel);
console.log('aimodel创建', aiModel)
const res = await aiModel.streamText({
data: {
@ -210,7 +216,9 @@ export async function fetchRecommendQuestions(options: RecommendOptions): Promis
const res = await wx.cloud.extend.AI.bot.getRecommendQuestions({
data: {
botId,
history: lastPair.map(item => ({ role: item.role, content: item.content })),
history: lastPair
.filter(item => item.content.trim().length > 0)
.map(item => ({ role: item.role, content: item.content })),
msg: prompt,
agentSetting: '',
introduction: '',

View File

@ -1,17 +1,18 @@
<template>
<view class="flex items-center bg-gray-100 rounded-full px-[30rpx] py-[20rpx]" :class="rootClass" :style="rootStyle">
<view class="flex items-center bg-gray-100 rounded-full" :class="rootClass" :style="rootStyle">
<view class="w-[36rpx] h-[36rpx] mr-2 flex items-center">
<image :src="searchIconUrl" class="w-[36rpx] h-[36rpx]" mode="aspectFit" />
<view class="w-[32rpx] h-[32rpx] mr-2 flex items-center">
<image :src="searchIconUrl" class="w-[32rpx] h-[32rpx]" mode="aspectFit" />
</view>
<input :value="searchInnerText" type="text" :placeholder="placeholder"
<input :value="searchInnerText" :type="inputType" :placeholder="placeholder"
class="flex-1 bg-transparent text-sm text-gray-800 outline-none text-start" :class="inputClass" @input="handleInput" @confirm="handleChange" @blur="handleChange"/>
<view class="w-5 h-5 ml-2 flex items-center" v-if="clearIcon">
<image v-if="searchInnerText" src="" class="w-5 h-5" mode="aspectFit" @tap="clearInput" />
</view>
<view v-if="searchIcon" class="text-[30rpx] px-[48rpx] py-[12rpx] bg-[#1580FF] text-[#fff] rounded-full" @click="handleSubmit"></view>
</view>
</template>
@ -44,9 +45,17 @@ const props = defineProps({
type:String,
default:''
},
inputType:{
type:String,
default:'text'
},
clearIcon:{
type:Boolean,
default: true
},
searchIcon:{
type:Boolean,
default: false
}
});
@ -76,4 +85,8 @@ const handleChange = (event) => {
emit('update:searchText', event.detail.value);
emit("complete",event.detail.value)
}
const handleSubmit = () => {
emit("submit",searchInnerText.value)
}
</script>

View File

@ -0,0 +1,131 @@
<script lang="ts" setup>
import { systemInfo } from '@/utils/systemInfo'
import MxRadioGroup from "@/pages-sub/components/radio/index.vue?async"
import { getAreaList, getHistoryYearList, getBatchLine } from "@/service"
// #ifdef MP-WEIXIN
definePage({
style: {
navigationStyle: 'custom',
},
excludeLoginPath: false,
})
// #endif
// #ifndef MP-WEIXIN
definePage({
style: {
navigationStyle: 'custom',
transparentTitle: 'always',
navigationBarTitleText: ''
},
excludeLoginPath: false,
})
// #endif
const handleBack = () => {
uni.navigateBack({ delta: 1 })
}
const areaList = ref<any[]>([])
const searchParams = ref({
keyword: '',
region: null,
nature: null,
natureLabel: "",
year: null
})
const visibleFlag1 = ref(false)
const visibleFlag3 = ref(false)
const partialData = ref<any[]>([])
const yearList = ref<any[]>([])
const handleChange = () => {
visibleFlag1.value = false;
visibleFlag3.value = false;
getBatchLine({
query: {
Area: searchParams.value.region,
Year: searchParams.value.year
}
}).then(resp => {
partialData.value = resp.result.items
})
}
onShow(() => {
Promise.all([
getAreaList().then(resp => {
if (resp.code === 200) {
areaList.value = [{ value: '', label: '不限' }, ...resp.result]
}
}),
getHistoryYearList().then(resp => {
if (resp.code === 200) {
yearList.value = [...resp.result]
searchParams.value.year = yearList.value[yearList.value.length - 1]?.value
}
})
]).then(() => {
handleChange()
})
})
</script>
<template>
<view class="gradient-custom flex flex-col h-screen">
<sar-navbar title="批次线" :show-back="true" @back="handleBack"
:root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, 0)`, 'padding-top': `${systemInfo?.statusBarHeight}px`, '--sar-navbar-item-color': 'black' }">
</sar-navbar>
<sar-dropdown
root-style="--sar-dropdown-placeholder-color:#666;--sar-dropdown-value-font-size:28rpx;--sar-dropdown-option-active-color:#1580FF;--sar-dropdown-box-shadow:0rpx 6rpx 8rpx 0rpx rgba(0,0,0,0.04);">
<sar-dropdown-item v-model:visible="visibleFlag1" :title="searchParams.region || '区域'">
<mx-radio-group v-model:value="searchParams.region" :options="areaList" label-key="label" value-key="value"
custom-root-class="px-[32rpx] pt-[30rpx] pb-[40rpx]"
active-item-class="bg-white text-[#1580FF] border-1 border-solid border-[#1580FF]!"
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]"
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid" @change="handleChange" />
</sar-dropdown-item>
<sar-dropdown-item v-model:visible="visibleFlag3" v-model="searchParams.year" :title="searchParams.year || '年份'">
<mx-radio-group v-model:value="searchParams.year" :options="yearList" label-key="label" value-key="value"
custom-root-class="px-[32rpx] pt-[30rpx] pb-[40rpx]"
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid"
active-item-class="bg-white text-[#1580FF] border-1 border-solid border-[#1580FF]!"
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]" @change="handleChange" />
</sar-dropdown-item>
</sar-dropdown>
<scroll-view :scroll-y="true" class="flex-1 bg-[#f8f8f8]">
<view class="p-[30rpx]">
<view class="flex justify-between gap-[40rpx] bg-white rounded-[16rpx] mb-[20rpx] p-[30rpx]"
v-for="(val, index) in partialData" :key="index">
<view class="max-w-[502rpx]">
<view class="text-[30rpx] text-wrap">{{ val.batch }}</view>
<view class="flex flex-wrap gap-[10rpx] mt-[10rpx]">
<view class="rounded-[8rpx] bg-[#F8F8F8] px-[10rpx] py-[4rpx] text-[24rpx] text-[#666] w-max">{{
val.area }}</view>
<view class="rounded-[8rpx] bg-[#F8F8F8] px-[10rpx] py-[4rpx] text-[24rpx] text-[#666] w-max">{{ val.year
}}</view>
</view>
</view>
<view class="flex text-[#1E40AF] items-center min-w-[100rpx]">
<view class="text-[40rpx] font-[DinBold]">
{{ val.score }}
</view>
<view class="text-[24rpx]"></view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<style lang="scss" scoped></style>

View File

@ -109,7 +109,7 @@ const handleComplete = () => {
:root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, 0)`, '--sar-navbar-item-color': 'black', '--sar-navbar-title-max-width': '100%' }">
<template #title>
<view class="w-[448rpx] ml-[90rpx]">
<mx-search v-model:searchText="searchParams.keyword" placeholder="输入高中名称" @complete="handleComplete"/>
<mx-search root-class="pl-[30rpx] py-[20rpx]" v-model:searchText="searchParams.keyword" placeholder="输入高中名称" @complete="handleComplete"/>
</view>
</template>
</sar-navbar>

View File

@ -87,7 +87,7 @@ const handleComplete = () => {
<sar-navbar title="中考资讯" :show-back="true" @back="handleBack"
:root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, 0)`, 'padding-top': `${systemInfo?.statusBarHeight}px`, '--sar-navbar-item-color': 'black' }">
</sar-navbar>
<mx-search v-model:searchText="searchParams.keyword" rootStyle="margin: 16rpx 30rpx 0;" @complete="handleComplete"/>
<mx-search v-model:searchText="searchParams.keyword" rootStyle="margin: 16rpx 30rpx 0;" root-class="pl-[30rpx] py-[20rpx]" @complete="handleComplete"/>
<mx-tabs :tabsList="tabs" rootClass="shadow-md mb-[20rpx]" @tab-change="handleChange" />
</view>
</template>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import MxRadioGroup from '@/pages-sub/components/radio/index.vue?async'
import MxSearch from '@/pages-sub/components/search/index.vue?async'
import { getAreaList, getHistoryYearList, getSchoolHistoricalScores, getSchoolNature } from '@/service'
import { systemInfo } from '@/utils/systemInfo'
import MxSearch from "@/pages-sub/components/search/index.vue?async"
import MxRadioGroup from "@/pages-sub/components/radio/index.vue?async"
import { getAreaList, getHistoryYearList, getSchoolHistoricalScores, getSchoolNature } from "@/service"
// #ifdef MP-WEIXIN
definePage({
@ -18,106 +18,107 @@ definePage({
style: {
navigationStyle: 'custom',
transparentTitle: 'always',
navigationBarTitleText: ''
navigationBarTitleText: '',
},
excludeLoginPath: false,
})
// #endif
const handleBack = () => {
function handleBack() {
uni.navigateBack({ delta: 1 })
}
const areaList = ref<any[]>([])
const natureList = ref<any[]>()
const searchParams = ref({
keyword: '',
region: null,
nature: null,
natureLabel: "",
year: null
natureLabel: '',
year: null,
})
const partialColumns = [
{
title: '院校名称',
prop: 'schoolName',
width: '32%',
align: "left"
},
{
title: '所在区',
prop: 'region',
width: '16%',
align: "center"
},
{
title: '统招位次',
prop: 'ranking',
width: '18%',
align: "center"
},
{
title: '办学性质',
prop: 'schoolNature',
width: '18%',
align: "center"
},
{
title: '分数',
prop: 'admissionScore',
width: '15%',
align: "center"
},
] as const
const visibleFlag1 = ref(false)
const visibleFlag2 = ref(false)
const visibleFlag3 = ref(false)
const partialData = ref<any[]>([])
interface HistoricalScore {
schoolName?: string
region?: string
schoolNature?: string
tags?: string
ranking?: number | string | null
admissionScore?: number | string | null
}
interface PagingRef {
reload: () => void
complete: (data: HistoricalScore[] | false) => void
}
const paging = ref<PagingRef | null>(null)
const partialData = ref<HistoricalScore[]>([])
const yearList = ref<any[]>([])
const handleChange = () => {
visibleFlag1.value = false;
visibleFlag2.value = false;
visibleFlag3.value = false;
function handleChange() {
visibleFlag1.value = false
visibleFlag2.value = false
visibleFlag3.value = false
paging.value?.reload()
}
function queryList(page: number, _pageSize: number) {
if (page > 1) {
paging.value?.complete([])
return
}
getSchoolHistoricalScores({
query: {
SchoolName: searchParams.value.keyword,
Region: searchParams.value.region,
SchoolType: searchParams.value.nature,
Year: searchParams.value.year
Year: searchParams.value.year,
},
}).then((resp) => {
if (resp.code === 200) {
paging.value?.complete(Array.isArray(resp.result) ? resp.result : [])
}
}).then(resp => {
partialData.value = resp.result
else {
paging.value?.complete(false)
}
}).catch(() => {
paging.value?.complete(false)
})
}
onShow(() => {
function virtualListChange(_vList: HistoricalScore[]) {
partialData.value = _vList
}
function getTagList(tags?: string) {
return (tags || '').split(/[\s,,、]+/).filter(Boolean).slice(0, 2)
}
onShow(() => {
Promise.all([
getAreaList().then(resp => {
getAreaList().then((resp) => {
if (resp.code === 200) {
areaList.value = [{ value: '', label: '不限' }, ...resp.result]
}
}),
getSchoolNature().then(resp => {
getSchoolNature().then((resp) => {
if (resp.code === 200) {
natureList.value = [{ value: '', label: '不限' }, ...resp.result]
}
}),
getHistoryYearList().then(resp => {
getHistoryYearList().then((resp) => {
if (resp.code === 200) {
yearList.value = [...resp.result]
searchParams.value.year = yearList.value[yearList.value.length - 1]?.value
}
})
}),
]).then(() => {
handleChange()
})
@ -125,61 +126,103 @@ onShow(() => {
</script>
<template>
<view class="gradient-custom flex flex-col h-screen">
<sar-navbar title="历年分数" :show-back="true" @back="handleBack"
:root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, 0)`, 'padding-top': `${systemInfo?.statusBarHeight}px`, '--sar-navbar-item-color': 'black' }">
<z-paging
ref="paging" use-virtual-list :force-close-inner-list="true" cell-height-mode="dynamic"
:auto="false" :auto-show-system-loading="false" :safe-area-inset-bottom="true" :paging-style="{ backgroundColor: '#f8f8f8' }"
@virtual-list-change="virtualListChange" @query="queryList"
>
<template #top>
<view class="gradient-custom" :style="{ 'padding-top': `${systemInfo?.statusBarHeight}px` }">
<sar-navbar
title="历年分数" :show-back="true" :root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, 0)`, '--sar-navbar-item-color': 'black', '--sar-navbar-title-max-width': '100%' }"
@back="handleBack"
>
<template #title>
<view class="ml-[90rpx] w-[448rpx]">
<mx-search v-model:search-text="searchParams.keyword" root-class="py-[20rpx] pl-[30rpx]" @complete="handleChange" />
</view>
</template>
</sar-navbar>
<mx-search v-model:searchText="searchParams.keyword" rootStyle="margin: 16rpx 30rpx 0;" @complete="handleChange" />
<sar-dropdown
root-style="--sar-dropdown-placeholder-color:#666;--sar-dropdown-value-font-size:28rpx;--sar-dropdown-option-active-color:#1580FF;--sar-dropdown-box-shadow:0rpx 6rpx 8rpx 0rpx rgba(0,0,0,0.04);">
root-style="--sar-dropdown-placeholder-color:#666;--sar-dropdown-value-font-size:28rpx;--sar-dropdown-option-active-color:#1580FF;--sar-dropdown-box-shadow:0rpx 6rpx 8rpx 0rpx rgba(0,0,0,0.04);"
>
<sar-dropdown-item v-model:visible="visibleFlag1" :title="searchParams.region || '区域'">
<mx-radio-group v-model:value="searchParams.region" :options="areaList" label-key="label" value-key="value"
<mx-radio-group
v-model:value="searchParams.region" :options="areaList" label-key="label" value-key="value"
custom-root-class="px-[32rpx] pt-[30rpx] pb-[40rpx]"
active-item-class="bg-white text-[#1580FF] border-1 border-solid border-[#1580FF]!"
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]"
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid" @change="handleChange" />
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid" @change="handleChange"
/>
</sar-dropdown-item>
<sar-dropdown-item v-model:visible="visibleFlag2" :title="searchParams.natureLabel || '办学性质'">
<mx-radio-group v-model:value="searchParams.nature" v-model:label="searchParams.natureLabel"
<mx-radio-group
v-model:value="searchParams.nature" v-model:label="searchParams.natureLabel"
:options="natureList" label-key="label" value-key="value" custom-root-class="px-[32rpx] pt-[30rpx] pb-[40rpx]"
active-item-class="bg-white text-[#1580FF] border-1 border-solid border-[#1580FF]!"
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]"
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid" @change="handleChange" />
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid" @change="handleChange"
/>
</sar-dropdown-item>
<sar-dropdown-item v-model:visible="visibleFlag3" v-model="searchParams.year" :title="searchParams.year || '年份'">
<mx-radio-group v-model:value="searchParams.year" :options="yearList" label-key="label" value-key="value"
<mx-radio-group
v-model:value="searchParams.year" :options="yearList" label-key="label" value-key="value"
custom-root-class="px-[32rpx] pt-[30rpx] pb-[40rpx]"
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid"
active-item-class="bg-white text-[#1580FF] border-1 border-solid border-[#1580FF]!"
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]" @change="handleChange" />
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]" @change="handleChange"
/>
</sar-dropdown-item>
</sar-dropdown>
</view>
</template>
<scroll-view :scroll-y="true" class="flex-1">
<view class="p-[30rpx]">
<sar-table bordered>
<sar-table-row>
<sar-table-cell v-for="item in partialColumns" :key="item.prop" bold :width="item.width">
<view
:class="`${item.align === 'center' ? '' : 'pl-[20rpx]'} py-[13rpx] text-[#333] text-[24rpx] font-500 bg-[#F3F4F8]`"
:style="{ 'text-align': item.align }">{{ item.title }}</view>
</sar-table-cell>
</sar-table-row>
<sar-table-row v-for="record in partialData" :key="record.id">
<sar-table-cell v-for="item in partialColumns" :key="item.prop" :width="item.width">
<view :class="`${item.align === 'center' ? '' : 'pl-[20rpx]'} text-[24rpx] text-[#333] py-[13rpx]`"
:style="{ 'text-align': item.align }">{{ record[item.prop] }}</view>
</sar-table-cell>
</sar-table-row>
<sar-table-row v-if="partialData.length === 0">
<sar-table-cell :colspan="partialColumns.length">
<view class="text-center text-[24rpx] text-[#999] py-[20rpx]">暂无数据</view>
</sar-table-cell>
</sar-table-row>
</sar-table>
v-for="(val, index) in partialData"
:key="index" class="mb-[20rpx] flex justify-between gap-[40rpx] rounded-[16rpx] bg-white p-[30rpx]"
>
<view class="">
<view class="max-w-[408rpx] text-wrap text-[30rpx]">
{{ val.schoolName }}
</view>
</scroll-view>
<view class="mt-[10rpx] flex flex-wrap gap-[10rpx]">
<view class="w-max px-[10rpx] py-[4rpx] text-[24rpx] text-[#666]">
{{
val.region }}·{{ val.schoolNature }}
</view>
<view
v-for="feature in getTagList(val.tags)"
:key="feature" class="rounded-[8rpx] bg-[#F8F8F8] px-[10rpx] py-[4rpx] text-[24rpx] text-[#666]"
>
{{ feature }}
</view>
</view>
</view>
<view class="flex items-center gap-[30rpx]">
<view class="flex flex-col items-end text-[#1E40AF]">
<view class="text-[40rpx] font-[DinBold]">
{{ val.ranking ?? '--' }}
</view>
<view class="mt-[10rpx] w-max text-[24rpx]">
位次
</view>
</view>
<view class="flex flex-col items-end text-[#1E40AF]">
<view class="text-[40rpx] font-[DinBold]">
{{ val.admissionScore ?? '--' }}
</view>
<view class="mt-[10rpx] w-max text-[24rpx]">
分数
</view>
</view>
</view>
</view>
</view>
</z-paging>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
@import url('@/style/index.scss');
</style>

View File

@ -75,7 +75,7 @@ onLoad(() => {
</view>
</view>
<view class="flex-1 ml-[30rpx]">
<mx-search root-class="mr-[40rpx]" placeholder="输入学校名称" v-model:searchText="searchParams.keyword"
<mx-search root-class="pl-[30rpx] py-[20rpx] mr-[40rpx]" placeholder="输入学校名称" v-model:searchText="searchParams.keyword"
@complete="handleChange" />
</view>
</view>

View File

@ -0,0 +1,191 @@
<script lang="ts" setup>
import { systemInfo } from '@/utils/systemInfo'
import MxRadioGroup from "@/pages-sub/components/radio/index.vue?async"
import MxSearch from "@/pages-sub/components/search/index.vue?async"
import { getAreaList, getHistoryYearList, getRankTable } from "@/service"
import { useUserStore } from "@/store"
// #ifdef MP-WEIXIN
definePage({
style: {
navigationStyle: 'custom',
},
excludeLoginPath: true,
})
// #endif
// #ifndef MP-WEIXIN
definePage({
style: {
navigationStyle: 'custom',
transparentTitle: 'always',
navigationBarTitleText: ''
},
excludeLoginPath: true,
})
// #endif
const userStore = useUserStore()
const handleBack = () => {
uni.navigateBack({ delta: 1 })
}
const areaList = ref<any[]>([])
const searchParams = ref({
score: userStore.userInfo.userExtend.expectedScore || '',
region: userStore.userInfo.userExtend.area || null,
nature: null,
natureLabel: "",
year: null
})
const visibleFlag1 = ref(false)
const visibleFlag3 = ref(false)
const partialData = ref<any[]>([])
const yearList = ref<any[]>([])
const minScore = ref(0)
const maxScore = ref(0)
const exceedRatioText = ref("0%")
const estimatedRank = ref(0)
const nearbyItems = ref<any[]>([])
const areaName = ref("")
const handleChange = () => {
visibleFlag1.value = false;
visibleFlag3.value = false;
getRankTable({
query: {
Area: searchParams.value.region,
Year: searchParams.value.year,
Score: searchParams.value.score || 0
}
}).then(resp => {
partialData.value = resp.result.items
minScore.value = resp.result.minScore
maxScore.value = resp.result.maxScore
exceedRatioText.value = resp.result.exceedRatioText
estimatedRank.value = resp.result.estimatedRank
yearList.value = [...resp.result.years]
searchParams.value.year = yearList.value[yearList.value.length - 1]?.value
nearbyItems.value = [...resp.result.nearbyItems.reverse()]
areaName.value=resp.result.selectedArea
if (searchParams.value.score === '') {
searchParams.value.score = resp.result.minScore
handleChange();
}
})
}
onShow(() => {
Promise.all([
getAreaList().then(resp => {
if (resp.code === 200) {
areaList.value = [...resp.result]
if(!searchParams.value.region){
searchParams.value.region = areaList.value[0]?.value || null
}
}
}),
]).then(() => {
handleChange()
})
})
</script>
<template>
<view class="gradient-custom flex flex-col h-screen">
<sar-navbar title="中考位次" :show-back="true" @back="handleBack"
:root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, 0)`, 'padding-top': `${systemInfo?.statusBarHeight}px`, '--sar-navbar-item-color': 'black' }">
</sar-navbar>
<sar-dropdown
root-style="--sar-dropdown-placeholder-color:#666;--sar-dropdown-value-font-size:28rpx;--sar-dropdown-option-active-color:#1580FF;--sar-dropdown-box-shadow:0rpx 6rpx 8rpx 0rpx rgba(0,0,0,0.04);">
<sar-dropdown-item v-model:visible="visibleFlag1" :title="searchParams.region || '区域'">
<mx-radio-group v-model:value="searchParams.region" :options="areaList" label-key="label"
value-key="value" custom-root-class="px-[32rpx] pt-[30rpx] pb-[40rpx]"
active-item-class="bg-white text-[#1580FF] border-1 border-solid border-[#1580FF]!"
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]"
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid"
@change="handleChange" />
</sar-dropdown-item>
<sar-dropdown-item v-model:visible="visibleFlag3" v-model="searchParams.year"
:title="searchParams.year || '年份'">
<mx-radio-group v-model:value="searchParams.year" :options="yearList" label-key="label"
value-key="value" custom-root-class="px-[32rpx] pt-[30rpx] pb-[40rpx]"
custom-item-class="w-full py-[16rpx] text-center border-[1rpx] border-solid"
active-item-class="bg-white text-[#1580FF] border-1 border-solid border-[#1580FF]!"
default-item-class="bg-[#F3F4F8] border-[#F3F4F8]" @change="handleChange" />
</sar-dropdown-item>
</sar-dropdown>
<view class="bg-[#F8F8F8] flex-1">
<mx-search v-model:searchText="searchParams.score" input-type="number" :searchIcon="true"
rootStyle="margin: 32rpx 30rpx 0;background-color:#fff;" root-class="pl-[30rpx]" @submit="handleChange" />
<view class="text-[#9E9E9E] text-[24rpx] bg-[#F8F8F8] ml-[50rpx] my-[20rpx]">支持查询180-660</view>
<view class="mx-[30rpx] bg-white px-[30rpx] pt-[30rpx] pb-[20rpx] rounded-[20rpx]">
<view class="flex items-center justify-between">
<view class="flex flex-col">
<view class="text-[#333] font-500 text-[28rpx]">你的预估位次</view>
<view class="flex items-baseline text-[#333] text-[28rpx] mt-[20rpx]">
<view></view>
<view class="font-[DinBold] text-[68rpx] font-900 text-[#000] mx-[10rpx]">{{ estimatedRank }}</view>
<view></view>
</view>
<view class="text-[24rpx] text-[#666] mt-[20rpx]">超过了{{ areaName }}{{ exceedRatioText }}的学生</view>
</view>
<image src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/sy_weicichaxun.png"
mode="scaleToFill" class="w-[208rpx] h-[208rpx]" />
</view>
<view class="mt-[30rpx] min-h-[300rpx]">
<view class="text-[28rpx] font-500 text-[#333] mb-[10rpx]">附近分数段</view>
<view class="">
<view class="grid grid-cols-4 bg-[#F3F4F8] text-[24prx] text-[#333] font-500 py-[14rpx]">
<view class="flex items-center justify-center">分数</view>
<view class="flex items-center justify-center">人数</view>
<view class="flex items-center justify-center">累计人数</view>
<view class="flex items-center justify-center">超越比例</view>
</view>
<view class="grid grid-cols-4 py-[8rpx]"
:class="searchParams.score === val.score ? 'text-[#1580FF] bg-[#F3F4F8] text-[26prx] font-500' : 'text-[24prx] text-[#666] bg-[#fff]'"
v-for="(val, index) in nearbyItems" :key="index">
<view class="flex items-center justify-center">{{ val.score }}</view>
<view class="flex items-center justify-center">{{ val.currentCount }}</view>
<view class="flex items-center justify-center">{{ val.cumulativeCount }}</view>
<view class="flex items-center justify-center">{{ val.exceedRatioText }}</view>
</view>
</view>
</view>
</view>
<view class="text-[#9E9E9E] text-[24rpx] mt-[20rpx] ml-[50rpx]">:本表数据为累计人数:即分数{{'>'}}该分数的考生总数</view>
<view class="mx-[40rpx] mt-[68rpx] bg-[#FEF6F6] rounded-[16rpx]">
<view class="h-[52rpx] w-[178rpx] flex items-center m-[-8rpx] ml-[20rpx]">
<image src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/tb_weicishuoming.png" mode="scaleToFill"
class="h-[52rpx] w-[178rpx]" />
</view>
<view class="flex flex-col text-[#E03C33] p-[20rpx]">
<view class="text-[24rpx] mt-[4rpx]">
<view>位次比分数更重要建议结合近年各高中录取位次科学填报志愿 </view>
</view>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.gradient-custom {
background: linear-gradient(180deg, #ecf2ff 0%, #f8f8f8 40rpx, #fff 128rpx);
background-position: 50% 50%;
background-origin: padding-box;
background-clip: border-box;
background-size: auto auto;
}
</style>

View File

@ -2,14 +2,13 @@
import { getLiveLinks, trackPromoterRedirect } from '@/service'
let referralCode = ''
let weChatLiveId = ''
let weChatLiveId = import.meta.env.VITE_WX_VIDEO_ID || ''
let douyinLiveUrl = ''
onLoad((options) => {
referralCode = options?.referralCode || ''
getLiveLinks().then((resp) => {
weChatLiveId = resp?.wechatLiveUrl || ''
douyinLiveUrl = resp?.douyinLiveUrl || ''
}).catch((error) => {
console.error('[live-links]', error)

View File

@ -25,7 +25,12 @@ const schools = ref(content.schools)
<view v-for="(val, index) in schools" :key="index" class="flex mt-[26rpx]">
<view
class="py-[29rpx] text-[30rpx] text-[#333] grid gap-[8rpx] bg-white px-[30rpx] py-[20rpx] not-last:mb-[30rpx] rounded-[16rpx] w-full">
<view class="text-[32rpx] font-600">{{ val.schoolName }}</view>
<view class="text-[32rpx] font-600 flex items-center gap-[10rpx]">
<view
class="w-[44rpx] h-[44rpx] rounded-[8rpx] text-[26rpx] font-600 flex items-center justify-center"
:class="`${val.tags === '稳' ? 'text-[#FA8E23] bg-[#ffeede]' : val.tags === '保' ? 'bg-[#dcf6f0] text-[#15C496]' : 'bg-[#fce5e3] text-[#EB5241]'}`">
{{ val.tags }}</view>
{{ val.schoolName }}</view>
<view class="text-[24rpx] text-[#333]">{{new Date().getFullYear()}}计划招生{{ val.planCount }}</view>
<view class="text-[#333] text-[24rpx]">

View File

@ -3,7 +3,7 @@ import MxSearch from "@/pages-sub/components/search/index.vue"
import MxCheckbox from "@/pages-sub/components/checkbox/index.vue?async"
import MxRadio from "@/pages-sub/components/radio/index.vue?async"
import { getAreaList, getBusSchoolAdmission, getSchoolNature } from "@/service"
import { useWishlistStore } from "@/store"
import { useWishlistStore,useUserStore } from "@/store"
const tableData = ref<any[]>([])
const tableColumns = [
@ -26,8 +26,8 @@ const handleChoose = (val:any) => {
chooseDataMap.delete(val.schoolId)
}
} else {
if (chooseData.value.length > 2) {
uni.showToast({ title: "志愿最多只能3个", icon: "none" })
if (chooseData.value.length > 6) {
uni.showToast({ title: "志愿最多只能7个", icon: "none" })
return;
}
chooseData.value.push(val.schoolId);
@ -46,6 +46,7 @@ const cwbs = ref([{ value: '冲', label: '冲' }, { value: '稳', label: '稳' }
const natureList = ref([])
const totalCount = ref(0)
const regions = ref([])
const userStore = useUserStore();
const searchParams = ref({
cwb: null,
@ -74,7 +75,8 @@ const handleChangeSchool = () => {
schoolName: searchParams.value.keyword,
schoolNature: searchParams.value.nature,
region: searchParams.value.region,
tags: searchParams.value.cwb
tags: searchParams.value.cwb,
score: userStore.userInfo.userExtend.expectedScore || 0
}
}).then(resp => {
popVisible.value = false;

View File

@ -47,7 +47,7 @@ const handleSubmit = () => {
let params = {
areaName: userInfo.value.userExtend.area,
totalScore: userInfo.value.userExtend.score,
totalScore: userInfo.value.userExtend.expectedScore,
id: userInfo.value.userExtend.id
}
saveUserAreaScore({ data: params }).then((resp) => {
@ -128,7 +128,7 @@ onShow(() => {
class="flex items-center text-[30rpx] justify-between mx-[40rpx] py-[34rpx] px-[30rpx] bg-[#F7F8FA] mb-[20rpx]">
<view class="text-[#404142] text-left min-w-[150rpx] mr-[40rpx] ">总分</view>
<view class="flex-1 flex items-center">
<MxInput type="number" v-model:value="userInfo.userExtend.score" placeholder="请填写"
<MxInput type="number" v-model:value="userInfo.userExtend.expectedScore" placeholder="请填写"
root-class="text-right" class="w-full" inputDirection="text-right"/>
</view>
@ -154,7 +154,7 @@ onShow(() => {
class="h-[52rpx] w-[178rpx]" />
</view>
<view class="flex flex-col text-[#E03C33] mt-[20rpx] pl-[26rpx] pr-[34rpx]">
<view class="flex flex-col text-[#E03C33] m-[20rpx]">
<view class="text-[24rpx] mt-[4rpx]">
<view>根据济南2026年中考招生政策进入模拟志愿填报阶段的考生默认历史生物地理道法等水平考试等级均已达到普通高中基础填报要求C级及以上本产品仅用于统招平行志愿的模拟</view>
</view>

View File

@ -93,11 +93,11 @@ const disableSubmit = computed(() => {
</view>
<view class="text-[30rpx] items-start mt-[50rpx]">
<view class="text-[#333]">
未被第一批次录取的考生可填报第二批次 可选择3所普通高中
</view>
<view class="my-[20rpx] text-[#333]">
系统按分数优先投档平行志愿3所学校并列不分先后
本工具用于模拟济南中考志愿填报
根据分数区域和学校信息生成志愿表
结果仅供参考最终以官方公布为准
</view>
</view>
<view
class="text-[34rpx] font-500 text-white bg-[#1580FF] rounded-full py-[20rpx] text-center mx-[40rpx] mt-[40rpx]"

View File

@ -0,0 +1,313 @@
import { ref } from 'vue'
type ChannelLiveStatus = 1 | 2 | 3 | 4
type ChannelNoticeStatus = 0 | 1 | 2
type ChannelVideoType = 'live' | 'notice'
interface ChannelLiveInfo {
feedId?: string
nonceId?: string
description?: string
status?: ChannelLiveStatus
headUrl?: string
nickname?: string
replayStatus?: string
otherInfos?: ChannelLiveInfo[]
}
interface ChannelNoticeInfo {
noticeId?: string
status?: ChannelNoticeStatus
startTime?: string
headUrl?: string
nickname?: string
reservable?: boolean
otherInfos?: ChannelNoticeInfo[]
}
export interface ChannelVideoItem {
id: string
type: ChannelVideoType
title: string
coverUrl: string
statusText: string
buttonText: string
nickname: string
startTimeText: string
startTimeStamp: number
feedId?: string
nonceId?: string
noticeId?: string
reservable?: boolean
}
interface ChannelsQueryApi {
getChannelsLiveInfo: (options: {
finderUserName: string
success: (res: ChannelLiveInfo) => void
fail: (err: unknown) => void
}) => void
getChannelsLiveNoticeInfo: (options: {
finderUserName: string
success: (res: ChannelNoticeInfo) => void
fail: (err: unknown) => void
}) => void
}
interface ChannelsActionApi {
openChannelsLive: (options: {
finderUserName: string
feedId?: string
nonceId?: string
fail: (err: unknown) => void
}) => void
reserveChannelsLive: (options: {
noticeId: string
success: () => void
fail: (err: unknown) => void
}) => void
}
const DEFAULT_CHANNEL_COVER = 'https://lwzk.ycymedu.com/img/home/sy_daoxiao.png'
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
return typeof value === 'function'
}
function getChannelsQueryApi(): ChannelsQueryApi | null {
const api = uni as unknown
if (!isRecord(api)) {
return null
}
const canGetLiveInfo = isFunction(api.getChannelsLiveInfo)
const canGetNoticeInfo = isFunction(api.getChannelsLiveNoticeInfo)
if (!canGetLiveInfo || !canGetNoticeInfo) {
return null
}
return api as unknown as ChannelsQueryApi
}
function getChannelsActionApi(): ChannelsActionApi | null {
const api = uni as unknown
if (!isRecord(api)) {
return null
}
const canOpenLive = isFunction(api.openChannelsLive)
const canReserveLive = isFunction(api.reserveChannelsLive)
if (!canOpenLive || !canReserveLive) {
return null
}
return api as unknown as ChannelsActionApi
}
function getStartTimeStamp(startTime?: string): number {
if (!startTime) {
return Number.MAX_SAFE_INTEGER
}
const timestamp = Number(startTime)
if (Number.isFinite(timestamp)) {
return String(Math.trunc(timestamp)).length === 10 ? timestamp * 1000 : timestamp
}
const dateTime = new Date(startTime).getTime()
return Number.isFinite(dateTime) ? dateTime : Number.MAX_SAFE_INTEGER
}
function formatStartTime(startTime?: string): string {
const timestamp = getStartTimeStamp(startTime)
if (timestamp === Number.MAX_SAFE_INTEGER) {
return startTime ? `${startTime}开播` : '直播预告'
}
const date = new Date(timestamp)
const month = date.getMonth() + 1
const day = date.getDate()
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${month}.${day} ${hours}:${minutes}开播`
}
function flattenLiveInfo(info: ChannelLiveInfo): ChannelLiveInfo[] {
return [info, ...(info.otherInfos ?? [])]
}
function flattenNoticeInfo(info: ChannelNoticeInfo): ChannelNoticeInfo[] {
return [info, ...(info.otherInfos ?? [])]
}
function normalizeLiveInfo(info: ChannelLiveInfo, index: number): ChannelVideoItem {
const id = info.feedId || info.nonceId || `live-${index}`
return {
id: `live-${id}`,
type: 'live',
title: info.description || '视频号直播',
coverUrl: info.headUrl || DEFAULT_CHANNEL_COVER,
statusText: '正在直播',
buttonText: '查看直播',
nickname: info.nickname || '',
startTimeText: '',
startTimeStamp: 0,
feedId: info.feedId,
nonceId: info.nonceId,
}
}
function normalizeNoticeInfo(info: ChannelNoticeInfo, index: number): ChannelVideoItem {
const id = info.noticeId || `notice-${index}`
const startTimeStamp = getStartTimeStamp(info.startTime)
return {
id: `notice-${id}`,
type: 'notice',
title: info.nickname ? `${info.nickname}的直播预告` : '视频号直播预告',
coverUrl: info.headUrl || DEFAULT_CHANNEL_COVER,
statusText: formatStartTime(info.startTime),
buttonText: info.reservable === false ? '直播预告' : '预约直播',
nickname: info.nickname || '',
startTimeText: formatStartTime(info.startTime),
startTimeStamp,
noticeId: info.noticeId,
reservable: info.reservable,
}
}
function getLiveVideoList(finderUserName: string, channelsApi: ChannelsQueryApi): Promise<ChannelVideoItem[]> {
return new Promise((resolve) => {
channelsApi.getChannelsLiveInfo({
finderUserName,
success: (res) => {
const liveList = flattenLiveInfo(res)
.filter(item => item.status === 2)
.map(normalizeLiveInfo)
resolve(liveList)
},
fail: (err) => {
console.error('获取视频号直播信息失败:', err)
resolve([])
},
})
})
}
function getNoticeVideoList(finderUserName: string, channelsApi: ChannelsQueryApi): Promise<ChannelVideoItem[]> {
return new Promise((resolve) => {
channelsApi.getChannelsLiveNoticeInfo({
finderUserName,
success: (res) => {
const noticeList = flattenNoticeInfo(res)
.filter(item => item.status === 0)
.map(normalizeNoticeInfo)
.sort((prev, next) => prev.startTimeStamp - next.startTimeStamp)
resolve(noticeList)
},
fail: (err) => {
console.error('获取视频号直播预告失败:', err)
resolve([])
},
})
})
}
export function useChannelLive(finderUserName: string) {
const channelVideoList = ref<ChannelVideoItem[]>([])
function goToLiveHomePage(){
uni.openChannelsUserProfile({
finderUserName,
success: () => {
console.log('跳转视频号主页成功')
},
fail: (err) => {
console.error('跳转视频号主页失败:', err)
},
})
}
async function getChannelLiveNoticeInfo() {
if (!finderUserName) {
uni.showToast({ title: '直播链接获取中,请稍后再试', icon: 'none' })
return
}
const channelsApi = getChannelsQueryApi()
if (!channelsApi) {
channelVideoList.value = []
return
}
const [liveList, noticeList] = await Promise.all([
getLiveVideoList(finderUserName, channelsApi),
getNoticeVideoList(finderUserName, channelsApi),
])
channelVideoList.value = [...liveList, ...noticeList]
}
function handleChannelVideoAction(item: ChannelVideoItem) {
const channelsApi = getChannelsActionApi()
if (!channelsApi) {
return
}
if (item.type === 'live') {
channelsApi.openChannelsLive({
finderUserName,
feedId: item.feedId,
nonceId: item.nonceId,
fail: (err) => {
console.error('跳转视频号直播失败:', err)
},
})
return
}
if (!item.noticeId || item.reservable === false) {
uni.showToast({ title: '该直播暂不可预约', icon: 'none' })
return
}
channelsApi.reserveChannelsLive({
noticeId: item.noticeId,
success: () => {
uni.showModal({
title: '预约成功',
content: `开播时间: ${item.startTimeText} 请留意视频号消息提醒。`,
showCancel: false,
confirmText:'我知道了',
})
},
fail: (err) => {
console.error('预约视频号直播失败:', err)
},
})
}
return {
channelVideoList,
getChannelLiveNoticeInfo,
handleChannelVideoAction,
goToLiveHomePage
}
}

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import { systemInfo } from '@/utils/systemInfo'
import { getMyScore, getTopNew, getCountdown, getFirstPageInfo } from "@/service"
import { getCountdown, getFirstPageInfo, getMyScore, getTopNew } from '@/service'
import { useTokenStore } from '@/store'
import { systemInfo } from '@/utils/systemInfo'
import { useChannelLive } from './hooks/useChannelLive'
defineOptions({
name: 'Home',
@ -29,6 +29,8 @@ definePage({
// #endif
const tokenStore = useTokenStore()
const wechatVideoId = import.meta.env.VITE_WX_VIDEO_ID || ''
const { channelVideoList, getChannelLiveNoticeInfo, handleChannelVideoAction,goToLiveHomePage } = useChannelLive(wechatVideoId)
onShareAppMessage(() => {
return {
@ -43,17 +45,22 @@ onShareTimeline(() => {
}
})
const subMenus = [{ name: '中考资讯', imgPath: 'https://lwzk.ycymedu.com/img/home/sy_zixun.png', url: "/pages-sub/information/middleSchool" }, { name: '高中汇总', imgPath: 'https://lwzk.ycymedu.com/img/home/sy_huizong.png', url: '/pages-sub/information/highSchool' }, { name: '历年分数', imgPath: 'https://lwzk.ycymedu.com/img/home/sy_fenshu.png', url: '/pages-sub/information/overTheYear' }, { name: '指标生', imgPath: 'https://lwzk.ycymedu.com/img/home/sy_daoxiao.png', url: '/pages-sub/information/quota' }]
const subMenus = [
{ name: '历年分数', imgPath: 'https://lwzk.ycymedu.com/img/home/sy_fenshu.png', url: '/pages-sub/information/overTheYear' },
{ name: '高中汇总', imgPath: 'https://lwzk.ycymedu.com/img/home/sy_daoxiao.png', url: '/pages-sub/information/highSchool' },
{ name: '批次线', imgPath: 'https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/sy_picixian.png', url: '/pages-sub/information/batchline' },
{ name: '位次查询', imgPath: 'https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/syweici.png', url: '/pages-sub/information/rank' },
]
const notifies = ref([])
const notifies = ref<any[]>([])
const opacity = ref(0)
const totalScore = ref("0")
const schoolCount = ref("0")
const wishlistCount = ref("0")
const totalScore = ref('0')
const schoolCount = ref('0')
const wishlistCount = ref('0')
const navigateToCreateWish = () => {
uni.navigateTo({ url: "/pages-sub/wishlist/create/first" })
function navigateToCreateWish() {
uni.navigateTo({ url: '/pages-sub/wishlist/create/first' })
}
onPageScroll((e) => {
@ -61,25 +68,25 @@ onPageScroll((e) => {
opacity.value = Math.min(scrollTop / 100, 1)
})
const navigateToCountdownPage = () => {
function navigateToCountdownPage() {
uni.navigateTo({ url: '/pages-sub/countdown/index' })
}
const navigateToSubPage = (url: string) => {
function navigateToSubPage(url: string) {
uni.navigateTo({ url })
}
const navigateToCustom = () => {
uni.navigateTo({ url: "/pages-sub/about/onlineCustom" })
function navigateToCustom() {
uni.navigateTo({ url: '/pages-sub/about/onlineCustom' })
}
const navigateToNewsPage = () => {
function navigateToNewsPage() {
uni.navigateTo({
url: '/pages-sub/information/middleSchool'
url: '/pages-sub/information/middleSchool',
})
}
const navigateToNewsDetail = (id: number) => {
function navigateToNewsDetail(id: number) {
uni.navigateTo({ url: `/pages-sub/information/middleDetail?id=${id}` })
}
@ -87,7 +94,7 @@ const countdown = ref(0)
onLoad(() => {
const updateManager = uni.getUpdateManager()
updateManager.onCheckForUpdate(function (res) {
updateManager.onCheckForUpdate((res) => {
//
if (res.hasUpdate) {
uni.showToast({
@ -97,11 +104,11 @@ onLoad(() => {
}
})
updateManager.onUpdateReady(function () {
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success: function (res) {
success(res) {
if (res.confirm) {
// applyUpdate
updateManager.applyUpdate()
@ -110,167 +117,279 @@ onLoad(() => {
})
})
updateManager.onUpdateFailed(function () {
updateManager.onUpdateFailed(() => {
//
uni.showToast({
title: '更新失败',
icon: 'none',
})
})
})
onShow(() => {
getTopNew({ query: { Top: 3 } }).then(resp => {
getTopNew({ query: { Top: 3 } }).then((resp) => {
if (resp.code === 200) {
notifies.value = resp.result
}
})
if (tokenStore.hasLogin) {
getMyScore().then(resp => {
getMyScore().then((resp) => {
if (resp.code === 200 && resp.result) {
totalScore.value = resp.result.totalScore
}
})
getFirstPageInfo().then(resp => {
getFirstPageInfo().then((resp) => {
if (resp.code === 200) {
schoolCount.value = resp.result.schoolCount
wishlistCount.value = resp.result.zyCount
}
})
}
getCountdown().then(resp => {
getCountdown().then((resp) => {
if (resp.code === 200) {
countdown.value = resp.result
}
})
getChannelLiveNoticeInfo()
})
</script>
<template>
<view class="custom-background flex flex-col">
<sar-navbar :fixed="true" fixation-style="top:unset;"
:root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, ${opacity})`, '--sar-navbar-height': `${systemInfo?.statusBarHeight + 44}px` }">
<sar-navbar
:fixed="true" fixation-style="top:unset;"
:root-style="{ '--sar-navbar-bg': `rgba(255, 255, 255, ${opacity})`, '--sar-navbar-height': `${systemInfo?.statusBarHeight + 44}px` }"
>
<template #left>
<view class="flex items-center justify-center text-[#333] text-[32rpx] ml-[32rpx]"
:style="{ 'padding-top': `${systemInfo?.statusBarHeight}px` }">济南</view>
<view
class="ml-[32rpx] flex items-center justify-center text-[32rpx] text-[#333]"
:style="{ 'padding-top': `${systemInfo?.statusBarHeight}px` }"
>
济南
</view>
</template>
<template #title>
<view :style="{ 'padding-top': `${systemInfo?.statusBarHeight}px` }" class="flex justify-center">
<view class="h-[32rpx] w-[226rpx] flex justify-center">
<image class="h-[32rpx] w-[226rpx]" src="https://lwzk.ycymedu.com/img/home/sy_logo.png"
mode="aspectFit" />
<image
class="h-[32rpx] w-[226rpx]" src="https://lwzk.ycymedu.com/img/home/sy_logo.png"
mode="aspectFit"
/>
</view>
</view>
</template>
</sar-navbar>
<scroll-view scroll-y class="flex-1">
<view class="mx-[40rpx] bg-[#3D72FD] rounded-[32rpx] min-h-[460rpx] mt-[20rpx] font-[DinBold] overflow-hidden">
<view class="mx-[40rpx] mt-[20rpx] min-h-[460rpx] overflow-hidden rounded-[32rpx] bg-[#3D72FD] font-[DinBold]">
<view
class="bg-[#4c7bfc] flex items-baseline justify-center rounded-[32rpx] py-[18rpx] text-white text-[36rpx] font-[JinBuFont]"
@click="navigateToCountdownPage" v-if="countdown > 0">
v-if="countdown > 0"
class="flex items-baseline justify-center rounded-[32rpx] bg-[#4c7bfc] py-[18rpx] text-[36rpx] text-white font-[JinBuFont]" @click="navigateToCountdownPage"
>
距离中考还有&nbsp;<text class="text-[52rpx] text-white">{{ countdown }}</text>&nbsp;
<view class="w-[14rpx] h-[24rpx] ml-[10rpx]">
<image src="https://lwzk.ycymedu.com/img/home/sy_gengduo.png" mode="scaleToFill"
class="w-[14rpx] h-[24rpx]" />
<view class="ml-[10rpx] h-[24rpx] w-[14rpx]">
<image
src="https://lwzk.ycymedu.com/img/home/sy_gengduo.png" mode="scaleToFill"
class="h-[24rpx] w-[14rpx]"
/>
</view>
</view>
<view
class="bg-[#4c7bfc] flex items-baseline justify-center rounded-[32rpx] py-[18rpx] text-white text-[36rpx] font-[JinBuFont]"
v-else>
v-else
class="flex items-baseline justify-center rounded-[32rpx] bg-[#4c7bfc] py-[18rpx] text-[36rpx] text-white font-[JinBuFont]"
>
正在考试
</view>
<view class="flex items-center justify-between mt-[48rpx] pl-[46rpx] pr-[64rpx]">
<view class="flex flex-col text-white items-center gap-[8rpx]">
<view class="mt-[48rpx] flex items-center justify-between pl-[46rpx] pr-[64rpx]">
<view class="flex flex-col items-center gap-[8rpx] text-white">
<view class="flex items-center">
<text class="text-[68rpx] font-700">{{ totalScore }}</text>
<view class="w-[30rpx] h-[30rpx] ml-[4rpx]">
<image class="w-[30rpx] h-[30rpx]"
src="https://lwzk.ycymedu.com/img/home/sy_bianji.png" mode="scaleToFill" />
<view class="ml-[4rpx] h-[30rpx] w-[30rpx]">
<image
class="h-[30rpx] w-[30rpx]"
src="https://lwzk.ycymedu.com/img/home/sy_bianji.png" mode="scaleToFill"
/>
</view>
</view>
<view class="text-[30rpx] text-[#b0c6ff]">预估成绩</view>
<view class="text-[30rpx] text-[#b0c6ff]">
预估成绩
</view>
</view>
<view class="flex flex-col text-white items-center gap-[8rpx]">
<view class="flex flex-col items-center gap-[8rpx] text-white">
<view class="flex items-baseline">
<text class="text-[68rpx] font-700">{{ schoolCount }}</text>
<view class="text-[32rpx] ml-[4rpx]"></view>
<view class="ml-[4rpx] text-[32rpx]">
</view>
</view>
<view class="text-[30rpx] text-[#b0c6ff]">
适合普高
</view>
<view class="text-[30rpx] text-[#b0c6ff]">适合普高</view>
</view>
<view class="flex flex-col text-white items-center gap-[8rpx]">
<view class="flex flex-col items-center gap-[8rpx] text-white">
<view class="flex items-baseline">
<text class="text-[68rpx] font-700">{{ wishlistCount }}</text>
<view class="text-[32rpx] ml-[4rpx]"></view>
<view class="ml-[4rpx] text-[32rpx]">
</view>
</view>
<view class="text-[30rpx] text-[#b0c6ff]">
志愿表
</view>
<view class="text-[30rpx] text-[#b0c6ff]">志愿表</view>
</view>
</view>
<view
class="flex items-center justify-center rounded-[52rpx] py-[28rpx] mx-[74rpx] bg-gradient-to-b from-[#FCFDFF] to-[#B8D3FF] shadow-[0_8px_20px_-6px_rgba(38,129,255,0.39)] my-[48rpx]"
@click="navigateToCreateWish">
<view class="w-[40rpx] h-[40rpx]">
<image class="w-[40rpx] h-[40rpx]"
src="https://lwzk.ycymedu.com/img/home/sy_chuangjian.png" mode="scaleToFill" />
class="mx-[74rpx] my-[48rpx] flex items-center justify-center rounded-[52rpx] from-[#FCFDFF] to-[#B8D3FF] bg-gradient-to-b py-[28rpx] shadow-[0_8px_20px_-6px_rgba(38,129,255,0.39)]"
@click="navigateToCreateWish"
>
<view class="h-[40rpx] w-[40rpx]">
<image
class="h-[40rpx] w-[40rpx]"
src="https://lwzk.ycymedu.com/img/home/sy_chuangjian.png" mode="scaleToFill"
/>
</view>
<view class="ml-[8rpx] text-[40rpx] text-[#3D72FD] font-600">
创建志愿表
</view>
<view class="text-[40rpx] text-[#3D72FD] ml-[8rpx] font-600">创建志愿表</view>
</view>
</view>
<!-- 咨询 -->
<view class="grid grid-cols-2 mx-[32rpx] gap-[24rpx] mt-[60rpx]">
<view class="rounded-[24rpx] bg-[#F3F4F8] py-[20rpx] pl-[30rpx] pr-[24rpx] flex items-center justify-between"
v-for="(item, index) in subMenus" :key="index" @click="navigateToSubPage(item.url)">
<view class="grid grid-cols-2 mx-[32rpx] mt-[60rpx] gap-[24rpx]">
<view
v-for="(item, index) in subMenus"
:key="index" class="flex items-center justify-between rounded-[24rpx] bg-[#F3F4F8] py-[20rpx] pl-[30rpx] pr-[24rpx]" @click="navigateToSubPage(item.url)"
>
<text class="text-[34rpx] text-[#333] font-600">{{ item.name }}</text>
<view class="w-[108rpx] h-[108rpx]">
<image :src="item.imgPath" mode="scaleToFill" class="w-[108rpx] h-[108rpx]" />
<view class="h-[108rpx] w-[108rpx]">
<image :src="item.imgPath" mode="scaleToFill" class="h-[108rpx] w-[108rpx]" />
</view>
</view>
</view>
<view class="mx-[30rpx] mt-[60rpx]">
<view class="h-[216rpx]">
<image src="https://lwzk.ycymedu.com/img/home/sy_banner.png" mode="scaleToFill"
class="h-[216rpx]" />
<view class="mb-[30rpx] flex items-center">
<view class="mr-[12rpx] h-[48rpx] w-[48rpx]">
<image
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/sy_zhibo.png" mode="scaleToFill"
class="h-[48rpx] w-[48rpx]"
/>
</view>
<text class="text-[40rpx] text-black font-600">最新直播</text>
</view>
<scroll-view scroll-x class="w-full">
<view class="w-full flex gap-[30rpx] whitespace-nowrap" v-if="channelVideoList.length > 0">
<view
v-for="item in channelVideoList"
:key="item.id"
class="h-[430rpx] min-h-[430rpx] min-w-[440rpx] w-[440rpx] rounded-[18rpx] bg-[#F3F4F8]"
>
<view class="relative h-[280rpx] rounded-[18rpx]">
<image
:src="item.coverUrl"
mode="scaleToFill"
class="h-full w-full"
/>
<view v-if="item.type === 'live'" class="absolute bottom-0 h-[48rpx] w-full flex items-center justify-between rounded-[0_0_18rpx_18rpx] bg-black bg-opacity-50">
<view class="ml-[20rpx] flex items-center">
<view class="mr-[8rpx] h-[12rpx] w-[12rpx] rounded-full bg-white" />
<view class="text-[24rpx] text-white">
{{ item.statusText }}
</view>
</view>
<view v-if="item.nickname" class="mr-[20rpx] flex items-center">
<view class="mr-[10rpx] h-[20rpx] w-[30rpx] flex items-center">
<image
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/sy_liulan.png"
mode="widthFix"
/>
</view>
<view class="text-[24rpx] text-white">
{{ item.nickname }}
</view>
</view>
</view>
<view v-else class="absolute bottom-0 h-[48rpx] w-full flex items-center justify-between rounded-[0_0_18rpx_18rpx] bg-black bg-opacity-50">
<view class="ml-[20rpx] text-[24rpx] text-white">
{{ item.startTimeText }}
</view>
</view>
</view>
<view class="ml-[20rpx] mt-[20rpx] text-[34rpx] text-[#333] font-500">
{{ item.title }}
</view>
<view
class="ml-[20rpx] mt-[10rpx] h-[52rpx] w-[148rpx] flex items-center justify-center rounded-[376rpx] text-[26rpx]"
:class="item.type === 'live' ? 'text-white bg-[#3D72FD] border-solid border-[2rpx] border-[#3D72FD]' : 'bg-[#E0E6F8] text-[#3D72FD]'"
@click="handleChannelVideoAction(item)"
>
{{ item.buttonText }}
</view>
</view>
</view>
<view class="w-full flex flex-col items-center justify-center" v-if="channelVideoList.length === 0">
<view class="w-[236rpx] h-[236rpx]">
<image
src="https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/home/sy_zanweikaobo.png"
mode="scaleToFill"
/>
</view>
<view @click="goToLiveHomePage()" class="mt-[20rpx] flex items-center justify-center rounded-[376rpx] text-[26rpx] text-white bg-[#3D72FD] border-solid border-[2rpx] border-[#3D72FD] px-[40rpx] py-[12rpx]"> 关注官方视频号 </view>
</view>
</scroll-view>
</view>
<view class="mx-[30rpx] mt-[60rpx]">
<view class="flex items-center mb-[30rpx]">
<view class="h-[48rpx] w-[48rpx] mr-[12rpx]">
<image src="https://lwzk.ycymedu.com/img/home/sy_zixuntun.png" mode="scaleToFill"
class="h-[48rpx] w-[48rpx]" />
<view class="mb-[30rpx] flex items-center">
<view class="mr-[12rpx] h-[48rpx] w-[48rpx]">
<image
src="https://lwzk.ycymedu.com/img/home/sy_zixuntun.png" mode="scaleToFill"
class="h-[48rpx] w-[48rpx]"
/>
</view>
<text class="text-black text-[40rpx] font-600">最新资讯</text>
<text class="text-[40rpx] text-black font-600">最新资讯</text>
<view class="text-[30rpx] text-[#A6A6A6] ml-auto" @click="navigateToNewsPage"></view>
<view class="w-[14rpx] h-[30rpx] flex items-center">
<image class="w-[14rpx] h-[30rpx]"
src="https://lwzk.ycymedu.com/img/home/sy_zixunjiantou.png" mode="scaleToFill" />
<view class="ml-auto text-[30rpx] text-[#A6A6A6]" @click="navigateToNewsPage">
更多
</view>
<view class="h-[30rpx] w-[14rpx] flex items-center">
<image
class="h-[30rpx] w-[14rpx]"
src="https://lwzk.ycymedu.com/img/home/sy_zixunjiantou.png" mode="scaleToFill"
/>
</view>
</view>
<view class="flex flex-col border-b-1 border-b-solid border-[#eee] pb-[22rpx] mb-[28rpx]"
v-for="(value, index) in notifies" :key="index" @click="navigateToNewsDetail(value.id)">
<text class="text-[#333] text-[32rpx] font-400">{{ value.title }}</text>
<view class="flex items-center justify-between text-[#999] mt-[20rpx]">
<view
v-for="(value, index) in notifies"
:key="index" class="mb-[28rpx] flex flex-col border-b-1 border-[#eee] border-b-solid pb-[22rpx]" @click="navigateToNewsDetail(value.id)"
>
<text class="text-[32rpx] text-[#333] font-400">{{ value.title }}</text>
<view class="mt-[20rpx] flex items-center justify-between text-[#999]">
<view class="flex items-center">
<view class="w-[28rpx] h-[28rpx] mr-[10rpx] flex items-center">
<image src="https://lwzk.ycymedu.com/img/home/sy_shijian.png" mode="scaleToFill"
class="w-[28rpx] h-[28rpx]" />
<view class="mr-[10rpx] h-[28rpx] w-[28rpx] flex items-center">
<image
src="https://lwzk.ycymedu.com/img/home/sy_shijian.png" mode="scaleToFill"
class="h-[28rpx] w-[28rpx]"
/>
</view>
<text class="text-[26rpx]">{{ value.pubtime }}</text>
</view>
<view class="flex items-center">
<view class="w-[28rpx] h-[28rpx] mr-[10rpx] flex items-center">
<image src="https://lwzk.ycymedu.com/img/home/sy_chakan.png" mode="scaleToFill"
class="w-[28rpx] h-[28rpx]" />
<view class="mr-[10rpx] h-[28rpx] w-[28rpx] flex items-center">
<image
src="https://lwzk.ycymedu.com/img/home/sy_chakan.png" mode="scaleToFill"
class="h-[28rpx] w-[28rpx]"
/>
</view>
<text class="text-[26rpx]">{{ value.viewcount }}</text>
</view>
@ -278,9 +397,11 @@ onShow(() => {
</view>
</view>
<view class="fixed bottom-[200rpx] right-[60rpx]" @click="navigateToCustom">
<view class="w-[160rpx] h-[160rpx]">
<image class="w-[160rpx] h-[160rpx]" src="https://lwzk.ycymedu.com/img/home/sy_kefu.png"
mode="scaleToFill" />
<view class="h-[160rpx] w-[160rpx]">
<image
class="h-[160rpx] w-[160rpx]" src="https://lwzk.ycymedu.com/img/home/sy_kefu.png"
mode="scaleToFill"
/>
</view>
</view>
</scroll-view>
@ -289,7 +410,7 @@ onShow(() => {
<style scoped>
.custom-background {
background: linear-gradient(180deg, #C7E0FF 0%, rgba(199, 224, 255, 0) 530rpx);
background: linear-gradient(180deg, #c7e0ff 0%, rgba(199, 224, 255, 0) 530rpx);
background-position: 50% 50%;
background-origin: padding-box;
background-clip: border-box;

View File

@ -157,6 +157,20 @@ export const getSchoolHistoricalScores = (options: { query: any }) => {
});
}
export const getBatchLine = (options: { query: any }) => {
return request<API.Response>('/api/busBatchLine/h5List', {
method: 'GET',
...options,
});
}
export const getRankTable = (options: { query: any }) => {
return request<API.Response>('/api/busScoreSection/h5List', {
method: 'GET',
...options,
});
}
export const getSchoolNature = () => {
return request<API.Response>('/api/zhiYuan/naturelist', {
method: 'GET',
@ -244,6 +258,12 @@ export const getMyScore = () => {
});
}
export const getShowAiStatus = () => {
return request<API.Response>('/api/sysDictData/detail?Status=1&Id=812248362119237 ', {
method: 'GET',
});
}
export const saveMyScore = (options: { data: any }) => {
return request<API.Response>('/api/busMiddleSchoolApply/add', {
method: "POST",

BIN
src/static/tabbar/ai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -61,6 +61,7 @@ export interface CustomTabBarItem {
iconType: 'uiLib' | 'unocss' | 'iconfont' | 'image' // 不建议用 image 模式需要配置2张图
icon: any // 其实是 string 类型,这里是为了避免 ts 报错 (tabbar/index.vue 里面 uni-icons 那行)
iconActive?: string // 只有在 image 模式下才需要传递的是高亮的图片PS 不建议用 image 模式)
openType?: 'switchTab' | 'navigateTo'
badge?: CustomTabBarItemBadge
isBulge?: boolean // 是否是中间的鼓包tabbarItem
}
@ -89,6 +90,18 @@ export const customTabbarList: CustomTabBarItem[] = [
iconActive: '/static/tabbar/talented-active.png',
// badge: 'dot',
},
{
text: '',
pagePath: 'pages-ai/ai/index',
// 注意 unocss 图标需要如下处理:(二选一)
// 1在fg-tabbar.vue页面上引入一下并注释掉见tabbar/index.vue代码第2行
// 2配置到 unocss.config.ts 的 safelist 中
iconType: 'image',
icon: '/static/tabbar/ai.png',
iconActive: '/static/tabbar/ai.png',
openType: 'navigateTo',
// badge: 'dot',
},
{
text: '测评',
pagePath: 'pages/evaluation/index',
@ -157,7 +170,11 @@ export const customTabbarEnable
*/
export const needHideNativeTabbar = selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE
const _tabbarList = customTabbarEnable ? customTabbarList.map(item => ({ text: item.text, pagePath: item.pagePath })) : nativeTabbarList
const _tabbarList = customTabbarEnable
? customTabbarList
.filter(item => item.openType !== 'navigateTo')
.map(item => ({ text: item.text, pagePath: item.pagePath }))
: nativeTabbarList
export const tabbarList = customTabbarEnable ? customTabbarList : nativeTabbarList
const _tabbar: TabBar = {

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
// i-carbon-code
import type { CustomTabBarItem } from './config'
import { getShowAiStatus } from '@/service'
import { customTabbarEnable, needHideNativeTabbar, tabbarCacheEnable } from './config'
import { tabbarList, tabbarStore } from './store'
@ -22,16 +23,22 @@ function handleClickBulge() {
}
function handleClick(index: number) {
const item = tabbarList[index]
//
if (index === tabbarStore.curIdx) {
return
}
if (tabbarList[index].isBulge) {
if (item.isBulge) {
handleClickBulge()
return
}
const url = tabbarList[index].pagePath
//
if (item.openType !== 'navigateTo' && index === tabbarStore.curIdx) {
return
}
const url = item.pagePath
if (item.openType === 'navigateTo') {
uni.navigateTo({ url })
return
}
tabbarStore.setCurIdx(index)
if (tabbarCacheEnable) {
@ -41,6 +48,26 @@ function handleClick(index: number) {
uni.navigateTo({ url })
}
}
const aiShowStatus = ref("0")
function removeNavigateToItem() {
const index = tabbarList.findIndex(item => item.openType === 'navigateTo')
if (index !== -1) {
tabbarList.splice(index, 1)
}
}
function aiShowStatusFn() {
getShowAiStatus().then((resp) => {
if (resp.code === 200) {
aiShowStatus.value = resp.result.value
if (aiShowStatus.value === "0") {
removeNavigateToItem()
}
}
})
}
// #ifndef MP-WEIXIN
// custom:true hide
onLoad(() => {
@ -69,6 +96,10 @@ function getImageByIndex(index: number, item: CustomTabBarItem) {
}
return tabbarStore.curIdx === index ? item.iconActive : item.icon
}
onBeforeMount(() => {
aiShowStatusFn()
})
</script>
<template>
@ -89,7 +120,7 @@ function getImageByIndex(index: number, item: CustomTabBarItem) {
<image class="mt-6rpx h-200rpx w-200rpx" src="/static/tabbar/scan.png" />
</view>
</view>
<view v-else class="relative px-3 flex flex-col justify-center items-center">
<view v-else class="relative flex flex-col items-center justify-center px-3">
<template v-if="item.iconType === 'uiLib'">
<!-- TODO: 以下内容请根据选择的UI库自行替换 -->
<!-- <wd-icon name="home" /> (https://wot-design-uni.cn/component/icon.html) -->
@ -101,11 +132,14 @@ function getImageByIndex(index: number, item: CustomTabBarItem) {
<view :class="item.icon" class="text-20px" />
</template>
<template v-if="item.iconType === 'image'">
<view class="h-24px w-24px">
<view v-if="item.openType !== 'navigateTo'" class="h-24px w-24px">
<image :src="getImageByIndex(index, item)" mode="scaleToFill" class="h-24px w-24px" />
</view>
<view v-if="item.openType === 'navigateTo' && aiShowStatus" class="h-48px w-42px">
<image :src="getImageByIndex(index, item)" mode="scaleToFill" class="h-48px w-42px" />
</view>
</template>
<view class="mt-2px text-14px">
<view v-if="item.text" class="mt-2px text-14px">
{{ item.text }}
</view>
<!-- 角标显示 -->

View File

@ -26,7 +26,7 @@ if (customTabbarEnable && BULGE_ENABLE) {
export function isPageTabbar(path: string) {
const _path = path.split('?')[0]
return tabbarList.some(item => item.pagePath === _path)
return tabbarList.some(item => item.openType !== 'navigateTo' && item.pagePath === _path)
}
/**
@ -56,13 +56,13 @@ const tabbarStore = reactive({
this.setCurIdx(0)
return
}
const index = tabbarList.findIndex(item => item.pagePath === path)
const index = tabbarList.findIndex(item => item.openType !== 'navigateTo' && item.pagePath === path)
FG_LOG_ENABLE && console.log('index:', index, path)
// console.log('tabbarList:', tabbarList)
if (index === -1) {
const pagesPathList = getCurrentPages().map(item => item.route.startsWith('/') ? item.route : `/${item.route}`)
// console.log(pagesPathList)
const flag = tabbarList.some(item => pagesPathList.includes(item.pagePath))
const flag = tabbarList.some(item => item.openType !== 'navigateTo' && pagesPathList.includes(item.pagePath))
if (!flag) {
this.setCurIdx(0)
return

View File

@ -108,8 +108,6 @@ export function getCurrentPageI18nKey() {
console.warn('路由不正确')
return ''
}
console.log(currPage)
console.log(currPage.style.navigationBarTitleText)
return currPage.style?.navigationBarTitleText || ''
}
@ -120,10 +118,13 @@ export function getEnvBaseUrl() {
// 请求基准地址
let baseUrl = import.meta.env.VITE_SERVER_BASEURL
// https://senior.ycymedu.com 六纬中考通
// https://liebian.ycymedu.com 智能中专学校的
// https://xqwgy.ycymedu.com/ 深泉外国语学院
// # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://senior.ycymedu.com'
const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://senior.ycymedu.com'
const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://senior.ycymedu.com'
const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://xqwgy.ycymedu.com'
const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://xqwgy.ycymedu.com'
const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://xqwgy.ycymedu.com'
// 微信小程序端环境区分
if (isMpWeixin) {