create project

main
ch 3 years ago
parent 1c522d1002
commit d1671e1637

8
.gitignore vendored

@ -0,0 +1,8 @@
node_modules
dist
*.local
.history
.idea
dist/
.hbuilderx
keys

@ -0,0 +1,27 @@
<!--
* @Author: ch cwl_ch@163.com
* @Date: 2022-11-21 17:23:51
* @LastEditors: ch
* @LastEditTime: 2022-12-13 14:09:42
* @Description: file content
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,9 @@
{
"compilerOptions": {
"types": [
"@dcloudio/types",
"miniprogram-api-typings",
"mini-types"
]
}
}

@ -0,0 +1,67 @@
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@dcloudio/uni-app": "3.0.0-3060520221121001",
"@dcloudio/uni-app-plus": "3.0.0-3060520221121001",
"@dcloudio/uni-components": "3.0.0-3060520221121001",
"@dcloudio/uni-h5": "3.0.0-3060520221121001",
"@dcloudio/uni-mp-alipay": "3.0.0-3060520221121001",
"@dcloudio/uni-mp-baidu": "3.0.0-3060520221121001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3060520221121001",
"@dcloudio/uni-mp-lark": "3.0.0-3060520221121001",
"@dcloudio/uni-mp-qq": "3.0.0-3060520221121001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3060520221121001",
"@dcloudio/uni-mp-weixin": "3.0.0-3060520221121001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3060520221121001",
"@dcloudio/uni-ui": "^1.4.23",
"@gykeji/jsutil": "^2.0.2",
"vue": "^3.2.37",
"vue-i18n": "^9.1.9",
"vuex": "^4.1.0"
},
"devDependencies": {
"@dcloudio/types": "^3.0.13",
"@dcloudio/uni-automator": "3.0.0-3060520221121001",
"@dcloudio/uni-cli-shared": "3.0.0-3060520221121001",
"@dcloudio/uni-stacktracey": "3.0.0-3060520221121001",
"@dcloudio/vite-plugin-uni": "3.0.0-3060520221121001",
"sass": "^1.56.2",
"vite": "^2.9.14"
}
}

@ -0,0 +1,48 @@
<script setup>
import { onLaunch } from "@dcloudio/uni-app";
import { useStore } from "vuex";
import { ApiGetUserInfo } from "./api/user";
import { ApiGetCityList } from "./api/city";
const $store = useStore();
onLaunch(() => {
console.log('App Launch')
getLocation();
getUserInfo();
});
/**
* @Description: 获取用户信息
* @return {*}
*/
const getUserInfo = async () => {
const { error, result } = await ApiGetUserInfo();
if (result) {
$store.commit("setUserInfo", result);
}
};
/**
* @Description: 默认定位到当前城市
* @return {*}
*/
const getLocation = async () => {
const {error, result}= await ApiGetCityList();
uni.getLocation({
type: 'gcj02',
geocode: true,
success (res) {
const {address,longitude,latitude} = res;
$store.commit('setCity',{
adcode : result.find(i => i.citycode === address.cityCode).adcode,
cityCode: address.cityCode,
name: address.city,
center: `${longitude},${latitude}`,
})
}
});
}
</script>
<style>
/*每个页面公共css */
</style>

@ -0,0 +1,47 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-28 14:42:03
* @LastEditors: ch
* @LastEditTime: 2022-12-28 14:54:32
* @Description: file content
*/
import { _ToAsyncAwait } from '@gykeji/jsutil';
import MAP_CON from '../config/gdMapConf';
const formatCity = (data)=>{
let arr = [];
data.forEach(i => {
if(i.citycode.length){
arr.push(i);
}
if(i.districts.length){
arr = arr.concat(formatCity(i.districts))
}
});
return arr;
}
/**
* @Description: 获取城市列表直接使用高德API
* @return {*}
*/
const ApiGetCityList = () => _ToAsyncAwait(
new Promise((res, rej) => {
uni.request({
method: 'GET',
url: `${MAP_CON.cityApiUrl}?subdistrict=2&key=${MAP_CON.cityKey}`,
success(result){
res(
formatCity(result.data.districts[0].districts).sort((a,b)=>{
return a.name.localeCompare(b.name, 'zh-CN');
})
);
},
error(e){
rej(e)
}
})
})
)
export {
ApiGetCityList
}

@ -0,0 +1,72 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-19 15:33:58
* @LastEditors: ch
* @LastEditTime: 2022-12-30 16:48:20
* @Description: file content
*/
import { MsbRequest } from "../plugins/requset";
const ApiGetUserProgressOrder = () => MsbRequest.get("/UserProgressOrder");
/**
* @Description: 获取价格
* @param {*}
* @return {*}
*/
const ApiGetPrice = (
params = {
depLongitude, //城市编码
depLatitude, //出发地纬度
destLongitude, //目的地纬度
destLatitude, //目的地纬度
cityCode, //城市编码
vehicleType, //车辆类型
}
) => MsbRequest.post("/forecast-price", params);
/**
* @Description:乘客下单
* @return {*}
*/
const ApiPostOrderAdd = (
data = {
address,
departTime,
orderTime,
departure,
depLongitude,
depLatitude,
destination,
destLongitude,
destLatitude,
encrypt,
fareType,
fareVersion,
passengerId,
passengerPhone,
vehicleType,
}
) => MsbRequest.post("/order/add", data);
/**
* @Description: 乘客取消订单
* @param {*} orderId
* @return {*}
*/
const ApiPostOrderCancel = ({orderId}) => MsbRequest.post('/order/cancel',{orderId},{
'content-type':'application/x-www-form-urlencoded'
});
/**
* @Description: 查询当前用户正在进行的订单
* @return {*}
*/
const ApiGetCurrentOrder = () => MsbRequest.get('/order/current');
export {
ApiGetUserProgressOrder,
ApiGetPrice,
ApiPostOrderAdd,
ApiPostOrderCancel,
ApiGetCurrentOrder
};

@ -0,0 +1,38 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-19 11:30:32
* @LastEditors: ch
* @LastEditTime: 2022-12-30 18:19:04
* @Description: file content
*/
import { MsbRequest } from '../plugins/requset';
/**
* @Description: 获取验证码
* @param {*} passengerPhone
* @return {*}
*/
const ApiGetVerifyCode = ({passengerPhone}) => MsbRequest.post('/verification-code',{
passengerPhone
},{notVerifyToken:true})
/**
* @Description: 验证验证码
* @param {*} passengerPhone
* @param {*} verificationCode
* @return {*}
*/
const ApiPostVerifyCodeCheck = ({passengerPhone,verificationCode}) => MsbRequest.post('/verification-code-check',{
passengerPhone,
verificationCode,
},{notVerifyToken:true})
/**
* @Description: 根据token获取乘客信息
* @return {*}
*/
const ApiGetUserInfo = () => MsbRequest.get('/users/');
export {
ApiGetVerifyCode,
ApiPostVerifyCodeCheck,
ApiGetUserInfo
}

@ -0,0 +1,191 @@
<template>
<view :eventBus="eventBus" :change:eventBus="renderScript.receiveEvent" id="map-container">
</view>
</template>
<script>
export default {
data(){
return {
eventBus : {},
searchFn: null
}
},
computed:{
city (){return this.$store.state.city},
},
methods:{
setCenter (...args) {
this.eventBus = { name : 'setCenter', args }
},
driving (...args) {
this.eventBus = { name : 'driving', args }
},
clearDriving (...args) {
this.eventBus = { name : 'clearDriving', args }
},
search (cb, ...args) {
this.searchFn = cb;
this.eventBus = { name : 'search', args, city:this.city }
},
searchResult (res) {
this.searchFn(res);
}
}
};
</script>
<script module="renderScript" lang="renderjs">
import AMapLoader from '@amap/amap-jsapi-loader';
import {MarkerIcon} from '../plugins/Base64Markers.js';
import gdMapConf from '../config/gdMapConf';
window._AMapSecurityConfig = {
securityJsCode: gdMapConf.securityJsCode,
}
let AMap = null
let map = null
let driving = null;
export default {
data() {
return {
}
},
mounted(){
AMapLoader.load({
"key": gdMapConf.key, // WebKey load
"version": "2.0", // JSAPI 1.4.15
// 使'AMap.Scale'
"plugins": ['AMap.Driving','AMap.PlaceSearch','AMap.AutoComplete', 'AMap.Geolocation'],
"AMapUI": { // AMapUI
"version": '1.1', // AMapUI
"plugins":['overlay/SimpleMarker'], // AMapUI ui
},
"Loca":{ // Loca
"version": '2.0' // Loca
},
}).then((Amap)=>{
AMap = Amap;
map = new AMap.Map('map-container',{
resizeEnable: true,
zoom: 13 //
});
}).catch((e)=>{
console.error(e); //
});
},
methods: {
//
receiveEvent(newParams, oldValue, ownerVm, vm) {
let {name, args} = newParams || {};
switch (name) {
case 'setCenter':
this.setMapCenter(...args)
break;
case 'search':
this.mapSearch(newParams.city, ...args);
break;
case 'driving':
this.mapDriving(...args);
break;
case 'clearDriving':
this.mapClearDriving(...args);
break;
}
},
setMapCenter(lng,lat){
if(!map){
setTimeout(()=> {
this.setMapCenter(lng,lat);
}, 500);
return false
}
map.setCenter([lng,lat])
},
mapSearch(city, str, cb){
// this.$ownerInstance.callMethod('searchResult', 'xxxxxxsssxx')
AMap.plugin(["AMap.PlaceSearch"], ()=> {
//
var placeSearch = new AMap.PlaceSearch({
pageSize: 5, //
pageIndex: 1, //
city: city.cityCode || city.citycode, //
citylimit: true, //
// map: map, //
// autoFitView: true // 使 Marker
});
//
placeSearch.search(str, (status, result)=>{
if(result.info === 'OK'){
// cb && cb(result.poiList);
this.$ownerInstance.callMethod('searchResult', result.poiList)
}
});
});
},
mapClearDriving(){
if(driving){
driving.clear()
}
},
mapDriving(startLngLat, endLngLat){
if(!AMap){
setTimeout(()=> {
this.mapDriving(startLngLat, endLngLat);
}, 500);
return false;
}
if(driving){
driving.clear();
}else{
driving = new AMap.Driving({map});
}
driving.search(new AMap.LngLat(...startLngLat), new AMap.LngLat(...endLngLat));
},
}
};
</script>
<style scoped="false">
#map-container {
width: 100%;
height: 100%;
}
>>> .amap-logo,
>>> .amap-copyright {
display: none !important;
}
>>> .amap-info-contentContainer {
background: #fff;
border: 2px solid #ddd;
border-radius: 0.2em;
padding: 1em;
}
>>> .amap-info-contentContainer .info {
display: flex;
flex-direction: column;
}
>>> .info__btn {
margin-top: 4px;
}
#panel {
position: absolute;
background-color: white;
max-height: 90%;
overflow-y: auto;
top: 10px;
right: 10px;
width: 280px;
}
</style>

@ -0,0 +1,26 @@
<template>
<!-- #ifdef H5 -->
<web-view v-if="userInfo.id" :src="url" ref="h5WebviewRef" style="width:0px; height:0px"/>
<!-- #endif -->
<!-- #ifndef H5 -->
<web-view v-if="userInfo.id" :src="url" :fullscreen="false" @message="handleMessage" :webview-styles="{width:'0px',height:'0px'}"/>
<!-- #endif -->
</template>
<script setup>
import {computed, onMounted, ref} from 'vue';
import { useStore } from 'vuex';
const $store = useStore();
const $emits = defineEmits(['receiveMsg']);
const userInfo = computed(()=> $store.state.userInfo);
let url = ref(`/static/sseMessage.html?userId=${userInfo.value.id}&uri=${encodeURIComponent($store.state.serverConf.sse)}`);
onMounted(()=>{
if(window){
window.addEventListener("message", handleMessage, false);
}
})
const handleMessage = (e)=>{
$emits('receiveMsg', e.data.data.arg)
}
</script>

@ -0,0 +1,31 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-27 14:52:17
* @LastEditors: ch
* @LastEditTime: 2022-12-30 16:33:24
* @Description: 字典表
*/
const ORDER_STATUS = {
// 乘客发起订单
orderStart : 1,
// 司机接单
driverReceive: 2,
// 司机去接乘客
driverToPickUp: 3,
// 司机到底上车点
driverArriveStartPoint: 4,
// 行程开始,乘客上车
tripStart: 5,
// 行程结束,到达目的地
tripFinish: 6,
// 发起收款,待支付
awaitPay: 7,
// 付款完成,订单完成
orderFinish: 8,
// 订单取消
orderCancel: 9,
}
export {
ORDER_STATUS
}

@ -0,0 +1,24 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-28 14:39:29
* @LastEditors: ch
* @LastEditTime: 2022-12-30 16:35:53
* @Description: 高德地图配置需要自行去高德开放平台申请
*/
export default {
// 高德地图JS Api key
key:'accb4fe7de2b5efd04f187dc0f978777',
// 高德地图JS Api key对应的秘钥正式环境最好不要放前端
securityJsCode : '617eba916a16270f5fa3203a4ee2e2a6',
// 城市获取key
cityKey : 'fe5524e832a0fc6e2bcaf1bb781ac830',
// 高德城市请求地址
cityApiUrl : 'https://restapi.amap.com/v3/config/district',
// 默认选中城市
city: {
adcode: "110000",
center: "116.407387,39.904179",
citycode: "010",
name: "北京市"
}
}

@ -0,0 +1,27 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-30 14:47:25
* @LastEditors: ch
* @LastEditTime: 2022-12-30 15:42:42
* @Description: 服务端配置
*/
export default {
// #ifdef H5
// 支付服务
pay: '/payApi',
// sse服务
sse: '/sseApi',
// 其他接口服务
other: '/api',
// #endif
// #ifdef APP-PLUS ||MP
// 支付服务
pay: 'http://192.168.40.193:9001',
// sse服务
sse: 'http://192.168.40.193:60001',
// 其他接口服务
other: 'http://192.168.40.193:8081'
// #endif
}

@ -0,0 +1,13 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-19 10:58:26
* @LastEditors: ch
* @LastEditTime: 2022-12-30 14:51:56
* @Description: 本地存储key管理
*/
const STORAGE_KEY = {
token : 'tk',
userInfo : 'u_i',
serverConf: 's_c'
}
export default STORAGE_KEY;

@ -0,0 +1,19 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-11-21 17:23:51
* @LastEditors: ch
* @LastEditTime: 2022-12-13 17:41:14
* @Description: file content
*/
import {
createSSRApp
} from "vue";
import App from "./App.vue";
import store from './store'
export function createApp() {
const app = createSSRApp(App);
app.use(store)
return {
app,
};
}

@ -0,0 +1,76 @@
{
"name" : "飞滴出行",
"appid" : "__UNI__390779D",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {
"maps" : {},
"payment" : {
"alipay" : {
"__platform__" : [ "ios", "android" ]
}
}
}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}

@ -0,0 +1,60 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "飞滴出行",
"titleImage":"/static/logo.png"
}
},
{
"path": "pages/city",
"style": {
"navigationBarTitleText": "飞滴出行",
"titleImage":"/static/logo.png"
}
},
{
"path": "pages/createOrder",
"style": {
// "navigationBarTitleText": "飞滴出行",
"titleImage":"/static/logo.png"
}
},
{
"path": "pages/orderDetail",
"style": {
// "navigationBarTitleText": "飞滴出行",
"titleImage":"/static/logo.png"
}
},
{
"path": "pages/pay",
"style": {
// "navigationBarTitleText": "飞滴出行",
"titleImage":"/static/logo.png"
}
},
{
"path": "pages/login",
"style": {
// "navigationBarTitleText": "飞滴出行",
"titleImage":"/static/logo.png"
}
},
{
"path": "pages/account",
"style": {
// "navigationBarTitleText": "飞滴出行",
"titleImage":"/static/logo.png"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}

@ -0,0 +1,76 @@
<template>
<view class="wrapper">
<view class="title">在此设置你的后端服务地址</view>
<view class="item">
<text class="label">支付服务</text>
<input class="input" v-model="serverConf.pay" />
</view>
<view class="item">
<text class="label">SSE服务</text>
<input class="input" v-model="serverConf.sse" />
</view>
<view class="item">
<text class="label">其他服务</text>
<input class="input" v-model="serverConf.other" />
</view>
<button class="btn" @click="handleSave"></button>
<view class="desc">保存成功后如不生效请重启APP</view>
<button class="btn btn__logout" @click="handleLogout">退</button>
</view>
</template>
<script setup>
import { computed} from "vue";
import { useStore } from "vuex";
import { ShowToast } from "../utils";
const $store = useStore();
let serverConf = computed({
get: ()=> $store.state.serverConf,
set(val){
}
});
const handleSave = () =>{
$store.commit('setServerConf', serverConf.value);
ShowToast('保存成功');
uni.redirectTo({url:'/pages/index/index'});
}
const handleLogout = () =>{
$store.commit('setToken', '');
uni.redirectTo({url:'/pages/login'});
}
</script>
<style lang="scss" scoped>
.item,.title{
margin: $uni-spacing-max 0;
}
.wrapper{
margin: $uni-spacing-max;
}
.input{
border: 1px solid $uni-border-color;
height: 36rpx;
margin-top: $uni-spacing-base;
}
.label{
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.btn{
font-size: $uni-font-size-lg;
margin-top: $uni-spacing-max;
background: $uni-color-primary;
color: $uni-text-color-inverse;
&::after{
display: none;
}
&__logout{
background: $uni-color-error;
}
}
.desc{
font-size: $uni-font-size-base;
color: $uni-color-red;
margin-top: $uni-spacing-max;
}
</style>

@ -0,0 +1,87 @@
<template>
<view class="wrapper">
<view class="search-box">
<input class="search-input" v-model="searchStr" placeholder="请输入关键词搜索"/>
<text @click="handleCancel"></text>
</view>
<view class="city-item" v-for="item in filterList" :key="item.adcode" @click="handleCity(item)">
<text>{{item.name}}</text>
</view>
</view>
</template>
<script setup>
import { onMounted, ref, computed } from "vue";
import { useStore } from "vuex";
import gdMapConf from "../config/gdMapConf";
const $store = useStore();
let cityList = ref([]);
let searchStr = ref('');
let filterList = computed(()=>{
return cityList.value.filter(i=> i.name.includes(searchStr.value))
});
onMounted(()=>{
getCityList()
})
const getCityList = () =>{
uni.request({
method: 'GET',
url: `${gdMapConf.cityApiUrl}?subdistrict=2&key=${gdMapConf.cityKey}`,
success(res){
cityList.value = formatCity(res.data.districts[0].districts).sort((a,b)=>{
return a.name.localeCompare(b.name, 'zh-CN');
});
}
})
};
const formatCity = (data)=>{
let arr = [];
data.forEach(i => {
if(i.citycode.length){
arr.push(i);
}
if(i.districts.length){
arr = arr.concat(formatCity(i.districts))
}
});
return arr;
}
const handleCity = (item) => {
$store.commit('setCity', item);
uni.navigateBack();
}
const handleCancel = () =>{
uni.navigateBack();
}
</script>
<style lang="scss" scoped>
.wrapper{
padding: $uni-spacing-max;
}
.search-box{
display: flex;
align-items: center;
font-size: $uni-font-size-base;
margin-bottom: $uni-spacing-max;
}
.search-input{
background: $uni-bg-color-grey;
font-size: $uni-font-size-sm;
height: 50rpx;
padding: 0 10rpx;
border: 1px solid $uni-border-color;
border-radius: $uni-border-radius-base;
margin-right: $uni-spacing-big;
flex: 1;
}
.city-item{
height: 70rpx;
line-height: 70rpx;
font-size: $uni-font-size-base;
border-bottom: 1px solid #eee;
}
</style>

@ -0,0 +1,194 @@
<template>
<view class="map-wrapper">
<BMap ref="mapRef"/>
<view class="panel">
<view class="panel--bar">
<text>预计价格</text>
<text class="price">{{priceResult.price}}</text>
</view>
<view class="panel--bar">
<text>出发时间</text>
<view class="time-bar">
<picker mode="date" fields="day" :value="departDay" :start="start" :end="end">
<view class="time">{{departDay}}</view>
</picker>
<picker mode="time" :value="departTime" @change="handleTimeChange" start="00:00" end="23:59">
<view> {{departTime}}</view>
</picker>
</view>
</view>
<view class="operation">
<button class="btn btn__cancel" @click="handleCancel"></button>
<button class="btn" @click="handleConfirm"></button>
</view>
</view>
</view>
</template>
<script setup>
import BMap from '../component/BMap.vue';
import { onLoad} from '@dcloudio/uni-app';
import { computed, nextTick, onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import { HandleApiError } from '../utils';
import { ApiGetPrice, ApiPostOrderAdd, ApiGetCurrentOrder } from '../api/order';
import { _FormatDate } from '@gykeji/jsutil';
const $store = useStore();
const city = computed(()=> $store.state.city);
const userInfo = computed(()=> $store.state.userInfo);
const start = _FormatDate(new Date(), 'yyyy-mm-dd');
const end = _FormatDate(new Date().getTime() + 3 * 24 * 60 * 60 * 1000, 'yyyy-mm-dd');
const mapRef = ref(null);
const startDate = new Date()
let $routerQuery = {};
let priceResult = ref({});
let departTime = ref();
let departDay = ref();
onLoad((option) => {
$routerQuery = option;
});
onMounted(()=>{
getUserProgressOrder();
getPrice();
departDay.value = _FormatDate(new Date(), 'yyyy-mm-dd');
departTime.value = _FormatDate(new Date().getTime() + 5 * 60 * 1000, 'hh:ii');
})
const getPrice = async ()=>{
const {slng:depLongitude, slat:depLatitude, elng:destLongitude, elat:destLatitude,s:dep,e:dest} = $routerQuery;
const {error, result} = await ApiGetPrice({
depLongitude,
depLatitude,
destLongitude,
destLatitude,
vehicleType: 1,
cityCode: city.value.adcode
});
if(!HandleApiError(error)){
priceResult.value = result;
nextTick(()=>{
mapDriving([depLongitude,depLatitude],[destLongitude, destLatitude]);
})
}
}
const handleTimeChange = (e) =>{
departTime.value = e.detail.value;
}
const handleConfirm = async () =>{
const {slng:depLongitude, slat:depLatitude, elng:destLongitude, elat:destLatitude,s:dep,e:dest} = $routerQuery;
const {error, result} = await ApiPostOrderAdd({
address: city.value.adcode,
departTime: `${departDay.value} ${departTime.value}:01`,
orderTime: _FormatDate(new Date(),'yyyy-mm-dd hh:ii:ss'),
departure: dep,
depLongitude,
depLatitude,
destination:dest,
destLongitude,
destLatitude,
encrypt: 14,//
fareType: priceResult.value.fareType,
fareVersion: priceResult.value.fareVersion,
passengerId: userInfo.value.id,
passengerPhone: userInfo.value.passengerPhone,
vehicleType: priceResult.value.vehicleType
});
if(!HandleApiError(error)){
uni.redirectTo({url:'/pages/orderDetail'})
}
}
const handleCancel = ()=>{
uni.navigateBack();
}
const mapDriving = (dep,dest)=>{
if(!mapRef.value.driving){
setTimeout(()=>{
mapDriving(dep,dest)
}, 500)
return false
}
mapRef.value.driving(dep,dest);
}
const getUserProgressOrder = async () => {
const { result } = await ApiGetCurrentOrder();
if (result) {
uni.redirectTo({url:'/pages/orderDetail'})
}
}
</script>
<style scoped lang="scss">
.map-wrapper {
width: 100%;
height: calc(100vh - 30px);
}
.panel{
position: fixed;
bottom: 30rpx;
left: 30rpx;
right: 30rpx;
background: $uni-bg-color;
border-radius: $uni-border-radius-lg;
z-index: 9;
padding: $uni-spacing-row-max;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
&--bar{
display: flex;
justify-content: space-between;
align-items: center;
height: 70rpx;
text{
font-size: $uni-font-size-base;
color: $uni-text-color;
}
}
.price{
font-size: 38rpx;
color: $uni-color-red;
font-weight: bold;
}
.time-bar{
display: flex;
align-items: center;
&::after{
display: block;
content: '';
height: 15rpx;
width: 15rpx;
border: 1px solid $uni-border-color;
transform: rotate(45deg);
border-left: 0;
border-bottom: 0;
margin-left: $uni-spacing-base;
}
}
}
.operation{
display: flex;
align-items: center;
margin-top: $uni-spacing-lg;
justify-content: space-between;
}
.btn{
font-size: $uni-font-size-lg;
margin: 0;
background: $uni-color-primary;
color: $uni-text-color-inverse;
width: 45%;
&__cancel{
background: $uni-bg-color-grey;
color: $uni-text-color;
border: 1px solid $uni-border-color;
}
&::after{
display: none;
}
}
</style>

@ -0,0 +1,97 @@
<template>
<view class="map-wrapper">
<BMap ref="mapRef" />
<view class="city" @click="handleCity()">{{city.name}}</view>
<view class="system" @click="handleSystem()"></view>
<SelectPoint @confirm="handleConfrimPoint"/>
</view>
</template>
<script setup>
import {computed, onMounted, provide, ref, watch} from 'vue';
import { useStore } from 'vuex';
import BMap from '../../component/BMap.vue';
import SelectPoint from './modules/SelectPoint.vue';
const $store = useStore();
const mapRef = ref(null);
let city = computed(()=> $store.state.city);
provide('mapSearch',(str,cb) => mapRef.value.search(cb, str));
onMounted(()=>{
setCenter();
watch(city, setCenter);
})
/**
* @Description: 城市变更
* 重新设置地图显示区域
* 清除已画好的路线
* @return {*}
*/
function setCenter (){
const center = city.value.center.split(',');
mapRef.value.setCenter(...center);
setTimeout(()=>mapRef.value.clearDriving(), 500);
}
/**
* @Description:  确认路线
* 绘制路线页面状态改为价格
* @param {*} start
* @param {*} end
* @return {*}
*/
const handleConfrimPoint = async (start, end) =>{
const [startLng, startLat] = start.location;
const [endLng, endLat ] = end.location;
uni.navigateTo({
url : `/pages/createOrder?slng=${startLng}&slat=${startLat}&elng=${endLng}&elat=${endLat}&s=${start.name}&e=${end.name}`
})
}
/**
* @Description: 重新选城市
* @return {*}
*/
const handleCity = () =>{
uni.navigateTo({url: '/pages/city'});
}
const handleSystem = () =>{
uni.navigateTo({url: '/pages/account'});
}
</script>
<style scoped lang="scss">
.map-wrapper {
width: 100%;
height: 100vh;
}
.city,.system{
position: fixed;
background: #fff;
padding: 7rpx 15rpx 10rpx;
z-index: 9;
top: calc(var(--window-top) + 30rpx);
right: 30rpx;
border-radius: 8rpx;
box-shadow: 0 3rpx 5rpx 5rpx #ccc;
font-size: $uni-font-size-sm;
color: $uni-text-color;
display: flex;
align-items: center;
}
.city{
left: 30rpx;
right: auto;
&::after{
content: '';
display: block;
border: 10rpx solid #666;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
margin-left: 10rpx;
margin-top: 12rpx;
border-radius: 4rpx;
}
}
</style>

@ -0,0 +1,105 @@
<template>
<view :class="['point-search', {'point-search_show':myVisible}]">
<view class="search-box">
<text class="search-city" @click="handleCity">{{city.name}}</text>
<input class="search-input" v-model="searchStr" @input="handleSearch" placeholder="请输入关键词搜索"/>
<text @click="myVisible = false">取消</text>
</view>
<view class="point-list">
<view class="point-item" v-for="item in searchList" :key="item.id" @click="handleChangePoint(item)">
<view class="point-item--name">{{item.name}}</view>
<view class="point-item--addres">{{item.address}}</view>
</view>
</view>
</view>
</template>
<script setup>
import {computed, inject, ref} from 'vue';
import { useStore } from 'vuex';
const $props = defineProps({
visible: {
type : Boolean,
default : false
}
});
const $emits = defineEmits(['change','update:visible']);
const $store = useStore();
const piMapSearch = inject('mapSearch');
const city = computed(()=> $store.state.city)
let myVisible = computed({
get:()=> $props.visible,
set(val){
$emits('update:visible',val)
}
});
let searchStr = ref('');
let searchList = ref([]);
const handleSearch = () =>{
piMapSearch(searchStr.value,(result)=>{
searchList.value = result.pois;
})
}
const handleChangePoint = (item) =>{
$emits('change', item);
myVisible.value = false;
}
const handleCity = () =>{
uni.navigateTo({url: '/pages/city'});
}
</script>
<style scoped lang="scss">
.point-search{
position: fixed;
height: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9;
background: #fff;
overflow: auto;
transition: all 1s;
}
.point-search_show{
height: calc(100vh - var(--window-top));
padding: $uni-spacing-max;
box-sizing: border-box;
}
.search-box{
display: flex;
align-items: center;
font-size: $uni-font-size-base;
margin-bottom: $uni-spacing-max;
}
.search-input{
background: $uni-bg-color-grey;
font-size: $uni-font-size-sm;
height: 50rpx;
padding: 0 10rpx;
border: 1px solid $uni-border-color;
border-radius: $uni-border-radius-base;
margin-right: $uni-spacing-big;
flex: 1;
}
.search-city{
font-size: $uni-font-size-base;
margin-right: $uni-spacing-big;
max-width: 150rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.point-item{
font-size: $uni-font-size-base;
border-bottom: 1px solid #eee;
padding: $uni-spacing-lg 0 $uni-spacing-max;
&--name{
margin: $uni-spacing-big 0;
}
&--addres{
font-size: $uni-font-size-base;
color: $uni-text-color-grey;
line-height: 32rpx;
}
}
</style>

@ -0,0 +1,104 @@
<template>
<view class="select-box" >
<view class="start" @click="startPointVisible = true">
<text v-if="!startPoint.id"></text>
<template v-else>
<text>您将从</text>
<view class="start-point-text">{{startPoint.name}}</view>
<text>上车</text>
</template>
</view>
<view class="end" @click="endPointVisible = true">
<text>您要去哪儿</text>
</view>
</view>
<PointList v-model:visible="startPointVisible" @change="handleChangeStart"/>
<PointList v-model:visible="endPointVisible" @change="handleChangeEnd"/>
</template>
<script setup>
import {ref, watch, computed} from 'vue';
import { useStore } from 'vuex';
import PointList from './PointList.vue'
const $emits = defineEmits(['confirm']);
const $store = useStore();
let startPointVisible = ref(false);
let startPoint = ref({});
let endPointVisible = ref(false);
let endPoint = ref({});
let city = computed(()=> $store.state.city);
watch( city, ()=>{
startPoint.value = {};
endPoint.value = {};
});
const handleChangeStart = (item) =>{
startPoint.value = item;
endPoint.value = {};
}
const handleChangeEnd = (item) =>{
endPoint.value = item;
if(startPoint.value.id){
$emits('confirm', startPoint.value, endPoint.value);
}
}
</script>
<style scoped lang="scss">
.select-box{
position: fixed;
bottom: 100rpx;
left: $uni-spacing-row-max;
right: $uni-spacing-row-max;
background: $uni-bg-color;
border-radius: $uni-border-radius-lg;
z-index: 9;
padding: $uni-spacing-row-max;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
}
.start{
height: 60rpx;
font-size: $uni-font-size-base;
display: flex;
align-items: center;
padding: 0 $uni-spacing-lg;
margin-bottom: $uni-spacing-big;
&::before{
display: block;
content: '';
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #19c235;
margin-right: $uni-spacing-row-base;
}
}
.start-point-text{
color: $uni-color-primary;
font-weight: bold;
margin: 0 $uni-spacing-sm;
max-width: 400rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.end{
background: #eee;
border-radius: $uni-border-radius-base;
height: 70rpx;
line-height: 70rpx;
padding: 0 $uni-spacing-lg;
display: flex;
align-items: center;
font-size: $uni-font-size-lg;
&::before{
display: block;
content: '';
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #f0ad4e;
margin-right: $uni-spacing-row-base;
}
}
</style>

@ -0,0 +1,123 @@
<template>
<view class="container">
<view class="logo">飞滴出行一路畅行</view>
<view class="login">
<input placeholder="请输入手机号" type="number" maxlength="11" v-model="phone" class="login--input" />
<view class="login--code">
<input placeholder="验证码" type="number" maxlength="6" class="login--input" v-model="code" />
<text @click="handleGetVerifyCode" :class="['login--send-btn', {'login--send-btn__disabled':isDisableCode}]">{{codeText}}</text>
</view>
</view>
<button class="login--btn" @click="handleLogin"></button>
</view>
</template>
<script setup>
import { _IsPhone } from '@gykeji/jsutil';
import {computed, onMounted, ref} from 'vue';
import {ApiGetVerifyCode, ApiPostVerifyCodeCheck} from '../api/user'
import {HandleApiError, ShowToast} from '../utils';
import {useStore} from 'vuex';
const $store = useStore();
const token = computed(()=> $store.state.token);
let codeText = ref('获取验证码');
let codeTimerNum = ref(0);
let codeTimer = null;
let isDisableCode = computed(()=> codeTimerNum.value !== 0);
let phone = ref('');
let code = ref('');
onMounted(()=>{
if(token.value){
uni.redirectTo({url:'/pages/index/index'});
}
})
const handleGetVerifyCode = async () =>{
if(isDisableCode.value || !verifyPhone(phone.value)){
return false;
}
codeTimerNum.value = 10;
calcTimer();
const {error, result} = await ApiGetVerifyCode({passengerPhone:phone.value});
if(!HandleApiError(error)){
ShowToast('验证码发送成功');
}
}
const handleLogin = async () =>{
if(!verifyPhone(phone.value)){
return false;
}
if(!code.value){
ShowToast('请输入正确验证码');
return false;
}
const {error, result} = await ApiPostVerifyCodeCheck({
passengerPhone: phone.value,
verificationCode: code.value
});
if(!HandleApiError(error)){
$store.commit('setToken', result.accessToken);
uni.redirectTo({url:'/pages/index/index'});
}
}
const calcTimer = () =>{
codeTimerNum.value--;
if(codeTimerNum.value === 0){
clearTimeout(codeTimer);
codeText.value = '获取验证码';
return false;
};
codeText.value = `${codeTimerNum.value}s后重新获取`;
setTimeout(()=>{
calcTimer();
}, 1000)
}
const verifyPhone = (phone) =>{
let result = phone && _IsPhone(phone);
if(!result){
ShowToast('请填写正确手机号!');
result = false;
}
return result;
}
</script>
<style lang="scss" scoped>
.logo{
font-size: 46rpx;
text-align: center;
margin: 100rpx 0 84rpx 0;
font-weight: bold;
}
.login{
width: 650rpx;
margin: 0 auto;
&--input{
border-bottom: 2rpx solid $uni-border-color;
width: 650rpx;
height: 100rpx;
font-size: $uni-font-size-lg;
}
&--code{
position: relative;
}
&--send-btn{
position: absolute;
right: 0;
top: 36rpx;
color: $uni-color-primary;
&__disabled{
color: $uni-text-color-disable;
}
}
&--btn{
width: 650rpx;
font-size: $uni-font-size-lg;
margin: 60rpx 50rpx 40rpx;
background: $uni-color-primary;
color: $uni-text-color-inverse;
}
}
</style>

@ -0,0 +1,129 @@
<template>
<view class="wrapper">
<BMap ref="mapRef"/>
<BSseMessage @receiveMsg="handleReceiveMsg"/>
<view class="operation">
<template v-if="orderDetail.orderStatus >= ORDER_STATUS.tripFinish ">
<view class="desc" v-if="orderDetail.orderStatus === ORDER_STATUS.tripFinish"></view>
<view v-if="orderDetail.orderStatus === ORDER_STATUS.awaitPay">
<text class="desc">您的行程已结束请您尽快完成支付</text>
<button @click="handlePay" class="btn">{{orderDetail.price}}立即支付</button>
</view>
</template>
<template v-else>
<view class="desc" v-if="orderDetail.orderStatus === ORDER_STATUS.orderStart">...</view>
<view class="desc">
<text>{{orderDetail.vehicleNo}}</text>
<text v-if="orderDetail.orderStatus === ORDER_STATUS.driverReceive"></text>
<text v-if="orderDetail.orderStatus === ORDER_STATUS.driverToPickUp"></text>
<text v-if="orderDetail.orderStatus === ORDER_STATUS.driverArriveStartPoint"></text>
<text v-if="orderDetail.orderStatus === ORDER_STATUS.tripStart"></text>
</view>
<button @click="handleCancel" class="btn btn_cancel">取消订单</button>
</template>
</view>
</view>
</template>
<script setup>
import { onLoad } from "@dcloudio/uni-app"
import { ApiGetCurrentOrder, ApiPostOrderCancel } from "../api/order"
import { HandleApiError } from "../utils";
import { ORDER_STATUS} from '../config/dicts';
import BSseMessage from '../component/BSseMessage.vue';
import BMap from '../component/BMap.vue';
import { onMounted, ref } from "vue";
let $routerQuery = {};
let orderDetail = ref({});
let mapRef = ref(null);
onLoad((option)=>{
$routerQuery = option;
});
onMounted(()=>{
getOrderDetail();
})
const getOrderDetail = async () =>{
const {error, result} = await ApiGetCurrentOrder();
if(!result){
uni.navigateTo({url:'/pages/index/index'})
}else{
orderDetail.value = result;
mapRef.value.driving([result.depLongitude, result.depLatitude], [result.destLongitude, result.destLatitude]);
}
}
/**
* @Description: 取消订单
* @return {*}
*/
const handleCancel = async () =>{
const {error, result} = await ApiPostOrderCancel({orderId: orderDetail.value.id});
if(!HandleApiError(error)){
uni.redirectTo({url:'/pages/index/index'});
}
}
/**
* @Description: 点击支付跳转到支付网页
* @return {*}
*/
const handlePay = () =>{
uni.navigateTo({
url : `/pages/pay?id=${orderDetail.value.id}&price=${orderDetail.value.price}`
})
}
/**
* @Description: 接收订单变更消息
* 暂时只有订单收款时会通知使用价格判断
* 比较完善的消息通知应该有消息分类每个节点变化都通知
* @param {*} msg
* @return {*}
*/
const handleReceiveMsg = (msg) =>{
if(msg.price){
orderDetail.value.price = msg.price;
orderDetail.value.orderStatus = ORDER_STATUS.awaitPay;
}
}
</script>
<style lang="scss" scoped>
.wrapper {
width: 100%;
height: calc(100vh - 30px);
}
.operation{
position: fixed;
bottom: 30rpx;
left: 30rpx;
right: 30rpx;
background: $uni-bg-color;
border-radius: $uni-border-radius-lg;
z-index: 9;
padding: $uni-spacing-max;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
}
.desc{
height: 60rpx;
display: flex;
justify-content: space-between;
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.btn{
font-size: $uni-font-size-lg;
margin-top: $uni-spacing-max;
background: $uni-color-primary;
color: $uni-text-color-inverse;
&__cancel{
background: $uni-bg-color-grey;
color: $uni-text-color;
border: 1px solid $uni-border-color;
}
&::after{
display: none;
}
}
</style>

@ -0,0 +1,17 @@
<template>
<web-view :src="url" />
</template>
<script setup>
import { onLoad } from "@dcloudio/uni-app"
import { ref } from "@vue/reactivity";
import { useStore } from "vuex";
const $store = useStore()
let url = ref()
onLoad((option)=>{
url.value = `${$store.state.serverConf.pay}/alipay/pay?subject=${decodeURIComponent('车费')}&outTradeNo=${option.id}&totalAmount=${option.price}`;
// #ifdef H5
window.location.href = url.value;
// endif
})
</script>

File diff suppressed because one or more lines are too long

@ -0,0 +1,123 @@
/*
* @Author: ch
* @Date: 2022-03-17 16:36:59
* @LastEditors: ch
* @LastEditTime: 2023-01-03 11:23:23
* @Description: 针对uniapp request请求做了一次封装使用思维参考axios
*
*
* 方法
* method(option) 自定义请求同uni.request
* get(url, params, header) url 请求地址 params 请求参数
* header 请求头会针对当前请求头设置特定的请求头传了此参数request拦截器会失效
* post(url, data, header) 同上
* put(url, data, header) 同上
* delete(url, data, header) 同上
* use(hookName, callback) 注入hook拦截器 hookName 拦截器名request/response/error callback拦截器具体见拦截器说明
*
* 属性
* baseUrl 请求地址前缀
*
* 拦截器
* request 请求前拦截在这可统一设置请求头请求体等参数uni.request的第一个参数option都可以重置
* success 请求成功结果拦截
* error 请求错误拦截
*
* 示例
* const myReq = new MsbUniRequest();
* myReq.baseUrl = 'xxxx'
* myReq.use('request', (option)=>{
* // option 返回请求配置
* .....这里可以对option做一系列操作
* return option //最后返回一个正确的请求配置
* })
* myReq.use('success', (res)=>{
* //res 返回请求结果
* let newRes = ..... //这里可以对请求结果做统一处理
* return newRes
* })
*
* myReq.use('error', (error)=>{
* //error 返回错误结果
* let newError = ..... //这里可以对请求错误做统一处理
* return newError
* })
*/
class MsbUniRequest {
constructor (option){
this.baseUrl = '';
this.header = {
repeat : true
}
this.hook = {
request : null,
success : null,
error : null
}
}
method(option){
option.header = {...this.header,...option.header};
option.url = this.baseUrl ? this.baseUrl + option.url : option.url;
if(this.hook.request){
option = this.hook.request(option);
};
if(!option){
throw new Error('没有请求配置或是request拦截未做return');
}
if(option.constructor === Promise){
return option
}
return new Promise((resolve, reject)=>{
uni.request({
...option,
success: res => {
const response = res || res[1];
// 200 - 399状态为正常
if(response.statusCode >= 200 && response.statusCode < 400){
if(!this.hook.success){
resolve(response);
}else{
let newRes = this.hook.success(response, option);
// 业务结果处理可能为一个Promise对象根据结果调用错误或正确状态
if(newRes && newRes.constructor === Promise){
newRes.then(res => {
resolve(res);
}, error =>{
reject(error);
})
}else{
resolve(newRes);
}
}
return false;
}
reject(this.hook.error ? this.hook.error(response, option) : response);
},
fail: error =>{
reject(this.hook.error ? this.hook.error(error, option) : error);
}
});
});
}
use(hookName, cb){
this.hook[hookName] = cb;
}
get(url, data, header){
return this.method({method : 'GET', url, data, header});
}
post(url, data, header){
return this.method({method : 'POST', url, data, header});
}
put(url, data, header){
return this.method({method : 'PUT', url, data, header});
}
delete(url, data, header){
return this.method({method : 'DELETE', url, data, header});
}
}
export default MsbUniRequest;

@ -0,0 +1,125 @@
/*
* @Author: ch
* @Date: 2022-03-17 17:42:32
* @LastEditors: ch
* @LastEditTime: 2022-12-30 15:44:40
* @Description: 项目接口请求统一处理器返回一个需要token和不需要token的请求封装方法
*/
import MsbUniRequest from './msbUniRequest';
import $store from '@/store';
import { _ToAsyncAwait } from '@gykeji/jsutil';
// const ENV = process.env;
/**
* 接口返回成功结果统一处理
* @param {*} response
* @param {*} option
*/
const successIntercept = (response, option) =>{
clearRepeat(option);
if(response.statusCode === 200){
const result = response.data;
if(result.code === 1){
return result.data;
}
if(result.code === 0){
uni.navigateTo({url : '/login'});
$store.commit('setToken', '');
return result;
}
return Promise.reject(result);
}
return response;
}
/**
* 接口返回错误结果统一处理
* @param {*} error
* @param {*} option
*/
const errorIntercept = (error, option) =>{
clearRepeat(option);
if(error.statusCode === 404){
error.errMsg = '404 请求地址未找到';
}
return {message:error.errMsg,code:error.statusCode}
}
//正在执行的请求标识
let repeatFlag = [];
/**
* 验证是否重复请求没有则添加一条到标记
* @param {*} option
*/
const repeatVerify = (option)=>{
let flag = {
url : option.url,
method : option.method,
data : option.data
}
if(repeatFlag.includes(JSON.stringify(flag))){
return Promise.reject({message:'请勿频繁操作'});
}
repeatFlag.push(JSON.stringify(flag));
return false;
};
/**
* 清除请求标记
* @param {*} option
*/
const clearRepeat = (option) =>{
repeatFlag = repeatFlag.filter( i => {
return i !== JSON.stringify({
url : option.url,
method : option.method,
data : option.data
})
});
}
// 接口封装
const Request = new MsbUniRequest();
Request.baseUrl = $store.state.serverConf.other;
Request.use('request', (option) => {
const token = $store.state.token;
if(!token && !option.header.notVerifyToken){
// 登录状态处理没有token直接跳转至登录
uni.redirectTo({
url: '/pages/login'
});
return Promise.reject({message:'要先登录才能操作哦~'});
}
if(!option.header.notVerifyToken){
option.header = {...option.header, Authorization:token};
}
if(option.header.repeat){
// 如果当前请求不允许重复调用,则检查重复请求,当前接口有正在请求则不发起请求
const isRepeatVerify = repeatVerify(option);
if(isRepeatVerify){
return isRepeatVerify;
}
}
delete option.header.repeat
delete option.header.notVerifyToken
return option;
})
Request.use('success', successIntercept);
Request.use('error', errorIntercept);
const MsbRequest = {
get : (...args) => _ToAsyncAwait(Request.get(...args)),
post: (...args) => _ToAsyncAwait(Request.post(...args)),
put: (...args) => _ToAsyncAwait(Request.put(...args)),
delete: (...args) => _ToAsyncAwait(Request.delete(...args)),
}
export {
MsbRequest
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

@ -0,0 +1,67 @@
<!--
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-19 17:45:07
* @LastEditors: ch
* @LastEditTime: 2022-12-30 15:30:32
* @Description: file content
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<title>-</title>
</head>
<script type="text/javascript" src="./uniwebview.js"></script>
<script type="text/javascript">
const identity = 1;
let routerQuery = getQuery();
let source = null;
if(window.EventSource){
console.info("此浏览器支持SSE");
source = new EventSource(`${routerQuery.uri}/connect?userId=${routerQuery.userId}&identity=${identity}`);
// 监听服务的推送的消息
source.addEventListener("message",function (e){
content = e.data;
setMessageContent(content);
});
}else {
setMessageContent("此浏览器不支持");
}
function setMessageContent(content){
uni.postMessage({
data: JSON.parse(content)
});
}
/**
* @Description: 获取url参数
* @return {*}
*/
function getQuery (){
let queryArr = window.location.search.substr(1).split('&');
let obj = {};
queryArr.forEach(item => {
const arr = item.split('=');
obj[arr[0]] = decodeURIComponent(arr[1]);
})
return obj;
}
// 暂未调用
function sourceClose(){
console.info("close方法执行");
// 客户端source的关闭
source.close();
// 服务端map的移除
httpRequest = new XMLHttpRequest();
httpRequest.open("get","http://localhost:9000/close?userId="+userId+"&identity="+identity);
httpRequest.send();
}
</script>
</html>

File diff suppressed because one or more lines are too long

@ -0,0 +1,38 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-13 17:29:11
* @LastEditors: ch
* @LastEditTime: 2022-12-30 16:24:03
* @Description: file content
*/
import { createStore } from 'vuex';
import STORAGE_KEY from '../config/storageKey';
import SERVER_CONF from '../config/serverConf'
import gdMapConf from '../config/gdMapConf';
const serverConf = uni.getStorageSync(STORAGE_KEY.serverConf);
export default createStore({
state: {
city : gdMapConf.city,
token : uni.getStorageSync(STORAGE_KEY.token) || '',
userInfo : JSON.parse(uni.getStorageSync(STORAGE_KEY.userInfo) || '{}'),
serverConf : serverConf ? JSON.parse(serverConf) : SERVER_CONF
},
mutations: {
setCity(state, data) {
state.city = data;
},
setToken (state, token = ''){
state.token = token;
uni.setStorageSync(STORAGE_KEY.token, token);
},
setUserInfo (state, userInfo = {}){
state.userInfo = userInfo;
uni.setStorageSync(STORAGE_KEY.userInfo, JSON.stringify(userInfo));
},
setServerConf (state, config){
state.serverConf = config;
uni.setStorageSync(STORAGE_KEY.serverConf, JSON.stringify(config));
}
}
});

@ -0,0 +1,86 @@
/**
* uni-app
*
* uni-app https://ext.dcloud.net.cn使
* 使scss使 import 便App
*
*/
/**
* App使
*
* 使scss scss 使 import
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
$uni-color-red: #f00;
/* 文字基本颜色 */
$uni-text-color: #333; //
$uni-text-color-inverse: #fff; //
$uni-text-color-grey: #999; //
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color: #fff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; //
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //
/* 边框颜色 */
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm: 12px;
$uni-font-size-base: 14px;
$uni-font-size-lg: 16px;
/* 图片尺寸 */
$uni-img-size-sm: 20px;
$uni-img-size-base: 26px;
$uni-img-size-lg: 40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 8px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-sm: 5rpx;
$uni-spacing-base: 10rpx;
$uni-spacing-lg: 15rpx;
$uni-spacing-big: 20rpx;
$uni-spacing-max: 30rpx;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
$uni-spacing-row-max: 30rpx;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; //
/* 文章场景相关 */
$uni-color-title: #2c405a; //
$uni-font-size-title: 20px;
$uni-color-subtitle: #555; //
$uni-font-size-subtitle: 18px;
$uni-color-paragraph: #3f536e; //
$uni-font-size-paragraph: 15px;

@ -0,0 +1,23 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-12-19 10:44:39
* @LastEditors: ch
* @LastEditTime: 2022-12-27 15:47:26
* @Description: file content
*/
const HandleApiError = (error, name) =>{
let result = false;
if (error) {
const tip = name ? `${name}错误:` : '';
ShowToast(error.message ? `${tip}${error.message}` : `请求失败: ${error}`);
result = true;
}
return result;
}
const ShowToast = (str) =>{
uni.showToast({title:str,duration:3000, icon:'none'});
}
export {
HandleApiError,
ShowToast
}

@ -0,0 +1,38 @@
/*
* @Author: ch cwl_ch@163.com
* @Date: 2022-11-21 17:23:51
* @LastEditors: ch
* @LastEditTime: 2022-12-30 16:32:05
* @Description: file content
*/
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import fs from 'fs';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
uni(),
],
server: {
// 浏览器本地开发代理配置
proxy: {
"/api": {
target: "http://192.168.40.193:8081",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, "/")
},
"/sseApi": {
target: "http://192.168.40.193:60001",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/sseApi/, "/")
},
"/payApi": {
target: "http://192.168.40.193:9001",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/payApi/, "/")
},
},
},
})
Loading…
Cancel
Save