添加地址

This commit is contained in:
tangping 2023-05-26 01:12:06 +08:00
parent 64cb883ebb
commit eaee69667c
21 changed files with 936 additions and 262 deletions

View File

@ -32,7 +32,9 @@ const config = {
alias: {
'@/components': path.resolve(__dirname, '..', 'src/components'),
'@/assets': path.resolve(__dirname, '..', 'src/assets'),
'@/images': path.resolve(__dirname, '..', 'src/assets/images')
'@/images': path.resolve(__dirname, '..', 'src/assets/images'),
'@/config': path.resolve(__dirname, '..', 'src/config'),
'@/utils': path.resolve(__dirname, '..', 'src/utils'),
},
mini: {
postcss: {

View File

@ -31,6 +31,7 @@ export default defineAppConfig({
'pages/message/index',
'pages/message-list/index',
'pages/message-detail/index',
'/pages/login-quick/index'
],
window: {
backgroundTextStyle: 'light',
@ -39,11 +40,5 @@ export default defineAppConfig({
navigationBarTextStyle: 'black',
navigationStyle: 'custom'
},
components: [
'pages/index/index',
'pages/login/index',
'pages/register/index',
'pages/forgot/index',
'pages/goods-detail/index',
]
})

5
src/config/config.js Normal file
View File

@ -0,0 +1,5 @@
export default {
api: "http://1.14.121.134:9101",
debugApi: "http://1.14.121.134:9101",
debug: false
}

View File

@ -11,6 +11,9 @@ import checked from '@/images/checked.png'
import Taro from '@tarojs/taro';
import { useRouter } from '@tarojs/taro';
import { Button, Textarea, Address } from '@nutui/nutui-react-taro';
import { cities, createAddress, getAddress, updateAddress } from '../../utils/api';
import { useCallback } from 'react';
import { backOrGo, closeLoading, errorNotice, loading, redirectTo } from '../../utils/utils';
@ -19,7 +22,7 @@ function Index() {
const param = useRouter().params
const [id] = useState(param.id)
const [home] = useState(param.home)
const [addrId, setAddrId] = useState(0)
const [addrId, setAddrId] = useState([])
const [isDefault, setIsDefault] = useState(false)
const [user, setUser] = useState('')
const [phone, setPhone] = useState('')
@ -28,13 +31,8 @@ function Index() {
const [text, setText] = useState('请选择地址')
const [normal, setNormal] = useState(false)
const [province, setProvince] = useState([
{ id: 1, name: '北京', title: 'B' },
{ id: 2, name: '广西', title: 'G' },
{ id: 3, name: '江西', title: 'J' },
{ id: 4, name: '四川', title: 'S' },
{ id: 5, name: '浙江', title: 'Z' },
])
const [cityList, setCityList] = useState([])
const [province, setProvince] = useState([])
const [city, setCity] = useState([])
@ -46,27 +44,60 @@ function Index() {
city,
country,
town,
addressIdStr: '0_0_0_0',
})
const onChange = (cal) => {
useEffect(() => {
cities().then(res => {
if (!res) return
setCityList(res.items)
setProvince(res.items?.filter(item => item.deep == 0))
})
}, [])
useEffect(() => {
if (!id) return
getAddress().then(re => {
if (!re) return
const data = re.items.filter(item => item.id == id)
if (data.length < 1) return
const addr = data[0]
console.log(addr)
setAddrId([addr.province_id, addr.city_id, addr.county_id])
setText(`${addr.province.name}${addr.city.name}${addr.county.name}`)
setAddrInfo(addr.address)
setUser(addr.recipient_name)
setPhone(addr.recipient_phone)
setAddress({ province: addr.province, city: addr.city, country: addr.county, town: [], addressIdStr: `${addr.province_id}_${addr.city_id}_${addr.county_id}_0` })
if (addr.province_id && addr.city_id) {
cities(addr.province_id).then(r => {
setCity(r.items)
})
}
if (addr.city_id && addr.county_id) {
cities(addr.city_id).then(r => {
console.log(r.items, addr, addrId)
setCountry(r.items)
})
}
})
}, [id])
const onChange = async (cal) => {
const re = await cities(cal.value.id)
const d = re.items
if (!d.length) {
setNormal(false)
return
}
setTimeout(() => {
switch (cal.next) {
case 'city':
setCity([
{ id: 7, name: '朝阳区', title: 'C' },
{ id: 8, name: '崇文区', title: 'C' },
{ id: 9, name: '昌平区', title: 'C' },
{ id: 6, name: '石景山区', title: 'S' },
{ id: 3, name: '八里庄街道', title: 'B' },
{ id: 10, name: '北苑', title: 'B' },
])
setCity(d)
break;
case 'country':
setCountry([
{ id: 3, name: '八里庄街道', title: 'B' },
{ id: 9, name: '北苑', title: 'B' },
{ id: 4, name: '常营乡', title: 'C' },
])
setCountry(d)
break;
default:
setNormal(false)
@ -74,35 +105,61 @@ function Index() {
}, 200)
}
const close = (val) => {
console.log(val, "Data")
setNormal(false)
if (val.data && !!val.data.addressStr) {
setText((val.data).addressStr)
setAddress({ province: val.data.province, city: val.data.city, country: val.data.country, town: val.data.town })
setAddress({ province: val.data.province, city: val.data.city, country: val.data.country, town: val.data.town, addressIdStr: val.data.addressIdStr })
}
}
useEffect(() => {
}, [id])
//
const navDetailFn = (id) => {
Taro.navigateTo({
url: `/pages/pay-success/index?id=${id}`
})
}
//
const backFn = () => {
Taro.getCurrentPages().length > 0 && Taro.navigateBack()
Taro.getCurrentPages().length > 0 && Taro.navigateBack({ delta: 1 })
}
const submit = async () => {
const addressId = address.addressIdStr.split('_')
if (!phone || !user || !addrInfo || address.addressIdStr == '0_0_0_0') {
errorNotice('请填写地址完整信息')
return
}
if (addrInfo.length < 6) {
errorNotice('详细地址信息错误')
return
}
const data = {
zipcode: '000000',
recipient_phone: phone,
recipient_name: user,
address: addrInfo,
province_id: Number(addressId[0]),
city_id: Number(addressId[1]),
county_id: Number(addressId[2]),
is_default: isDefault
}
if (id) {
loading('地址编辑中...')
const re = await updateAddress(id, data)
closeLoading()
if (re) {
backOrGo('/pages/address/index')
}
return
}
loading('地址新增中...')
const re = await createAddress(data)
closeLoading()
if (re) {
backOrGo('/pages/address/index')
}
}
return (
<View className='addr-container'>
<View className='addr-c-container'>
<View className='addr-detail-title'>
<Image src={backNav} className="square-35 absolute left-10 nav-icon" onClick={backFn} />
{
@ -111,36 +168,36 @@ function Index() {
</View>
<View className='addr-body' >
<View className='addr-item'>
<View className='addr-form'>
<View className='addr-form-name'>收货人</View>
<Input className='addr-form-input' placeholder='请输入收货人' value={user} onInput={(e) => {
<View className='addr-c-body' >
<View className='addr-c-item'>
<View className='addr-c-form'>
<View className='addr-c-form-name'>收货人</View>
<Input className='addr-c-form-input' placeholder='请输入收货人' value={user} onInput={(e) => {
setUser(e.detail.value)
}} />
</View>
<View className='addr-form'>
<View className='addr-form-name'>电话</View>
<Input className='addr-form-input' placeholder='请输入收货' value={phone} onInput={(e) => {
<View className='addr-c-form'>
<View className='addr-c-form-name'>电话</View>
<Input className='addr-c-form-input' placeholder='请输入收货电话' value={phone} onInput={(e) => {
setPhone(e.detail.value)
}} />
</View>
<View className='addr-form'>
<View className='addr-form-name'>所在地区</View>
<View className='flex justify-between items-center addr-form-input' onClick={() => { setNormal(true) }}>
<View className='addr-c-form'>
<View className='addr-c-form-name'>所在地区</View>
<View className='flex justify-between items-center addr-c-form-input' onClick={() => { setNormal(true) }}>
<Label>{text}</Label>
<Image src={next} className='next-icon' />
</View>
</View>
<View className='addr-form bt-none'>
<View className='addr-form-name '>详细地址</View>
<View className='addr-c-form bt-none'>
<View className='addr-c-form-name '>详细地址</View>
</View>
<Textarea placeholder='请输入详细地址' autosize rows="4" maxlength={100} defaultValue={addrInfo} onChange={(e) => {
setAddrInfo(e)
}} />
</View>
<View className=' mt-22 addr-item'>
<View className='addr-form bt-none' onClick={() => {
<View className=' mt-22 addr-c-item'>
<View className='addr-c-form bt-none' onClick={() => {
setIsDefault(x => !x)
}}>
<View className='text-black'>设为默认地址</View>
@ -148,14 +205,14 @@ function Index() {
</View>
</View>
</View>
<Button className='addr-btn' >保存地址</Button>
<Button className='addr-c-btn' onClick={submit} >保存地址</Button>
<Address
modelValue={normal}
modelSelect={addrId}
province={province}
city={city}
country={country}
town={town}
customAddressTitle="请选择所在地区"
onChange={onChange}
onClose={close}

View File

@ -1,4 +1,4 @@
.addr-container {
.addr-c-container {
font-family: Source Han Sans CN-Bold, Source Han Sans CN;
font-size: 14px;
background: #FBFBFD;
@ -11,7 +11,7 @@
border-radius: 16px 16px 16px 16px;
}
.addr-detail-title {
.addr-c-detail-title {
width: 100vw;
height: 60px;
font-size: 18px;
@ -24,7 +24,7 @@
justify-content: center;
}
.addr-body {
.addr-c-body {
padding: 21px;
height: auto;
box-sizing: border-box;
@ -34,7 +34,7 @@
align-content: center;
}
.addr-box {
.addr-c-box {
width: 333px;
background: #FFFFFF;
box-shadow: 4px 7px 9px 0px rgba(56, 63, 68, 0.03);
@ -43,7 +43,7 @@
padding: 0 12px;
}
.addr-item {
.addr-c-item {
width: 333px;
background: #FFFFFF;
box-shadow: 4px 7px 9px 0px rgba(56, 63, 68, 0.03);
@ -60,7 +60,7 @@
font-size: 12px;
}
.addr-form {
.addr-c-form {
width: 308px;
box-sizing: border-box;
height: 48px;
@ -73,18 +73,18 @@
font-size: 14px;
}
.addr-form-name {
.addr-c-form-name {
font-weight: bold;
color: #000000;
font-size: 15px;
margin-right: 10px;
}
.addr-form-input {
.addr-c-form-input {
width: 230px;
}
.addr-btn {
.addr-c-btn {
width: 233px;
height: 55px;
border-radius: 107px 107px 107px 107px;
@ -98,12 +98,21 @@
left: calc((100vw - 233px)/2);
}
.addr-item>.nut-textarea {
.addr-c-item>.nut-textarea {
padding-left: 0;
padding-right: 0;
}
.addr-item .weui-input {
.addr-c-item .weui-input {
color: var(--nutui-textarea-text-color, var(--nutui-gray-1, #1a1a1a));
}
.addr-c-container .nut-popup {
background-color: #FFFFFF !important;
}
.addr-c-container textarea:-internal-autofill-selected,
.addr-c-container input:-internal-autofill-selected {
background-color: none !important;
}

View File

@ -12,6 +12,10 @@ import edit from '@/images/edit.png'
import Taro from '@tarojs/taro';
import { useRouter } from '@tarojs/taro';
import { Button } from '@nutui/nutui-react-taro';
import { deleteAddress, getAddress } from '../../utils/api';
import { backTo, closeLoading, loading, redirectTo, successNotice } from '../../utils/utils';
import { SetData } from '../../utils/storage';
import { useDidShow } from '@tarojs/taro';
@ -20,11 +24,21 @@ function Index() {
const param = useRouter().params
const [id] = useState(param.id)
const [home] = useState(param.home)
const [gid] = useState(param.gid)
const [addrId, setAddrId] = useState(0)
const [list, setList] = useState([])
const [ref, setRef] = useState(0)
useEffect(() => {
}, [id])
getAddress().then(re => {
if (!re) return
setList(re.items)
})
if (!!id) {
setAddrId(id)
}
}, [id, ref])
//
@ -33,6 +47,26 @@ function Index() {
url: `/pages/address-create/index?id=${id}`
})
}
const onSelect = (aid) => {
if (!!gid && !!aid) {
// redirectTo(`/pages/settle/index?id=${gid}&aid=${aid}`)
SetData(gid, aid, 5)
backTo()
}
}
//
const deleteAddr = async (id) => {
if (!id) return
loading('正在删除中')
const re = await deleteAddress(id).finally(() => {
closeLoading()
})
successNotice('删除成功')
setRef(d => d + 1)
}
//
const backFn = () => {
Taro.getCurrentPages().length > 0 && Taro.navigateBack()
@ -49,27 +83,32 @@ function Index() {
</View>
<View className='addr-body' >
<View className='addr-item'>
{
list.map((item, index) => {
return <View className='addr-item' key={item.id}>
<View className='addr-icon w-6 h-6' onClick={() => {
setAddrId(1)
setAddrId(item.id)
}}>
<Image src={addrId == 1 ? checked : uncheck} className="w-6 h-6" />
<Image src={addrId == item.id ? checked : uncheck} className="w-6 h-6" />
</View>
<View className='addr-item-content' onClick={() => {
setAddrId(1)
setAddrId(item.id)
onSelect(item.id)
}}>
<View className='addr-item-title'>
<View className='addr-item-name'>李四 18080093730</View>
<View className='addr-default'>默认地址</View>
<View className='addr-item-name'>{item.recipient_name} {item.recipient_phone}</View>
{item.id_default && <View className='addr-default'>默认地址</View>}
</View>
<View className='addr-item-info'>四川省成都市天府二街</View>
<View className='addr-item-info'>{item.province?.name}{item.city?.name}{item.county?.name}{item.address}</View>
</View>
<View className='addr-edit-icon' onClick={() => {
navDetailFn('')
navDetailFn(item.id)
}}>
<Image src={edit} />
</View>
</View>
})
}
</View>
<Button className='addr-btn' onClick={() => {

View File

@ -7,6 +7,7 @@ import './index.scss'
import { Button } from "@nutui/nutui-react-taro"
import { useState } from "react"
import Taro from "@tarojs/taro"
import { sendCode } from "../../utils/api"
const activeEye = eye
@ -47,7 +48,7 @@ const Login = () => {
}
//
const countDown = () => {
const countDown = async () => {
if (!mobile) {
return
}
@ -65,6 +66,9 @@ const Login = () => {
setIntervalTime(start)
}
}, 1000)
const re = await sendCode(mobile)
if (!re) return
Taro.showToast({ title: '验证码发送成功', icon: 'success' })
}
return <View className="login-frame bg-slate-50 h-screen text-base">

View File

@ -11,6 +11,8 @@ import Taro from '@tarojs/taro';
import { useRouter } from '@tarojs/taro';
import { useDidShow } from '@tarojs/taro';
import { Button } from '@nutui/nutui-react-taro';
import { mallDetail } from '../../utils/api';
import { ImagePreview } from '@nutui/nutui-react-taro';
@ -20,25 +22,37 @@ function Index() {
const [id] = useState(param.id)
const [swiperProgress, setSwiperProgress] = useState(1)
const list = [
'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg'
]
useDidShow(() => {
console.log(12)
})
const [thumbnails, setThumbnails] = useState([])
const [info, setInfo] = useState({})
const [showReview, setShowReview] = useState(false)
const [imageNum, setImageNum] = useState(1)
useEffect(() => {
if (!id) {
return
}
detail(id)
}, [id])
const detail = async () => {
const re = await mallDetail(id)
if (!re) return
setInfo(re)
if (re.thumbnails?.length) {
let images = re.thumbnails
if (!!re.cover_image) {
images.shift(re.cover_image)
}
setThumbnails(images)
}
}
//
@ -53,7 +67,7 @@ function Index() {
}
return (
<View className='home-container'>
<View className='home-container' catchMove>
<View className='goods-detail-title'>
<Image src={backNav} className="square-35 absolute left-10 " onClick={backFn} />
商品详情
@ -75,7 +89,7 @@ function Index() {
setSwiperProgress(x => x % 3 + 1)
}}
>
{list.map((item) => {
{thumbnails.map((item) => {
return (
<SwiperItem key={item} className='goods-swiper-item'>
<Image src={item} alt="" />
@ -84,27 +98,37 @@ function Index() {
})}
</Swiper>
<View className='goods-swiper-progress'>
<View style={{ backgroundColor: '#F67952', height: '100%', width: list.length ? ((swiperProgress / list.length) > 1 ? 1 : (swiperProgress / list.length)) * 100 + '%' : 0 }}></View>
<View style={{ backgroundColor: '#F67952', height: '100%', width: thumbnails.length ? ((swiperProgress / thumbnails.length) > 1 ? 1 : (swiperProgress / thumbnails.length)) * 100 + '%' : 0 }}></View>
</View>
</View>
<View className='goods-content'>
<View className='line-clamp-2 goods-name'>
银美孚1号 全合成油5w全合成油 5W-30 SN级 4L
{info.name}
</View>
<View className='goods-desc line-clamp-2'>
{/* <View className='goods-desc line-clamp-2'>
<View>规格</View>
</View>
</View> */}
<View className='goods-item-price'>
<Text className='goods-item-price-sale'>¥199</Text>
<Text className='goods-item-price-origin'>原价888</Text>
<Text className='goods-item-price-sale'>¥{info.price}</Text>
{/* <Text className='goods-item-price-origin'>原价888</Text> */}
</View>
<View className='goods-content-box'>
图文内容专区
{
info.content_images?.map((item, index) => {
return (
<Image src={item} alt="" key={item} style={{ width: '100%' }} onClick={() => {
setImageNum(index + 1)
// setShowReview(true)
}} />
)
})
}
</View>
</View>
</View>
<Button className='buy-btn' onClick={navDetailFn}>立即购买</Button>
</View>
)
}

View File

@ -68,7 +68,7 @@
.goods-name {
width: 100%;
height: 48px;
height: auto;
font-size: 16px;
font-weight: bold;
color: #10254E;

View File

@ -1,36 +1,41 @@
import React from 'react'
import { Image, Text, View } from '@tarojs/components'
import {
Button
} from "@nutui/nutui-react-taro";
import './index.scss'
import TabbarAction from '@/components/action';
import { Infiniteloading } from '@nutui/nutui-react-taro';
import { useEffect } from 'react';
import { useState } from 'react';
import { mallList } from '../../utils/api';
import Taro from '@tarojs/taro';
function Index() {
const limit = 10
const [list, setList] = useState([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
useEffect(() => {
if (list.length > 50) {
setHasMore(false)
if (page < 1) {
return
}
let l = []
for (let i = list.length; i < 10 + list.length; i++) {
l.push(i)
}
setList([...list, ...l])
fetchList(page)
}, [page])
const fetchList = async (page) => {
const offset = (page - 1) * limit
const res = await mallList('mall', offset, limit)
if (!res) return
if (res.items?.length + list.length >= res.total) {
setHasMore(false)
}
setList(res.items)
setTotal(res.total)
}
//
const navDetailFn = (id) => {
@ -48,7 +53,7 @@ function Index() {
containerId="customScroll"
useWindow={false}
loadTxt="loading"
loadMoreTxt="没有数据啦~"
loadMoreTxt={<View></View>}
loadIcon='loading'
hasMore={hasMore}
onLoadMore={(x) => {
@ -59,21 +64,21 @@ function Index() {
<View className='goods-container '>
{
list.map(item => {
return <View className='goods-item' key={item} onClick={() => {
navDetailFn(item)
return <View className='goods-item' key={item.id} onClick={() => {
navDetailFn(item.id)
}}>
<View className='goods-item-image'>
<Image src='https://img.yzcdn.cn/vant/cat.jpeg' />
<Image src={item.cover_image} />
</View>
<View className='goods-item-name line-clamp-2'>
这个是商品的名称但是可能有一点点的长
{item.name}
</View>
<View className='goods-item-desc line-clamp-2 '>
{/* <View className='goods-item-desc line-clamp-2 '>
这个是商品的介绍
</View>
</View> */}
<View className='goods-item-price'>
<Text className='goods-item-price-sale'>¥199</Text>
<Text className='goods-item-price-origin'>原价888</Text>
<Text className='goods-item-price-sale'>¥{item.price}</Text>
{/* <Text className='goods-item-price-origin'>原价888</Text> */}
</View>
</View>
})

View File

@ -33,8 +33,8 @@
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-around;
padding: 0 8px;
justify-content: space-between;
padding: 0 12px;
margin-top: 24px;
// column-count: 2;
// column-gap: 10px;

View File

@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '登录'
})

View File

@ -0,0 +1,116 @@
import { Image, Input, Text, View } from "@tarojs/components"
import back from '@/images/back.png'
import checked from '@/images/checked.png'
import eyeClose from '@/images/eyeClose.png'
import eye from '@/images/eye.png'
import './index.scss'
import { Button } from "@nutui/nutui-react-taro"
import { useState } from "react"
import Taro from "@tarojs/taro"
import { useDidShow } from "@tarojs/taro"
import { login, sendCode } from "../../utils/api"
import { JWT, JWTEXPIRE, SetData, USERINFO } from "../../utils/storage"
const activeEye = eye
const Login = () => {
const [mobile, setMobile] = useState('')
const [smsCode, setSmsCode] = useState('')
const [interval, setIntervalTime] = useState(0)
//
const backFn = () => {
Taro.getCurrentPages().length > 0 && Taro.navigateBack()
}
//
const countDown = async () => {
if (!mobile) {
return
}
setIntervalTime(60)
if (interval > 0) {
return
}
let start = 60
const timer = setInterval(() => {
if (start > 0) {
start--
if (start <= 0) {
clearInterval(timer)
}
setIntervalTime(start)
}
}, 1000)
const re = await sendCode(mobile)
if (!re) return
Taro.showToast({ title: '验证码发送成功', icon: 'success' })
}
const loginSubmit = async () => {
console.log(mobile, smsCode, "code")
if (!mobile || !smsCode) {
Taro.showToast({ title: '请完善登陆参数', icon: 'error' })
return
}
Taro.showLoading({ title: '登陆中,请稍后' })
const re = await login(mobile, '', smsCode)
Taro.hideLoading()
if (re) {
SetData(JWT, re.token, JWTEXPIRE)
SetData(USERINFO, re.user)
Taro.redirectTo({ url: '/pages/index/index' })
}
}
return <View className="login-frame bg-slate-50 h-screen text-base">
<View className="login-header flex justify-center items-center text-lg font-bold">
<Image src={back} className="square-35 absolute left-7" onClick={backFn} />
<View>新起点</View>
</View>
<View className="login-container relative">
<View className="relative font-bold text-lg block h-6 ">登录</View>
<View>
<View className="form-item mt-58">
<View className="form-label">手机号</View>
<View className="form-control relative">
<Input className="form-input" name="mobile" placeholder="请输手机号" id='mobile' onInput={(v) => {
setMobile(v.detail.value)
}} />
{
mobile && <Image className="w-6 h-6 absolute right-0 bottom-16" src={checked} />
}
</View>
</View>
<View className="form-item mt-22">
<View className="form-label">验证码</View>
<View className="form-control relative">
<Input className="form-input" name="smsCode" type="text" placeholder="请输入验证码" id='code' value={smsCode} onInput={(v) => {
setSmsCode(v.detail.value)
}} />
<Button className="code-btn" disabled={interval > 0} onClick={countDown}>{interval ? interval + 's' : '获取验证码'}</Button>
</View>
</View>
<View className="login-footer flex flex-col justify-center">
<Button className="login-btn" onClick={loginSubmit}>登录</Button>
<View className="quick-login" onClick={() => {
Taro.redirectTo({ url: '/pages/login/index' })
}}>密码登录</View>
</View>
</View>
</View>
</View>
}
export default Login

View File

@ -0,0 +1,123 @@
.login-header {
height: 86px;
}
.login-frame {
height: auto;
font-size: 14px;
font-family: Source Han Sans CN-Bold, Source Han Sans CN;
}
.login-container {
width: 375px;
height: calc(100vh - 86px);
background: #FFFFFF;
border-radius: 20px 20px 0 0;
box-shadow: 0px 8px 51px 0px rgba(230, 234, 238, 0.8);
box-sizing: border-box;
padding: 38px 30px;
}
.form-item {
height: 68px;
opacity: 1;
}
.form-label {
font-size: 12px;
font-weight: bold;
font-family: Source Han Sans CN-Bold, Source Han Sans CN;
color: #2a2b2d;
line-height: 16px;
}
.form-input>.weui-input {
border: none;
height: 52px;
line-height: 52px;
border-bottom: 1px solid #262A34;
position: relative;
font-size: 16px;
color: #000;
}
.form-input>.weui-input:focus {
border-bottom: 2px solid #F67952;
}
.form-input-placeholder,
.form-input>.weui-input::placeholder {
font-size: 16px;
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
font-weight: bold;
color: #aeafb4;
}
.form-control {
height: 52px;
position: relative;
}
.forgot-password {
height: 24px;
font-size: 16px;
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
font-weight: 400;
color: #666;
line-height: 24px;
text-align: right;
}
.form-helper {
margin-top: 16px;
display: flex;
justify-content: space-between;
}
.login-btn {
width: 253px;
height: 55px;
background: #F67952;
border-radius: 68px 68px 68px 68px;
opacity: 1;
border: none;
color: white;
font-size: 16px;
font-family: Source Han Sans CN-Bold, Source Han Sans CN;
font-weight: bold;
}
.login-footer {
margin-top: 50px;
display: flex;
flex-direction: column;
align-items: center;
}
.quick-login {
font-size: 16px;
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
font-weight: 400;
color: #000000;
line-height: 24px;
margin-top: 30px;
}
.code-btn {
width: 83px;
height: 30px;
background: #F67952;
border-radius: 4px 4px 4px 4px;
opacity: 1;
font-size: 12px;
font-family: Source Han Sans CN-Bold, Source Han Sans CN;
font-weight: bold;
color: #FFFFFF;
border: none;
padding: 0;
position: absolute;
right: 0;
bottom: 12px;
}

View File

@ -8,23 +8,22 @@ import { Button } from "@nutui/nutui-react-taro"
import { useState } from "react"
import Taro from "@tarojs/taro"
import { useDidShow } from "@tarojs/taro"
import { login, sendCode } from "../../utils/api"
import { JWT, JWTEXPIRE, SetData, USERINFO } from "../../utils/storage"
import { successNotice } from "../../utils/utils"
const activeEye = eye
const Login = () => {
const [account, setAccount] = useState('')
const [pwd, setPwd] = useState()
const [showPwd, setShowPwd] = useState(false)
const [loginMode, setLoginMode] = useState('account')
const [mobile, setMobile] = useState('')
const [smsCode, setSmsCode] = useState('')
const [pwd, setPwd] = useState('')
const [interval, setIntervalTime] = useState(0)
useDidShow(() => {
})
const [showPwd, setShowPwd] = useState()
//
@ -32,33 +31,8 @@ const Login = () => {
Taro.getCurrentPages().length > 0 && Taro.navigateBack()
}
//
const registerFn = () => {
Taro.navigateTo({
url: '/pages/register/index'
})
}
//
const forgotPasswordFn = () => {
Taro.navigateTo({
url: '/pages/forgot/index'
})
}
//
const cleanFn = () => {
if (loginMode === 'account') {
setAccount('')
setPwd('')
return
}
setMobile('')
setSmsCode('')
}
//
const countDown = () => {
const countDown = async () => {
if (!mobile) {
return
}
@ -76,6 +50,26 @@ const Login = () => {
setIntervalTime(start)
}
}, 1000)
const re = await sendCode(mobile)
if (!re) return
Taro.showToast({ title: '验证码发送成功', icon: 'success' })
}
const loginSubmit = async () => {
if (!mobile || !pwd) {
Taro.showToast({ title: '请完善登陆参数', icon: 'error' })
return
}
Taro.showLoading({ title: '登陆中,请稍后' })
const re = await login(mobile, pwd)
Taro.hideLoading()
if (re) {
SetData(JWT, re.token, JWTEXPIRE)
SetData(USERINFO, re.user)
Taro.redirectTo({ url: '/pages/index/index' })
}
}
return <View className="login-frame bg-slate-50 h-screen text-base">
@ -85,46 +79,11 @@ const Login = () => {
</View>
<View className="login-container relative">
<View className="relative font-bold text-lg block h-6 ">登录</View>
{
loginMode === 'account' ? <View>
<View className="form-item mt-58">
<View className="form-label">账号</View>
<View className="form-control relative">
<Input className="form-input" placeholder="请输入账号" onInput={(v) => {
setAccount(v.detail.value)
}} />
{
!!account && <Image className="w-6 h-6 absolute right-0 bottom-16" src={checked} />
}
</View>
</View>
<View className="form-item mt-22">
<View className="form-label">密码</View>
<View className="form-control relative">
<Input className="form-input" type={showPwd ? 'text' : 'password'} placeholder="请输入密码" onInput={(v) => {
setPwd(v.detail.value)
}} />
<Image className="w-6 h-6 absolute right-0 bottom-16" src={showPwd ? activeEye : eyeClose} onClick={() => {
setShowPwd(v => !v)
}} />
</View>
</View>
<View className="form-helper">
<View className="forgot-password" onClick={registerFn}>注册账号</View>
<View className="forgot-password" onClick={forgotPasswordFn}>忘记密码</View>
</View>
<View className="login-footer flex flex-col justify-center">
<Button className="login-btn">登录</Button>
<View className="quick-login" onClick={() => {
setLoginMode('mobile')
cleanFn()
}}>快捷登录</View>
</View>
</View> : <View>
<View>
<View className="form-item mt-58">
<View className="form-label">手机号</View>
<View className="form-control relative">
<Input className="form-input" placeholder="请输手机号" placeholderClass="form-input-placeholder" onInput={(v) => {
<Input className="form-input" name="mobile" placeholder="请输手机号" id='mobile' onInput={(v) => {
setMobile(v.detail.value)
}} />
{
@ -134,26 +93,24 @@ const Login = () => {
</View>
</View>
<View className="form-item mt-22">
<View className="form-label">验证</View>
<View className="form-label"></View>
<View className="form-control relative">
<Input className="form-input" maxlength={6} placeholder="请输入验证码" onInput={(v) => {
setSmsCode(v.detail.value)
<Input className="form-input" name="pwd" type={showPwd ? 'text' : 'password'} id='pwd' placeholder="请输入密码" value={pwd} onInput={(v) => {
setPwd(v.detail.value)
}} />
<Image className="w-6 h-6 absolute right-0 bottom-16" src={showPwd ? activeEye : eyeClose} onClick={() => {
setShowPwd(v => !v)
}} />
<Button className="code-btn" disabled={interval > 0} onClick={countDown}>{interval ? interval + 's' : '获取验证码'}</Button>
</View>
</View>
<View className="login-footer flex flex-col justify-center">
<Button className="login-btn">登录</Button>
<Button className="login-btn" onClick={loginSubmit}>登录</Button>
<View className="quick-login" onClick={() => {
setLoginMode('account')
cleanFn()
}}>密码登录</View>
Taro.redirectTo({ url: '/pages/login-quick/index' })
}}>快捷登录</View>
</View>
</View>
}
</View>
</View>

View File

@ -7,6 +7,7 @@ import './index.scss'
import { Button } from "@nutui/nutui-react-taro"
import { useState } from "react"
import Taro from "@tarojs/taro"
import { register, sendCode } from "../../utils/api"
const activeEye = eye
@ -32,7 +33,7 @@ const Login = () => {
//
const countDown = () => {
const countDown = async () => {
if (!mobile) {
return
}
@ -50,6 +51,33 @@ const Login = () => {
setIntervalTime(start)
}
}, 1000)
const re = await sendCode(mobile)
if (!re) return
Taro.showToast({ title: '验证码发送成功', icon: 'success' })
}
const registerFn = async () => {
if (!mobile || !smsCode || !pwd || !confirmPassword || !payPassword || !confirmPayPassword) {
Taro.showToast({ title: '请完善注册参数', icon: 'error' })
return
}
Taro.showLoading({ title: '正在注册中~', })
const re = await register({
nick_name: mobile,
phone: mobile,
verification_code: smsCode,
password: pwd,
confirm_password: confirmPassword,
pay_password: payPassword,
confirm_pay_pwd: confirmPayPassword
})
Taro.hideLoading()
if (re) {
Taro.showToast({ title: '注册成功', icon: 'success' })
setTimeout(() => {
Taro.redirectTo({ url: '/pages/login/index' })
}, 1000)
}
}
return <View className="login-frame bg-slate-50 h-screen text-base">
@ -61,7 +89,7 @@ const Login = () => {
<View className="form-item ">
<View className="form-label">手机号</View>
<View className="form-control relative">
<Input className="form-input" placeholder="请输手机号" placeholderClass="form-input-placeholder" onInput={(v) => {
<Input className="form-input" placeholder="请输手机号" required placeholderClass="form-input-placeholder" onInput={(v) => {
setMobile(v.detail.value)
}} />
{
@ -124,7 +152,7 @@ const Login = () => {
</View>
</View>
<View className="login-footer flex flex-col justify-center">
<Button className="login-btn">注册</Button>
<Button className="login-btn" onClick={registerFn}>注册</Button>
</View>
</View>

View File

@ -9,6 +9,9 @@ import next from '@/images/next.png'
import Taro from '@tarojs/taro';
import { useRouter } from '@tarojs/taro';
import { Button } from '@nutui/nutui-react-taro';
import { getAddress, mallDetail } from '../../utils/api';
import { GetData } from '../../utils/storage';
import { useDidShow } from '@tarojs/taro';
@ -16,20 +19,40 @@ function Index() {
const param = useRouter().params
const [id] = useState(param.id)
const [swiperProgress, setSwiperProgress] = useState(1)
const list = [
'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg'
]
const [aid, setAid] = useState(param.aid)
const [addrData, setAddrData] = useState({})
const [info, setInfo] = useState({})
useEffect(() => {
mallDetail(id).then(re => {
if (!re) return
setInfo(re)
})
if (!aid && !!addrData.id) {
return
}
getAddress().then(re => {
if (!re) return
const addr = re.items.filter(item => {
if (aid) {
return item.id == aid
}
return item.is_default
})
if (!!addr.length) {
setAddrData(addr[0])
}
})
}, [id])
}, [id, aid])
useDidShow(() => {
const aa = GetData(id)
setAid(aa)
})
//
@ -45,9 +68,9 @@ function Index() {
}
//
const goAddr = (id) => {
const goAddr = (aid) => {
Taro.navigateTo({
url: '/pages/address/index?id=' + id
url: `/pages/address/index?gid=${id}&id=` + aid
})
}
@ -63,9 +86,9 @@ function Index() {
<View className='address-title'>选择收货地址</View>
<View className='address-area' onClick={() => { goAddr('') }}>
<View className='address-item'>
<View className='address-item-name'>李四 18080093730</View>
<View className='address-info'>四川省 成都市 天府二街</View>
{/* <View className='address-item-name text-gold'>选择地址</View> */}
{!!addrData.id && <View className='address-item-name'>{addrData.recipient_name} {addrData.recipient_phone}</View>}
{!!addrData.id && <View className='address-info'>{addrData.province?.name}{addrData.city.name}{addrData.county.name}{addrData.address}</View>}
{!addrData.id && <View className='address-item-name text-gold'>选择地址</View>}
</View>
<View className='address-next'>
<Image src={next} />
@ -76,11 +99,11 @@ function Index() {
<View className='settle-goods'>
<View className='settle-goods-title'>商品信息</View>
<View className='settle-goods-info'>
<Image className='settle-goods-img' />
<Image className='settle-goods-img' src={info.cover_image} />
<View className='settle-goods-content'>
<View className='settle-goods-content-title line-clamp-1'>商品名称</View>
<View className='settle-goods-content-title line-clamp-1'>{info.name}</View>
<View className='settle-goods-content-price'>
<View className='settle-goods-content-price-1'>¥199</View>
<View className='settle-goods-content-price-1'>¥{info.price}</View>
<View className='settle-goods-content-price-num'>x1</View>
</View>
</View>
@ -89,7 +112,7 @@ function Index() {
<View className='settle-price-container'>
<View>总价</View>
<View className='settle-price-p'>¥199</View>
<View className='settle-price-p'>¥{info.price * 1}</View>
</View>
</View>
<Button className='buy-btn' onClick={navDetailFn}>去支付</Button>

47
src/utils/api.js Normal file
View File

@ -0,0 +1,47 @@
import { d, put } from './request'
import { g, p } from './request'
export const mallList = async (channel = 'mall', offset = 0, limit = 10) => {
return await g(`/products/${channel}`)
}
export const mallDetail = async (id) => {
return await g(`/products/detail/${id}`)
}
export const login = async (account, pwd, code) => {
return await p(`/users/login`, { phone: account, password: pwd, verification_code: code })
}
export const register = async (data = {}) => {
return await p(`/users/register`, data)
}
export const sendCode = async (phone) => {
return await p('/users/sms-verification-code', { phone })
}
// 创建地址
export const createAddress = async (data = {}) => {
return await p(`/users/addresses`, data)
}
export const updateAddress = async (id, data = {}) => {
return await put(`/users/addresses/${id}`, data)
}
export const deleteAddress = async (id) => {
return await d(`/users/addresses/${id}`)
}
export const getAddress = async (data = {}) => {
return await g(`/users/addresses`, data)
}
// 获取城市
export const cities = async (pid = 0) => {
return await g('/cities', { pid })
}

135
src/utils/request.js Normal file
View File

@ -0,0 +1,135 @@
import Taro from '@tarojs/taro';
import config from '../config/config';
import {
DelData,
GetData,
JWT
} from './storage';
const loginPages = '/pages/login/index';
function RequestError(message, code) {
this.name = 'RequestError'
this.message = message || '请求失败'
this.code = code
}
// 基础请求
// api/user/center_show , get param == contribution , base
export function baseRequest(
url,
method = 'GET',
data = {},
base = config.api // https://api.yidongpaidui.com/
) {
if (config.debug) { // false
base = config.debugApi
}
let jwt = GetData(JWT);
jwt = typeof jwt === 'string' ? jwt : ''
let header = {
'Content-Type': 'application/json',
'Jwt': `Bearer ${jwt}`,
'Authorization': 'Bearer ' + jwt
}
return Taro.request({
url: base + url,
data: data,
dataType: "json",
method: method,
header: header
}).then(re => {
if (config.debug) {
console.log(`1、${method} debug request url is: ${url}\r\n2、request Data : `, data, '\r\n3、request header:', header, '\r\n4、response Data is :', re);
}
let msg = '请求失败'
if (re.statusCode !== 200 || re.data.error.code != 0) {
msg = re.data.error.message
if (!!msg) {
Taro.showModal({
"title": "信息提示",
showCancel: false,
"content": msg,
mask: true
})
}
if (re.data.error.code.toString() === '40101') {
DelData(JWT)
const current = Taro.getCurrentPages()
const route = current[0].route
if (route != 'pages/login/index') {
Taro.navigateTo({
url: loginPages
})
}
}
RequestError.prototype = Object.create(Error.prototype)
RequestError.prototype.constructor = RequestError
return null
}
return re.data.data
})
}
// post
export function p(url, data, base) {
return baseRequest(url, 'POST', data, base)
}
// get
export function g(url, data, base) {
// api/user/center_show , get param == contribution , base
return baseRequest(url, 'GET', data, base)
}
// delete
export function d(url, data, base) {
return baseRequest(url, 'DELETE', data, base)
}
//put
export function put(url, data, base) {
return baseRequest(url, 'PUT', data, base)
}
// 上传图片
export const upload = async (file) => {
let base = config.api
if (config.debug) {
base = config.debugApi
}
let jwt = GetData(JWT);
jwt = typeof jwt === 'string' ? jwt : ''
let header = {
// 'Jwt': `Bearer ${jwt}`,
'Authorization': 'Bearer ' + jwt
}
const re = await Taro.uploadFile({
url: `${base}api/camp_chat/upload_image`,
filePath: file,
name: 'image',
header: header,
})
if (re.statusCode == 200) {
const resp = JSON.parse(re.data)
if (resp.hasOwnProperty("data")) {
return resp.data
}
}
Taro.showModal({
"title": "信息提示",
showCancel: false,
"content": re.errMsg,
mask: true
})
RequestError.prototype = Object.create(Error.prototype)
RequestError.prototype.constructor = RequestError
throw new RequestError(re.errMsg, re.statusCode)
}

66
src/utils/storage.js Normal file
View File

@ -0,0 +1,66 @@
import Taro from '@tarojs/taro';
// storage key
export const JWT = '_jujwt'; // jwt
export const JWTEXPIRE = 24 * 3600; // jwt 过期时间
export const OPENDATA = '_d' // 微信开放数据 openid unionId sessionKey
export const WXUSERINFO = '_wu' // 微信GetUserInfo 数据
export const USERINFO = '_u' // 用户信息
export const TASKKEY = "_task" // 任务key
// 同步设置缓存
export function SetData(key, data, t = 0) {
if (t > 0) {
let time = (new Date()).getTime()
time = Number(String(time).substr(0, 10))
t = time + t
}
let storage = {
expire: t,
_data: data
}
return Taro.setStorageSync(key, storage)
}
// 异步设置缓存
export function SetAsyncData(key, data, t = 0) {
if (t > 0) {
let time = (new Date()).getTime()
time = Number(String(time).substr(0, 10))
t = time + t
}
let storage = {
expire: t,
_data: data
}
return Taro.setStorage({
key: key,
data: storage
})
}
// 取出缓存 -- _jujwt
export function GetData(key) {
let storage = Taro.getStorageSync(key)
if (!storage) {
return {}
}
if (storage.expire > 0) {
let time = (new Date()).getTime()
time = Number(String(time).substr(0, 10))
if (time > storage.expire) {
DelData(key)
return {}
}
}
return storage._data
}
// 删除缓存
export function DelData(key) {
return Taro.removeStorage({
key: key
})
}

36
src/utils/utils.js Normal file
View File

@ -0,0 +1,36 @@
import Taro from "@tarojs/taro"
export const successNotice = (content) => {
Taro.showToast({ title: content, icon: 'success' })
}
export const errorNotice = (content) => {
Taro.showToast({ title: content, icon: 'error' })
}
export const loading = (title = '加载中') => {
Taro.showLoading({ title })
}
export const closeLoading = () => {
Taro.hideLoading()
}
export const redirectTo = (url) => {
Taro.redirectTo({ url })
}
export const navigateTo = (url) => {
Taro.navigateTo({ url })
}
export const backTo = () => {
Taro.navigateBack({ delta: 1 })
}
export const backOrGo = (url) => {
Taro.getCurrentPages().length > 0 ? Taro.navigateBack() : redirectTo(url)
}