@ -0,0 +1,41 @@ | |||||
// 案例相关接口 | |||||
const api = { | |||||
/** | |||||
* 获取服务分类列表 | |||||
*/ | |||||
queryCategoryServiceList: { | |||||
url: '/config/queryCategoryServiceList', | |||||
method: 'GET', | |||||
}, | |||||
/** | |||||
* 获取专业分类列表 | |||||
*/ | |||||
queryCategoryMajorList: { | |||||
url: '/config/queryCategoryMajorList', | |||||
method: 'GET', | |||||
}, | |||||
/** | |||||
* 获取阶段分类列表 | |||||
*/ | |||||
queryCategoryPeriodList: { | |||||
url: '/config/queryCategoryPeriodList', | |||||
method: 'GET', | |||||
}, | |||||
/** | |||||
* 获取案例文章列表 | |||||
*/ | |||||
queryAriticleList: { | |||||
url: '/index/queryAriticleList', | |||||
method: 'GET', | |||||
}, | |||||
/** | |||||
* 获取案例文章详情 | |||||
*/ | |||||
queryAriticleById: { | |||||
url: '/index/queryAriticleById', | |||||
method: 'GET', | |||||
}, | |||||
} | |||||
export default api |
@ -0,0 +1,43 @@ | |||||
<template> | |||||
<view class="arrow"> | |||||
<view class="arrow-blank arrow-blank-top"></view> | |||||
<view class="arrow-blank arrow-blank-bottom"></view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.arrow { | |||||
position: relative; | |||||
width: 28rpx; | |||||
height: 32rpx; | |||||
background-image: linear-gradient(to right, #A3BCEF, #4883F9); | |||||
&-blank { | |||||
position: absolute; | |||||
left: 0; | |||||
&-top { | |||||
top: 0; | |||||
border-top: 8rpx solid #FFFFFF; | |||||
border-right: 14rpx solid #FFFFFF; | |||||
border-bottom: 8rpx solid transparent; | |||||
border-left: 14rpx solid transparent; | |||||
} | |||||
&-bottom { | |||||
bottom: 0; | |||||
border-top: 8rpx solid transparent; | |||||
border-right: 14rpx solid #FFFFFF; | |||||
border-bottom: 8rpx solid #FFFFFF; | |||||
border-left: 14rpx solid transparent; | |||||
} | |||||
} | |||||
} | |||||
</style> |
@ -0,0 +1,158 @@ | |||||
<template> | |||||
<view class="drop-down"> | |||||
<button class="btn" plain @click="openPicker" > | |||||
<view class="flex btn-label"> | |||||
<view :style="{ color }">{{ label }}</view> | |||||
<uv-icon name="arrow-down-fill" :color="color" size="14rpx"></uv-icon> | |||||
</view> | |||||
</button> | |||||
<template v-if="popupVisible"> | |||||
<view class="drop-down-overlay" @click="closePicker"></view> | |||||
<view class="card drop-down-popup" :style="popupStyle"> | |||||
<view | |||||
v-for="item in options" | |||||
class="flex drop-down-option" | |||||
:class="[item.value === value ? 'is-active' : '']" | |||||
:key="item.value" | |||||
@click="onSelect(item.value)" | |||||
> | |||||
<text>{{ item.label }}</text> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
props: { | |||||
value: { | |||||
type: String | Number, | |||||
default: null, | |||||
}, | |||||
options: { | |||||
type: Array, | |||||
default() { | |||||
return [] | |||||
} | |||||
}, | |||||
label: { | |||||
type: String, | |||||
default: '' | |||||
}, | |||||
}, | |||||
data() { | |||||
return { | |||||
popupVisible: false, | |||||
popupStyle: {}, | |||||
} | |||||
}, | |||||
computed: { | |||||
isActive() { | |||||
return this.options.find(option => option.value === this.value) | |||||
}, | |||||
color() { | |||||
return this.isActive ? '#4883F9' : '#000000' | |||||
} | |||||
}, | |||||
methods: { | |||||
openPicker() { | |||||
this.popupVisible = true | |||||
return | |||||
uni.createSelectorQuery().in(this) | |||||
.select('.btn') | |||||
.fields({ | |||||
node: true, | |||||
size: true, | |||||
rect: true, | |||||
}) | |||||
.exec(async (res) => { | |||||
console.log('res', res) | |||||
const { | |||||
top, | |||||
left, | |||||
} = res[0] | |||||
console.log('top', top, 'left', left) | |||||
// this.popupStyle = { | |||||
// top: `${parseInt(top)}px`, | |||||
// left: `${parseInt(left)}px`, | |||||
// } | |||||
console.log('popupStyle', this.popupStyle) | |||||
this.popupVisible = true | |||||
}) | |||||
}, | |||||
closePicker() { | |||||
this.popupVisible = false | |||||
}, | |||||
onSelect(val) { | |||||
const selected = this.value | |||||
const newVal = selected === val ? null : val | |||||
this.$emit('input', newVal) | |||||
this.$emit('change', newVal) | |||||
}, | |||||
}, | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.drop-down { | |||||
position: relative; | |||||
clear: both; | |||||
.btn { | |||||
width: 213rpx; | |||||
padding: 27rpx 0; | |||||
font-size: 24rpx; | |||||
background: transparent; | |||||
border: none; | |||||
&-label { | |||||
column-gap: 9rpx; | |||||
} | |||||
} | |||||
&-overlay { | |||||
position: fixed; | |||||
width: 100vw; | |||||
height: calc(100vh - #{$navbar-height} - var(--status-bar-height) - 20rpx); | |||||
background: transparent; | |||||
bottom: 0; | |||||
left: 0; | |||||
font-size: 0; | |||||
} | |||||
&-popup { | |||||
position: absolute; | |||||
z-index: 99; | |||||
width: 213rpx; | |||||
background: #FFFFFF; | |||||
border-radius: 15rpx; | |||||
transform: translateY(-15rpx); | |||||
} | |||||
&-option { | |||||
color: #000000; | |||||
font-size: 24rpx; | |||||
padding: 7rpx 0; | |||||
text-align: center; | |||||
&.is-active { | |||||
color: #4883F9; | |||||
} | |||||
& + & { | |||||
border-top: 1rpx solid #EFEFEF; | |||||
} | |||||
} | |||||
} | |||||
</style> |
@ -1,20 +1,218 @@ | |||||
<template> | <template> | ||||
<view class="page__view"> | <view class="page__view"> | ||||
<view class="bg"></view> | |||||
<view class="main"> | |||||
<!-- 搜索栏 --> | |||||
<view class="search"> | |||||
<uv-search v-model="keyword" placeholder="输入关键词搜索" bgColor="#FBFBFB" @custom="search" @search="search"> | |||||
<template #prefix> | |||||
<image class="search-icon" src="@/static/image/icon-search.png" mode="widthFix"></image> | |||||
</template> | |||||
</uv-search> | |||||
</view> | |||||
<!-- 轮播图 --> | |||||
<view class="swiper"> | |||||
<uv-swiper :list="bannerList" keyName="image" indicator indicatorMode="dot" indicatorActiveColor="#4883F9" indicatorInactiveColor="#FFFFFF" height="239rpx"></uv-swiper> | |||||
</view> | |||||
<view class="flex filter"> | |||||
<view class="filter-item"> | |||||
<suspendDropdown v-model="queryParams.categoryServiceId" label="服务分类筛选" :options="serviceOptions" @change="onFilterChange"></suspendDropdown> | |||||
</view> | |||||
<view class="filter-item"> | |||||
<suspendDropdown v-model="queryParams.categoryMajorId" label="专业筛选" :options="majorOptions" @change="onFilterChange"></suspendDropdown> | |||||
</view> | |||||
<view class="filter-item"> | |||||
<suspendDropdown v-model="queryParams.categoryPeriodId" label="阶段筛选" :options="periodOptions" @change="onFilterChange"></suspendDropdown> | |||||
</view> | |||||
</view> | |||||
<view class="list"> | |||||
<view class="list-item" v-for="item in list" :key="item.id" @click="jumpToDetail(item.thesisId)"> | |||||
<image class="img" :src="item.image" mode="aspectFill"></image> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
<tabber select="case" /> | <tabber select="case" /> | ||||
</view> | </view> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import mixinsList from '@/mixins/list.js' | |||||
import tabber from '@/components/base/tabbar.vue' | import tabber from '@/components/base/tabbar.vue' | ||||
import suspendDropdown from '@/components/base/suspendDropdown.vue' | |||||
export default { | export default { | ||||
mixins: [mixinsList], | |||||
components: { | components: { | ||||
tabber, | tabber, | ||||
suspendDropdown, | |||||
}, | |||||
data() { | |||||
return { | |||||
keyword: '', | |||||
bannerList: [], | |||||
serviceOptions: [], | |||||
majorOptions: [], | |||||
periodOptions: [], | |||||
list: [], | |||||
queryParams: { | |||||
pageNo: 1, | |||||
pageSize: 10, | |||||
title: null, | |||||
categoryServiceId: null, | |||||
categoryMajorId: null, | |||||
categoryPeriodId: null, | |||||
}, | |||||
mixinsListApi: 'queryAriticleList', | |||||
} | |||||
}, | |||||
onLoad() { | |||||
this.fetchBanner() | |||||
this.fetchOptions() | |||||
this.getData() | |||||
}, | }, | ||||
methods: { | |||||
// todo: delete | |||||
getData() { | |||||
this.list = [ | |||||
{ id: '001', image: '/static/image/temp-1.png' }, | |||||
{ id: '001', image: '/static/image/temp-2.png' }, | |||||
{ id: '001', image: '/static/image/temp-3.png' }, | |||||
] | |||||
this.total = this.list.length | |||||
}, | |||||
search() { | |||||
this.queryParams.pageNo = 1 | |||||
this.queryParams.pageSize = 10 | |||||
this.queryParams.title = this.keyword | |||||
this.getData() | |||||
}, | |||||
onFilterChange() { | |||||
this.queryParams.pageNo = 1 | |||||
this.queryParams.pageSize = 10 | |||||
this.getData() | |||||
}, | |||||
// 获取轮播图 | |||||
async fetchBanner() { | |||||
try { | |||||
this.bannerList = (await this.$fetch('queryBannerList', { type: '1' }))?.records // type:0-首页 1-案例 2-服务 3-其他 | |||||
} catch (err) { | |||||
} | |||||
}, | |||||
async fetchOptions() { | |||||
this.$fetch('queryCategoryServiceList').then(res => { | |||||
this.serviceOptions = res?.records?.map(item => ({ label: item.title, value: item.id })) || [] | |||||
}).catch(err => { | |||||
}) | |||||
this.$fetch('queryCategoryMajorList').then(res => { | |||||
this.majorOptions = res?.records?.map(item => ({ label: item.title, value: item.id })) || [] | |||||
}).catch(err => { | |||||
}) | |||||
this.$fetch('queryCategoryPeriodList').then(res => { | |||||
this.periodOptions = res?.records?.map(item => ({ label: item.title, value: item.id })) || [] | |||||
}).catch(err => { | |||||
}) | |||||
}, | |||||
jumpToDetail(articleId) { | |||||
uni.navigateTo({ | |||||
url: `/pages_order/case/index?articleId=${articleId}` | |||||
}) | |||||
}, | |||||
} | |||||
} | } | ||||
</script> | </script> | ||||
<style scoped lang="scss"> | <style scoped lang="scss"> | ||||
.bg { | |||||
width: 100%; | |||||
height: 438rpx; | |||||
background-image: linear-gradient(#4883F9, #4883F9, #4883F9, #FCFDFF); | |||||
} | |||||
.main { | |||||
position: absolute; | |||||
top: 0; | |||||
left: 0; | |||||
width: 100%; | |||||
padding: 192rpx 0 182rpx 0; | |||||
} | |||||
.search { | |||||
margin: 0 18rpx; | |||||
width: calc(100% - 20rpx * 2); | |||||
background-color: #FFFFFF; | |||||
border-radius: 37rpx; | |||||
padding: 13rpx 0 13rpx 18rpx; | |||||
box-sizing: border-box; | |||||
display: flex; | |||||
align-items: center; | |||||
/deep/ .uv-search__action { | |||||
color: $uni-color; | |||||
padding: 10rpx 18rpx; | |||||
} | |||||
&-icon { | |||||
width: 26rpx; | |||||
height: auto; | |||||
} | |||||
} | |||||
.swiper { | |||||
margin: 29rpx 18rpx 0 18rpx; | |||||
border-radius: 25rpx 25rpx 0 0; | |||||
overflow: hidden; | |||||
/deep/ .uv-swiper-indicator__wrapper__dot { | |||||
width: 15rpx; | |||||
height: 15rpx; | |||||
} | |||||
/deep/ .uv-swiper-indicator__wrapper__dot--active { | |||||
width: 15rpx; | |||||
} | |||||
} | |||||
.filter { | |||||
column-gap: 33rpx; | |||||
padding: 0 23rpx; | |||||
background: #FFFFFF; | |||||
&-item { | |||||
flex: 1; | |||||
} | |||||
} | |||||
.list { | |||||
padding: 34rpx 37rpx; | |||||
&-item { | |||||
width: 100%; | |||||
height: 284rpx; | |||||
border-radius: 15rpx; | |||||
overflow: hidden; | |||||
& + & { | |||||
margin-top: 25rpx; | |||||
} | |||||
.img { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
} | |||||
} | |||||
</style> | </style> |
@ -0,0 +1,160 @@ | |||||
<template> | |||||
<view class="page__view"> | |||||
<!-- 导航栏 --> | |||||
<navbar :title="title" leftClick @leftClick="$utils.navigateBack" bgColor="#4883F9" color="#FFFFFF" /> | |||||
<!-- 轮播图 --> | |||||
<view class="swiper"> | |||||
<uv-swiper :list="bannerList" keyName="image" :autoplay="bannerList.length" :indicator="bannerList.length" indicatorMode="dot" indicatorActiveColor="#4883F9" indicatorInactiveColor="#FFFFFF" height="424rpx"></uv-swiper> | |||||
</view> | |||||
<!-- 学生情况 --> | |||||
<view class="section"> | |||||
<view class="section-header"> | |||||
<arrow></arrow> | |||||
<view>学生情况</view> | |||||
</view> | |||||
<view class="section-content"> | |||||
<!-- todo: check key --> | |||||
<uv-parse :content="detail.content"></uv-parse> | |||||
</view> | |||||
</view> | |||||
<!-- 服务项目 --> | |||||
<view class="section"> | |||||
<view class="section-header"> | |||||
<arrow></arrow> | |||||
<view>服务项目</view> | |||||
</view> | |||||
<view class="section-content"> | |||||
<!-- todo: check key --> | |||||
<uv-parse :content="detail.content"></uv-parse> | |||||
</view> | |||||
</view> | |||||
<!-- 服务过程 --> | |||||
<view class="section"> | |||||
<view class="section-header"> | |||||
<arrow></arrow> | |||||
<view>服务过程</view> | |||||
</view> | |||||
<view class="section-content"> | |||||
<!-- todo: check key --> | |||||
<uv-parse :content="detail.content"></uv-parse> | |||||
</view> | |||||
</view> | |||||
<!-- 服务结果 --> | |||||
<view class="section"> | |||||
<view class="section-header"> | |||||
<arrow></arrow> | |||||
<view>服务结果</view> | |||||
</view> | |||||
<view class="section-content"> | |||||
<!-- todo: check key --> | |||||
<uv-parse :content="detail.content"></uv-parse> | |||||
</view> | |||||
</view> | |||||
<!-- 服务感受 --> | |||||
<view class="section"> | |||||
<view class="section-header"> | |||||
<arrow></arrow> | |||||
<view>服务感受</view> | |||||
</view> | |||||
<view class="section-content"> | |||||
<!-- todo: check key --> | |||||
<uv-parse :content="detail.content"></uv-parse> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
import arrow from '@/components/base/arrow.vue' | |||||
export default { | |||||
components: { | |||||
arrow, | |||||
}, | |||||
data() { | |||||
return { | |||||
title: '', | |||||
bannerList: [], | |||||
detail: {}, | |||||
} | |||||
}, | |||||
onLoad({ articleId }) { | |||||
this.getData(articleId) | |||||
}, | |||||
methods: { | |||||
async getData(articleId) { | |||||
try { | |||||
// todo | |||||
await this.$fetch('queryAriticleById', { articleId }) | |||||
} catch (err) { | |||||
} | |||||
}, | |||||
}, | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.page__view { | |||||
box-sizing: border-box; | |||||
padding-bottom: 36rpx; | |||||
background: #FFFFFF; | |||||
} | |||||
.swiper { | |||||
margin: 20rpx 18rpx 47rpx 18rpx; | |||||
/deep/ .uv-swiper-indicator__wrapper__dot { | |||||
width: 15rpx; | |||||
height: 15rpx; | |||||
} | |||||
/deep/ .uv-swiper-indicator__wrapper__dot--active { | |||||
width: 15rpx; | |||||
} | |||||
} | |||||
.section { | |||||
width: 100%; | |||||
padding: 0 18rpx; | |||||
box-sizing: border-box; | |||||
& + & { | |||||
margin-top: 40rpx; | |||||
} | |||||
&-header { | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: flex-start; | |||||
column-gap: 5rpx; | |||||
padding-left: 11rpx; | |||||
font-size: 32rpx; | |||||
font-weight: 700; | |||||
color: #000000; | |||||
} | |||||
&-content { | |||||
margin-top: 24rpx; | |||||
padding: 24rpx; | |||||
box-sizing: border-box; | |||||
white-space: pre-line; | |||||
font-size: 28rpx; | |||||
color: #000000; | |||||
background: #F8F8F8; | |||||
border-radius: 15rpx; | |||||
} | |||||
} | |||||
</style> |