feat: 测评首页编写
parent
e20ecf787a
commit
6f25dee2fa
|
|
@ -1,4 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- TabBar占位块 - 与TabBar高度一致 -->
|
||||||
|
<view
|
||||||
|
v-if="showPlaceholder"
|
||||||
|
class="tabbar-placeholder"
|
||||||
|
:style="{ height: `${tabbarTotalHeight}px` }"
|
||||||
|
></view>
|
||||||
|
|
||||||
|
<!-- TabBar组件 -->
|
||||||
<view class="custom-tabbar pb-safe">
|
<view class="custom-tabbar pb-safe">
|
||||||
<view class="tabbar-content">
|
<view class="tabbar-content">
|
||||||
<view
|
<view
|
||||||
|
|
@ -25,12 +33,28 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TabesItem } from '@/service/app/types'
|
import { TabesItem } from '@/service/app/types'
|
||||||
import { tabbarList } from '@/hooks/useTabbarList'
|
import { tabbarList } from '@/hooks/useTabbarList'
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
currentPage: {
|
currentPage: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
// 是否显示占位块
|
||||||
|
showPlaceholder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 安全区域底部高度
|
||||||
|
const safeAreaBottom = ref(0)
|
||||||
|
|
||||||
|
// 计算TabBar总高度 (TabBar高度 + 安全区域高度)
|
||||||
|
const tabbarTotalHeight = computed(() => {
|
||||||
|
// 100rpx转为px,不同设备可能有差异
|
||||||
|
const tabbarHeight = uni.upx2px(100)
|
||||||
|
return tabbarHeight + safeAreaBottom.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const changeItem = (item: TabesItem) => {
|
const changeItem = (item: TabesItem) => {
|
||||||
|
|
@ -41,6 +65,20 @@ const changeItem = (item: TabesItem) => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.hideTabBar()
|
uni.hideTabBar()
|
||||||
|
|
||||||
|
// 获取系统信息以计算安全区域
|
||||||
|
uni.getSystemInfo({
|
||||||
|
success: (res) => {
|
||||||
|
if (res.safeAreaInsets) {
|
||||||
|
safeAreaBottom.value = res.safeAreaInsets.bottom || 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露高度信息给父组件
|
||||||
|
defineExpose({
|
||||||
|
tabbarTotalHeight,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -106,4 +144,16 @@ onMounted(() => {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 5rpx;
|
bottom: 5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 占位块样式 */
|
||||||
|
.tabbar-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 安全区域适配 */
|
||||||
|
.pb-safe {
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
<template>
|
||||||
|
<view class="tab-container">
|
||||||
|
<!-- tab标题栏 -->
|
||||||
|
<scroll-view
|
||||||
|
scroll-x
|
||||||
|
class="tab-scroll-view"
|
||||||
|
:scroll-left="scrollLeft"
|
||||||
|
scroll-with-animation
|
||||||
|
show-scrollbar="false"
|
||||||
|
:id="tabScrollId"
|
||||||
|
>
|
||||||
|
<view class="tab-items-container">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in tabs"
|
||||||
|
:key="index"
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: currentIndex === index }"
|
||||||
|
@click="handleTabClick(index)"
|
||||||
|
:id="`tab-item-${index}`"
|
||||||
|
>
|
||||||
|
<text class="tab-text">{{ item.title }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 独立的滑块元素 -->
|
||||||
|
<view
|
||||||
|
class="tab-line"
|
||||||
|
:style="{
|
||||||
|
transform: `translateX(${lineLeft}px)`,
|
||||||
|
width: `${lineWidth}rpx`,
|
||||||
|
backgroundColor: props.themeColor,
|
||||||
|
}"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, nextTick, onMounted } from 'vue'
|
||||||
|
|
||||||
|
// 唯一ID,防止多个Tab组件冲突
|
||||||
|
const tabScrollId = `tab-scroll-${Date.now()}`
|
||||||
|
|
||||||
|
// 定义组件属性
|
||||||
|
const props = defineProps({
|
||||||
|
// tabs数据,格式:[{title: '标签1'}, {title: '标签2'}]
|
||||||
|
tabs: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
// 默认选中的索引
|
||||||
|
modelValue: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
// 主题色
|
||||||
|
themeColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#3C9CFD',
|
||||||
|
},
|
||||||
|
// 滑块宽度
|
||||||
|
lineWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 48,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
|
||||||
|
// 当前激活的索引
|
||||||
|
const currentIndex = ref(props.modelValue)
|
||||||
|
|
||||||
|
// 滚动位置
|
||||||
|
const scrollLeft = ref(0)
|
||||||
|
|
||||||
|
// 滑块位置
|
||||||
|
const lineLeft = ref(0)
|
||||||
|
|
||||||
|
// 监听props变化
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newVal) => {
|
||||||
|
if (currentIndex.value !== newVal) {
|
||||||
|
currentIndex.value = newVal
|
||||||
|
updateTabPosition()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听currentIndex变化
|
||||||
|
watch(
|
||||||
|
() => currentIndex.value,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
updateTabPosition()
|
||||||
|
// 向父组件同步更新
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
emit('change', {
|
||||||
|
index: newVal,
|
||||||
|
item: props.tabs[newVal] || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听tabs变化,重新计算滑块位置
|
||||||
|
watch(
|
||||||
|
() => props.tabs,
|
||||||
|
() => {
|
||||||
|
nextTick(() => {
|
||||||
|
updateTabPosition()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// 组件挂载后初始化
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
updateTabPosition()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
|
// 更新标签位置和滑块位置 - 优化版
|
||||||
|
const updateTabPosition = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
// 创建查询对象
|
||||||
|
const query = uni.createSelectorQuery().in(instance)
|
||||||
|
|
||||||
|
// 获取滚动视图和当前选中标签的信息
|
||||||
|
query.select(`#${tabScrollId}`).boundingClientRect()
|
||||||
|
query.select(`#tab-item-${currentIndex.value}`).boundingClientRect()
|
||||||
|
|
||||||
|
query.exec((res) => {
|
||||||
|
if (res && res[0] && res[1]) {
|
||||||
|
const scrollView = res[0]
|
||||||
|
const currentTab = res[1]
|
||||||
|
|
||||||
|
// 1. 计算滑块位置 - 直接使用当前标签的中心位置
|
||||||
|
const tabCenter = currentTab.left + currentTab.width / 2 - scrollView.left
|
||||||
|
|
||||||
|
// 2. 计算滑块左侧应该在的位置(居中)
|
||||||
|
const lineWidthPx = uni.upx2px(Number(props.lineWidth))
|
||||||
|
lineLeft.value = tabCenter - lineWidthPx / 2
|
||||||
|
|
||||||
|
// 3. 计算滚动位置,使选中的标签居中显示在滚动视图中
|
||||||
|
const offsetLeft = currentTab.left - scrollView.left
|
||||||
|
scrollLeft.value = offsetLeft - scrollView.width / 2 + currentTab.width / 2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击标签切换
|
||||||
|
const handleTabClick = (index) => {
|
||||||
|
if (currentIndex.value !== index) {
|
||||||
|
currentIndex.value = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tab-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-scroll-view {
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-items-container {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active .tab-text {
|
||||||
|
font-weight: bold;
|
||||||
|
color: v-bind('props.themeColor');
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-line {
|
||||||
|
position: absolute;
|
||||||
|
height: 6rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
/* 平滑过渡效果 */
|
||||||
|
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,68 +1,219 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="collapse-container">
|
<view class="uni-collapse">
|
||||||
<div class="collapse-header" @click="toggleCollapse">
|
<slot />
|
||||||
<slot name="header"></slot>
|
</view>
|
||||||
<div class="collapse-icon" :class="{ 'is-active': isCollapsed }">
|
|
||||||
<i class="el-icon-arrow-down"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="collapse-content" :class="{ 'is-collapsed': isCollapsed }">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, computed, watch, provide, reactive, onMounted, nextTick } from 'vue'
|
||||||
name: 'Collapse',
|
|
||||||
props: {
|
const props = defineProps({
|
||||||
defaultCollapsed: {
|
value: {
|
||||||
type: Boolean,
|
type: [String, Array],
|
||||||
default: false,
|
default: '',
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
modelValue: {
|
||||||
return {
|
type: [String, Array],
|
||||||
isCollapsed: this.defaultCollapsed,
|
default: '',
|
||||||
|
},
|
||||||
|
accordion: {
|
||||||
|
// 是否开启手风琴效果
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'activeItem', 'input', 'update:modelValue'])
|
||||||
|
|
||||||
|
// 子组件集合
|
||||||
|
const childrens = ref([])
|
||||||
|
const names = ref([])
|
||||||
|
|
||||||
|
// 计算当前使用的值(兼容 vue2 和 vue3 的双向绑定)
|
||||||
|
const dataValue = computed(() => {
|
||||||
|
// 优先使用modelValue(Vue3),如果为空则使用value(Vue2)
|
||||||
|
if (props.modelValue !== '') {
|
||||||
|
return props.modelValue
|
||||||
|
}
|
||||||
|
return props.value
|
||||||
|
})
|
||||||
|
// 发送事件
|
||||||
|
const emitValue = (val) => {
|
||||||
|
emit('input', val)
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册子组件
|
||||||
|
const register = (childInstance) => {
|
||||||
|
console.log('注册子组件:', childInstance.nameSync)
|
||||||
|
childrens.value.push(childInstance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注销子组件
|
||||||
|
const unregister = (childInstance) => {
|
||||||
|
const index = childrens.value.findIndex((child) => child.nameSync === childInstance.nameSync)
|
||||||
|
if (index !== -1) {
|
||||||
|
childrens.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置打开状态 - 进一步优化
|
||||||
|
const setOpen = (val) => {
|
||||||
|
console.log('设置打开状态:', val, childrens.value.length)
|
||||||
|
|
||||||
|
// 处理不同类型的值
|
||||||
|
const isString = typeof val === 'string' || typeof val === 'number'
|
||||||
|
const isArray = Array.isArray(val)
|
||||||
|
|
||||||
|
// 清空所有项的打开状态
|
||||||
|
childrens.value.forEach((vm) => {
|
||||||
|
if (typeof vm.setOpen === 'function') {
|
||||||
|
vm.setOpen(false) // 使用子组件提供的setOpen方法
|
||||||
|
} else {
|
||||||
|
vm.isOpen = false // 兼容旧的方式
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 根据不同模式设置打开状态
|
||||||
|
childrens.value.forEach((vm) => {
|
||||||
|
if (isString) {
|
||||||
|
// 手风琴模式 - 单值
|
||||||
|
if (val.toString() === vm.nameSync) {
|
||||||
|
if (typeof vm.setOpen === 'function') {
|
||||||
|
vm.setOpen(true)
|
||||||
|
} else {
|
||||||
|
vm.isOpen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isArray) {
|
||||||
|
// 多选模式 - 数组
|
||||||
|
val.forEach((v) => {
|
||||||
|
if (v.toString() === vm.nameSync) {
|
||||||
|
if (typeof vm.setOpen === 'function') {
|
||||||
|
vm.setOpen(true)
|
||||||
|
} else {
|
||||||
|
vm.isOpen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
emitValue(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听值变化设置打开状态
|
||||||
|
watch(
|
||||||
|
() => dataValue.value,
|
||||||
|
(val) => {
|
||||||
|
console.log('折叠面板值变化:', val, '手风琴模式:', props.accordion)
|
||||||
|
if (val !== undefined && val !== null) {
|
||||||
|
setOpen(val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
{ immediate: true }, // 立即执行以处理默认值
|
||||||
toggleCollapse() {
|
)
|
||||||
this.isCollapsed = !this.isCollapsed
|
|
||||||
},
|
// 设置手风琴模式 - 修复版
|
||||||
},
|
const setAccordion = (self) => {
|
||||||
|
if (!props.accordion) return
|
||||||
|
|
||||||
|
console.log('设置手风琴模式,当前组件:', self?.uid || self?.nameSync)
|
||||||
|
|
||||||
|
childrens.value.forEach((vm) => {
|
||||||
|
// 检查各种可能的标识符比较方式
|
||||||
|
const isSameComponent =
|
||||||
|
// 如果self是组件实例的情况
|
||||||
|
(self.uid && vm.instance?.uid && self.uid === vm.instance.uid) ||
|
||||||
|
// 如果self只有nameSync属性的情况
|
||||||
|
(self.nameSync && self.nameSync === vm.nameSync) ||
|
||||||
|
// 如果self就是vm实例本身的情况
|
||||||
|
self === vm
|
||||||
|
|
||||||
|
// 如果不是同一个组件,则关闭它
|
||||||
|
if (!isSameComponent) {
|
||||||
|
console.log('关闭其他面板:', vm.nameSync)
|
||||||
|
vm.isOpen = false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重新计算高度
|
||||||
|
const resize = () => {
|
||||||
|
childrens.value.forEach((vm) => {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
vm.getCollapseHeight && vm.getCollapseHeight()
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
vm.getNvueHwight && vm.getNvueHwight()
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态变化事件
|
||||||
|
const onChange = (isOpen, self) => {
|
||||||
|
let activeItem = ''
|
||||||
|
|
||||||
|
if (props.accordion) {
|
||||||
|
// 手风琴模式返回单个值
|
||||||
|
activeItem = isOpen ? self.nameSync : ''
|
||||||
|
} else {
|
||||||
|
// 非手风琴模式返回数组
|
||||||
|
const activeItems = []
|
||||||
|
childrens.value.forEach((vm) => {
|
||||||
|
if (vm.isOpen) {
|
||||||
|
activeItems.push(vm.nameSync)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
activeItem = activeItems
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('折叠面板状态变化:', activeItem)
|
||||||
|
emit('change', activeItem)
|
||||||
|
emitValue(activeItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供给子组件的内容
|
||||||
|
provide('uniCollapseContext', {
|
||||||
|
childrens,
|
||||||
|
names,
|
||||||
|
setAccordion,
|
||||||
|
onChange,
|
||||||
|
register,
|
||||||
|
unregister,
|
||||||
|
// 新增一个方法,让子组件可以查询当前活动项
|
||||||
|
getActiveNames: () => {
|
||||||
|
// 如果是手风琴模式,返回单个值组成的数组
|
||||||
|
if (props.accordion) {
|
||||||
|
return typeof dataValue.value === 'string' || typeof dataValue.value === 'number'
|
||||||
|
? [dataValue.value.toString()]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
// 如果是多选模式,确保返回数组
|
||||||
|
return Array.isArray(dataValue.value) ? dataValue.value.map((v) => v.toString()) : []
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件挂载后初始化
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
console.log('折叠面板挂载完成,当前值:', dataValue.value)
|
||||||
|
if (dataValue.value !== undefined && dataValue.value !== null && dataValue.value !== '') {
|
||||||
|
setOpen(dataValue.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss">
|
||||||
.collapse-container {
|
.uni-collapse {
|
||||||
display: grid;
|
/* #ifndef APP-NVUE */
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
/* #endif */
|
||||||
justify-content: space-between;
|
/* #ifdef APP-NVUE */
|
||||||
cursor: pointer;
|
flex: 1;
|
||||||
}
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
.collapse-icon {
|
background-color: #fff;
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-icon.is-active {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-content {
|
|
||||||
overflow: hidden;
|
|
||||||
transition: grid-template-rows 0.3s ease;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-content.is-collapsed {
|
|
||||||
grid-template-rows: 0fr;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,447 @@
|
||||||
|
<template>
|
||||||
|
<view class="uni-collapse-item">
|
||||||
|
<!-- onClick(!isOpen) -->
|
||||||
|
<view
|
||||||
|
@click="onClick(!isOpen)"
|
||||||
|
class="uni-collapse-item__title"
|
||||||
|
:class="{
|
||||||
|
'is-open': isOpen && titleBorder === 'auto',
|
||||||
|
'uni-collapse-item-border': titleBorder !== 'none',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<view class="uni-collapse-item__title-wrap">
|
||||||
|
<slot name="title" :expanded="isOpen">
|
||||||
|
<view class="uni-collapse-item__title-box" :class="{ 'is-disabled': disabled }">
|
||||||
|
<image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
|
||||||
|
<text class="uni-collapse-item__title-text">{{ title }}</text>
|
||||||
|
</view>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="showArrow && !$slots.title"
|
||||||
|
:class="{
|
||||||
|
'uni-collapse-item__title-arrow-active': isOpen,
|
||||||
|
'uni-collapse-item--animation': showAnimation === true,
|
||||||
|
}"
|
||||||
|
class="uni-collapse-item__title-arrow"
|
||||||
|
>
|
||||||
|
<view :color="disabled ? '#ddd' : '#bbb'" class="i-carbon-chevron-down" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="uni-collapse-item__wrap"
|
||||||
|
:class="{ 'is--transition': showAnimation }"
|
||||||
|
:style="{ height: (isOpen ? height : 0) + 'px' }"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
:id="elId"
|
||||||
|
ref="collapseHook"
|
||||||
|
class="uni-collapse-item__wrap-content"
|
||||||
|
:class="{ open: isheight, 'uni-collapse-item--border': border && isOpen }"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
inject,
|
||||||
|
watch,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
onUpdated,
|
||||||
|
getCurrentInstance,
|
||||||
|
nextTick,
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
const dom = weex.requireModule('dom')
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CollapseItem 折叠面板子组件
|
||||||
|
* @description 折叠面板子组件
|
||||||
|
* @property {String} title 标题文字
|
||||||
|
* @property {String} thumb 标题左侧缩略图
|
||||||
|
* @property {String} name 唯一标志符
|
||||||
|
* @property {Boolean} open = [true|false] 是否展开组件
|
||||||
|
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
|
||||||
|
* @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
|
||||||
|
* @property {Boolean} disabled = [true|false] 是否展开面板
|
||||||
|
* @property {Boolean} showAnimation = [true|false] 开启动画
|
||||||
|
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
|
||||||
|
*/
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 列表标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
// 是否显示动画,app 端默认不开启动画,卡顿严重
|
||||||
|
showAnimation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-PLUS
|
||||||
|
// 是否显示动画
|
||||||
|
showAnimation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// 是否展开
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 缩略图
|
||||||
|
thumb: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 标题分隔线显示类型
|
||||||
|
titleBorder: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto',
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showArrow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 生成随机元素ID,用于解决百度小程序获取同一个元素位置信息的bug
|
||||||
|
const elId = ref(`Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`)
|
||||||
|
const isOpen = ref(false)
|
||||||
|
const isheight = ref(null)
|
||||||
|
const height = ref(0)
|
||||||
|
const nameSync = ref('0')
|
||||||
|
const collapseHook = ref(null)
|
||||||
|
const proxy = getCurrentInstance()
|
||||||
|
|
||||||
|
// 获取折叠面板父组件上下文
|
||||||
|
const collapseContext = inject('uniCollapseContext', null)
|
||||||
|
|
||||||
|
// 监听open属性变化
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
(val) => {
|
||||||
|
isOpen.value = val
|
||||||
|
onClick(val, 'init')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始化方法
|
||||||
|
const init = (type) => {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
getCollapseHeight(type)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
getNvueHwight(type)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卸载组件时从父组件中移除
|
||||||
|
const uninstall = () => {
|
||||||
|
if (collapseContext) {
|
||||||
|
collapseContext.unregister({
|
||||||
|
nameSync: nameSync.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击事件处理 - 修复版
|
||||||
|
const onClick = (isOpenVal, type) => {
|
||||||
|
if (props.disabled) return
|
||||||
|
|
||||||
|
// 保存旧状态用于比较
|
||||||
|
const oldIsOpen = isOpen.value
|
||||||
|
|
||||||
|
// 设置新状态
|
||||||
|
isOpen.value = isOpenVal
|
||||||
|
|
||||||
|
// 如果是打开操作且在手风琴模式下,关闭其他面板
|
||||||
|
if (isOpen.value && !oldIsOpen && collapseContext) {
|
||||||
|
// 传递一个包含必要信息的对象,确保能被识别
|
||||||
|
collapseContext.setAccordion({
|
||||||
|
nameSync: nameSync.value,
|
||||||
|
uid: proxy.uid,
|
||||||
|
instance: proxy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非初始化操作时,触发onChange回调
|
||||||
|
if (type !== 'init' && collapseContext) {
|
||||||
|
collapseContext.onChange(isOpen.value, {
|
||||||
|
nameSync: nameSync.value,
|
||||||
|
isOpen: isOpen.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取折叠面板高度(非APP-NVUE)
|
||||||
|
const getCollapseHeight = (type, index = 0) => {
|
||||||
|
const views = uni.createSelectorQuery().in(proxy)
|
||||||
|
views
|
||||||
|
.select(`#${elId.value}`)
|
||||||
|
.fields(
|
||||||
|
{
|
||||||
|
size: true,
|
||||||
|
},
|
||||||
|
(data) => {
|
||||||
|
// TODO 百度中可能获取不到节点信息,需要循环获取
|
||||||
|
if (index >= 10) return
|
||||||
|
if (!data) {
|
||||||
|
index++
|
||||||
|
getCollapseHeight(false, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
height.value = data.height + 1
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
height.value = data.height
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
isheight.value = true
|
||||||
|
if (type) return
|
||||||
|
onClick(isOpen.value, 'init')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取NVUE环境下的高度
|
||||||
|
const getNvueHwight = (type) => {
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
dom.getComponentRect(collapseHook.value, (option) => {
|
||||||
|
if (option && option.result && option.size) {
|
||||||
|
height.value = option.size.height + 1
|
||||||
|
isheight.value = true
|
||||||
|
if (type) return
|
||||||
|
onClick(props.open, 'init')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载后初始化 - 注册部分的修改
|
||||||
|
onMounted(() => {
|
||||||
|
if (!collapseContext) {
|
||||||
|
console.error('找不到折叠面板父组件上下文!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置nameSync
|
||||||
|
nameSync.value = props.name !== '' ? props.name.toString() : `${collapseContext.childrens.length}`
|
||||||
|
|
||||||
|
console.log('折叠项挂载:', nameSync.value)
|
||||||
|
|
||||||
|
// 注册到父组件 - 确保传递所有必要的参数
|
||||||
|
collapseContext.register({
|
||||||
|
nameSync: nameSync.value,
|
||||||
|
isOpen: isOpen.value,
|
||||||
|
instance: proxy,
|
||||||
|
uid: proxy.uid,
|
||||||
|
// 添加方法引用,使父组件可以直接操作
|
||||||
|
setOpen: (val) => {
|
||||||
|
isOpen.value = val
|
||||||
|
nextTick(() => init())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化动画高度
|
||||||
|
init()
|
||||||
|
|
||||||
|
// 检查是否应该默认展开
|
||||||
|
nextTick(() => {
|
||||||
|
if (collapseContext.getActiveNames) {
|
||||||
|
const activeNames = collapseContext.getActiveNames()
|
||||||
|
console.log('当前活动项:', activeNames, '本项:', nameSync.value)
|
||||||
|
if (activeNames.includes(nameSync.value.toString())) {
|
||||||
|
console.log('默认展开:', nameSync.value)
|
||||||
|
isOpen.value = true
|
||||||
|
// 更新高度
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件更新后重新初始化
|
||||||
|
onUpdated(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
init(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件卸载时从父组件移除
|
||||||
|
onUnmounted(() => {
|
||||||
|
uninstall()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.uni-collapse-item {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
&__title {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
transition: border-bottom-color 0.3s;
|
||||||
|
|
||||||
|
// transition-property: border-bottom-color;
|
||||||
|
// transition-duration: 5s;
|
||||||
|
&-wrap {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-box {
|
||||||
|
padding: 0 15px;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
&.is-disabled {
|
||||||
|
.uni-collapse-item__title-text {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.uni-collapse-item-border {
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
white-space: nowrap;
|
||||||
|
color: inherit;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
lines: 1;
|
||||||
|
/* #endif */
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrap {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
will-change: height;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
background-color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
&.is--transition {
|
||||||
|
// transition: all 0.3s;
|
||||||
|
transition-property: height, border-bottom-width;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
will-change: height;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #303133;
|
||||||
|
// transition: height 0.3s;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
|
||||||
|
&.uni-collapse-item--border {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-color: red;
|
||||||
|
border-bottom-color: #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--animation {
|
||||||
|
transition-property: transform;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -70,6 +70,8 @@ const { columns } = useTable()
|
||||||
|
|
||||||
.table-row .table-cell {
|
.table-row .table-cell {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding-left: 10rpx;
|
||||||
|
padding-right: 10rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-row:first-child {
|
.table-row:first-child {
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,22 @@
|
||||||
<wd-index-bar sticky class="overflow-y h-0 flex-auto">
|
<wd-index-bar sticky class="overflow-y h-0 flex-auto">
|
||||||
<view v-for="item in cities" :key="item.letter">
|
<view v-for="item in cities" :key="item.letter">
|
||||||
<wd-index-anchor :index="item.letter" />
|
<wd-index-anchor :index="item.letter" />
|
||||||
<wd-cell
|
<!-- <wd-cell
|
||||||
border
|
border
|
||||||
clickable
|
clickable
|
||||||
v-for="city in item.provinces"
|
v-for="city in item.provinces"
|
||||||
:key="city.id"
|
:key="city.id"
|
||||||
:title="city.provincename"
|
:title="city.provincename"
|
||||||
@click="chooseCity(city)"
|
@click="chooseCity(city)"
|
||||||
></wd-cell>
|
></wd-cell> -->
|
||||||
|
<view
|
||||||
|
v-for="city in item.provinces"
|
||||||
|
:key="city.id"
|
||||||
|
@click="chooseCity(city)"
|
||||||
|
class="px-[32rpx] py-[24rpx] province-item"
|
||||||
|
>
|
||||||
|
{{ city.provincename }}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</wd-index-bar>
|
</wd-index-bar>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -92,4 +100,12 @@ onLoad(() => {
|
||||||
height: calc(100vh - var(--window-top) - constant(safe-area-inset-bottom));
|
height: calc(100vh - var(--window-top) - constant(safe-area-inset-bottom));
|
||||||
height: calc(100vh - var(--window-top) - env(safe-area-inset-bottom));
|
height: calc(100vh - var(--window-top) - env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
.province-item {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.province-item:not(:last-child) {
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -47,17 +47,22 @@
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card-swiper mb-[32rpx]">
|
<view class="card-swiper mb-[32rpx] h-[126rpx]">
|
||||||
<wd-swiper
|
<swiper
|
||||||
:display-multiple-items="universityBaseInfo?.universityResult.imglist ? 3 : 0"
|
class="mx-[32rpx]"
|
||||||
custom-image-class="custom-image"
|
circular
|
||||||
custom-next-image-class="custom-image-prev"
|
:autoplay="true"
|
||||||
custom-prev-image-class="custom-image-prev"
|
|
||||||
:list="universityBaseInfo?.universityResult.imglist || []"
|
|
||||||
previousMargin="8rpx"
|
|
||||||
nextMargin="8rpx"
|
|
||||||
:indicator="false"
|
:indicator="false"
|
||||||
></wd-swiper>
|
:display-multiple-items="universityBaseInfo?.universityResult.imglist ? 3 : 0"
|
||||||
|
>
|
||||||
|
<swiper-item
|
||||||
|
v-for="item in universityBaseInfo?.universityResult.imglist"
|
||||||
|
:key="item"
|
||||||
|
class="flex justify-center"
|
||||||
|
>
|
||||||
|
<image :src="item" mode="scaleToFill" class="w-full h-full mx-[4rpx] rounded-[8rpx]" />
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<z-tabs
|
<z-tabs
|
||||||
|
|
@ -198,41 +203,4 @@ const handleStar = () => {
|
||||||
|
|
||||||
font-size: 24rpx !important;
|
font-size: 24rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.wd-button__icon) {
|
|
||||||
margin-right: 8rpx !important;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-swiper {
|
|
||||||
--wot-swiper-radius: 0;
|
|
||||||
--wot-swiper-item-padding: 0 8rpx;
|
|
||||||
--wot-swiper-nav-dot-color: #e7e7e7;
|
|
||||||
--wot-swiper-nav-dot-active-color: #4d80f0;
|
|
||||||
|
|
||||||
:deep(.custom-image) {
|
|
||||||
height: 126rpx !important;
|
|
||||||
border-radius: 8rpx 8rpx 8rpx 8rpx;
|
|
||||||
}
|
|
||||||
:deep(.custom-image-prev) {
|
|
||||||
height: 126rpx !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.wd-swiper__track) {
|
|
||||||
height: 126rpx !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.tabs-wrapper) {
|
|
||||||
--wot-tabs-nav-fs: 28rpx;
|
|
||||||
--wot-tabs-nav-height: 80rpx;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
:deep(.wd-tabs__line) {
|
|
||||||
bottom: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.wd-tabs__nav--wrap) {
|
|
||||||
border-bottom: 2rpx solid #f7f7f7;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<scroll-view class="h-full">
|
<scroll-view class="h-full">
|
||||||
<view class="font-semibold text-[#303030] text-[28rpx] mb-[32rpx] px-[32rpx]">
|
<view class="font-semibold text-[#303030] text-[28rpx] px-[32rpx]">
|
||||||
{{ subMajorList.name }}
|
{{ subMajorList.name }}
|
||||||
<text class="text-[22rpx] text-[#bfbfbf] font-normal">({{ subMajorList.count }}个)</text>
|
<text class="text-[22rpx] text-[#bfbfbf] font-normal">({{ subMajorList.count }}个)</text>
|
||||||
</view>
|
</view>
|
||||||
<wd-collapse v-model="value" accordion>
|
|
||||||
<wd-collapse-item
|
<Collapse v-model="collapseValue" accordion type="line">
|
||||||
:name="item.key"
|
<CollapseItem v-for="(item, index) in subMajorList.items" :key="item.key" :name="`${index}`">
|
||||||
custom-class="custom-collapse-item"
|
|
||||||
v-for="item in subMajorList.items"
|
|
||||||
:key="item.key"
|
|
||||||
>
|
|
||||||
<template #title="{ expanded }">
|
<template #title="{ expanded }">
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<text :class="`text-[24rpx] text-[#303030] ${expanded ? 'text-[#1580FF]!' : ''}`">
|
<text :class="`text-[24rpx] text-[#303030] ${expanded ? 'text-[#1580FF]!' : ''}`">
|
||||||
|
|
@ -30,9 +26,9 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<view :scroll-x="true" class="flex flex-col gap-[48rpx] h-full">
|
<view class="flex flex-col gap-[48rpx] h-full mb-[40rpx]">
|
||||||
<view
|
<view
|
||||||
class="text-[24rpx] text-[#303030] ml-[26rpx]"
|
class="text-[24rpx] text-[#303030] ml-[32rpx]"
|
||||||
v-for="childMajor in item.childMajors"
|
v-for="childMajor in item.childMajors"
|
||||||
:key="childMajor.zymc"
|
:key="childMajor.zymc"
|
||||||
@click="navigatorToMajorInfo(childMajor)"
|
@click="navigatorToMajorInfo(childMajor)"
|
||||||
|
|
@ -40,12 +36,15 @@
|
||||||
{{ childMajor.zymc }}
|
{{ childMajor.zymc }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</wd-collapse-item>
|
</CollapseItem>
|
||||||
</wd-collapse>
|
</Collapse>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Collapse from '@/pages-sub/components/collapse/Collapse.vue'
|
||||||
|
import CollapseItem from '@/pages-sub/components/collapse/CollapseItem.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
subMajorList: {
|
subMajorList: {
|
||||||
type: Object as () => {
|
type: Object as () => {
|
||||||
|
|
@ -65,17 +64,7 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const value = ref<string>('')
|
const collapseValue = ref<string>('0')
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.subMajorList,
|
|
||||||
(newV) => {
|
|
||||||
if (newV?.items?.length > 0) {
|
|
||||||
value.value = newV.items[0].key
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
const navigatorToMajorInfo = (item: unknown) => {
|
const navigatorToMajorInfo = (item: unknown) => {
|
||||||
const _item = item as { specId: string }
|
const _item = item as { specId: string }
|
||||||
|
|
@ -94,5 +83,10 @@ const navigatorToMajorInfo = (item: unknown) => {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.uni-collapse-item__wrap-content) {
|
||||||
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@
|
||||||
<image
|
<image
|
||||||
src="@/pages-sub/static/images/ucenter/vip-flag.png"
|
src="@/pages-sub/static/images/ucenter/vip-flag.png"
|
||||||
mode="scaleToFill"
|
mode="scaleToFill"
|
||||||
v-if="userStore.userInfo?.estimatedAchievement.isVIP"
|
class="w-[120rpx] h-[28rpx]"
|
||||||
|
v-if="isVIP"
|
||||||
/>
|
/>
|
||||||
<image
|
<image
|
||||||
v-else
|
v-else
|
||||||
|
|
@ -67,12 +68,14 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<button class="submit-button" @click="handleActive">立即兑换</button>
|
<button class="submit-button" @click="handleActive" :disabled="isVIP">
|
||||||
|
{{ isVIP ? '已开通' : '立即兑换' }}
|
||||||
|
</button>
|
||||||
<view
|
<view
|
||||||
class="text-[#999] text-[24rpx] text-center mt-[20rpx] mb-[30rpx] text-center"
|
class="text-[#999] text-[24rpx] text-center mt-[20rpx] mb-[30rpx] text-center"
|
||||||
v-show="userStore.userInfo.estimatedAchievement.isVIP"
|
v-show="isVIP"
|
||||||
>
|
>
|
||||||
到期时间:2025-08-31
|
到期时间:{{ endTime }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="custom-background h-[780rpx] w-full absolute top-0 left-0 z-[-1]"></view>
|
<view class="custom-background h-[780rpx] w-full absolute top-0 left-0 z-[-1]"></view>
|
||||||
|
|
@ -121,7 +124,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Navbar from '@/pages-sub/components/navbar/Navbar.vue'
|
import Navbar from '@/pages-sub/components/navbar/Navbar.vue'
|
||||||
import MessageBox from '@/pages-sub/components/messageBox/MessageBox.vue'
|
import MessageBox from '@/pages-sub/components/messageBox/MessageBox.vue'
|
||||||
import { activeCard } from '@/service/index/api'
|
import { activeCard, getBingInfo } from '@/service/index/api'
|
||||||
|
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
|
|
||||||
|
|
@ -130,6 +133,10 @@ const userStore = useUserStore()
|
||||||
const cardNumber = ref('')
|
const cardNumber = ref('')
|
||||||
const cardPassword = ref('')
|
const cardPassword = ref('')
|
||||||
|
|
||||||
|
const isVIP = computed(() => {
|
||||||
|
return userStore.userInfo.estimatedAchievement.isVIP
|
||||||
|
})
|
||||||
|
|
||||||
const navigatorBack = () => {
|
const navigatorBack = () => {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +154,8 @@ const handleActive = () => {
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
showMessageBox.value = true
|
showMessageBox.value = true
|
||||||
|
userStore.setIsVIP(true)
|
||||||
|
userStore.setVipCode(cardNumber.value)
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.message,
|
title: res.message,
|
||||||
|
|
@ -155,6 +164,17 @@ const handleActive = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const endTime = ref('')
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
getBingInfo().then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
endTime.value = (res.result as { endTime: string }).endTime
|
||||||
|
cardNumber.value = (res.result as { cardCode: string }).cardCode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
"path": "pages/evaluation/index/index",
|
"path": "pages/evaluation/index/index",
|
||||||
"type": "page",
|
"type": "page",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "测评师"
|
"navigationBarTitleText": "测评"
|
||||||
},
|
},
|
||||||
"needLogin": true
|
"needLogin": true
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<view class="item-wrapper relative mt-[32rpx]">
|
||||||
|
<view
|
||||||
|
class="flag text-[22rpx] text-center absolute top-0 right-0"
|
||||||
|
:class="{ free: item.isFree }"
|
||||||
|
>
|
||||||
|
{{ isFree ? '免费' : 'VIP' }}
|
||||||
|
</view>
|
||||||
|
<view class="flex items-stretch gap-[30rpx]">
|
||||||
|
<view class="flex flex-col gap-[29rpx] mt-[16rpx]">
|
||||||
|
<view class="text-[#333] text-[40rpx] font-semibold">{{ item.name }}</view>
|
||||||
|
<view class="text-[24rpx] text-[#999]">
|
||||||
|
{{ item.summary }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<image
|
||||||
|
:src="item.minImg"
|
||||||
|
mode="scaleToFill"
|
||||||
|
class="w-[240rpx] h-[240rpx] min-w-[240rpx] min-h-[240rpx]"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps({
|
||||||
|
isFree: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
onLoad(() => {})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.item-wrapper {
|
||||||
|
background: linear-gradient(270deg, #e5f2fa 0%, #dae8fa 100%);
|
||||||
|
border-radius: 18rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
width: 64rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
|
||||||
|
border-radius: 0rpx 16rpx 0rpx 16rpx;
|
||||||
|
}
|
||||||
|
.free {
|
||||||
|
background: linear-gradient(51deg, #00c3fc 0%, #00acf7 100%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip {
|
||||||
|
background: linear-gradient(229deg, #fab55e 0%, #ffeec3 100%);
|
||||||
|
color: #663800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,17 +1,72 @@
|
||||||
<route lang="json5" type="page">
|
<route lang="json5" type="page">
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
navigationBarTitleText: '测评师',
|
navigationBarTitleText: '测评',
|
||||||
},
|
},
|
||||||
needLogin: true,
|
needLogin: true,
|
||||||
}
|
}
|
||||||
</route>
|
</route>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="">测评</view>
|
<view class="flex flex-col h-screen">
|
||||||
<TabBar :current-page="3"></TabBar>
|
<Tabs v-model:modelValue="currentIndex" :tabs="tabs" @change="handleChange"></Tabs>
|
||||||
|
<swiper class="flex-1" :current="currentIndex">
|
||||||
|
<swiper-item>
|
||||||
|
<view class="mx-[32rpx] overflow-auto h-full">
|
||||||
|
<EvaluationItem v-for="item in academicList" :key="item.id" :item="item"></EvaluationItem>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
<swiper-item>
|
||||||
|
<view class="mx-[32rpx] overflow-auto h-full">
|
||||||
|
<EvaluationItem v-for="item in healthList" :key="item.id" :item="item"></EvaluationItem>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
<swiper-item>
|
||||||
|
<view class="mx-[32rpx] overflow-auto h-full">
|
||||||
|
<EvaluationItem v-for="item in learningList" :key="item.id" :item="item"></EvaluationItem>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<TabBar :current-page="3"></TabBar>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import TabBar from '@/components/bar/TabBar.vue'
|
import TabBar from '@/components/bar/TabBar.vue'
|
||||||
|
import Tabs from '@/components/tab/Tabs.vue'
|
||||||
|
import EvaluationItem from '../components/EvaluationItem.vue'
|
||||||
|
import { getEvaluationList } from '@/service/index/api'
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: '学职定位',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '心理健康',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '学习状态',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const currentIndex = ref(0)
|
||||||
|
|
||||||
|
const handleChange = (e: any) => {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const academicList = ref([])
|
||||||
|
const healthList = ref([])
|
||||||
|
const learningList = ref([])
|
||||||
|
onLoad(() => {
|
||||||
|
getEvaluationList({ menuid: '340509778657349' }).then((res) => {
|
||||||
|
academicList.value = res.result as any[]
|
||||||
|
})
|
||||||
|
getEvaluationList({ menuid: '339908117110853' }).then((res) => {
|
||||||
|
healthList.value = res.result as any[]
|
||||||
|
})
|
||||||
|
getEvaluationList({ menuid: '340509778686021' }).then((res) => {
|
||||||
|
learningList.value = res.result as any[]
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,10 @@ export const activeCard = (params: { cardCode: string; cardPwd: string }) => {
|
||||||
return http.post('/api/Zyvip/bind', params)
|
return http.post('/api/Zyvip/bind', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getBingInfo = () => {
|
||||||
|
return http.get('/api/Zyvip/bingInfo')
|
||||||
|
}
|
||||||
|
|
||||||
export const getAdmissionTrends = (params: {
|
export const getAdmissionTrends = (params: {
|
||||||
ascription?: string[] | null
|
ascription?: string[] | null
|
||||||
/**
|
/**
|
||||||
|
|
@ -362,3 +366,7 @@ export const getAdmissionTrendsByCollege = (params: {
|
||||||
}) => {
|
}) => {
|
||||||
return http.get('/api/admissionstreds/major', params)
|
return http.get('/api/admissionstreds/major', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getEvaluationList = (params: { menuid: string }) => {
|
||||||
|
return http.get('/api/busScale/list', params)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,14 @@ export const useUserStore = defineStore(
|
||||||
userInfo.value.wishList = []
|
userInfo.value.wishList = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setIsVIP = (val: boolean) => {
|
||||||
|
userInfo.value.estimatedAchievement.isVIP = val
|
||||||
|
}
|
||||||
|
|
||||||
|
const setVipCode = (val: string) => {
|
||||||
|
userInfo.value.estimatedAchievement.vipCode = val
|
||||||
|
}
|
||||||
|
|
||||||
// 清除用户信息
|
// 清除用户信息
|
||||||
const clearUserInfo = () => {
|
const clearUserInfo = () => {
|
||||||
userInfo.value = { ...initState }
|
userInfo.value = { ...initState }
|
||||||
|
|
@ -205,6 +213,8 @@ export const useUserStore = defineStore(
|
||||||
deleteWishListMajor,
|
deleteWishListMajor,
|
||||||
sortWishMajorList,
|
sortWishMajorList,
|
||||||
sortWishCollegeList,
|
sortWishCollegeList,
|
||||||
|
setIsVIP,
|
||||||
|
setVipCode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue