This commit is contained in:
hr121 2025-08-07 04:48:33 +08:00
commit 5033f7320f
6 changed files with 751 additions and 10 deletions

View File

@ -73,7 +73,8 @@ export default {
{
name: '路线',
icon: 'route.png',
select: 'route-select.png'
select: 'route-select.png',
navTitle: '路线'
},
{
name: '我的',

View File

@ -376,7 +376,7 @@ export default {
else this.$refs.uToast.show({type: 'error',title: "订单详情获取失败!"});
let resp = await this.$api.allRoutesByScenicId(res.attractionId);
if(resp){
this.routes = resp || [];
this.routes = resp.filter(item=>this.orderDetail.routeIds.includes(item.key)) || [];
} else {
this.$refs.uToast.show({type: 'error',title: "景区路线获取失败!"});
}

View File

@ -0,0 +1,352 @@
<template>
<view class="search-combox" :class="border ? '' : 'search-combox__no-border'">
<view v-if="label" class="search-combox__label" :style="labelStyle">
<text>{{label}}</text>
</view>
<view class="search-combox__input-box">
<input class="search-combox__input" type="text" :placeholder="placeholder"
placeholder-class="search-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus"
@blur="onBlur" />
<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
</uni-icons>
</view>
<view class="search-combox__selector" v-if="showSelector">
<view class="uni-popper__arrow"></view>
<scroll-view scroll-y="true" class="search-combox__selector-scroll">
<view class="search-combox__selector-empty" v-if="filterCandidatesLength === 0">
<text>{{emptyTips}}</text>
</view>
<view class="search-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
@click="onSelectorClick(index)">
<text
:style="(isJSON?item[keyName]?item[keyName]==inputVal:false:item==inputVal)?'font-weight: bold;background-color: '+selectedBackground+';color: '+selectedColor:''">{{isJSON?item[keyName]?item[keyName]:'字段'+keyName+'不存在':item}}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
/**
* Combox 组合输入框
* @description 组合输入框一般用于既可以输入也可以选择的场景
* @property {String} label 左侧文字
* @property {String} labelWidth 左侧内容宽度
* @property {String} placeholder 输入框占位符
* @property {Array} candidates 候选项列表
* @property {String} emptyTips 筛选结果为空时显示的文字
* @property {String} value 组合框的值
* @property {String} selectedBackground 选中项背景颜色
* @property {String} selectedColor 选中项文字颜色
* @property {Boolean} isJSON 是否是json数组
* @property {String} keyName json数组显示的字段值
*/
export default {
name: 'searchCombox',
emits: ['input', 'update:modelValue', 'select'],
props: {
isJSON: {
type: Boolean,
default: false
},
keyName: {
type: String,
default: ''
},
selectedBackground: {
type: String,
default: '#e5e5e5'
},
selectedColor: {
type: String,
default: '#409eff'
},
border: {
type: Boolean,
default: true
},
label: {
type: String,
default: ''
},
labelWidth: {
type: String,
default: 'auto'
},
placeholder: {
type: String,
default: ''
},
candidates: {
type: Array,
default () {
return []
}
},
emptyTips: {
type: String,
default: '无匹配项'
},
// #ifndef VUE3
value: {
type: [String, Number],
default: ''
},
// #endif
// #ifdef VUE3
modelValue: {
type: [String, Number],
default: ''
},
// #endif
},
data() {
return {
isInput: false,
showSelector: false,
inputVal: ''
}
},
computed: {
labelStyle() {
if (this.labelWidth === 'auto') {
return ""
}
return `width: ${this.labelWidth}`
},
filterCandidates() {
if (this.isInput) {
if (this.isJSON) {
return this.candidates.filter((item) => {
return item[this.keyName].toString().indexOf(this.inputVal) > -1
})
} else {
return this.candidates.filter((item) => {
return item.toString().indexOf(this.inputVal) > -1
})
}
} else {
return this.candidates
}
},
filterCandidatesLength() {
return this.filterCandidates.length
}
},
watch: {
// #ifndef VUE3
value: {
handler(newVal) {
this.inputVal = newVal
this.isInput = true
},
immediate: true
},
// #endif
// #ifdef VUE3
modelValue: {
handler(newVal) {
this.inputVal = newVal
this.isInput = true
},
immediate: true
},
// #endif
},
methods: {
toggleSelector() {
this.showSelector = !this.showSelector
this.isInput = false
},
onFocus() {
this.showSelector = true
this.isInput = false
},
onBlur() {
setTimeout(() => {
this.showSelector = false
this.isInput = false
}, 153)
},
onSelectorClick(index) {
let item = this.filterCandidates[index]
if (this.isJSON) {
this.inputVal = item[this.keyName]
} else {
this.inputVal = item
}
this.showSelector = false
this.$emit('input', this.inputVal)
this.$emit('update:modelValue', this.inputVal)
this.$emit('select', item)
},
onInput() {
setTimeout(() => {
this.$emit('input', this.inputVal)
this.$emit('update:modelValue', this.inputVal)
})
}
}
}
</script>
<style lang="scss" scoped>
@media only screen and (max-width: 999px) {
/* 针对手机: */
.search-combox {
font-size: 14px;
border: 0px solid #e5e5e5;
border-radius: 4px;
padding: 0px 10px;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
// height: 40px;
flex-direction: row;
align-items: center;
// border-bottom: solid 1px #DDDDDD;
}
}
@media only screen and (min-width: 1000px) {
/* 针对手机: */
.search-combox {
font-size: 14px;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 0px 10px;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
// height: 40px;
flex-direction: row;
align-items: center;
// border-bottom: solid 1px #DDDDDD;
}
}
.search-combox__label {
font-size: 16px;
line-height: 22px;
padding-right: 10px;
color: #999999;
}
.search-combox__input-box {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
}
.search-combox__input {
flex: 1;
font-size: 14px;
height: 22px;
line-height: 22px;
}
.search-combox__input-plac {
font-size: 14px;
color: #999; //placeholder-style="color:#FFFFFF"
}
.search-combox__selector {
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
position: absolute;
top: calc(100% + 12px);
left: 0;
width: 100%;
background-color: #FFFFFF;
border: 1px solid #EBEEF5;
border-radius: 6px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
// z-index: 2;
padding: 4px 0;
height: 190px;
z-index: 999;
}
.search-combox__selector-scroll {
/* #ifndef APP-NVUE */
max-height: 180px;
box-sizing: border-box;
/* #endif */
}
.search-combox__selector-empty,
.search-combox__selector-item {
/* #ifndef APP-NVUE */
display: flex;
cursor: pointer;
/* #endif */
line-height: 36px;
font-size: 14px;
// text-align: center;
text-indent: 1rem;
// border-bottom: solid 1px #DDDDDD;
padding: 0px 0px;
}
.search-combox__selector-empty text,
.search-combox__selector-item text {
width: 100%;
}
.search-combox__selector-item:hover {
background-color: #e5e5e5;
}
.search-combox__selector-empty:last-child,
.search-combox__selector-item:last-child {
/* #ifndef APP-NVUE */
border-bottom: none;
/* #endif */
}
// picker
.uni-popper__arrow,
.uni-popper__arrow::after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 6px;
}
.uni-popper__arrow {
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
top: -6px;
left: 80%;
margin-right: 3px;
border-top-width: 0;
border-bottom-color: #EBEEF5;
}
.uni-popper__arrow::after {
content: " ";
top: 1px;
margin-left: -6px;
border-top-width: 0;
border-bottom-color: #fff;
}
.search-combox__no-border {
border: none;
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<view class="route-detail" :style="{backgroundImage: `url(${fileUrl+bgIcon})`}">
<Topnav :topLevel="topLevel" title="路线详情" defaultBackColor="#333333"
defaultNavTextColor="#333333" showBack />
<view :style="{height: CustomBar+'px'}" />
<u-section :title="form.name" :right="false" font-size="40"
color="#111827" line-color="#FBBF24" class="route-detail-title" />
<view class="route-detail-content">
<view class="rd-aim">
<u-icon style="margin-right: 8rpx;" name="map" size="30" color="#F59E0B" />
起飞点
</view>
<view class="rd-value">
经纬度<text>{{ form.startPoint || '暂无' }}</text>
</view>
</view>
<view class="route-detail-content">
<view class="rd-aim">
<u-icon style="margin-right: 8rpx;" name="map" size="30" color="#F59E0B" />
降落点
</view>
<view class="rd-value">
经纬度<text>{{ form.endPoint || '暂无' }}</text>
</view>
</view>
<view class="route-detail-content">
<view class="rd-aim">备注信息</view>
<view class="rd-remark">
{{ form.remark || '运载路线' }}
</view>
</view>
<u-toast ref="uToast"></u-toast>
</view>
</template>
<script>
import configService from '@/common/config.service.js';
import Topnav from '@/components/topnav/index.vue';
export default {
// #ifdef MP
options: {
styleIsolation: 'shared'
},
// #endif
components: { Topnav },
data(){
return{
// #ifdef MP
//
CustomBar: this.CustomBar || 0,
// #endif
fileUrl: configService.fileUrl + 'aerocraft/route/',//线
//
bgIcon: 'detail-bg.png',
//
topLevel: 0,
//
form:{
id: ''
}
}
},
onPageScroll(e) {
const level = e.scrollTop/60;
if(level<=1) this.topLevel = level;
else this.topLevel = 1;
},
onLoad(e) {
this.form.id = e.id;
this.init(e.id);
},
methods:{
//
async init(id = this.form.id){
if(!id) return;
let res = await this.$api.singleRoute(id);
if(res){
this.form = res;
}else{
this.$refs.uToast.show({type:'error',title: "路线详情获取失败!"});
}
},
}
}
</script>
<style scoped lang="scss">
.route-detail{
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #FFFFFF;
background-size: contain;
background-repeat: no-repeat;
&-title{
margin: 36rpx 28rpx 42rpx;
padding-bottom: 26rpx;
box-shadow: 0rpx 2rpx 4rpx 0rpx rgba(0, 0, 0, 0.05);
&::v-deep .u-section__title__text{
margin-left: 10rpx !important;
}
}
&-content{
border-radius: 20rpx;
box-shadow: 0rpx -4rpx 6rpx 0rpx rgba(234,233,217,0.3), 0rpx 4rpx 6rpx 0rpx rgba(234,233,217,0.3);
background: #FFFFFF;
margin: 0 28rpx 40rpx;
padding: 40rpx 40rpx 36rpx 40rpx;
display: flex;
flex-direction: column;
.rd-aim{
font-family: Source Han Sans SC;
font-size: 32rpx;
font-weight: 500;
display: flex;
align-items: center;
}
.rd-value{
margin-top: 26rpx;
font-family: Source Han Sans SC;
font-size: 28rpx;
color: #6B7280;
display: flex;
align-items: center;
margin-left: 40rpx;
text{
margin-left: 4rpx;
color: #374151;
}
}
.rd-remark{
margin-top: 26rpx;
font-family: Source Han Sans SC;
font-size: 28rpx;
color: #374151;
padding: 28rpx;
background: #FFFBEB;
border-radius: 14rpx;
}
}
}
</style>

View File

@ -1,25 +1,89 @@
<template>
<view class="aircraft-route">
<view class="">
路线
<!-- <view class="aircraft-route" :style="{backgroundImage: `url(${fileUrl+bgIcon})`}"> -->
<!-- <view class="route-content">
<view class="top-abs" :style="{top: StatusBar+'px',height: CustomBar-StatusBar+'px'}">
路线
</view>
<view :style="{height: CustomBar + 'px'}" />
<view class="route-search">
<u-icon :name="fileUrl+searchIcon" size="36" />
<searchCombox style="flex: 1;margin: 0 10rpx;" :candidates="scenics" :isJSON="true" keyName="name" placeholder="搜索景区"
v-model="query" @select="handleSearchSelect" selectedColor="#F8B500" />
<u-icon class="triangle" :name="fileUrl+triangleIcon" size="24" />
</view>
<view class="route-item" v-for="(item, index) in routes" :key="index" @click="showDetail(item)">
<view class="route-item-top">
<u-icon :name="fileUrl+routeIcon" size="36" />
<text>{{ item.name }}</text>
</view>
<view class="route-item-second">
<view class="route-detail">
<view class="route-detail-aim">起飞点</view>
<view class="route-detail-center" :style="{backgroundImage: `url(${fileUrl+aimIcon})`}">
目的地<u-icon style="margin-left: 8rpx;" :name="fileUrl+rightIcon" size="18" />
</view>
<view class="route-detail-aim">降落点</view>
</view>
<view class="route-detail">
<view class="route-detail-value">{{ item.startPoint || '暂无' }}</view>
<view class="route-detail-value">{{ item.endPoint || '暂无' }}</view>
</view>
</view>
<view class="route-item-remark">
{{ item.remark || '运载路线' }}
</view>
</view>
</view> -->
<u-toast ref="uToast"></u-toast>
</view>
</template>
<script>
import configService from '@/common/config.service.js';
import searchCombox from './components/search-combox.vue';
export default {
props: {
topLevel:{
type: Number,
default: 0
},
// #ifdef MP
options: {
styleIsolation: 'shared'
},
// #endif
components: {searchCombox},
data(){
return{
// #ifdef MP
//
StatusBar: this.StatusBar || 0,
CustomBarHeight: this.Custom.height+(this.Custom.top-this.StatusBar)*2 || 0,
CustomBar: this.CustomBar || 0,
// #endif
fileUrl: configService.fileUrl + 'aerocraft/route/',//线
//
bgIcon: 'bg.png',
//
searchIcon: 'search.png',
//
triangleIcon: 'inverted-triangle.png',
// 线
routeIcon: 'route.png',
//
rightIcon: 'right.png',
//
aimIcon: 'aim.png',
//
scenics: [],
// 线
routes: [],
//
query: '',
//
form:{
current: 1,
size: 10,
// loadmoreloadingnomore
isFinish: 'nomore',
routeId: ''
},
}
},
computed: {
@ -27,13 +91,189 @@ export default {
methods:{
//
async init(){
this.form = {size: 10,current: 1,isFinish: 'nomore',routeId: ''};
await this.getRouteList();
//
let scenRes = await this.$api.allScenic();
if(!scenRes)
this.$refs.uToast.show({type: 'error',title: "景区列表获取失败!"});
else this.scenics = await scenRes;
},
//
search(){
this.form = { ...this.form, current: 1, isFinish: 'nomore' };
this.getRouteList();
},
//
async getRouteList(){
this.form.isFinish = 'loading';
let routeRes = await this.$api.getRoutes(this.form);
if(routeRes){
const { records, size, total, current } = routeRes;
if(current == 1) this.routes = records || [];
else this.routes.push(...records);
this.form.isFinish = size*current >= total ? 'nomore' : 'loadmore';
}else{
this.$refs.uToast.show({type:'error',title: "路线列表获取失败!"});
this.form.isFinish = 'loadmore';
}
},
//
getMore(){
if(this.form.isFinish === 'nomore') return;
this.form.current++;
this.getRouteList();
},
//
handleSearchSelect(e){
console.log('触发搜索:',e.id);
},
//
showDetail(item){
if(item.id){
uni.navigateTo({
url: `/aircraft/server/route/detail?id=${item.id}`
})
}
}
}
}
</script>
<style scoped lang="scss">
.aircraft-route{
display: flex;
flex-direction: column;
height: 100%;
background-color: #F5F5F5;
background-size: contain;
background-repeat: no-repeat;
padding-bottom: 30rpx;
.route-content{
width: 100%;
position: relative;
padding: 0 24rpx 0;
.top-abs{
position: absolute;
display: flex;
align-items: center;
font-family: Source Han Sans SC;
font-weight: 800;
font-size: 44rpx;
color: #333333;
}
.route-search{
margin-top: 18rpx;
margin-bottom: 4rpx;
background: #FFFFFF;
border-radius: 12rpx;
padding: 22rpx 30rpx;
display: flex;
align-items: center;
.triangle{
animation: spinBack 0.1s linear;
}
}
.route-item{
margin-top: 26rpx;
display: flex;
flex-direction: column;
padding: 36rpx 30rpx 30rpx 30rpx;
background: #FFFFFF;
border-radius: 16rpx;
&-top{
display: flex;
align-items: center;
text{
margin-left: 12rpx;
font-family: Source Han Sans SC;
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
}
&-second{
margin-top: 20rpx;
display: flex;
flex-direction: column;
.route-detail{
display: flex;
align-items: center;
justify-content: space-between;
font-family: Source Han Sans SC;
width: 100%;
&-aim{
padding: 10rpx 0;
font-weight: bold;
font-size: 34rpx;
color: #333333;
overflow: auto;
white-space: nowrap;
}
&-center{
white-space: nowrap;
margin: 0 auto;
display: flex;
align-items: center;
background-size: contain;
background-repeat: no-repeat;
font-weight: 400;
font-size: 28rpx;
color: #666666;
padding: 12rpx 62rpx 12rpx 66rpx;
}
&-value{
white-space: nowrap;
overflow: auto;
margin-top: 20rpx;
font-weight: 400;
font-size: 28rpx;
color: #333333;
}
}
}
&-remark{
margin-top: 30rpx;
background: #FFFADD;
border-radius: 10rpx;
font-family: Source Han Sans SC;
font-weight: 400;
font-size: 28rpx;
color: #666666;
padding: 16rpx 20rpx;
}
}
&:focus-within{
.triangle{
animation: spin 0.3s linear;
transform: rotate(180deg);
}
}
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(180deg);
}
}
@keyframes spinBack {
from {
transform: rotate(180deg);
}
to {
transform: rotate(0deg);
}
}
::v-deep .search-combox__input-plac{
color: #999999 !important;
}
::v-deep .search-combox__input{
margin-top: 2rpx;
font-family: Source Han Sans SC !important;
font-weight: 400;
font-size: 30rpx !important;
}
</style>

View File

@ -74,6 +74,12 @@
"navigationBarTitleText": "新增订单"
}
},
{
"path": "route/detail",
"style": {
"navigationBarTitleText": "路线详情"
}
},
{
"path": "my/child_pages/history",
"style": {