@ -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> | |||
<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" /> | |||
</view> | |||
</template> | |||
<script> | |||
import mixinsList from '@/mixins/list.js' | |||
import tabber from '@/components/base/tabbar.vue' | |||
import suspendDropdown from '@/components/base/suspendDropdown.vue' | |||
export default { | |||
mixins: [mixinsList], | |||
components: { | |||
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> | |||
<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> |
@ -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> |