diff --git a/App.vue b/App.vue index 2dff378..6b707a0 100644 --- a/App.vue +++ b/App.vue @@ -2,17 +2,19 @@ import { onLaunch, onShow, onHide } from '@dcloudio/uni-app' import { useTokenStore } from './stores/token' import { reLaunch } from './utils/router' + import { useAuthUser } from './composables/useAuthUser' + const { token } = useAuthUser() /** 静默登录逻辑 */ const silentLogin = async () => { - const tokenStore = useTokenStore() - if (tokenStore.token && !tokenStore.isTokenExpired()) { + console.log(token.value, '==') + if (token.value) { reLaunch('/pages/news-list/news-list') return } // 没有token去登录页 - reLaunch('/pages/login/login?id=1') + reLaunch('/pages/login/login') } onLaunch(() => { diff --git a/api/index.js b/api/index.js index 9f80457..02863d0 100644 --- a/api/index.js +++ b/api/index.js @@ -27,9 +27,19 @@ export const getUserData = () => { } /** 获取用户地址列表 */ -export const getUserAddress = () => { +export const getUserAddress = (data, loading = true) => { return http({ url: '/api/service/userAddress/list', + method: 'get', + loading, + data + }) +} + +/** 获取用户地址详情 */ +export const getUserAddressDetail = id => { + return http({ + url: `/api/service/userAddress/${id}`, method: 'get' }) } @@ -42,3 +52,29 @@ export const addUserAddress = data => { data }) } + +/** 修改地址 */ +export const updateUserAddress = data => { + return http({ + url: '/api/service/userAddress', + method: 'put', + data + }) +} + +/** 删除地址 */ +export const deleteUserAddress = id => { + return http({ + url: `/api/service/userAddress/${id}`, + method: 'delete' + }) +} + +/** 获取全国省市区县辖区街道 */ +export const getRegion = data => { + return http({ + url: '/api/third/area/list', + method: 'get', + data + }) +} diff --git a/api/mall.js b/api/mall.js index 0069545..2359718 100644 --- a/api/mall.js +++ b/api/mall.js @@ -24,3 +24,13 @@ export const getProductDetail = productId => { method: 'get' }) } + + +/** 商品评价列表 */ +export const getProductCommentList = data => { + return http({ + url: '/api/service/productReview/list', + method: 'get', + data + }) +} \ No newline at end of file diff --git a/components/bottom-view/bottom-view.vue b/components/bottom-view/bottom-view.vue index b841aac..2fd2e23 100644 --- a/components/bottom-view/bottom-view.vue +++ b/components/bottom-view/bottom-view.vue @@ -14,9 +14,9 @@ left: 0; right: 0; bottom: 0; - padding: 16rpx 24rpx env(safe-area-inset-bottom); /* 关键:适配 iPhone 等安全区域 */ + padding: 16rpx 24rpx calc(env(safe-area-inset-bottom) + 20rpx); /* 关键:适配 iPhone 等安全区域 */ background: #fff; box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1); - z-index: 999; + z-index: 100; } diff --git a/components/nav-bar/nav-bar.vue b/components/nav-bar/nav-bar.vue index e38e980..eaf6460 100644 --- a/components/nav-bar/nav-bar.vue +++ b/components/nav-bar/nav-bar.vue @@ -1,29 +1,56 @@ @@ -34,6 +61,15 @@ width: 100%; z-index: 99; } + + .nav-bar_bg { + background: #ffffff; + } + + .top_left-icon { + height: 36rpx; + } + .status_bar { height: var(--status-bar-height); width: 100%; @@ -45,5 +81,27 @@ display: flex; align-items: center; justify-content: space-between; + .nav-bar-title { + position: absolute; + width: calc(100% - 72rpx); + height: 100%; + display: flex; + justify-content: center; + align-items: center; + pointer-events: none; + text { + font-family: PingFang SC, PingFang SC; + font-weight: 500; + font-size: 32rpx; + color: #333333; + text-align: center; + font-style: normal; + text-transform: none; + } + } + } + + .nav-bar-placeholder { + height: calc(var(--status-bar-height) + 44px); } diff --git a/composables/useAuthUser.js b/composables/useAuthUser.js index f51432a..0d45879 100644 --- a/composables/useAuthUser.js +++ b/composables/useAuthUser.js @@ -1,16 +1,20 @@ import { storeToRefs } from 'pinia' import { useUserStore } from '@/stores/user' +import { useTokenStore } from '@/stores/token' /** * 统一提供响应式的用户信息和相关操作 */ export const useAuthUser = () => { const userStore = useUserStore() + const tokenStore = useTokenStore() // 响应式状态(state & getters) const { userInfo } = storeToRefs(userStore) + const { token } = storeToRefs(tokenStore) return { - userInfo + userInfo, + token } } diff --git a/pages.json b/pages.json index 379008b..17b110c 100644 --- a/pages.json +++ b/pages.json @@ -72,6 +72,12 @@ "navigationStyle": "custom" } }, + { + "path": "pages/mall/comment", + "style": { + "navigationBarTitleText": "评价" + } + }, { "path": "pages/mall/confirm-order", "style": { @@ -81,7 +87,8 @@ { "path": "pages/address/index", "style": { - "navigationBarTitleText": "地址列表" + "navigationBarTitleText": "我的地址", + "navigationStyle": "custom" } }, { diff --git a/pages/address/add.vue b/pages/address/add.vue index db42426..a4d13c7 100644 --- a/pages/address/add.vue +++ b/pages/address/add.vue @@ -1,13 +1,29 @@ diff --git a/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs b/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs new file mode 100644 index 0000000..5e5e1ee --- /dev/null +++ b/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs @@ -0,0 +1,349 @@ +var MIN_DISTANCE = 10; + +/** + * 判断当前是否为H5、app-vue + */ +var IS_HTML5 = false +if (typeof window === 'object') IS_HTML5 = true + +/** + * 监听页面内值的变化,主要用于动态开关swipe-action + * @param {Object} newValue + * @param {Object} oldValue + * @param {Object} ownerInstance + * @param {Object} instance + */ +function showWatch(newVal, oldVal, ownerInstance, instance) { + var state = instance.getState() + getDom(instance, ownerInstance) + if (newVal && newVal !== 'none') { + openState(newVal, instance, ownerInstance) + return + } + + if (state.left) { + openState('none', instance, ownerInstance) + } + resetTouchStatus(instance) +} + +/** + * 开始触摸操作 + * @param {Object} e + * @param {Object} ins + */ +function touchstart(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState(); + getDom(instance, ownerInstance) + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 开始触摸时移除动画类 + instance.requestAnimationFrame(function() { + instance.removeClass('ani'); + ownerInstance.callMethod('closeSwipe'); + }) + + // 记录上次的位置 + state.x = state.left || 0 + // 计算滑动开始位置 + stopTouchStart(e, ownerInstance) +} + +/** + * 开始滑动操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchmove(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 是否可以滑动页面 + stopTouchMove(e); + if (state.direction !== 'horizontal') { + return; + } + + if (e.preventDefault) { + // 阻止页面滚动 + e.preventDefault() + } + + move(state.x + state.deltaX, instance, ownerInstance) +} + +/** + * 结束触摸操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchend(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + + if (disabled) return + // 滑动过程中触摸结束,通过阙值判断是开启还是关闭 + // fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13 + moveDirection(state.left, instance, ownerInstance) + +} + +/** + * 设置移动距离 + * @param {Object} value + * @param {Object} instance + * @param {Object} ownerInstance + */ +function move(value, instance, ownerInstance) { + value = value || 0 + var state = instance.getState() + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + // 获取可滑动范围 + state.left = range(value, -rightWidth, leftWidth); + instance.requestAnimationFrame(function() { + instance.setStyle({ + transform: 'translateX(' + state.left + 'px)', + '-webkit-transform': 'translateX(' + state.left + 'px)' + }) + }) + +} + +/** + * 获取元素信息 + * @param {Object} instance + * @param {Object} ownerInstance + */ +function getDom(instance, ownerInstance) { + var state = instance.getState() + var leftDom = ownerInstance.selectComponent('.button-group--left') + var rightDom = ownerInstance.selectComponent('.button-group--right') + var leftStyles = { + width: 0 + } + var rightStyles = { + width: 0 + } + + if (leftDom) { + leftStyles = leftDom.getBoundingClientRect() + } + + if (rightDom) { + rightStyles = rightDom.getBoundingClientRect() + } + state.leftWidth = leftStyles.width || 0 + state.rightWidth = rightStyles.width || 0 + state.threshold = instance.getDataset().threshold +} + +/** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ +function range(num, min, max) { + return Math.min(Math.max(num, min), max); +} + + +/** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + * @param {Object} ownerInstance + * @param {Object} ins + */ +function moveDirection(left, ins, ownerInstance) { + var state = ins.getState() + var threshold = state.threshold + var position = state.position + var isopen = state.isopen || 'none' + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + if (state.deltaX === 0) { + openState('none', ins, ownerInstance) + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > 0 && + rightWidth + + left < threshold)) { + // right + openState('right', ins, ownerInstance) + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > 0 && + leftWidth - left < threshold)) { + // left + openState('left', ins, ownerInstance) + } else { + // default + openState('none', ins, ownerInstance) + } +} + + +/** + * 开启状态 + * @param {Boolean} type + * @param {Object} ins + * @param {Object} ownerInstance + */ +function openState(type, ins, ownerInstance) { + var state = ins.getState() + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + var left = '' + state.isopen = state.isopen ? state.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + // && !state.throttle + + if (state.isopen !== type) { + state.throttle = true + ownerInstance.callMethod('change', { + open: type + }) + + } + + state.isopen = type + // 添加动画类 + ins.requestAnimationFrame(function() { + ins.addClass('ani'); + move(left, ins, ownerInstance) + }) + // 设置最终移动位置,理论上只要进入到这个函数,肯定是要打开的 +} + + +function getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; +} + +/** + * 重置滑动状态 + * @param {Object} event + */ +function resetTouchStatus(instance) { + var state = instance.getState(); + state.direction = ''; + state.deltaX = 0; + state.deltaY = 0; + state.offsetX = 0; + state.offsetY = 0; +} + +/** + * 设置滑动开始位置 + * @param {Object} event + */ +function stopTouchStart(event) { + var instance = event.instance; + var state = instance.getState(); + resetTouchStatus(instance); + var touch = event.touches[0]; + if (IS_HTML5 && isPC()) { + touch = event; + } + state.startX = touch.clientX; + state.startY = touch.clientY; +} + +/** + * 滑动中,是否禁止打开 + * @param {Object} event + */ +function stopTouchMove(event) { + var instance = event.instance; + var state = instance.getState(); + var touch = event.touches[0]; + if (IS_HTML5 && isPC()) { + touch = event; + } + state.deltaX = touch.clientX - state.startX; + state.deltaY = touch.clientY - state.startY; + state.offsetY = Math.abs(state.deltaY); + state.offsetX = Math.abs(state.deltaX); + state.direction = state.direction || getDirection(state.offsetX, state.offsetY); +} + +function isPC() { + var userAgentInfo = navigator.userAgent; + var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + var flag = true; + for (var v = 0; v < Agents.length - 1; v++) { + if (userAgentInfo.indexOf(Agents[v]) > 0) { + flag = false; + break; + } + } + if(userAgentInfo.indexOf('Phone') > 0 && userAgentInfo.indexOf('Harmony') > 0){ + flag = false; + } + return flag; +} + +var movable = false + +function mousedown(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + touchstart(e, ins) + movable = true +} + +function mousemove(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + if (!movable) return + touchmove(e, ins) +} + +function mouseup(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + touchend(e, ins) + movable = false +} + +function mouseleave(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + movable = false +} + +module.exports = { + showWatch: showWatch, + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend, + mousedown: mousedown, + mousemove: mousemove, + mouseup: mouseup, + mouseleave: mouseleave +} diff --git a/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue b/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue new file mode 100644 index 0000000..9b32b3d --- /dev/null +++ b/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/uni_modules/uni-swipe-action/package.json b/uni_modules/uni-swipe-action/package.json new file mode 100644 index 0000000..fb1df50 --- /dev/null +++ b/uni_modules/uni-swipe-action/package.json @@ -0,0 +1,112 @@ +{ + "id": "uni-swipe-action", + "displayName": "uni-swipe-action 滑动操作", + "version": "1.3.16", + "description": "SwipeAction 滑动操作操作组件", + "keywords": [ + "", + "uni-ui", + "uniui", + "滑动删除", + "侧滑删除" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "", + "uni-app": "^4.27", + "uni-app-x": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x" + }, + "uni_modules": { + "dependencies": [ + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app": { + "vue": { + "vue2": { + "extVersion": "1.3.14", + "minVersion": "" + }, + "vue3": { + "extVersion": "1.3.14", + "minVersion": "" + } + }, + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "vue": "-", + "nvue": "-", + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-", + "alipay": "-", + "toutiao": "-", + "baidu": "-", + "kuaishou": "-", + "jd": "-", + "harmony": "-", + "qq": "-", + "lark": "-" + }, + "quickapp": { + "huawei": "-", + "union": "-" + } + }, + "uni-app-x": { + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-" + } + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-swipe-action/readme.md b/uni_modules/uni-swipe-action/readme.md new file mode 100644 index 0000000..93a5cac --- /dev/null +++ b/uni_modules/uni-swipe-action/readme.md @@ -0,0 +1,11 @@ + + +## SwipeAction 滑动操作 +> **组件名:uni-swipe-action** +> 代码块: `uSwipeAction`、`uSwipeActionItem` + + +通过滑动触发选项的容器 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-swipe-action) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/utils/index.js b/utils/index.js index 084c7c1..ea215ca 100644 --- a/utils/index.js +++ b/utils/index.js @@ -4,12 +4,28 @@ * @returns */ export const formatRMB = amount => { - const formatted = new Intl.NumberFormat('zh-CN', { - style: 'currency', - currency: 'CNY', - minimumFractionDigits: 2, - maximumFractionDigits: 2 - }).format(amount) + // 处理 null、undefined 或非数字输入 + if (amount == null || amount === '') { + return '0.00' + } - return formatted.replace(/^[¥\s]+/, '') // 移除开头的 ¥ 和可能的空格 + // 转为数字 + let num = Number(amount) + if (isNaN(num)) { + return '0.00' + } + + // 保留两位小数(四舍五入) + num = Math.round(num * 100) / 100 + + // 转为固定两位小数字符串 + let str = num.toFixed(2) + + // 分离整数和小数部分 + const [integer, decimal] = str.split('.') + + // 添加千分位分隔符(从右往左每三位加逗号) + const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + + return `${formattedInteger}.${decimal}` } diff --git a/utils/use-ui.js b/utils/use-ui.js index 16edc7a..691494d 100644 --- a/utils/use-ui.js +++ b/utils/use-ui.js @@ -48,12 +48,26 @@ const showToast = (message, type = 'none', duration = 2000) => { }) } -// 导出响应式状态和方法 +/** 对话框带确认取消按钮 */ +const showDialog = (title, content) => { + return new Promise(resolve => { + uni.showModal({ + title, + content, + confirmText: '确定', + cancelText: '取消', + success: res => resolve(res.confirm) + }) + }) +} + +/** 导出响应式状态和方法 */ export const useUI = () => { return { isLoading: isLoading, // 可用于模板中 v-if="isLoading" showLoading, hideLoading, - showToast + showToast, + showDialog } } diff --git a/utils/validate.js b/utils/validate.js index d5b91d6..02d4ec3 100644 --- a/utils/validate.js +++ b/utils/validate.js @@ -1,7 +1,7 @@ // 正则 /** 手机号正则(中国大陆) */ -const PHONE_REGEX = /^1[3-9]\d{9}$/ +export const PHONE_REGEX = /^1[3-9]\d{9}$/ /** 邮箱正则 */ const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/