This commit is contained in:
2023-08-15 13:30:58 +08:00
parent c081f0fc29
commit b444858bda
30 changed files with 984 additions and 3 deletions

10
src/api/user.ts Normal file
View File

@@ -0,0 +1,10 @@
import { request } from "../utils/request";
// 登录
export const login = (data: object) => request("/login", data, "POST");
// 获取用户信息
export const getUserInfo = () => request("/user/detail", {}, "GET");
// 支付订单
export const payOrder = (data: object) => request("/order/place", data, "POST");

View File

@@ -1,5 +1,4 @@
import { createApp } from "vue";
import { createPinia } from 'pinia'
import "./app.scss";
@@ -9,6 +8,4 @@ const App = createApp({
// 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
});
App.use(createPinia())
export default App;

91
src/components/Auth.vue Normal file
View File

@@ -0,0 +1,91 @@
<template>
<nut-overlay v-model:visible="visible" :close-on-click-overlay="false">
<view class="text">
<view>授权提醒</view>
<view style="margin-top: 10px"
>请授权头像信息以便为您提供更好的服务</view
>
<view class="bom">
<view class="btn b" @click="onCancel">随便逛逛</view>
<view class="btn a" @tap="onOk">去授权</view>
</view>
</view>
</nut-overlay>
</template>
<script setup lang="ts">
// import { computed, ref } from "vue";
import Taro from "@tarojs/taro";
import { login } from "../api/user";
defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "update:info"]);
const onCancel = () => {
emit("update:visible", false);
};
const onOk = () => {
Taro.getUserProfile({
desc: "用于完善会员资料",
success: async (user) => {
console.log(user);
Taro.login({
success: async ({ code }) => {
const { data }: any = await login({
code: code,
});
Taro.setStorageSync("token", data.token);
emit("update:visible", false);
},
fail: (err) => {
console.log(err);
},
});
},
fail: (err) => {
console.log(err);
},
});
};
</script>
<style lang="scss">
.text {
text-align: center;
background-color: #fff;
width: 500px;
padding: 20px;
margin: auto;
transform: translateY(150%);
border-radius: 10px;
.bom {
display: flex;
justify-content: space-evenly;
align-items: center;
margin-top: 20px;
.btn {
width: 150px;
padding: 10px;
border-radius: 15px;
color: #fff;
margin-bottom: 10px;
}
.a {
background-color: #ff5000;
}
.b {
background-color: rgba(255, 80, 0, 0.5);
}
}
}
</style>

124
src/components/Pay.vue Normal file
View File

@@ -0,0 +1,124 @@
<template>
<nut-popup
position="bottom"
closeable
round
safe-area-inset-bottom
:close-on-click-overlay="false"
:style="{ height: 'auto' }"
v-model:visible="isShowPay"
@click-close-icon="closePay"
>
<view class="div">
<view style="text-align: center">支付方式</view>
<nut-cell-group>
<nut-cell
v-if="payType === 'wx'"
title="微信支付"
desc="使用微信快捷支付"
is-link
@click="goPay()"
>
<template v-slot:icon>
<IconFont
size="30"
name="https://img11.360buyimg.com/imagetools/jfs/t1/137646/13/7132/1648/5f4c748bE43da8ddd/a3f06d51dcae7b60.png"
/>
</template>
</nut-cell>
<nut-cell
v-if="payType === 'jf'"
title="积分支付"
desc="剩余积分:18888"
is-link
@click="goPay()"
>
<template v-slot:icon>
<IconFont
size="30"
name="https://img11.360buyimg.com/imagetools/jfs/t1/137646/13/7132/1648/5f4c748bE43da8ddd/a3f06d51dcae7b60.png"
/>
</template>
</nut-cell>
</nut-cell-group>
</view>
</nut-popup>
</template>
<script setup lang="ts">
import {IconFont} from "@nutui/icons-vue-taro";
import Taro from "@tarojs/taro";
import {payOrder} from "../api/user";
const prop = defineProps({
isShowPay: {
required: true,
type: Boolean,
default: false,
},
payType: {
required: true,
type: String,
default: "wx",
},
});
const emit = defineEmits(["errPay", "closePay"]);
const goPay = async () => {
if (prop.payType === "wx") {
const {data} = await payOrder({gid: 1, token: Taro.getStorageSync("token")});
console.log(data);
// Taro.request({
// url: "http://192.168.2.3:9000/app/order/place",
// method: "POST",
// data: {
// gid: 1,
// token: Taro.getStorageSync("token"),
// },
// success: function ({data}) {
// console.log(data);
// Taro.requestPayment({
// timeStamp: data.data.data.timeStamp,
// nonceStr: data.data.data.nonceStr,
// package: data.data.data.package,
// signType: "MD5",
// paySign: data.data.data.paySign,
// success: function (res) {
// console.log(res);
// },
// fail: function (res) {
// console.log("1111", res);
// emit("errPay", false);
// },
// });
// },
// fail: function (res) {
// console.log("1111", res);
// emit("errPay", false);
// },
// });
}
};
const closePay = () => {
emit("closePay", false);
};
</script>
<style lang="scss">
.nut-popup {
.nut-popup__container {
border-radius: 10px;
}
}
.div {
padding: 20px;
// text-align: center;
.nut-cell {
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: "订单核销",
});

View File

@@ -0,0 +1,61 @@
<template>
<view>
<view class="card">
<nut-button block type="primary" @click="scanCode"
>扫码核销</nut-button
>
</view>
</view>
</template>
<script setup lang="ts">
import Taro from "@tarojs/taro";
// url参数转对象
const urlParse = (url: string) => {
const obj: any = {};
const reg = /[?&][^?&]+=[^?&]+/g;
const arr = url.match(reg);
if (arr) {
arr.forEach((item) => {
const tempArr = item.substring(1).split("=");
const key = decodeURIComponent(tempArr[0]);
const val = decodeURIComponent(tempArr[1]);
obj[key] = val;
});
}
return obj;
};
const scanCode = () => {
Taro.scanCode({
onlyFromCamera: true,
scanType: ["qrCode"],
success: (res) => {
console.log(urlParse(res.result));
},
});
};
</script>
<style lang="scss">
page {
background-color: #f5f5f5;
background-image: url("../../../static/admin/cancellation-header.png");
background-repeat: no-repeat;
background-size: 100% 30%;
}
.card {
width: 80%;
height: 300px;
box-sizing: border-box;
background-color: #fff;
margin: 300px auto;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 100px;
}
</style>

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '商品详情',
navigationStyle: 'custom',
})

View File

@@ -0,0 +1,293 @@
<template>
<view class="app">
<view class="head-wrapper" :style="'top:' + BarHeight + 'px'">
<view class="head-menu">
<Left class="iconfont" @click="returns" />
<Home class="iconfont" @click="goHome" />
</view>
</view>
<!-- 幻灯片 -->
<nut-swiper
:init-page="0"
:pagination-visible="true"
pagination-color="#426543"
auto-play="3000"
>
<nut-swiper-item v-for="(itm, idx) in swiperList" :key="idx">
<img :src="itm.url" :alt="itm.id.toString()" />
</nut-swiper-item>
</nut-swiper>
<!-- 标题价格 -->
<view class="card">
<view>
<nut-price
size="large"
:price="8888"
position="after"
symbol="积分"
/>
</view>
<view class="title"
>MIUCHO可爱卡通学生通勤手提电脑包13.3寸14村女适用苹果联想小米</view
>
</view>
<nut-cell title="请选择规格: " is-link @click="openSku"></nut-cell>
<!-- 产品介绍 -->
<view class="rich-box">
<view class="title">产品介绍</view>
<view></view>
<nut-empty description="暂无产品介绍"></nut-empty>
</view>
<!-- 底部 -->
<view class="bottom-box">
<view class="left">
<view class="icon" @click="toPage('/pages/index/index')">
<Home />
<view>首页</view>
</view>
<view class="icon" @click="toPage('/pages/cart/index')">
<Cart />
<view>购物车</view>
</view>
<view class="icon" @click="toPage('/pages/kefu/index', 2)">
<My />
<view>客服</view>
</view>
</view>
<!-- 占位 -->
<view style="height: 155px"></view>
<view>
<nut-button
style="margin-right: 10px"
type="warning"
@click="addCart()"
>加入购物车</nut-button
>
<nut-button type="primary" @click="toOrderDetail()"
>立即兑换</nut-button
>
</view>
</view>
<!-- 规格选择 -->
<nut-popup
position="bottom"
overlay-class="overlay"
safe-area-inset-bottom
closeable
round
:style="{ height: '45%', zIndex: 1 }"
v-model:visible="isSkuShow"
></nut-popup>
<!-- <nut-sku
v-model:visible="isSkuShow"
:sku="sku"
:goods="goods"
@selectSku="selectSku"
@clickBtnOperate="clickBtnOperate"
@close="close"
></nut-sku> -->
</view>
</template>
<script setup lang="ts">
var statusBarHeight = Taro.getSystemInfoSync().statusBarHeight;
import { ref } from "vue";
import Taro from "@tarojs/taro";
import { Left, Home, Cart, My } from "@nutui/icons-vue-taro";
const BarHeight = ref((statusBarHeight as number) + 7);
const swiperList = ref([
{
id: 1,
url: "https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg",
},
{
id: 2,
url: "https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg",
},
{
id: 3,
url: "https://storage.360buyimg.com/jdc-article/welcomenutui.jpg",
},
{
id: 4,
url: "https://storage.360buyimg.com/jdc-article/fristfabu.jpg",
},
]);
const isSkuShow = ref(false);
const sku = ref([]);
const goods = ref({});
const returns = () => {
Taro.navigateBack({
delta: 1,
});
};
const goHome = () => {
Taro.switchTab({
url: "/pages/index/index",
});
};
const openSku = () => {
isSkuShow.value = true;
};
const selectSku = () => {};
const clickBtnOperate = () => {};
const close = () => {};
const addCart = () => {
if (isSkuShow.value === false) return openSku();
Taro.showToast({
title: "加入购物车成功",
icon: "none",
});
isSkuShow.value = false;
};
const toOrderDetail = () => {
if (isSkuShow.value === false) return openSku();
Taro.navigateTo({
url: "/pages/goods/order_create/index",
});
isSkuShow.value = false;
};
const toPage = (url: string, type: number = 1) => {
if (type === 1) {
Taro.switchTab({
url: url,
});
} else {
// Taro.navigateTo({
// url: url,
// });
Taro.showToast({
title: "暂未开放",
icon: "none",
});
}
};
</script>
<style lang="scss">
// sku遮罩
.overlay {
background-color: rgba(0, 0, 0, 0.5);
z-index: 1 !important;
}
.head-wrapper {
z-index: 999;
display: flex;
align-items: center;
position: fixed;
left: 30rpx;
top: 0;
/* #ifdef MP */
// height: 43px;
/* #endif */
/* #ifdef H5 */
height: 114rpx;
/* #endif */
}
.head-menu {
display: flex;
align-items: center;
height: 54rpx;
width: 140rpx;
background: rgba(0, 0, 0, 0.25);
border-radius: 27rpx;
.iconfont {
flex: 1;
text-align: center;
color: #fff;
box-sizing: border-box;
}
}
.nut-swiper-item img {
width: 100%;
height: 450px;
}
.card {
padding: 30px;
background-color: #fff;
.title {
font-size: 30px;
font-weight: bold;
color: #333;
}
}
.rich-box {
background-color: #fff;
margin-top: 20px;
width: 100vw;
box-sizing: border-box;
text-align: center;
padding: 15px 0;
.title {
font-size: 30px;
font-weight: bold;
color: #333;
}
}
.nut-sku {
// 适配ios底部安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.bottom-box {
border-top: 1px solid #e5e5e585;
position: fixed;
bottom: 0;
height: 5.5vh;
background: #fff;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
// 适配ios底部安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
z-index: 999;
.left {
display: flex;
align-items: center;
justify-content: space-between;
width: 300px;
padding: 0 20px;
box-sizing: border-box;
.icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #333;
font-size: 25px;
}
}
.icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #333;
font-size: 25px;
}
}
</style>

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: "提交订单"
});

View File

@@ -0,0 +1,183 @@
<template>
<view class="app">
<view class="bg">
<view class="card">
<view class="left">
<view>嘎嘎酒吧</view>
<view>18888888888</view>
<view>广西壮族自治区南宁市江南区</view>
</view>
<view class="right" @click="toLocal">
<Find class="icon" />
<view class="text">距离我{{ distance }}km</view>
</view>
</view>
</view>
<view class="order-info">21312312</view>
<nut-form>
<nut-form-item body-align="right" label="用户姓名">
<nut-input
:border="false"
v-model="basicData.name"
placeholder="请输入姓名"
type="text"
/>
</nut-form-item>
<nut-form-item body-align="right" label="联系电话">
<nut-input
:border="false"
v-model="basicData.phone"
placeholder="请输入联系电话"
type="text"
/>
</nut-form-item>
<nut-form-item body-align="right" label="订单备注">
<nut-textarea
class="nut-input-text"
v-model="basicData.notes"
placeholder="请输入备注"
type="text"
/>
</nut-form-item>
</nut-form>
<!-- 底部 -->
<view class="bottom-box">
<view
>总计<nut-price
:price="8888.01"
position="after"
symbol="积分"
/></view>
<nut-button type="primary" @click="orderPay">提交订单</nut-button>
</view>
</view>
</template>
<script setup lang="ts">
import { Find } from "@nutui/icons-vue-taro";
import Taro from "@tarojs/taro";
import { calculateDistance } from "../../../utils";
import { ref } from "vue";
const distance = ref("");
const basicData = ref({
name: "",
phone: "",
notes: "",
});
Taro.useLoad(() => {
Taro.getLocation({
type: "wgs84",
success: function (res) {
const latitude = res.latitude;
const longitude = res.longitude;
distance.value = calculateDistance(
108.24898,
22.83646,
longitude,
latitude
);
},
});
});
const toLocal = () => {
Taro.openLocation({
latitude: 22.83646,
longitude: 108.24898,
scale: 18,
});
};
const orderPay = () => {
Taro.showToast({
title: "支付成功",
icon: "success",
success: () => {
setTimeout(() => {
Taro.redirectTo({
url: "/pages/goods/order_status/index",
});
}, 2000);
},
});
};
</script>
<style lang="scss">
.bg {
background: linear-gradient(0deg, #f5f5f5, #0396ffc7);
background-size: 100% 100%;
background-repeat: no-repeat;
padding: 10px 0;
.card {
width: 90%;
height: 75%;
margin: 0 auto;
background: #fff;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
position: relative;
border-bottom: 1px dashed #ccc;
display: flex;
align-items: center;
justify-content: space-evenly;
.left {
width: 70%;
color: #333;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.right {
width: 30%;
text-align: center;
.icon {
font-size: 50px;
}
.text {
font-size: 25px;
color: #999;
}
}
}
}
.order-info {
background: #fff;
padding: 20px;
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
margin-bottom: 10px;
}
.nut-input-text {
height: 100px;
}
.bottom-box {
position: fixed;
bottom: 0;
width: 100%;
box-sizing: border-box;
height: 9vh;
background: #fff;
// IOS安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
}
</style>

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: "订单状态",
});

View File

@@ -0,0 +1,97 @@
<template>
<view>
<view class="card">
<view class="icon">
<Check
font-class-name="nutui-iconfont check"
size="70"
color="#fff"
/>
<!-- <CloseLittle font-class-name="nutui-iconfont check" size="70" color="#fff" /> -->
</view>
<view class="text-box">
<view class="title">支付成功</view>
<nut-button block type="primary" @click="toOrderPage"
>查看订单</nut-button
>
<nut-button
block
plain
style="margin-top: 10px"
type="primary"
@click="toHome"
>返回首页</nut-button
>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { Check, CloseLittle } from "@nutui/icons-vue-taro";
import Taro from "@tarojs/taro";
const toHome = () => {
Taro.switchTab({
url: "/pages/index/index",
});
};
const toOrderPage = () => {
Taro.redirectTo({
url: "/pages/users/order_list/index?type=0",
});
};
</script>
<style lang="scss">
page {
background-color: #f5f5f5;
}
.card {
width: 90%;
height: 600px;
box-sizing: border-box;
background-color: #fff;
border-radius: 10px;
margin: 200px auto;
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
flex-direction: column;
.icon {
width: 200px;
height: 200px;
background-color: red;
border-radius: 50%;
position: absolute;
top: -100px;
left: 50%;
transform: translateX(-50%);
border: #f5f5f5 10px solid;
text-align: center;
.check {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.text-box {
text-align: center;
width: 100%;
box-sizing: border-box;
padding: 0 100px;
.title {
font-size: 60px;
color: #333;
margin: 100px;
}
}
}
</style>

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '订单详情'
})

View File

@@ -0,0 +1,7 @@
<template>
<view>这里是订单详情</view>
</template>
<script setup lang="ts"></script>
<style lang="scss"></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/static/merchantBg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/static/tabbar/1-001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
src/static/tabbar/1-002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
src/static/tabbar/2-001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/static/tabbar/2-002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
src/static/tabbar/3-001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/static/tabbar/3-002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
src/static/tabbar/4-001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
src/static/tabbar/4-002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

29
src/utils/index.ts Normal file
View File

@@ -0,0 +1,29 @@
// 经纬度计算距离
export function calculateDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): string {
const R = 6371; // 地球平均半径(单位:千米)
const dLat = toRadians(lat2 - lat1);
const dLon = toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRadians(lat1)) *
Math.cos(toRadians(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
return distance.toFixed(2);
}
// 将角度转换为弧度
function toRadians(degrees: number): number {
return (degrees * Math.PI) / 180;
}

46
src/utils/request.ts Normal file
View File

@@ -0,0 +1,46 @@
import Taro from "@tarojs/taro";
const BASE_URL = () => {
if (process.env.NODE_ENV === "development") {
return "http://192.168.2.3:9000";
} else {
return "https://api.imooc.hybrid.lgdsunday.club";
}
};
// interface Res {
//
// }
type Method = "GET" | "POST" | "PUT" | "DELETE";
export const request = (
url: string,
data: object = {},
method: Method = "GET"
): Promise<any> => {
return new Promise((resolve, reject) => {
Taro.request({
url: BASE_URL() + "/app" + url,
data: data,
method: method,
header: {
"content-type": "application/json",
token: Taro.getStorageSync("token"),
},
success: ({data}) => {
console.log(data);
if (data.code !== 200)
return reject({code: 1, msg: data.msg});
resolve(data);
},
fail: () => {
Taro.showToast({
title: "服务器异常",
icon: "none",
});
reject({code: 1, msg: "服务器异常"});
},
});
});
};