| @ -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> | |||