商品详情

This commit is contained in:
2024-03-08 18:06:33 +08:00
parent 2df1a93e6b
commit 107783062b
25 changed files with 675 additions and 63 deletions

View File

@@ -5,7 +5,7 @@ VITE_APP_TITLE='uniapp-vue3-project'
VITE_APP_ENV='development'
# 接口地址
VITE_APP_BASE_API='https://apidev.lakeapp.cn/'
VITE_APP_BASE_API='https://apidev.lakeapp.cn/ext/'
# 删除console
VITE_DROP_CONSOLE=false

View File

@@ -14,6 +14,7 @@ const WX_DESC = envType == 'production' ? '正式环境' : '测试环境'
(async () => {
// @ts-ignore
const manifest = path.resolve(__dirname, './src/manifest.json')
console.log(manifest)
// @ts-ignore
const manifestConfig = JSON.parse(fs.readFileSync(manifest).toString())
const appId = manifestConfig['mp-weixin'].appid

14
src/api/goods/index.ts Normal file
View File

@@ -0,0 +1,14 @@
import { get } from '@/utils';
enum URL {
CategoryList = '/goods/category_list',
GoodsList = '/goods/category_list',
GoodsDetail = '/goods/info',
Order = '/user/logout',
}
export const getCategoryList = () => get({ url: URL.CategoryList });
export const getGoodsList = () => get({ url: URL.GoodsList });
export const getGoodsDetail = () => get({ url: URL.GoodsDetail });

View File

@@ -1,18 +1,19 @@
/**
* 用户信息相关接口
*/
import type { LoginByCodeParams, LoginParams, LoginResult } from './types';
import type { LoginParams, LoginResult } from './types';
import { get, post } from '@/utils/request';
import type { UserState } from '@/store/modules/user/types';
enum URL {
login = '/user/login',
loginByCode = '/user/loginByCode',
// login = '/member/login',
login = 'wechat/LoginByMa',
loginByCode = 'wechat/LoginByMaCode',
logout = '/user/logout',
profile = '/user/profile',
}
export const getUserProfile = () => get<UserState>({ url: URL.profile });
export const login = (data: LoginParams) => post<LoginResult>({ url: URL.login, data });
export const loginByCode = (data: LoginByCodeParams) => post<any>({ url: URL.loginByCode, data });
export const loginByCode = (code: string, companyId: string) => post<any>({ url: URL.loginByCode + `?code=${code}` });
export const logout = () => post<any>({ url: URL.logout });

View File

@@ -1,6 +1,8 @@
export interface LoginParams {
phone: string;
// phone: string;
code: string;
userInfo: any;
referrerUserId: string;
}
export interface LoginByCodeParams {

View File

@@ -1,11 +1,10 @@
{
"name": "SUKE-MP",
"name": "SURE-MP",
"appid": "",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
/* 5+App */
"app-plus":
{
"usingComponents": true,
@@ -18,12 +17,11 @@
"autoclose": true,
"delay": 0
},
/* */
"modules": {}
},
/* */
"mp-weixin":
{
// "appid": "wx92e663dc11d0c0a8",
"appid": "wx67a750d0ceed4d88",
"setting":
{

View File

@@ -59,6 +59,17 @@
}
]
},
{
"root": "pages/mall/subs",
"pages": [
{
"path": "goods/goods-detail",
"style": {
"navigationBarTitleText": "商品详情"
}
}
]
},
{
"root": "pages/mine/subs",
"pages": [
@@ -93,7 +104,7 @@
}
},
{
"path": "trade/index",
"path": "trade/trade-list",
"style": {
"navigationBarTitleText": "消费记录"
}

View File

@@ -1,47 +1,48 @@
<template>
<view>
<view class="login-form-wrap">
<view class="title">
<view class='login-form-wrap'>
<view class='title'>
欢迎登录
</view>
<input v-model="tel" class="u-border-bottom" type="number" placeholder="请输入手机号">
<view class="u-border-bottom my-40rpx flex">
<input v-model="code" class="flex-1" type="number" placeholder="请输入验证码">
<input v-model='tel' class='u-border-bottom' type='number' placeholder='请输入手机号'>
<view class='u-border-bottom my-40rpx flex'>
<input v-model='code' class='flex-1' type='number' placeholder='请输入验证码'>
<view>
<u-code ref="uCodeRef" @change="codeChange" />
<u-button :text="tips" type="success" size="mini" @click="getCode" />
<u-code ref='uCodeRef' @change='codeChange' />
<u-button :text='tips' type='success' size='mini' @click='getCode' />
</view>
</view>
<button :style="[inputStyle]" class="login-btn" @tap="submit">
<button :style='[inputStyle]' class='login-btn' @tap='submit'>
登录
</button>
<view class="alternative">
<view class="password">
<view class='alternative'>
<view class='password'>
密码登录
</view>
<view class="issue">
<view class='issue'>
遇到问题
</view>
</view>
</view>
<view class="login-type-wrap">
<view class="item wechat">
<view class="icon">
<u-icon size="35" name="weixin-fill" color="rgb(83,194,64)" />
<view class='login-type-wrap'>
<view class='item wechat' @click.stop='wechatLogin'>
<view class='icon'>
<u-icon size='35' name='weixin-fill' color='rgb(83,194,64)' />
</view>
微信
<!-- <button open-type='getUserInfo' @getuserinfo='bindGetUserInfo'>确认授权</button>-->
</view>
<view class="item QQ">
<view class="icon">
<u-icon size="35" name="qq-fill" color="rgb(17,183,233)" />
<view class='item QQ'>
<view class='icon'>
<u-icon size='35' name='qq-fill' color='rgb(17,183,233)' />
</view>
QQ
</view>
</view>
<view class="hint">
<view class='hint'>
登录代表同意
<text class="link">
<text class='link'>
用户协议隐私政策
</text>
并授权使用您的账号信息如昵称头像收获地址以便您统一管理
@@ -49,10 +50,12 @@
</view>
</template>
<script setup lang="ts">
<script setup lang='ts'>
import uCode from 'uview-plus/components/u-code/u-code.vue';
import { setToken } from '@/utils/auth';
import { useUserStore } from '@/store';
const userStore = useUserStore();
const tel = ref<string>('18502811111');
const code = ref<string>('1234');
const tips = ref<string>();
@@ -75,7 +78,7 @@ function getCode() {
if(uCodeRef.value?.canGetCode) {
// 模拟向后端请求验证码
uni.showLoading({
title: '正在获取验证码',
title: '正在获取验证码'
});
setTimeout(() => {
uni.hideLoading();
@@ -83,12 +86,15 @@ function getCode() {
// 通知验证码组件内部开始倒计时
uCodeRef.value?.start();
}, 1000);
}
else {
} else {
uni.$u.toast('倒计时结束后再发送');
}
}
function wechatLogin() {
userStore.authLogin();
}
function submit() {
if(uni.$u.test.mobile(tel.value)) {
setToken('1234567890');
@@ -97,7 +103,7 @@ function submit() {
}
</script>
<style lang="scss" scoped>
<style lang='scss' scoped>
.login-form-wrap {
margin: 80rpx auto 0;
width: 600rpx;

View File

@@ -18,7 +18,8 @@
<scroll-view class='goods-list' :scroll-y='true' type='custom'>
<grid-view type='masonry' :cross-axis-count='2'>
<view v-for='(item, index) in goodsList' :key='index' class='goods-item'>
<view v-for='(item, index) in goodsList' :key='index' class='goods-item'
@click.stop='goPath(`/pages/mall/subs/goods/goods-detail?goodsId=${item.id}`)'>
<image class='goods-image' :src='item.goodsImage' />
<text class='goods-name'>{{ item.goodsName }}</text>
<text class='goods-price'>¥{{ item.goodsPrice }}</text>
@@ -37,6 +38,8 @@
<script setup lang='ts'>
import { assetsUrl } from '@/utils/assets';
import { goPath } from '@/utils';
import { getCategoryList } from '@/api/goods';
const categoryList = ref<string[]>(['女装', '男装', '鞋包配饰', '母婴', '美妆个护', '运动户外', '户外运动', '户外运动']);
const categorySelectedIndex = ref<number>(0);
@@ -71,6 +74,16 @@ const goodsList = ref<any[]>([
}
]);
onLoad(() => {
fetchCategoryList();
});
const fetchCategoryList = () => {
getCategoryList().then(res => {
});
};
const changeCategory = (index: number) => {
categorySelectedIndex.value = index;
};

View File

@@ -0,0 +1,329 @@
<template>
<view class='content'>
<swiper class='swiper' :interval='1500' :duration='1000'>
<swiper-item v-for='(item,index) in bannerList' :key='index'>
<image src='/static/images/test_bg.png' />
</swiper-item>
</swiper>
<view class='goods-info-view c-flex-column'>
<view class='c-flex-row'>
<text class='goods-price accent-text-color'>39.89</text>
<text>销量2653</text>
</view>
<view class='c-flex-row'>
<text>女童夏装套装洋气装短袖阔腿裤子夏装夏装套装</text>
<view class='share-button c-flex-column'>
<image :src='assetsUrl("ic_share.png")'></image>
<text>分享</text>
</view>
</view>
</view>
<view class='goods-sku-view c-flex-column'>
<view class='c-flex-row'>
<text>选择</text>
<text>规格 颜色/尺码</text>
<image :src='assetsUrl("ic_arrow_right_gray.png")' />
<text>共1种颜色可选</text>
</view>
<view class='divider' style='height: 0.5rpx;margin-left: 100rpx' />
<view class='c-flex-row'>
<text>参数</text>
<text>品牌 风格 季节 款号</text>
<image :src='assetsUrl("ic_arrow_right_gray.png")' />
</view>
</view>
<view class='recommend-view c-flex-column'>
<text>浏览此商品的客户还浏览了</text>
<scroll-view scroll-x>
<view style='display: inline-block' v-for='(item, index) in [1, 2, 3, 4]'
:key='index'>
<view class='recommend-item c-flex-column'>
<image :src='assetsUrl("test_bg.png")' />
<text>女童夏装套装</text>
<text class='goods-price'>22</text>
</view>
</view>
</scroll-view>
</view>
<view class='bottom-view c-flex-row'>
<view class='small-button-view'>
<view class='small-button-item'>
<image :src='assetsUrl("ic_goods_store.png")' />
<text>商家</text>
</view>
<view class='small-button-item'>
<image :src='assetsUrl("ic_goods_order.png")' />
<text>订单</text>
</view>
<view class='small-button-item'>
<view class='shoppingcart-count'>
<image :src='assetsUrl("ic_goods_shoppingcart.png")' />
<text>12</text>
</view>
<text>购物车</text>
</view>
</view>
<view class='primary-button-view c-flex-row'>
<view class='add-shoppingcart-button'>加入购物车</view>
<view class='place-order-button'>立即下单</view>
</view>
</view>
</view>
</template>
<script lang='ts' setup>
import { assetsUrl } from '@/utils/assets';
const bannerList = ref([1, 2, 3, 4]);
</script>
<style lang='scss' scoped>
.content{
padding-bottom: 200rpx;
}
.swiper {
display: flex;
width: 100%;
height: 750rpx;
image {
width: 100%;
height: 750rpx;
}
}
.goods-info-view {
background: #FFFFFF;
padding: 20rpx 30rpx;
view:nth-of-type(1) {
text:nth-of-type(1) {
display: flex;
font-size: 40rpx;
text-align: center;
align-items: flex-end;
flex: 1;
}
.goods-price:before {
content: "¥";
font-size: 30rpx;
margin-bottom: 5rpx;
margin-right: 5rpx;
}
text:nth-of-type(2) {
font-size: 26rpx;
font-weight: 400;
color: #999999;
}
}
view:nth-of-type(2) {
text {
font-weight: bold;
font-size: 32rpx;
color: #333333;
margin-right: 10rpx;
}
.share-button {
align-items: center;
text {
font-size: 24rpx;
color: #636566;
white-space: nowrap;
}
image {
width: 36rpx;
height: 32rpx;
}
}
}
}
.goods-sku-view {
background: #FFFFFF;
padding: 0 30rpx;
margin-top: 20rpx;
view:nth-of-type(n+1) {
position: relative;
align-items: center;
height: 105rpx;
text:nth-of-type(1) {
width: 100rpx;
font-weight: 400;
font-size: 28rpx;
color: #999999;
}
text:nth-of-type(2) {
font-weight: 400;
font-size: 28rpx;
color: #333333;
flex: 1;
}
image {
width: 13rpx;
height: 24rpx;
}
text:nth-of-type(3) {
font-weight: 400;
font-size: 24rpx;
color: #999999;
position: absolute;
top: 80rpx;
left: 100rpx;
}
}
view:nth-of-type(1) {
align-items: flex-start;
padding-top: 40rpx;
}
}
.recommend-view {
background: #FFFFFF;
padding: 20rpx 30rpx;
margin-top: 20rpx;
text {
font-weight: bold;
font-size: 28rpx;
color: #333333;
}
scroll-view {
width: 100%;
margin-top: 20rpx;
white-space: nowrap;
height: 300rpx;
}
.recommend-item {
margin-right: 20rpx;
image {
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
}
text:nth-of-type(1) {
font-weight: bold;
font-size: 28rpx;
color: #333333;
margin-top: 20rpx;
}
text:nth-of-type(2) {
font-weight: bold;
font-size: 28rpx;
color: #F32B2B;
}
.goods-price:before {
content: "¥";
font-size: 28rpx;
}
}
}
.bottom-view {
background: #FFFFFF;
padding: 20rpx 30rpx 78rpx 33rpx;
position: fixed;
left: 0;
right: 0;
bottom: 0;
.small-button-view {
display: flex;
flex-direction: row;
flex: 1;
justify-content: space-between;
margin-right: 35rpx;
.small-button-item {
display: flex;
flex-direction: column;
align-items: center;
image {
width: 34rpx;
height: 34rpx;
margin-bottom: 10rpx;
}
text {
font-weight: 400;
font-size: 20rpx;
color: #333333;
}
.shoppingcart-count {
display: flex;
position: relative;
text {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: -15rpx;
right: -20rpx;
min-width: 30rpx;
min-height: 30rpx;
background: #F32B2B;
color: #FFFFFF;
border-radius: 15rpx;
border: 2rpx solid #FFFFFF;
}
}
}
}
.primary-button-view {
font-weight: bold;
font-size: 30rpx;
.add-shoppingcart-button {
display: flex;
width: 224rpx;
height: 80rpx;
align-items: center;
justify-content: center;
background: #FFE2E2;
color: #F32B2B;
border-radius: 40rpx 0 0 40rpx;
}
.place-order-button {
display: flex;
width: 198rpx;
height: 80rpx;
align-items: center;
justify-content: center;
background: #F32B2B;
color: #FFFFFF;
border-radius: 0 40rpx 40rpx 0;
}
}
}
</style>

View File

@@ -0,0 +1,11 @@
<template>
</template>
<script lang='ts' setup>
</script>
<style lang='scss' scoped>
</style>

View File

@@ -94,7 +94,7 @@ const serviceList = [
{
title: '消费记录',
icon: assetsUrl('ic_member_service_record.png'),
path: '/pages/mine/subs/trade/index'
path: '/pages/mine/subs/trade/trade-list'
},
{
title: '关注公众号',

View File

@@ -1,13 +1,145 @@
<template>
<view class='card-view'>
<view class='goods-info-view c-flex-row'>
<image class='goods-image' :src='assetsUrl("test_bg.png")' />
<view class='c-flex-column' style='flex: 1;'>
<view class='c-flex-row'>
<text class='goods-name'>女童夏装套装洋气装短袖阔腿裤子夏装套装</text>
<text class='status'>未支付</text>
</view>
<view class='bottom-view c-flex-row'>
<text>
均色120cm
</text>
<text>
共1件
</text>
<text>23.20</text>
</view>
</view>
</view>
<view class='action-view c-flex-row'>
<view class='countdown c-flex-row'>
<image :src='assetsUrl("ic_time.png")' />
<text class='primary-text-color'>支付剩余时间
<text class='accent-text-color'>05:23</text>
</text>
</view>
<view class='c-flex-row'>
<text class='secondary-text-color'>加入购物车</text>
<text class='accent-text-color'>立即支付</text>
</view>
</view>
</view>
</template>
<script lang='ts' setup>
import { assetsUrl } from '@/utils/assets';
defineProps({
item: Object
});
</script>
<style lang='scss' scoped>
.card-view {
padding: 30rpx 20rpx 20rpx 30rpx;
margin: 20rpx 24rpx;
}
.goods-info-view {
display: flex;
flex-direction: row;
position: relative;
.goods-image {
width: 180rpx;
height: 180rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.goods-name {
font-size: 30rpx;
font-weight: 400;
-webkit-line-clamp: 2;
text-overflow: ellipsis;
overflow: hidden;
margin-right: 20rpx;
color: #333333;
flex: 1;
}
.status {
font-size: 26rpx;
font-weight: 400;
color: #F32B2B;
right: 0;
}
.bottom-view {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
text:nth-of-type(1) {
font-size: 26rpx;
font-weight: 400;
flex: 1;
color: #999999;
}
text:nth-of-type(2) {
font-size: 24rpx;
color: #333333;
margin-right: 5rpx;
}
text:nth-of-type(3) {
font-size: 34rpx;
color: #333333;
font-weight: bold;
}
}
}
.action-view {
margin-top: 37rpx;
.countdown {
flex: 1;
image {
width: 37rpx;
height: 37rpx;
margin-right: 5rpx;
}
text {
font-size: 26rpx;
font-weight: 400;
}
}
view:nth-of-type(2) {
text {
display: flex;
align-items: center;
justify-content: center;
padding: 12rpx 28rpx;
font-size: 26rpx;
border-radius: 34rpx;
}
text:nth-of-type(1) {
border: 1rpx solid #ACACAC;
}
text:nth-of-type(2) {
border: 1rpx solid #F32B2B;
margin-left: 20rpx;
}
}
}
</style>

View File

@@ -1,9 +1,13 @@
<template>
<tabbar :titles="['全部', '进行中', '已结束']" @change='args => {}' />
<order-item v-for='(item,index) in orderList' :key='index' :item='item' />
</template>
<script lang='ts' setup>
import OrderItem from '@/pages/mine/subs/order/components/order-item.vue';
const orderList = ref([1, 2, 4, 5]);
</script>
<style lang='scss' scoped>

View File

@@ -0,0 +1,45 @@
<template>
<view class='card-view'>
<text class='category-title'>2023-02-12</text>
<view class='item c-flex-column' v-for='(item,index) in 3' :key='index'>
<view class='c-flex-row'>
<text class='primary-text-color' style='flex: 1'>消费</text>
<text class='accent-text-color'>-30</text>
</view>
<view class='c-flex-row'>
<text class='secondary-text-color' style='flex: 1'>2023-06-27 15:02:11</text>
<text style='color: #999999'>会员余额30.00</text>
</view>
<view class='divider' style='margin-top: 20rpx'/>
</view>
</view>
</template>
<script lang='ts' setup>
defineProps({
item: Object
});
</script>
<style lang='scss' scoped>
.card-view {
display: flex;
flex-direction: column;
padding: 16rpx 23rpx 20rpx 30rpx;
margin: 20rpx 0;
}
.category-title {
font-size: 34rpx;
font-weight: bold;
color: #333333;
}
.item {
margin: 10rpx 0;
view:nth-of-type(2) {
margin-top: 10rpx;
}
}
</style>

View File

@@ -1,11 +0,0 @@
<template>
<tabbar :titles='["全部","充值","消费"]' :item-active-color='"#D95554"' :indicator-color='"#D95554"'/>
</template>
<script lang='ts' setup>
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,52 @@
<template>
<tabbar :titles='["全部","充值","消费"]' :item-active-color='"#D95554"' :indicator-color='"#D95554"' />
<view class='content'>
<view class='c-flex-row'>
<text>选择日期</text>
<picker mode='date' @change='changeDate'>
<view class='current-date c-flex-row'>
<text>2024-02-10</text>
<image :src='assetsUrl("ic_triangle_down.png")' />
</view>
</picker>
</view>
<scroll-view :scroll-y='true'>
<trade-item v-for='(item,index) in tradeList' :key='index' :item='item' />
</scroll-view>
</view>
</template>
<script lang='ts' setup>
import { assetsUrl } from '@/utils/assets';
import TradeItem from '@/pages/mine/subs/trade/components/trade-item.vue';
const tradeList = ref([1, 2, 3, 4, 5]);
const changeDate = () => {
};
</script>
<style lang='scss' scoped>
.content {
padding: 20rpx 24rpx;
}
.current-date {
height: 50rpx;
background: #FFFFFF;
border-radius: 5rpx;
font-size: 28rpx;
font-weight: 400;
color: #333333;
padding: 0 20rpx;
margin-left: 10rpx;
image {
width: 19rpx;
height: 11rpx;
margin-left: 57rpx;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

View File

@@ -2,9 +2,8 @@ import { defineStore } from 'pinia';
import type { UserState, providerType } from './types';
import {
getUserProfile,
loginByCode,
login as userLogin,
logout as userLogout,
logout as userLogout, login
} from '@/api/user/index';
import { clearToken, setToken } from '@/utils/auth';
import type { LoginParams } from '@/api/user/types';
@@ -14,12 +13,12 @@ const useUserStore = defineStore('user', {
user_id: '',
user_name: '江阳小道',
avatar: '',
token: '',
token: ''
}),
getters: {
userInfo(state: UserState): UserState {
return { ...state };
},
}
},
actions: {
// 设置用户的信息
@@ -46,7 +45,7 @@ const useUserStore = defineStore('user', {
}
resolve(result);
} catch (error) {
reject(error)
reject(error);
}
});
},
@@ -63,7 +62,11 @@ const useUserStore = defineStore('user', {
provider,
success: async (result: UniApp.LoginRes) => {
if(result.code) {
const res = await loginByCode({ code: result.code });
const res = await login({
code: result.code,
userInfo: (await uni.getUserInfo()).userInfo,
referrerUserId: '1727303781559697409'
});
resolve(res);
} else {
reject(new Error(result.errMsg));
@@ -72,11 +75,11 @@ const useUserStore = defineStore('user', {
fail: (err: any) => {
console.error(`login error: ${err}`);
reject(err);
},
}
});
});
},
},
}
}
});
export default useUserStore;

View File

@@ -26,6 +26,7 @@ function requestInterceptors() {
const token = getToken();
if(token && config.header) {
config.header.token = token;
config.header.Authorization = token;
}
return config;
},