feat:迁移过去一部门

This commit is contained in:
dzc committed 2026-03-11 17:07:12 +08:00
1 parent 370024dc76
commit 838f05b3c0
35 files changed
+3372 -118

No files matched your search

+12
View File
@@ -7,4 +7,16 @@ declare namespace GlobalSetting {
username: string
password: string
}
// token相关
type TokenStorage = {
accessToken: string
refreshToken: string
accessTokenExpiresIn: string
refreshTokenExpiresIn: string
}
// 记住密码
type RememberUser = {
username: string
password: string
}
}
+166
View File
@@ -36,9 +36,42 @@
"navigationBarTitleText": "登录",
"disableScroll": true
}
},
{
"path": "pages/root/pwd-login",
"style": {
"navigationBarTitleText": "账号密码登录",
"disableScroll": true
}
}
],
"subPackages": [
{
"root": "pages/canteen",
"pages": [
{
"path": "detail",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
},
{
"path": "food",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
},
{
"path": "product-detail",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
}
]
},
{
"root": "pages/wallet",
"pages": [
@@ -64,6 +97,139 @@
}
}
]
},
{
"root": "pages/boxes",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
}
]
},
{
"root": "pages/order",
"pages": [
{
"path": "detail",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
}
]
},
{
"root": "pages/merchant",
"pages": [
{
"path": "detail",
"style": {
"navigationBarTitleText": "商户详情",
"disableScroll": true
}
},
{
"path": "brandDetail",
"style": {
"navigationBarTitleText": "商户品牌详情",
"disableScroll": true
}
},
{
"path": "cancel-qrcode",
"style": {
"navigationBarTitleText": "活动",
"disableScroll": true
}
},
{
"path": "my-act-list",
"style": {
"navigationBarTitleText": "我参加的活动",
"disableScroll": true
}
},
{
"path": "merchant-activity",
"style": {
"navigationBarTitleText": "商户活动",
"disableScroll": true
}
},
{
"path": "coupon-list",
"style": {
"navigationBarTitleText": "优惠券列表",
"disableScroll": true
}
}
]
},
{
"root": "pages/pay",
"pages": [
{
"path": "scanPay",
"style": {
"navigationBarTitleText": "支付",
"disableScroll": true
}
},
{
"path": "payComplete",
"style": {
"navigationBarTitleText": "支付完成",
"disableScroll": true
}
},
{
"path": "transferMoney",
"style": {
"navigationBarTitleText": "转账",
"disableScroll": true
}
}
]
},
{
"root": "pages/human-face",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
},
{
"path": "apply",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
}
]
},
{
"root": "pages/car",
"pages": [
{
"path": "user-car/apply",
"style": {
"navigationBarTitleText": "申请车辆"
}
},
{
"path": "user-car/list",
"style": {
"navigationBarTitleText": "我的车辆"
}
}
]
}
],
"globalStyle": {
+271
View File
@@ -0,0 +1,271 @@
<script setup lang="ts">
import { ref, reactive, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks, router } from "@@/index"
import { boxesApi } from "@@/api"
import { filePath } from "@/sheep/utils/file"
const state = reactive<any>({
detailId: null,
pa: {
status: null, // 移除状态筛选
},
})
// 重新设置图片路径
const boxesList = computed(() => {
return pageHook.list.map((item: any) => {
return { ...item, images: filePath(item.images) }
})
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, boxesApi.page, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
// 获取状态样式类
const getStatusClass = (status: number) => {
const statusMap: Record<number, string> = {
0: 'status-available',
1: 'status-partial',
2: 'status-full'
}
return statusMap[status] || 'status-available'
}
// 获取状态文本
const getStatusText = (status: number) => {
const statusMap: Record<number, string> = {
0: '空闲',
1: '部分空闲',
2: '已满'
}
return statusMap[status] || '空闲'
}
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.detailId = options.id
state.pa.merchantId = options.id
}
})
onShow(() => {
if (pageRef.value) {
pageRef.value.reload(false)
}
})
</script>
<template>
<crud-layout title="餐桌列表" bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="999"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<template #top>
<view class="header-section">
<view class="welcome-text">餐厅餐桌列表</view>
</view>
</template>
<view class="boxes-container">
<view class="boxes-grid">
<view v-for="item in boxesList" :key="item.id" class="box-card">
<view class="card-image-section">
<image v-if="item.images" :src="item.images" class="box-image" mode="aspectFill" />
<view v-else class="image-placeholder">
<text class="placeholder-icon">🍴</text>
<text class="placeholder-text">餐桌</text>
</view>
</view>
<view class="card-content">
<view class="box-title">{{ item.name }}</view>
<view class="box-subtitle">{{ item.tableNumber }}</view>
<view class="box-features">
<view class="feature-item">
<text class="feature-icon">👥</text>
<text class="feature-text">{{ item.tableTypeName }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</z-paging>
</crud-layout>
</template>
<style lang="scss" scoped>
.header-section {
padding: 40rpx 24rpx 32rpx 24rpx;
background: linear-gradient(135deg, #3B82F6 0%, #1D4ED8 100%);
border-radius: 0 0 32rpx 32rpx;
margin: 0 24rpx 32rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(59, 130, 246, 0.3);
}
.welcome-text {
color: #fff;
font-size: 38rpx;
font-weight: 600;
text-align: center;
letter-spacing: 1rpx;
}
.boxes-container {
padding: 0 24rpx;
background: transparent;
min-height: 100vh;
}
.boxes-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.box-card {
background: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(59, 130, 246, 0.08);
overflow: hidden;
position: relative;
border: 1rpx solid #DBEAFE;
transition: all 0.3s ease;
}
.box-card:hover {
transform: translateY(-4rpx);
box-shadow: 0 8rpx 24rpx rgba(59, 130, 246, 0.15);
border-color: #93C5FD;
}
.card-image-section {
position: relative;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.box-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.box-card:hover .box-image {
transform: scale(1.02);
}
.image-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.placeholder-icon {
font-size: 48rpx;
margin-bottom: 8rpx;
display: block;
}
.placeholder-text {
font-size: 24rpx;
color: #3B82F6;
font-weight: 500;
display: block;
}
.card-content {
padding: 24rpx;
background: #fff;
position: relative;
}
.card-content::before {
content: '';
position: absolute;
top: 0;
left: 24rpx;
right: 24rpx;
height: 1rpx;
background: linear-gradient(90deg, transparent 0%, #E5E7EB 20%, #E5E7EB 80%, transparent 100%);
}
.box-title {
font-size: 32rpx;
font-weight: 700;
color: #1E40AF;
margin-bottom: 12rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
}
.box-subtitle {
font-size: 26rpx;
color: #3B82F6;
margin-bottom: 20rpx;
display: inline-block;
background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%);
padding: 10rpx 18rpx;
border-radius: 12rpx;
color: #1D4ED8;
font-weight: 600;
border: 1rpx solid #DBEAFE;
box-shadow: 0 2rpx 8rpx rgba(59, 130, 246, 0.1);
}
.box-features {
margin-bottom: 0;
}
.feature-item {
display: flex;
align-items: center;
margin-bottom: 0;
padding: 8rpx 0;
}
.feature-icon {
font-size: 32rpx;
margin-right: 16rpx;
width: 48rpx;
text-align: center;
background: linear-gradient(135deg, #60A5FA 0%, #3B82F6 100%);
padding: 12rpx;
border-radius: 12rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(59, 130, 246, 0.2);
}
.feature-text {
font-size: 28rpx;
color: #1E40AF;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600;
}
</style>
@@ -0,0 +1,53 @@
<script setup lang="ts">
import ProductRow from "./product-row.vue"
import ProductTotal from "./product-total.vue"
import { filePath } from "@@/utils/file"
const emit = defineEmits(["update:modelValue", "submit", "add", "remove"])
export type Props = {
modelValue: boolean
cartList: any
totPrice: number
totCount: number
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
cartList: [],
totPrice: 0,
totCount: 0,
})
const doClose = () => {
emit("update:modelValue", false)
}
const doPay = () => {
emit("submit")
}
const getHeight = () => {
const { bottom } = uni.getSystemInfoSync().safeAreaInsets!
return `-${bottom + 16}px`
}
const add = (v) => emit("add", v)
const remove = (v) => emit("remove", v)
</script>
<template>
<tm-drawer z-index="100" v-model:show="props.modelValue" showFooter title="购物车" position="bottom" size="60%"
bgColor="#ECF4F9" @close="doClose">
<view v-for="(v, i) in props.cartList" :key="v.id">
<product-row v-model="v.productCount" mode="cart" :pic="filePath(v.productPic)" :count="v.productCount"
:name="v.productName" :price="v.productPrice" :marketPrice="v.marketPrice" :salesCount="v.salesCount"
:stock="v.stock" @add="add(v)" @remove="remove(v)" />
</view>
<template #footer>
<view class="z-200 p-x-30rpx p-y-30rpx bg-white" :style="{ marginBottom: getHeight() }">
<product-total :totPrice="props.totPrice" :totCount="props.totCount" @submit="doPay" @doContainer="" />
</view>
</template>
</tm-drawer>
</template>
@@ -0,0 +1,79 @@
<script setup lang="ts">
import { hooks, router } from "@@/index"
const emit = defineEmits(["update:modelValue", "add", "remove"])
export type Props = {
mode?: "default" | "cart"
modelValue: number
productId?: number | string | null
pic: string
name: string
price: string | number
marketPrice: number
salesCount: number
stock: number
count?: number
}
const props = withDefaults(defineProps<Props>(), {
mode: "default",
modelValue: 0,
productId: null,
pic: "",
name: "",
price: 0,
marketPrice: 0,
salesCount: 0,
stock: 0,
count: 0,
})
// 点击加
const add = () => {
if (props.mode !== "cart") {
if (props.modelValue + 1 > props.stock) return uni.showToast({ title: "库存不足", icon: "none" })
}
emit("update:modelValue", props.modelValue + 1)
emit("add")
}
// 点击减
const remove = () => {
if (props.modelValue - 1 < 0) return
emit("update:modelValue", props.modelValue - 1)
emit("remove")
}
</script>
<template>
<view class="flex-row items-start card p-x-15rpx p-y-25rpx m-b-20rpx"
@tap="props.productId && router.jump('/pages/canteen/product-detail', { id: props.productId })">
<image v-if="props.mode == 'default'" class="size-180rpx rounded-12rpx" :src="props.pic" mode="aspectFill" />
<image v-if="props.mode == 'cart'" class="size-120rpx rounded-12rpx" :src="props.pic" mode="aspectFill" />
<view class="flex-1 m-l-20rpx">
<view class="sub-text-28 show-line">{{ props.name }}</view>
<view class="m-t-10rpx flex-row items-center">
<view class="w-110rpx second-text-32 color-red!"> ¥{{ hooks.useMoney(props.price) }} </view>
<view v-if="props.mode == 'default'" class="m-l-20rpx second-text-24 line-through">¥{{
hooks.useMoney(props.marketPrice) }}</view>
<view v-if="props.mode == 'cart'" class="m-l-40rpx second-text-24">x{{ props.count }}</view>
</view>
<view v-if="props.mode == 'default'" class="flex-row items-center MT 10rpx">
<!-- <view class="second-text-24">{{ hooks.useFormatNum("库存", "", props.stock) }}</view> -->
<view class="second-text-24">库存:{{ props.stock }}</view>
<view class="w-1px h-20rpx m-x-5rpx bg-#999 mx-2" />
<view class="second-text-24">{{ hooks.useFormatNum("销量", "", props.salesCount) }}</view>
</view>
<view class="flex-row items-center justify-end m-t-20rpx">
<view class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-blue"
@tap.stop="remove">
<view class="iconfont icon-a--" />
</view>
<view class="sub-text-28 m-x-20rpx">{{ props.modelValue }}</view>
<view class="flex-row-center size-40rpx rounded-full bg-blue" @tap.stop="add">
<view class="font-size-40rpx color-white iconfont icon-a-" />
</view>
</view>
</view>
</view>
</template>
@@ -0,0 +1,38 @@
<script setup lang="ts">
import { hooks } from "@@/index"
const emit = defineEmits(["submit", "doContainer"])
export type Props = {
totPrice: number
totCount: number
}
const props = withDefaults(defineProps<Props>(), {
totPrice: 0,
totCount: 0,
})
const doPay = () => {
emit("submit")
}
const doContainer = () => {
emit("doContainer")
}
</script>
<template>
<view class="flex-row items-center justify-between" @tap="doContainer">
<view class="flex-row-center">
<view class="flex-row-center size-80rpx rounded-full bg-blue">
<view class="color-white! iconfont icon-biaoqianlanicon_gouwuche-xuanzhong" />
</view>
<view class="flex-col m-l-30rpx">
<view class="second-text-36 color-red!">¥ {{ hooks.useMoney(props.totPrice) }}</view>
<view class="sub-text-28">共{{ props.totCount }}件</view>
</view>
</view>
<view class="bg-blue p-x-30rpx p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap.stop="doPay">
购买</view>
</view>
</template>
+459
View File
@@ -0,0 +1,459 @@
<script setup lang="ts">
import { reactive, nextTick } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks, router } from "@@/index"
import { merchantApi, foodApi, payApi, orderApi } from "@@/api"
import { filePath } from "@@/utils/file"
import ProductRow from "./components/product-row.vue"
import ProductCart from "./components/product-cart.vue"
import ProductTotal from "./components/product-total.vue"
const state = reactive<any>({
detailId: null,
canteenDetail: {},
foodList: [],
cartList: [],
totPrice: 0,
totCount: 0,
classifyList: [],
currentClassifyIndex: 0,
showCartDialog: false,
showBookingDialog: false,
bookingTimeList: [],
selectedBookingTime: "",
})
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.detailId = options.id
}
})
onShow(() => {
getDetail()
getFoodCart()
getClassify()
})
const getHeight = () => {
const rpx = 480
return `calc(100vh - ${uni.upx2px(rpx)}px)`
}
// 获取食堂详情信息
const getDetail = async () => {
uni.showLoading({ title: "加载中...", mask: true })
const { code, data } = await merchantApi.detail({ id: state.detailId })
if (code == 0) state.canteenDetail = data
uni.hideLoading()
}
// 获取购物车信息
const getFoodCart = async () => {
const { data } = await orderApi.getShopCar({ merchantId: state.detailId, hardType: "CANTEEN" })
state.cartList = data ?? []
if (isEmpty(state.cartList)) {
state.foodList.forEach((v: any) => {
v.productCount = 0
})
} else {
syncCartCount(state.foodList)
}
state.totPrice = state.cartList.map((v) => Number(v.totalPrice)).reduce((pre, next) => pre + next, 0)
state.totCount = state.cartList.map((v) => v.productCount).reduce((pre, next) => pre + next, 0)
}
// 同步购物车购买数量
const syncCartCount = (foodList: any) => {
if (state.cartList && state.cartList.length) {
for (const i of foodList) {
for (const v of state.cartList) {
if (i.id == v.productId) {
i.productCount = v.productCount
}
}
}
}
state.foodList = foodList
}
// 获取菜品分类
const getClassify = async () => {
const { data } = await foodApi.classify({ merchantId: state.detailId })
state.classifyList = data ?? []
if (!isEmpty(state.classifyList)) {
getFoodList()
}
}
// 获取食物列表
const getFoodList = async () => {
const target = state.classifyList[state.currentClassifyIndex]
if (isEmpty(target)) return
const _pa = {
merchantId: state.detailId,
categoryId: target.id,
time: hooks.useTime(new Date(), "{y}-{m}-{d}"),
}
const { data } = await foodApi.product(_pa)
data.forEach((i: any) => {
i.productCount = 0
i.productId = i.id
})
syncCartCount(data)
}
// 切换选中的分类
const doClassify = (i: any) => {
state.currentClassifyIndex = i
getFoodList()
}
// 设置购物车商品的数量
const chentProductCartCount = async (item: any) => {
try {
uni.showLoading({ title: "加载中", mask: true })
// 检查商品库存
const { code, msg }: any = await foodApi.checkSpuStock({ spuId: item.productId, incrCount: item.productCount })
if (code != 0) {
item.productCount--
return uni.showToast({ title: msg, icon: "none" })
}
const targetIndex = state.cartList.findIndex((v) => v.productId == item.productId)
if (targetIndex !== -1) {
state.cartList[targetIndex].productCount = item.productCount
} else {
state.cartList.push({ ...item, productId: item.id, productCount: item.productCount })
}
nextTick(async () => {
await updateFoodCart()
})
} catch (e) {
console.error(e)
} finally {
uni.hideLoading()
}
}
// 保存购物车商品
const updateFoodCart = async () => {
if (isEmpty(state.cartList)) return
const list = state.cartList
.filter((v) => v.productCount !== undefined && v.productCount >= 0)
.map((v) => {
return {
productCount: v.productCount,
productId: v.productId,
}
})
if (isEmpty(list)) return
const _pa = { merchantId: state.detailId, list: list, hardType: "CANTEEN" }
const { data, code }: any = await orderApi.updateShopCar(_pa)
if (code == 0) await getFoodCart()
}
// 打开购物车
const doVisibleCart = () => (state.showCartDialog = true)
// 购买 打开预订时间段选择对话框
const doBuy = async () => {
state.selectedBookingTime = ""
try {
uni.showLoading({ title: "加载中...", mask: true })
const res: any = await orderApi.apptList()
if (res.code == 0 && res.data.length > 0) {
state.bookingTimeList = res.data
state.selectedBookingTime = res.data[0].id
// 打开预约时间段选择对话框
state.showBookingDialog = true
} else {
uni.showToast({ title: res.msg || "预约时间段获取失败", icon: "none" })
}
} catch (error: any) {
uni.showToast({ title: error.msg, icon: "none" })
} finally {
uni.hideLoading()
}
}
// 预约时间段对话框确认
const bookingDialogConfirm = () => {
if (!state.selectedBookingTime) return uni.showToast({ title: "请选择预订时间段", icon: "none" })
uni.showModal({
title: "提示",
content: "是否确定购买",
confirmColor: "#0094ff",
success: async (res) => {
if (!res.confirm) return
submitBuy()
},
})
}
// 确认购买
const submitBuy = async () => {
try {
if (isEmpty(state.cartList)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
uni.showLoading({ title: "支付中...", mask: true })
const _createtPa = {
carParentId: state.cartList[0].parentCarId,
hardType: "CANTEEN",
apptId: state.selectedBookingTime,
}
const { data: createData, code: createCode }: any = await payApi.createHardwareOrder(_createtPa)
if (createCode != 0) throw new Error()
uni.showToast({ title: "支付成功", icon: "none" })
state.showBookingDialog = false
router.jump("/pages/tabbar/order")
} catch (error) {
console.error(error)
} finally {
uni.hideLoading()
}
}
const doPhone = () => {
if (isEmpty(state.canteenDetail.phone)) {
uni.showToast({ title: "该商户没有预留电话", icon: "none" })
return
}
hooks.useMakePhoneCall(state.canteenDetail.phone)
}
const doFood = () => {
router.jump("/pages/canteen/food", { merchantId: state.detailId })
}
</script>
<template>
<crud-layout mainIsScroll tabBarType="just-flot-back" bg="/static/page/blue-bg.png">
<tm-sticky>
<swiper class="w-100vw h-30vh" circular :indicator-dots="true" indicator-active-color="#0779FD"
indicator-color="#ffffff" :autoplay="true">
<swiper-item v-for="(v, i) in state.canteenDetail?.swiperPic?.split(',')" :key="i">
<image class="w-full h-full" :src="filePath(v)" mode="aspectFill" />
</swiper-item>
</swiper>
<tm-sticky-header>
<view class="m-x-30rpx">
<!-- start 简介 start -->
<view class="h-15rpx" />
<view class="flex-row card p-20rpx">
<image class="w-180rpx h-180rpx rounded-12rpx" :src="filePath(state.canteenDetail.logoPic)"
mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
<view class="flex-row items-center justify-between">
<view class="flex-row-center sub-text-34">
<text>{{ state.canteenDetail.name }}</text>
</view>
<view>
<tm-tag size="s">{{ state.canteenDetail.deptName }}</tm-tag>
</view>
</view>
<view class="second-text-24">
<view class="second-text-28">营业时间:</view>
<view>
{{ state.canteenDetail.jobTime?.split(",")[0] ? "早:" +
state.canteenDetail.jobTime?.split(",")[0] : "" }}
</view>
<view>
{{ state.canteenDetail.jobTime?.split(",")[1] ? "中:" +
state.canteenDetail.jobTime?.split(",")[1] : "" }}
</view>
<view>
{{ state.canteenDetail.jobTime?.split(",")[2] ? "晚:" +
state.canteenDetail.jobTime?.split(",")[2] : "" }}
</view>
</view>
<view v-if="state.canteenDetail.addressDes" class="m-t-10rpx second-text-28">
<text>食堂地址:</text>
<text class="m-l-10rpx">{{ state.canteenDetail.addressDes }}</text>
</view>
</view>
</view>
<!-- end 简介 end -->
<!-- start 功能 start -->
<view class="h-15rpx" />
<view class="flex-row items-center justify-evenly card p-y-20rpx">
<view class="flex-row-center" @tap="doPhone">
<view class="flex-row-center w-50rpx h-50rpx bg-#167aff rounded-full">
<text class="font-size-28rpx! color-white iconfont icon-dianhua" />
</view>
<text class="m-l-20rpx sub-text-30">联系食堂</text>
</view>
<view class="w-1px h-30rpx bg-#c4c4c4" />
<view class="flex-row-center"
@tap.stop="router.jump('/pages/boxes/list', { id: state.detailId })">
<view class="flex-row-center w-50rpx h-50rpx bg-#167aff rounded-full">
<text class="font-size-28rpx! color-white iconfont icon-yuding" />
</view>
<text class="m-l-20rpx sub-text-28">餐桌信息</text>
</view>
<view class="w-1px h-30rpx bg-#c4c4c4" />
<view class="flex-row-center" @tap="doFood">
<view class="flex-row-center w-50rpx h-50rpx bg-#167aff rounded-full">
<text class="font-size-28rpx! color-white iconfont icon-caipu" />
</view>
<text class="m-l-20rpx sub-text-28">每日菜谱</text>
</view>
</view>
<!-- end 功能 end -->
</view>
</tm-sticky-header>
<view class="h-15rpx" />
<!-- start 商品 start -->
<view class="flex-row">
<scroll-view v-if="state.classifyList.length > 0" scroll-y :style="{ height: getHeight() }"
class="menu w-200rpx h-full p-l-12rpx p-b-20rpx bg-#f6f6f6">
<view class="menu_item flex-row-center" v-for="(v, i) in state.classifyList" :key="v.id"
:class="[{ menu_item_active: i == state.currentClassifyIndex }]" @tap="doClassify(i)">
<view class="menu_item_title show-line">
{{ v.name }}
</view>
</view>
</scroll-view>
<scroll-view v-if="state.foodList.length > 0" scroll-y :style="{ height: getHeight() }"
class="flex-1 p-b-20rpx">
<view v-for="(pV, pI) in state.foodList" :key="pI">
<view class="m-x-15rpx">
<product-row v-model="pV.productCount" :productId="pV.id" :pic="filePath(pV.picUrl)"
:name="pV.name" :price="pV.price" :marketPrice="pV.marketPrice"
:salesCount="pV.salesCount" :stock="pV.stock" @add="chentProductCartCount(pV)"
@remove="chentProductCartCount(pV)" />
</view>
</view>
</scroll-view>
<view v-else class="mx-auto mt-10 second-text-28">暂无信息</view>
</view>
</tm-sticky>
<!-- end 商品 end -->
<template #main-bottom>
<view v-if="state.classifyList.length > 0" class="cart z-200 p-x-30rpx p-y-30rpx bg-white">
<product-total :totPrice="state.totPrice" :totCount="state.totCount" @doContainer="doVisibleCart"
@submit="doBuy" />
</view>
</template>
</crud-layout>
<product-cart v-model="state.showCartDialog" :cartList="state.cartList" :totPrice="state.totPrice"
:totCount="state.totCount" @add="(v) => chentProductCartCount(v)" @remove="(v) => chentProductCartCount(v)"
@submit="doBuy" />
<tm-modal v-model:show="state.showBookingDialog" :overlayClick="false" :showCancel="true" title="请选择预订餐段"
cancelText="取消" confirmText="确认" position="bottom" height="520rpx" zIndex="800">
<tm-radio-group v-model="state.selectedBookingTime" direction="column">
<view v-for="(item, index) in state.bookingTimeList" :key="index" class="w-full flex items-center">
<tm-radio class="w-full h-full px-2" :value="item.id">
<template v-slot:label="checked">
<view class="text-30">{{ item.title }}</view>
<view class="second-text-28 flex">
<text class="w-1/2">可预订时间段:</text>
<text class="w-1/2">{{ item.startTime.slice(0, -3) }}-{{ item.endTime.slice(0, -3) }}</text>
</view>
<view class="second-text-28 flex">
<text class="w-1/2">用餐时间段:</text>
<text class="w-1/2">
{{ state.canteenDetail.jobTime?.split(",")[Number(index) + 1] ?
state.canteenDetail.jobTime?.split(",")[Number(index) + 1] : "" }}
</text>
</view>
</template>
</tm-radio>
</view>
</tm-radio-group>
<template #footer>
<view class="flex px-4 w-full">
<tm-button @click="state.showBookingDialog = false" :block="true" skin="thin">取消</tm-button>
&nbsp;&nbsp;
<tm-button @click="bookingDialogConfirm" :block="true">确认</tm-button>
</view>
</template>
</tm-modal>
</template>
<style lang="scss" scoped>
:deep() {
.layout_main>scroll-view {
padding: 0 !important;
}
}
.menu {
&_item {
width: 100%;
height: 88rpx;
position: relative;
transition: all linear 0.2s;
&_title {
width: 100%;
line-height: 32rpx;
font-size: 30rpx;
font-weight: 400;
color: #333;
margin-left: 28rpx;
position: relative;
z-index: 0;
&::before {
content: "";
width: 64rpx;
height: 12rpx;
background: linear-gradient(90deg, #0094ff, #e9f5fe) !important;
position: absolute;
left: -64rpx;
bottom: 0;
z-index: -1;
transition: all linear 0.2s;
}
}
&.menu_item_active {
background-color: #fff;
border-radius: 20rpx 0 0 20rpx;
&::before {
content: "";
position: absolute;
right: 0;
bottom: -20rpx;
width: 20rpx;
height: 20rpx;
background: radial-gradient(circle at 0 100%, transparent 20rpx, #fff 0);
}
&::after {
content: "";
position: absolute;
top: -20rpx;
right: 0;
width: 20rpx;
height: 20rpx;
background: radial-gradient(circle at 0% 0%, transparent 20rpx, #fff 0);
}
.menu_item_title {
font-weight: 600;
&::before {
left: 0;
}
}
}
}
}
.cart {
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
}
</style>
+169
View File
@@ -0,0 +1,169 @@
<script setup lang="ts">
import { ref, reactive, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import dayjs from "dayjs"
import { hooks, router } from "@@/index"
import { foodApi } from "@@/api"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import CustomWaterfallsFlow from "@@/components/other/custom-waterfalls-flow/custom-waterfalls-flow.vue"
import { filePath } from "@@/utils/file"
const menus = {
tabs: [
// { id: null, name: "全部", disabled: false },
{ id: 1, name: "早餐", disabled: false },
{ id: 2, name: "午餐", disabled: false },
{ id: 3, name: "晚餐", disabled: false },
],
weekDays: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
}
const state = reactive<any>({
dateList: [],
currentDateIndex: 0,
pa: {
merchantId: null,
startTime: null,
hourType: menus.tabs[0].id,
},
})
onLoad((options: any) => {
initDate()
if (!isEmpty(options.merchantId)) {
state.pa.merchantId = options.merchantId
}
})
onShow(() => {
if (pageRef.value && pageRef.value.reload) {
pageRef.value.reload(false)
}
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, foodApi.page, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
// 重新设置图片路径
const foodList = computed(() => {
return pageHook.list.map((item: any) => {
return { ...item, picUrl: filePath(item.picUrl) }
})
})
const initDate = () => {
const list: any = []
const now = new Date()
for (let i = 0; i < 10; i++) {
const date = dayjs(now).add(i, "d")
const weekDayStr = date.isSame(now, "day") ? "今天" : menus.weekDays[date.day()]
list.push({
weekDayStr,
date: date.toDate(),
dayTime: date.format("MM-DD"),
dateTime: date.format("YYYY-MM-DD"),
})
}
state.dateList = list
state.pa.startTime = state.dateList[0].dateTime
pageRef.value?.reload(false)
}
const doDateItem = (v: any, i: any) => {
state.currentDateIndex = i
state.pa.startTime = state.dateList[i].dateTime
pageHook.list = []
pageRef.value.reload(true)
}
const doTab = (e: any) => {
const target = menus.tabs[e]
state.pa.hourType = target.id
pageHook.list = []
pageRef.value.reload(true)
}
const doProduct = (v: any) => {
// router.jump("/pages/canteen/product-detail", { spuId: v.spuId })
}
</script>
<template>
<crud-layout title="每日菜谱" navBgColor="#1777FF" navBgTitleColor="white" bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="8"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<template #top>
<view class="bg-#1777FF p-15rpx">
<scroll-view scroll-x class="whitespace-nowrap">
<view class="flex-row items-center space-x-15rpx">
<view :class="{ active_date: state.currentDateIndex == i }"
class="flex-col-center flex-shrink-0 size-95rpx color-white rounded-8rpx font-size-24rpx font-700"
v-for="(v, i) in state.dateList" :key="i" @tap="doDateItem(v, i)">
<view>{{ v.dayTime }}</view>
<view>{{ v.weekDayStr }}</view>
</view>
</view>
</scroll-view>
</view>
<view class="bg-white rounded-16rpx px-20rpx m-x-10rpx m-t-15rpx">
<z-tabs :list="menus.tabs" @change="doTab" />
</view>
</template>
<view class="h-20rpx" />
<view class="m-x-20rpx">
<!-- <Waterfall v-if="foodList.length" :list="foodList" :gutter="10" :breakpoints="{
1800: { rowPerView: 4 },
1400: { rowPerView: 3 },
975: { rowPerView: 2 },
}" :hasAroundGutter="false" backgroundColor="#ffffff00" :crossOrigin="false" :animationDuration="0"
:posDuration="0">
<template #default="{ item, url, index }">
<view class="card">
<LazyImg class="rounded-2" :url="item.picUrl" />
<view class="p-x-15rpx p-y-25rpx flex items-center justify-between">
<view class="sub-text-28 show-line">{{ item.name }}</view>
<view class="second-text-32 color-red!"> ¥{{ hooks.useMoney(item.price) }} </view>
</view>
</view>
</template>
</Waterfall> -->
<custom-waterfalls-flow v-model="foodList" imageKey="picUrl" :columnSpace="1" @wapperClick="doProduct"
@imageClick="doProduct">
<template v-slot:default="v">
<view class="p-x-15rpx p-y-25rpx">
<view class="sub-text-28 show-line">{{ v.name }}</view>
<view class="m-t-10rpx flex-row items-center">
<view class="second-text-32 color-red!"> ¥{{ hooks.useMoney(v.price) }} </view>
<view class="m-l-20rpx second-text-24 line-through"> ¥{{ hooks.useMoney(v.marketPrice)
}} </view>
</view>
<view class="flex-row items-center MT 10rpx">
<view class="second-text-24">{{ hooks.useFormatNum("库存", "", v.stock) }}</view>
<view class="w-1px h-20rpx m-x-5rpx bg-#999" />
<view class="second-text-24">{{ hooks.useFormatNum("销量", "", v.salesCount) }}</view>
</view>
</view>
</template>
</custom-waterfalls-flow>
</view>
</z-paging>
</crud-layout>
</template>
<style lang="scss" scoped>
:deep() {
.layout_main>scroll-view {
padding: 0 !important;
}
}
.active_date {
background-color: white;
color: #1679ff !important;
}
</style>
+73
View File
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { productApi } from "@@/api"
import { filePath } from "@@/utils/file"
import { hooks } from "@@/index"
const state = reactive<any>({
detailId: null,
detail: null,
})
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.detailId = options.id
}
})
onShow(() => {
getDetail()
})
const getDetail = async () => {
uni.showLoading({
title: "加载中...",
})
const _pa = { id: state.detailId }
const { data } = await productApi.get(_pa)
state.detail = data
uni.hideLoading()
}
</script>
<template>
<crud-layout title="菜品详情" tabBarType="just-flot-back" bg="/static/page/blue-bg.png">
<view v-if="state.detail">
<image class="w-100vw" :src="filePath(state.detail.picUrl)" mode="widthFix" />
<view class="p-x-15rpx">
<view class="flex-col justify-between card p-y-20rpx p-x-30rpx">
<view class="flex-row items-center justify-between">
<view class="flex-row items-center">
<view class="second-text-46 color-red!">¥{{ hooks.useMoney(state.detail.price) }}</view>
<view class="m-l-20rpx second-text-28 line-through">¥{{
hooks.useMoney(state.detail.marketPrice) }}</view>
</view>
<view class="second-text-26">已售{{ state.detail.salesCount }}</view>
</view>
<view class="flex-col m-t-15rpx">
<view class="sub-text-32">{{ state.detail.name }}</view>
<view class="m-t-15rpx second-text-26">{{ state.detail.introduction }}</view>
</view>
</view>
<view v-if="false" class="m-t-15rpx flex-row items-center justify-between card p-y-20rpx p-x-30rpx">
<view class="prefix_title">评价</view>
</view>
<view v-if="state.detail.description"
class="m-t-15rpx flex-col justify-between card p-y-20rpx p-x-30rpx">
<view class="prefix_title">详情</view>
<view class="m-t-15rpx" v-html="state.detail.description"></view>
</view>
</view>
</view>
<view v-else class="second-text-28 mt-24 text-center">暂无数据</view>
</crud-layout>
</template>
<style lang="scss" scoped>
:deep() {
.layout_main>scroll-view {
padding: 0 !important;
}
}
</style>
+111
View File
@@ -0,0 +1,111 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import PlateNum from "@@/components/tongtong-plate/plate-number.vue";
import { hooks, router } from "@@/index"
import { userCarApi } from "@@/api"
import { serviceEnum } from "@/sheep/constant"
const { carTypeEnum } = serviceEnum
const applyFormRef = ref<any>(null)
const plateNumRef = ref<any>(null)
const visitorReasonList = ref<Array<{ title: string; id: string }>>(Object.keys(carTypeEnum).map((key) => ({
title: carTypeEnum[key],
id: key,
})))
const applyForm = reactive<any>({
id: "",
carType: "",
carTypeStr: "",
carNumber: "",
})
const applyFormRules = reactive<any>({
carTypeStr: [{ required: true, message: "请选择车辆类型" }],
carNumber: [{ required: true, message: "请输入车牌号" }],
})
onLoad(() => {
// 默认第一个
if (!applyForm.carType) {
const target = visitorReasonList.value[0]
applyForm.carType = target.id
applyForm.carTypeStr = target.title
}
})
onShow(() => {
})
// 选择车俩类型
const carNumberPickerConfirm = (e) => {
const target = visitorReasonList.value.find((item) => item.id == e[0])
if (!target) return
applyForm.carType = target.id
applyForm.carTypeStr = target.title
}
const doSubmit = async () => {
// 车牌号
const carNumber = plateNumRef.value.getPlate()
if (!carNumber) {
uni.showToast({ title: "请输入车牌号", icon: "none" })
return
}
applyForm.carNumber = carNumber
// 校验
applyFormRef.value.submit()
const { isPass } = applyFormRef.value.validate()
if (!isPass) return
try {
uni.showLoading({ title: "申请中...", mask: false })
const _pa = {
...applyForm
}
const { data, code }: any = await userCarApi.apply(_pa)
if (code != 0) throw new Error()
uni.hideLoading()
uni.showToast({ title: "申请成功", icon: "none" })
router.back()
} catch (error) {
console.error(error)
uni.hideLoading()
}
}
</script>
<template>
<crud-layout title="申请车辆" bg="/static/page/blue-bg.png">
<view class="h-30rpx" />
<view class="card p-x-30rpx p-y-40rpx">
<tm-form ref="applyFormRef" v-model="applyForm" :rules="applyFormRules" direction="vertical">
<tm-form-item name="carTypeStr" label="车辆类型" :showBottom="false">
<tm-picker @confirm="carNumberPickerConfirm" :list="visitorReasonList">
<tm-input :modelValue="applyForm.carTypeStr" placeholder="请选择车辆类型" style="pointer-events: none">
</tm-input>
</tm-picker>
</tm-form-item>
<tm-form-item name="carNumber" label="车牌号" :showBottom="false">
<PlateNum ref="plateNumRef" />
</tm-form-item>
</tm-form>
</view>
<view class=" bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-60rpx sub-text-30 color-white!"
@tap="doSubmit">
提交
</view>
</crud-layout>
</template>
<style lang="scss" scoped>
:deep() {
.tmFormItemLabel {
padding-bottom: 10rpx !important;
}
}
</style>
+81
View File
@@ -0,0 +1,81 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { hooks, router } from "@@/index"
import { userCarApi } from "@@/api"
import { serviceEnum } from "@/sheep/constant"
import { filePath } from "@@/utils/file"
const { carTypeEnum } = serviceEnum
const state = reactive<any>({
pa: {
pagesize: 10,
},
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, userCarApi.page, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
onLoad(() => { })
onShow(() => {
if (pageRef.value) {
pageRef.value.reload(false)
}
})
const doApply = () => {
router.jump("/pages/car/user-car/apply")
}
</script>
<template>
<crud-layout title="我的车辆" bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<view class="h-20rpx" />
<view class="position-relative card p-x-20rpx p-y-30rpx m-b-20rpx overflow-hidden"
v-for="(v, i) in pageHook.list" :key="v.id">
<view class="flex justify-between items-center m-t-20rpx">
<view>
<!-- 私家车 -->
<tm-tag v-if="v.carType == 1" size="m">{{ carTypeEnum[v.carType] }}</tm-tag>
<!-- 公车 -->
<tm-tag v-if="v.carType == 2" size="m">{{ carTypeEnum[v.carType] }}</tm-tag>
<!-- 军车 -->
<tm-tag v-if="v.carType == 3" size="m">{{ carTypeEnum[v.carType] }}</tm-tag>
<!-- 警车 -->
<tm-tag v-if="v.carType == 4" size="m">{{ carTypeEnum[v.carType] }}</tm-tag>
</view>
<view class="flex items-center" @tap.stop="hooks.useCopy(v.carNumber)">
<text class="main-text-28 m-r-6rpx">{{ v.carNumber }}</text>
<text v-if="v.carNumber" class="iconfont icon-fuzhi" />
</view>
</view>
<view class="flex justify-end m-t-10rpx">
<text class="second-text-26">{{ v.createTime }}</text>
</view>
<view class="position-absolute right-0 top-0 sub-text-24 color-white!">
<view class="p-x-8rpx p-y-6rpx bg-green rounded-bl-16rpx" v-if="v.enableStatus == 1">已启用</view>
<view class="p-x-8rpx p-y-6rpx bg-red rounded-bl-16rpx" v-if="v.enableStatus == 0">已禁用</view>
</view>
</view>
<template #bottom>
<view
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-60rpx sub-text-30 color-white!"
@tap="doApply">
申请
</view>
<view class="h-30rpx" />
</template>
</z-paging>
</crud-layout>
</template>
+106
View File
@@ -0,0 +1,106 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks, router } from "@@/index"
import { faceApi } from "@@/api"
import { filePath } from "@@/utils/file"
const state = reactive<any>({
detailType: "add",
detailId: null,
detail: null,
header: {},
})
const applyForm = reactive<any>({
faceImg: [],
})
const uploadFileUrl = ref<any>("")
const applyFormRules = reactive<any>({
faceImg: [{ required: true, message: "请上传人脸图片" }],
})
onLoad(async (options: any) => {
const { type, id } = options
if (!isEmpty(type)) state.detailType = type
if (!isEmpty(id)) state.detailId = id
state.header = hooks.useDefaultHeader()
if (type === "edit") {
applyForm.faceImg = []
uploadFileUrl.value = ""
await getDetail()
}
})
onShow(() => { })
const getDetail = async () => {
if (isEmpty(state.detailId)) return
const { data } = await faceApi.get({ id: state.detailId })
if (!isEmpty(data)) {
const img = filePath(data.faceImg)
applyForm.faceImg = [img]
uploadFileUrl.value = img
}
}
const beforeComplete = (uploadFile: any) => {
if (JSON.parse(uploadFile.data).code == 0) {
uploadFileUrl.value = JSON.parse(uploadFile.data).data.url
return true
} else {
uploadFileUrl.value = ""
return false
}
}
const applyFormRef = ref<any>(null)
const doSubmit = async () => {
applyFormRef.value.submit()
const { isPass } = applyFormRef.value.validate()
if (!isPass || !uploadFileUrl.value) return
try {
uni.showLoading({ title: "提交中...", mask: false })
const { data, code }: any =
state.detailType == "edit"
? await faceApi.put({ faceImg: uploadFileUrl.value, id: state.detailId })
: await faceApi.create({ faceImg: uploadFileUrl.value })
if (code != 0) throw new Error()
uni.hideLoading()
uni.showToast({ title: "提交成功", icon: "none" })
router.back()
} catch (error) {
console.error(error)
uni.hideLoading()
}
}
</script>
<template>
<crud-layout :title="state.detailId ? '修改人脸' : '人脸上传'" bg="/static/page/blue-bg.png">
<view class="h-30rpx" />
<view class="card px-30rpx py-40rpx">
<tm-form ref="applyFormRef" v-model="applyForm" :rules="applyFormRules" direction="vertical">
<tm-form-item name="faceImg" label="人脸图片" :required="false" :showBottom="false">
<tm-upload-photo :maxFileSize="1024 * 1024 * 5" :header="state.header"
:rangUrl="applyForm.faceImg[0]" :beforeComplete="beforeComplete" v-model="applyForm.faceImg"
:maxCount="1" :column="2" img-height="320" :url="'/api/admin/sys-file/upload'" />
</tm-form-item>
<view class="sub-text-20 c-red!">注:图片大小不超过5M</view>
</tm-form>
</view>
<view v-if="state.detailType != 'view'"
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-60rpx sub-text-30 color-white!"
@tap="doSubmit">
{{ state.detailId ? "修改" : "提交" }}
</view>
</crud-layout>
</template>
<style lang="scss" scoped>
:deep() {
.tmFormItemLabel {
padding-bottom: 10rpx !important;
}
}
</style>
+75
View File
@@ -0,0 +1,75 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onShow } from "@dcloudio/uni-app"
import { hooks, router } from "@@/index"
import { faceApi } from "@@/api"
import { filePath } from "@@/utils/file"
const state = reactive<any>({
pa: {
pagesize: 10,
},
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, faceApi.page, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
onShow(() => {
if (pageRef.value) {
pageRef.value.reload(false)
}
})
const doAdd = () => {
router.jump("/pages/human-face/apply")
}
const doEdit = (id: string) => {
router.jump("/pages/human-face/apply", { type: "edit", id })
}
const doDel = (id: string) => {
faceApi.del({ id }).then(
() => {
uni.showToast({ title: "删除成功", icon: "none" })
pageRef.value.reload(true)
},
() => {
uni.showToast({ title: "删除失败", icon: "none" })
},
)
}
</script>
<template>
<crud-layout title="人脸管理" bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<view class="h-20rpx" />
<view class="card flex-col-center p-20rpx mb-20rpx" v-for="(v, i) in pageHook.list" :key="v.id">
<image class="w-300rpx h-300rpx rounded-50rpx mb-2" :src="filePath(v.faceImg)" mode="aspectFill"
@click.stop="hooks.usePreviewImage(filePath(v.faceImg))" />
<view class="sub-text-30 mb-2">{{ v.username }}·{{ v.deptName }}</view>
<view v-if="false" class="flex w-full items-center justify-end gap-2 second-text-26">
<text class="botder_btn" @click="doEdit(v.id)">修改</text>
<tm-modal title="确定删除吗?" height="auto" @confirm="doDel(v.id)">
<template v-slot:trigger><text class="botder_btn">删除</text></template>
</tm-modal>
</view>
</view>
<template #bottom>
<view v-if="false"
class="bg-#0094ff py-20rpx text-center rounded-40rpx mx-30rpx my-4 sub-text-30 color-white!"
@tap="doAdd">
添加
</view>
</template>
</z-paging>
</crud-layout>
</template>
+235
View File
@@ -0,0 +1,235 @@
<script setup lang="ts">
import { reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks, router } from "@@/index"
import { orderApi, refundApi } from "@@/api"
import { filePath } from "@@/utils/file"
const state = reactive<any>({
detailId: null,
detail: {},
refundDetail: {},
shopCarList: [],
})
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.detailId = options.id
}
})
onShow(() => {
getDetail()
getShopCarList()
})
const getDetail = async () => {
if (isEmpty(state.detailId)) return
const _pa = { id: state.detailId }
const { data } = await orderApi.detail(_pa)
state.detail = data
if (state.detail.orderStatus == 10) {
getRefundDetail()
}
}
const getShopCarList = async () => {
if (isEmpty(state.detailId)) return
const _pa = { orderId: state.detailId }
const { data } = await orderApi.shopCarList(_pa)
state.shopCarList = data
}
const getRefundDetail = async () => {
if (isEmpty(state.detailId)) return
const _pa = { id: state.detailId }
const { data } = await refundApi.get(_pa)
state.refundDetail = data
}
const doCancelRefund = async () => {
uni.showModal({
title: "提示",
content: "是否确定取消申请退款",
confirmColor: "#0094ff",
success: async (res) => {
if (!res.confirm) return
uni.showLoading({ title: "取消中...", mask: false })
const _pa = { id: state.refundDetail.id }
const data = refundApi.cancel(_pa)
uni.hideLoading()
uni.showToast({ title: "取消成功", icon: "none" })
router.jump("/pages/tabbar/individualOrder", {}, 2)
},
})
}
</script>
<template>
<crud-layout title="订单详情" mainIsScroll bg="/static/page/blue-bg.png">
<view class="flex-row-center m-t-30rpx"
v-if="state.detail.orderStatus === '5' && ['2', '7'].includes(state.detail.orderType)">
<view class="flex-row-center bg-white w-550rpx rounded-16rpx">
<image class="w-500rpx" :src="filePath(state.detail.reserveCode)" mode="widthFix"
@click.stop="hooks.usePreviewImage(filePath(state.detail.reserveCode))" />
</view>
</view>
<view class="h-30rpx" />
<view class="relative flex-col justify-center overflow-hidden card p-x-20rpx p-y-30rpx m-b-20rpx">
<image class="absolute top--20rpx right--60rpx w-160rpx h-160rpx" src="/static/icon/guarantee.png"
mode="widthFix" />
<view class="sub-text-32">订单信息</view>
<view class="m-t-15rpx">
<view class="info_cell">
<view>用户</view>
<view>{{ state.detail.username }}</view>
</view>
<view class="info_cell">
<view>手机号</view>
<view>{{ state.detail.userMobile }}</view>
</view>
<view class="info_cell">
<view>商户</view>
<view>{{ state.detail.merchantName }}</view>
</view>
<view class="m-t-15rpx h-1px bg-#999 bg-opacity-5" />
<view class="info_cell">
<view>订单号</view>
<view>{{ state.detail.id }}</view>
</view>
<view class="info_cell">
<view>下单时间</view>
<view>{{ hooks.useTime(state.detail.createTime, "") }}</view>
</view>
<view class="info_cell">
<view>支付方式</view>
<view>
{{ hooks.useEnum("hardwareOrderTypeEnum", state.detail.orderType) }}
</view>
</view>
</view>
</view>
<view class="flex-col justify-center card p-x-20rpx p-y-30rpx m-b-20rpx">
<view class="sub-text-32">商品费用</view>
<view class="m-t-15rpx">
<view class="flex-row items-start justify-between" v-for="(v, i) in state.shopCarList" :key="i">
<view class="flex-row items-center">
<image class="w-160rpx h-160rpx rounded-8rpx" :src="filePath(v.productPic)" mode="aspectFill" />
<view class="flex-col m-l-15rpx second-text-26">
<view class="sub-text-28">{{ v.productName }}</view>
<view class="m-t-5rpx color-red">¥ {{ hooks.useMoney(v.productPrice) }}</view>
<view class="m-t-5rpx">x {{ v.productCount }}</view>
</view>
</view>
<view class="flex-row-center">
<view class="sub-text-32 color-red!">¥ {{ hooks.useMoney(v.totalPrice) }}</view>
</view>
</view>
<view class="m-y-15rpx h-1px bg-#999 bg-opacity-5" />
<view class="flex-row items-center justify-between">
<view class="sub-text-32">实付款</view>
<view class="flex-row items-end">
<view class="sub-text-28 color-red!">¥</view>
<view class="m-l-5rpx sub-text-32 color-red!">{{ hooks.useMoney(state.detail.payPrice) }}</view>
</view>
</view>
</view>
</view>
<!-- start 退款相关 start -->
<slot v-if="['10'].includes(state.detail.orderStatus)">
<view class="flex-row items-center justify-between card p-x-20rpx p-y-30rpx m-b-20rpx"
@tap="router.jump('/pages/refund/history', { id: state.refundDetail.id })">
<view>
<view class="sub-text-28">
<view>
{{ hooks.useEnum("hardwareSaleStatusEnum", state.refundDetail.status) }}
</view>
</view>
<view class="m-t-10rpx second-text-26">
{{ hooks.useTime(state.refundDetail.updateTime || state.refundDetail.createTime, "") }}
</view>
</view>
<view class="m-l-15rpx color-#999 font-size-26rpx! iconfont icon-youjiantou" />
</view>
<view class="flex-row items-center justify-between card p-x-20rpx p-y-30rpx m-b-20rpx">
<view class="sub-text-28">退款金额</view>
<view class="sub-text-28 color-red!">¥{{ hooks.useMoney(state.refundDetail.refundPrice) }}</view>
</view>
<view class="card p-x-20rpx p-y-30rpx space-y-10rpx m-b-20rpx second-text-24">
<view class="flex-row items-center">
<view>服务单号:</view>
<view>{{ state.refundDetail.no }}</view>
</view>
<view class="flex-row items-center">
<view>申请时间:</view>
<view>{{ hooks.useTime(state.refundDetail.createTime, "") }}</view>
</view>
<view class="flex-row items-center">
<view>售后类型:</view>
<view>{{ state.refundDetail.applyReason }}</view>
</view>
<view class="flex-row items-center">
<view>相关描述:</view>
<view>{{ state.refundDetail.applyDescription }}</view>
</view>
</view>
<view v-if="state.refundDetail.status == 62"
class="card p-x-20rpx p-y-30rpx space-y-10rpx m-b-20rpx second-text-24">
<view class="flex-row items-center">
<view>审核人:</view>
<view>{{ state.refundDetail.auditUsername }}</view>
</view>
<view class="flex-row items-center">
<view>审核时间:</view>
<view>{{ hooks.useTime(state.refundDetail.auditTime, "") }}</view>
</view>
<view class="flex-row items-center">
<view>拒绝原因:</view>
<view>{{ state.refundDetail.auditReason }}</view>
</view>
</view>
</slot>
<!-- end 退款相关 end -->
<view class="h-30rpx" />
<template #main-bottom>
<view v-if="state.detail.orderStatus == 10 && state.refundDetail.status == 10"
class="bg-#999 p-y-20rpx text-center rounded-40rpx m-x-30rpx m-y-30rpx sub-text-30 color-white!"
@tap="doCancelRefund">
取消申请
</view>
<view v-else-if="state.refundDetail.status == 62 || state.refundDetail.status == 61"
class="bg-#999 p-y-20rpx text-center rounded-40rpx m-x-30rpx m-y-30rpx sub-text-30 color-white!"
@tap="router.jump('/pages/refund/apply', { id: state.detailId }, 2)">
重新申请
</view>
</template>
</crud-layout>
</template>
<style lang="scss" scoped>
.info_cell {
display: flex;
align-items: center;
&:not(:first-child) {
margin-top: 15rpx;
}
&>view {
color: #999;
font-size: 28rpx;
font-weight: 500;
line-height: 1.4em;
}
&>view:nth-child(1) {
width: 130rpx;
}
&>view:nth-child(2) {
flex: 1;
}
}
</style>
+53
View File
@@ -0,0 +1,53 @@
<template>
<view class="w-full h-full">
<view class="flex-col-center p-30rpx">
<view class="flex-col-center m-y-50rpx">
<svg t="1742978804614" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="1708" width="64" height="64">
<path
d="M512 0C230.4 0 0 230.4 0 512s230.4 512 512 512 512-230.4 512-512S793.6 0 512 0z m288 371.2L460.8 710.4c-6.4 6.4-19.2 12.8-25.6 12.8-12.8 0-19.2-6.4-25.6-12.8L224 531.2c-12.8-12.8-12.8-38.4 0-57.6 12.8-12.8 38.4-12.8 57.6 0l147.2 147.2L736 313.6c12.8-12.8 38.4-12.8 57.6 0 19.2 19.2 19.2 44.8 6.4 57.6z"
fill="#2BCDA3" p-id="1709" data-spm-anchor-id="a313x.search_index.0.i2.beb13a81SVstw0"
class="selected"></path>
</svg>
<text class="text-36 mt-3 font-bold c-#333333!">支付成功</text>
</view>
<view class="flex-col-center m-b-60rpx">
<text class="text-90 c-#333333!">¥{{ scanPayData.payPrice }}</text>
</view>
<view class="w-full p-10rpx flex-col justify-center">
<view class="flex-row justify-between m-15rpx">
<text class="sub-text-30 c-#333333!">商家</text>
<text class="sub-text-30">{{ scanPayData.mchName }}</text>
</view>
<hr />
<view class="flex-row justify-between m-15rpx">
<text class="sub-text-30 c-#333333!">支付方式</text>
<text class="sub-text-30">扫一扫</text>
</view>
</view>
</view>
<view class="p-40rpx fixed bottom-30rpx left-0 right-0 font-bold">
<tm-button :block="true" @click="goBack">完成</tm-button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue"
import { onShow } from "@dcloudio/uni-app"
import { usePayStore } from "@/sheep/stores/pay"
const scanPayData = ref<any>({})
onShow(() => {
if (!usePayStore().scanPayData.mchId) return uni.showToast({ title: "支付信息不存在", icon: "none" })
scanPayData.value = usePayStore().scanPayData
})
const goBack = () => {
const url: string = window.location.href.split("h5")[0] + "h5/?launchParam=coopMerchant#/pages/tabbar/order"
window.location.replace(url)
}
</script>
+172
View File
@@ -0,0 +1,172 @@
<template>
<crud-layout bg="/static/page/blue-bg.png">
<view class="h-screen flex flex-col">
<view class="mx-auto text-34 py-2 h-80rpx">{{ scanPayState.mchName }}</view>
<tm-sheet :margin="[0, 10]" class="flex flex-row">
<span class="text-2xl text-[#333] mr-1 line-height-normal">¥</span>
<span class="text-3xl font-medium text-[#333]">{{ displayAmount }}</span>
</tm-sheet>
<tm-sheet v-if="walletList.length > 0" :margin="[0, 10]" class="flex flex-col">
<view class="text-gray-700 font-bold text-28 mb-1">选择支付钱包:</view>
<view v-for="(v, index) in walletList" :key="index"
class="border-6 border-solid border-#efefef my-2 duration-200 flex justify-between items-center p-3 rounded-2"
:class="{ 'border-6 border-solid border-blue!': selectWallet.id == v.id }"
@click="selectWallet = v">
<view class="flex items-center">
<image class="w-70 h-70 mr-2" :src="filePath(v.walletTypeIcon)" />
<view class="text-32">{{ v.walletTypeName }}</view>
</view>
<view class="text-28 flex items-center">
<view>余额:</view>
<text class="main-text-28">{{ hooks.useMoney(v.balance) }} 元</text>
</view>
</view>
</tm-sheet>
<tm-button class="mt-1" :block="true" @click="handlePay()" :disabled="loading">支 付</tm-button>
<number-keyboard v-if="numberKeyboardShow" v-model="amount" />
</view>
</crud-layout>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import payApi from "@@/api/pay"
import { merchantApi, orderApi, walletApi } from "@/sheep/api"
import { hooks } from "@@/index"
import { router } from "@/sheep"
import { filePath } from "@/sheep/utils/file"
import { displayPrice, storePrice } from "@/sheep/utils/format-money"
import { usePayStore } from "@/sheep/stores/pay"
import { isEmpty } from "radash"
import NumberKeyboard from "@/sheep/components/number-keyboard.vue"
const loading = ref(false)
const scanPayState = reactive({
mchId: "", // 商户id
mchName: "", // 商户名
orderId: "", // 订单id
action: "", // action === "harQrCodeScanPay":扫收款机上生成的码支付
})
const walletList = ref<any>([]) // 钱包列表
const selectWallet = ref<any>({}) // 当前选中的钱包
const orderInfo = ref<any>({}) // 订单信息
const amount = ref("") // 键盘输入的金额
const numberKeyboardShow = ref(true)
onLoad(async (options: any) => {
uni.showLoading({ title: "加载中..." })
const { mchId, orderId, action } = options
if (action === "harQrCodeScanPay") {
if (!options.mchId || !options.orderId) return uni.showToast({ title: "扫码参数错误", icon: "none" })
scanPayState.mchId = mchId
scanPayState.orderId = orderId
scanPayState.action = action
numberKeyboardShow.value = false
await getMchDetail(mchId)
await getOrderInfo()
const order = orderInfo.value
amount.value = displayPrice(order.totalPrice || 0 + order.extraPrice || 0, "")
return
}
const { id } = options
if (!isEmpty(id)) {
scanPayState.mchId = id
await getMchDetail(id)
return
}
return uni.showToast({ title: "扫码参数错误", icon: "none" })
})
onShow(async () => {
try {
await getWalletList()
} finally {
uni.hideLoading()
}
})
// 获取商户名
const getMchDetail = async (mchId: string) => {
if (!mchId) return uni.showToast({ title: "商户id错误", icon: "none" })
const { data } = await merchantApi.detail({ id: mchId })
scanPayState.mchName = data.name
}
// 获取钱包列表
const getWalletList = async () => {
const { code, data } = await walletApi.all()
if (code !== 0) return uni.showToast({ title: "获取钱包列表失败", icon: "none" })
walletList.value = data.records
if (walletList.value.length > 0) {
selectWallet.value = walletList.value.find((v: any) => v.walletTypeUniqueCode == "1")
}
}
// 获取订单信息
const getOrderInfo = async () => {
const { code, data }: any = await orderApi.detail({ id: scanPayState.orderId })
if (code !== 0) return uni.showToast({ title: "获取订单信息失败", icon: "none" })
orderInfo.value = data
}
// 格式化金额显示
const displayAmount = computed(() => {
if (!amount.value) return "0"
return amount.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
})
// 点击支付
const handlePay = () => {
if (!scanPayState.mchId) return uni.showToast({ title: "扫码参数不存在", icon: "none" })
if (!amount.value) return uni.showToast({ title: "请输入金额", icon: "none" })
if (Number(amount.value) === 0) return uni.showToast({ title: "金额不能为0", icon: "none" })
uni.showModal({
title: "确定支付吗?",
success: async (res) => {
if (res.confirm) {
await pay()
}
},
})
}
// 支付
const pay = async () => {
try {
uni.showLoading({ title: "支付中...", mask: true })
if (scanPayState.action === "harQrCodeScanPay") {
const { code, msg }: any = await orderApi.doneHardQrCodeScanOrder({
orderId: scanPayState.orderId,
walletTypeId: selectWallet.value.walletTypeId,
})
if (code != 0) return uni.showToast({ title: msg || "支付失败", icon: "error" })
} else {
const { code, msg } = await payApi.createQrCodeOrder({
merchantId: scanPayState.mchId,
customPrice: storePrice(Number(amount.value)),
walletId: selectWallet.value.id,
})
if (code != 0) return uni.showToast({ title: msg || "支付失败", icon: "error" })
}
uni.hideLoading()
usePayStore().setScanPayData({
mchId: scanPayState.mchId,
mchName: scanPayState.mchName,
payPrice: Number(amount.value).toFixed(2),
})
router.jump("/pages/pay/payComplete", {}, 2)
} catch (error: any) {
uni.showToast({ title: "请求错误", icon: "error" })
uni.hideLoading()
}
}
</script>
+179
View File
@@ -0,0 +1,179 @@
<template>
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<view class="h-20rpx"></view>
<view class="p-4 card flex items-center justify-between">
<view class="flex items-center">
<image class="w-64 h-64 mr-2" :src="filePath(payStore.sourceWalletData.walletTypeIcon)" />
<view>{{ payStore.sourceWalletData.walletTypeName }}</view>
</view>
<view class="flex items-center c-#0094ff! font-bold">
<view class="text-30">余额:</view>
<view class="text-30 mr-1">{{ hooks.useMoney(payStore.sourceWalletData.balance) + "元" }}</view>
</view>
</view>
<view class="p-4 card mt-2 flex items-center justify-between active:bg-[#f0f0f0]" @click="show = true">
<view class="flex items-center">
<image class="w-64 h-64 rounded-full mr-2" src="@/static/page/money-cny-circle-fill.svg" />
<view>转入到</view>
</view>
<view class="flex items-center">
<view class="second-text-28 mr-1">{{ targetWalletType.name }}</view>
<image class="w-38 h-38 rounded-full c-blueGray" src="@/static/page/arrow-right.svg" />
</view>
</view>
<view class="p-4 mt-2 card">
<text class="text-gray-700 font-bold text-lg">转账金额</text>
<view class="flex-row items-center">
<text class="text-3xl text-[#333] mr-2">¥</text>
<text v-if="!amount" class="text-xl text-gray-300">转账金额</text>
<text v-else class="text-3xl">{{ formattedAmount }}</text>
</view>
</view>
<!-- 金额限制提示 -->
<view class="h-30rpx">
<view v-if="storePrice(amount) > payStore.sourceWalletData?.balance" class="text-xs c-red px-4">
注:转账金额不能超过钱包余额</view>
</view>
<view class="font-bold mt-6">
<tm-button block @click="confirmTransfer" v-if="payStore.sourceWalletData.walletTypeId">确定转账</tm-button>
</view>
<number-keyboard v-model="amount" />
</crud-layout>
<!-- 选择转入方式 -->
<tm-drawer v-model:show="show" title="选择钱包" size="auto" bg-color="#efefef" :show-footer="false" :showClose="true">
<view v-for="(v, index) in wallet.walletList" :key="index"
class="card m-2 overflow-hidden relative border-4 border-solid border-white duration-200 flex justify-between items-center p-4"
:class="{ 'border-4 border-solid border-blue!': targetWalletType.id == v.walletTypeId }"
@click="selectTargetWalletType(v)">
<view class="flex items-center">
<image class="w-70 h-70 mr-2" :src="filePath(v.walletTypeIcon)" />
<view class="flex flex-col">
<view class="main-text-32">{{ v.walletTypeName }}</view>
<view class="text-gray-500 text-sm">{{ v.cardNumber }}</view>
</view>
</view>
<view class="text-32 c-#0094ff font-bold">余额:{{ hooks.useMoney(v.balance) + "元" }}</view>
</view>
</tm-drawer>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import NumberKeyboard from "@/sheep/components/number-keyboard.vue"
import { payApi, walletApi } from "@/sheep/api"
import { hooks, router } from "@@/index"
import { filePath } from "@/sheep/utils/file"
import { storePrice } from "@/sheep/utils/format-money"
import { usePayStore } from "@/sheep/stores/pay"
const payStore = usePayStore()
const amount = ref("")
const show = ref(false)
const sourceWalletType = reactive({
id: "",
name: "",
})
const targetWalletType = reactive({
id: "",
name: "",
})
const wallet = reactive<any>({
walletList: [],
})
onLoad(async () => {
if (!payStore.sourceWalletData.walletTypeId) return uni.showToast({ title: "钱包类型id不存在", icon: "none" })
sourceWalletType.id = payStore.sourceWalletData.walletTypeId
sourceWalletType.name = payStore.sourceWalletData.walletTypeName
uni.setNavigationBarTitle({ title: sourceWalletType.name || "源钱包" })
const res = await walletApi.all()
if (res.code == 0) {
wallet.walletList = res.data.records.filter((item: any) => item.walletTypeId != sourceWalletType.id)
}
if (wallet.walletList.length) {
targetWalletType.id = wallet.walletList[0]?.walletTypeId
targetWalletType.name = wallet.walletList[0]?.walletTypeName
}
})
// 选择目标钱包
const selectTargetWalletType = (v: any) => {
targetWalletType.id = v.walletTypeId
targetWalletType.name = v.walletTypeName
show.value = false
}
// 格式化金额显示
const formattedAmount = computed(() => {
if (!amount.value) return ""
// 处理小数点情况
if (amount.value.endsWith(".")) {
return amount.value
}
const parts = amount.value.split(".")
// 添加千位分隔符
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
// 处理小数部分
if (parts.length > 1) {
// 如果小数部分不足两位,不自动补零
return `${parts[0]}.${parts[1]}`
}
return parts[0]
})
const numAmount = computed(() => storePrice(parseFloat(amount.value.endsWith(".") ? amount.value.slice(0, -1) : amount.value)))
// 确认转账
const confirmTransfer = async () => {
if (!amount.value || amount.value === "0" || amount.value === "0." || parseFloat(amount.value) === 0) {
return uni.showToast({ title: "金额不能为零", icon: "none" })
}
if (numAmount.value > payStore.sourceWalletData.balance) {
return uni.showToast({ title: "转账金额不能超过钱包余额", icon: "none" })
}
uni.showModal({
title: "确定转入吗?",
success: async (res) => {
if (res.confirm) {
await confirmTransferAgain()
}
},
})
}
// 二次确认转账
const confirmTransferAgain = async () => {
try {
uni.showLoading({ title: "正在转入...", mask: true })
const res = await payApi.transferMoney({
sourceWalletTypeId: sourceWalletType.id,
targetWalletTypeId: targetWalletType.id,
balance: numAmount.value,
})
if (res.code == 0) {
uni.showToast({ title: "转账成功", icon: "success" })
payStore.setSourceWalletData({})
router.back()
} else {
uni.showToast({ title: res.msg, icon: "none" })
}
} catch (error: any) {
uni.showToast({ title: error.message, icon: "none" })
} finally {
uni.hideLoading()
}
}
</script>
+13 -10
View File
@@ -13,7 +13,6 @@ const affirmModalRef = ref()
const state = reactive<any>({
userInfo: null,
isAgree: false,
loginErrorVisible: false,
loginErrorCode: null,
})
@@ -47,12 +46,18 @@ const minAppGetphone = async (e) => {
uni.hideLoading()
return
}
uni.setStorageSync(constant.storageConstant.TOKEN, {
accessToken: data.accessToken.tokenValue,
accessTokenExpiresIn: data.accessToken.expiresAt,
refreshToken: data.refreshToken.tokenValue,
refreshTokenExpiresIn: data.refreshToken.expiresAt,
} as GlobalSetting.TokenStorage)
// 查询用户信息
const { data: userData } = await userApi.userInfo()
uni.setStorageSync(constant.storageConstant.USERINFO, userData)
uni.setStorageSync(constant.storageConstant.TOKEN, data)
uni.hideLoading()
uni.showToast({ title: "登录成功", icon: "success" })
router.jump("/pages/tabbar/canteen")
router.jump("/pages/tabbar/canteen", {}, 3)
} catch (error) {
console.error("登录错误", error);
}
@@ -91,6 +96,10 @@ const affirmConfirm = () => {
<button v-else class="bg-#0094ff color-white" open-type="getPhoneNumber" @getphonenumber="minAppGetphone">
手机号一键登录
</button>
<view class="flex justify-between items-center m-t-30rpx">
<text></text>
<text class="color-blue font-size-sm" @click="router.jump('/pages/root/pwd-login')">账号密码登录</text>
</view>
</view>
<affirm-check v-model="state.isAgree" class="absolute bottom-30rpx" />
@@ -113,10 +122,4 @@ const affirmConfirm = () => {
</crud-layout>
</template>
<style lang="scss" scoped>
:deep() {
.tmFormItemLabel {
padding-bottom: 10rpx !important;
}
}
</style>
<style lang="scss" scoped></style>
+128
View File
@@ -0,0 +1,128 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { constant, router } from "@@/index"
import { userApi } from "@@/api"
import AffirmCheck from "@/sheep/components/common/affirm-check.vue"
import AffirmModal from "@/sheep/components/common/affirm-modal.vue"
const affirmModalRef = ref()
const state = reactive<any>({
userInfo: null,
isAgree: false,
})
const applyForm = reactive<any>({
username: "",
password: "",
})
const applyFormRules = reactive<any>({
username: [{ required: true, message: "请输入用户名" }],
password: [{ required: true, message: "请输入密码" }],
})
onLoad(() => {
const rememberUser = uni.getStorageSync(constant.storageConstant.REMEMBER_USER)
if (rememberUser) {
applyForm.username = rememberUser.username
applyForm.password = rememberUser.password
}
})
onShow(() => {
const userInfoKey = constant.storageConstant.USERINFO
state.userInfo = uni.getStorageSync(userInfoKey)
})
const applyFormRef = ref<any>(null)
const doSubmit = async () => {
applyFormRef.value.submit()
const { isPass } = applyFormRef.value.validate()
if (!isPass) return
const tokenKey = constant.storageConstant.TOKEN
const userInfoKey = constant.storageConstant.USERINFO
const rememberUser = constant.storageConstant.REMEMBER_USER
uni.removeStorageSync(tokenKey)
uni.showLoading({ title: "登录中...", mask: true })
const res: any = await userApi.login({
...applyForm,
grant_type: "password",
scope: "server",
})
if (res.code != 0) {
uni.showToast({ title: res.msg + ",登录失败,请重新登录", icon: "error" })
return
}
uni.setStorageSync(tokenKey, {
accessToken: res.data.access_token,
refreshToken: res.data.refresh_token,
accessTokenExpiresIn: new Date(res.data.expires_in + Date.now()).toISOString(),
refreshTokenExpiresIn: "",
} as GlobalSetting.TokenStorage)
// 查询用户信息
const { data: userData } = await userApi.userInfo()
uni.setStorageSync(userInfoKey, userData)
uni.setStorageSync(rememberUser, {
username: applyForm.username,
password: applyForm.password,
} as GlobalSetting.RememberUser)
uni.hideLoading()
uni.showToast({ title: "登录成功", icon: "none" })
router.jump("/pages/tabbar/canteen", {}, 3)
}
const disposeLogin = () => {
applyFormRef.value.submit()
const { isPass } = applyFormRef.value.validate()
if (!isPass) return
// 没有同意
if (!state.isAgree) {
affirmModalRef.value.show()
return
}
doSubmit()
}
const affirmModalConfirm = () => {
state.isAgree = true
doSubmit()
}
</script>
<template>
<crud-layout title="账号密码登录" bg="/static/page/blue-bg.png">
<view class="h-30rpx" />
<view class="card p-x-30rpx p-y-40rpx">
<tm-form ref="applyFormRef" v-model="applyForm" :rules="applyFormRules" direction="vertical">
<tm-form-item name="username" label="用户名" :showBottom="false">
<tm-input v-model="applyForm.username" placeholder="请输入用户名.." />
</tm-form-item>
<view class="h-30rpx" />
<tm-form-item name="password" label="密码" :showBottom="false">
<tm-input password showEye v-model="applyForm.password" placeholder="请输入用户密码.." />
</tm-form-item>
</tm-form>
</view>
<view v-if="state.detailType != 'view'"
class="bg-blue p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-40rpx sub-text-30 color-white!"
@tap="disposeLogin">
登录
</view>
<view class="flex justify-between items-center m-t-30rpx">
<text></text>
<text class="color-blue font-size-sm" @click="router.jump('/pages/root/login')">手机号一键登录</text>
</view>
<affirm-check v-model="state.isAgree" class="absolute bottom-30rpx" />
<affirm-modal ref="affirmModalRef" @confirm="affirmModalConfirm" />
</crud-layout>
</template>
<style lang="scss" scoped>
:deep() {
.tmFormItemLabel {
padding-bottom: 10rpx !important;
}
}
</style>
-18
View File
@@ -26,7 +26,6 @@ onShow(() => {
if (app._initTabbar) {
app._initTabbar()
}
reportPageAccess("pages/tabbar/canteen")
if (pageRef.value) {
pageRef.value.reload(false)
}
@@ -48,23 +47,6 @@ const toDetail = async (v: any) => {
router.jump("/pages/canteen/detail", { id: v.id })
}
}
const createEventId = () => `evt_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
const reportPageAccess = async (pageCode: string) => {
const tokenStorage: any = uni.getStorageSync(STORAGE_KEYS.TOKEN)
const userInfo: any = uni.getStorageSync(STORAGE_KEYS.USER_INFO)
if (!userInfo?.sysUser?.userId) return
const systemInfo = uni.getSystemInfoSync()
await accessStatsApi.track({
eventId: createEventId(),
pageCode,
userId: userInfo?.sysUser?.userId,
eventTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
deviceId: tokenStorage?.access_token || "",
clientType: systemInfo?.platform || "",
osVersion: systemInfo?.system || "",
})
}
</script>
<template>
-18
View File
@@ -356,7 +356,6 @@ onShow(() => {
if (app._initTabbar) {
app._initTabbar()
}
reportPageAccess("pages/tabbar/my")
// 获取存储中的用户信息
storage.getUserInfo()
})
@@ -380,23 +379,6 @@ const isMchUser = async () => {
const doPhone = (phone: string) => {
hooks.useMakePhoneCall(phone)
}
const createEventId = () => `evt_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
const reportPageAccess = async (pageCode: string) => {
const tokenStorage: any = uni.getStorageSync(STORAGE_KEYS.TOKEN)
const userStorage: any = uni.getStorageSync(STORAGE_KEYS.USER_INFO)
if (!userStorage?.sysUser?.userId) return
const systemInfo = uni.getSystemInfoSync()
await accessStatsApi.track({
eventId: createEventId(),
pageCode,
userId: userStorage?.sysUser?.userId,
eventTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
deviceId: tokenStorage?.access_token || "",
clientType: systemInfo?.platform || "",
osVersion: systemInfo?.system || "",
})
}
</script>
<style lang="scss" scoped>
+122 -56
View File
@@ -1,17 +1,27 @@
<script setup lang="ts">
import { ref, reactive, nextTick } from "vue"
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import dayjs from "dayjs"
import { hooks, router, constant } from "@@/index"
import { accessStatsApi, merchantApi } from "@@/api"
import { hooks, router } from "@@/index"
import { orderApi } from "@@/api"
import { filePath } from "@@/utils/file"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import { getLaunchParam } from "@/sheep/utils/app"
const data = {
tabs: [
{ id: null, name: "全部", disabled: false },
{ id: 1, name: "未支付", disabled: false },
{ id: 5, name: "已支付", disabled: false },
{ id: 10, name: "售后", disabled: false },
{ id: 20, name: "已核销", disabled: false },
],
}
const state = reactive<any>({
pa: {
pagesize: 10,
type: "1",
orderStatus: data.tabs[0].id,
},
})
@@ -21,83 +31,139 @@ onLoad(() => {
app._initTabbar()
}
})
onShow(() => {
onShow(async () => {
const app = getApp({ allowDefault: true })
if (app._initTabbar) {
app._initTabbar()
}
reportPageAccess("pages/tabbar/canteen")
const isLogin = await hooks.useCheckLogin()
if (!isLogin) return
if (pageRef.value) {
pageRef.value.reload(false)
}
})
const doTab = (e: any) => {
const target = data.tabs[e]
state.pa.orderStatus = target.id
pageRef.value.reload(true)
}
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, merchantApi.page, null, "data.records")
const STORAGE_KEYS = {
TOKEN: constant.storageConstant.TOKEN,
USER_INFO: constant.storageConstant.USERINFO,
}
const pageHook = hooks.usePage(
pageRef,
getLaunchParam() == "canteen" // 食堂
? orderApi.caneenPage
: getLaunchParam() == "selfMerchant"
? orderApi.supPage
: getLaunchParam() == "coopMerchant"
? orderApi.supPage
: getLaunchParam() == "onlineShop"
? orderApi.supPage
: getLaunchParam() == "prosperityShop"
? orderApi.farmerSpuPage
: async () => [],
null,
"data.records",
)
const query = (page: number, pagesize: number) => {
const isLogin = hooks.useStaticCheckLogin()
if (!isLogin) return
pageHook.startPage(page, pagesize, false, state.pa)
}
const toDetail = async (v: any) => {
const isLogin = await hooks.useCheckLogin()
if (isLogin) {
router.jump("/pages/canteen/detail", { id: v.id })
}
// 查看订单和核销码
const doToDetail = (v: any) => {
router.jump("/pages/order/detail", { id: v.id })
}
const createEventId = () => `evt_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
const reportPageAccess = async (pageCode: string) => {
const tokenStorage: any = uni.getStorageSync(STORAGE_KEYS.TOKEN)
const userInfo: any = uni.getStorageSync(STORAGE_KEYS.USER_INFO)
if (!userInfo?.sysUser?.userId) return
const systemInfo = uni.getSystemInfoSync()
await accessStatsApi.track({
eventId: createEventId(),
pageCode,
userId: userInfo?.sysUser?.userId,
eventTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
deviceId: tokenStorage?.access_token || "",
clientType: systemInfo?.platform || "",
osVersion: systemInfo?.system || "",
})
const doAppraise = () => { }
const doRefund = (v: any) => {
router.jump("/pages/refund/apply", { id: v.id })
}
</script>
<template>
<crud-layout title="食堂" bg="/static/page/blue-bg.png">
<crud-layout title="订单" bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<view class="h-20rpx" />
<view class="flex-row card p-20rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i" @tap="toDetail(v)">
<image class="w-180rpx h-180rpx rounded-12rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
<view class="flex-row items-center justify-between">
<view class="flex-row-center sub-text-34">{{ v.name }}</view>
<view>
<tm-tag size="s">{{ v.deptName }}</tm-tag>
<template #top>
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
<z-tabs :list="data.tabs" @change="doTab" />
</view>
</template>
<view class="flex-col justify-center card p-3 m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i">
<view class="flex-row">
<image class="size-130rpx rounded-8rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 ml-20rpx">
<view class="flex-row items-center justify-between">
<view class="sub-text-30">{{ v.merchantName }}</view>
<!-- <view class="m-l-15rpx color-#999 font-size-26rpx! iconfont icon-youjiantou" /> -->
<tm-tag v-if="v.orderStatus == 1" color="info" size="s">未支付</tm-tag>
<tm-tag v-if="v.orderStatus == 5" color="success" size="s">已支付</tm-tag>
<tm-tag v-if="v.orderStatus == 10" color="error" size="s">售后</tm-tag>
<tm-tag v-if="v.orderStatus == 20" size="s">已核销</tm-tag>
<tm-tag v-if="v.orderStatus == 30" color="info" size="s">已取消(智能柜)</tm-tag>
</view>
<view class="flex-row items-center justify-between mt-5rpx second-text-24">
<view>
<text>下单时间</text>
<text class="ml-10rpx">{{ hooks.useTime(v.createTime, "{y}-{m}-{d} {h}:{i}:{s}")
}}</text>
</view>
<view>{{ hooks.useEnum("hardwareRefundStatusEnum", v.refundStatus) }}</view>
</view>
<view class="flex-row items-center justify-end mt-1">
<text
v-if="v.carList?.length && ['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)"
class="second-text-30 mr-2">
共 {{ v.carList.length }} 件
</text>
<text class="main-text-30 color-red!">¥ {{ hooks.useMoney(v.totalPrice) }}</text>
</view>
</view>
<view class="second-text-24">
<view class="second-text-28">营业时间:</view>
<view>{{ v.jobTime?.split(",")[0] ? "早:" + v.jobTime?.split(",")[0] : "" }}</view>
<view>{{ v.jobTime?.split(",")[1] ? "中:" + v.jobTime?.split(",")[1] : "" }}</view>
<view>{{ v.jobTime?.split(",")[2] ? "晚:" + v.jobTime?.split(",")[2] : "" }}</view>
</view>
<view v-if="v.addressDes" class="m-t-10rpx second-text-28">
<text>食堂地址</text>
<text class="m-l-10rpx">{{ v.addressDes }}</text>
</view>
<view v-if="['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)">
<view class="border-b-2rpx border-dashed border-#eeeeee mt-2"></view>
<tm-collapse>
<tm-collapse-item :name="i" title="详细购买" titleFontSize="28" activeColor="#000" titleHeight="80"
:showBottomLine="false">
<view v-if="v.carList.length" class="flex justify-between items-center mb-2 px-3"
v-for="(pV, pI) in v.carList" :key="pI">
<view class="flex items-center w-6/10">
<view class="size-100rpx">
<image v-if="v.payType == '9'" class="size-100rpx rounded-8rpx"
:src="pV.productPic" mode="aspectFill" />
<image v-else class="size-100rpx rounded-8rpx" :src="filePath(pV.productPic)"
mode="aspectFill" />
</view>
<view class="ml-2 overflow-hidden">
<view class="sub-text-26 truncate">{{ pV.productName }}</view>
<view class="sub-text-26">¥ {{ hooks.useMoney(pV.productPrice) }}</view>
<view class="sub-text-26">x {{ pV.productCount }}</view>
</view>
</view>
<view class="sub-text-30 w-3/10">¥ {{ hooks.useMoney(pV.totalPrice) }}</view>
</view>
<view v-else class="second-text-26 text-center">无数据</view>
<view class="border-b-2rpx border-dashed border-#eeeeee mt-2"></view>
</tm-collapse-item>
</tm-collapse>
</view>
<view v-if="['5', '10'].includes(v.orderStatus)" class="w-full flex-row items-center justify-end my-1">
<!-- <view v-if="v.orderStatus == 5 && v.orderType == 2" class="botder_btn" @tap.stop="doRefund(v)">退款</view> -->
<view v-if="v.orderStatus === '5' && ['2', '7'].includes(v.orderType)" class="m-l-20rpx botder_btn"
@tap.stop="doToDetail(v)">
核销码
</view>
</view>
</view>
<template #bottom>
</template>
</z-paging>
</crud-layout>
</template>
</template>
+9 -3
View File
@@ -1,7 +1,6 @@
import base64 from "base64-utf8"
import request from "@/sheep/utils/request"
import { storageConstant } from "@@/constant"
// 将 Basic Auth 字符串提取为常量
const BASIC_AUTH = "Basic " + base64.encode(import.meta.env.VITE_OAUTH2_APP_CLIENT)
@@ -21,7 +20,7 @@ export default {
return request({
url: "/auth/oauth2/token",
header: {
skipToken: true,
skipToken: "true",
Authorization: BASIC_AUTH,
"Content-Type": "application/x-www-form-urlencoded",
},
@@ -54,7 +53,6 @@ export default {
method: "POST",
params: {
username,
password,
randomStr,
code,
grant_type,
@@ -136,4 +134,12 @@ export default {
method: "POST",
data,
}),
// 用户修改个人资料
appModifyUser: (data: any) =>
request({
url: "/admin/user/app/modify",
method: "POST",
data,
}),
}
+100
View File
@@ -0,0 +1,100 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { storageConstant } from "@@/constant"
import { userApi } from '@@/api'
const emit = defineEmits(["close"])
type ModifyUserProps = {
warnText?: string
}
const props = withDefaults(defineProps<ModifyUserProps>(), {
warnText: ''
})
const formRef = ref<any>(null)
const showQrCodeDialog = ref(false)
const form = reactive({
name: "",
phone: "",
birthday: ""
})
const rules = {
birthday: [{ required: true, message: '请输入生日' }],
}
// 修改用户信息
const doFormSubmit = async () => {
formRef.value.submit()
const { isPass } = formRef.value.validate()
if (!isPass) return
uni.showLoading({ title: "修改中...", mask: true })
try {
const res: any = await userApi.appModifyUser({ ...form })
if (res.code != 0) {
uni.showToast({ title: res.msg ?? "修改失败", icon: "none" })
return
}
uni.showToast({ title: "修改成功", icon: "success" })
// 存入缓存
const { data: userData } = await userApi.userInfo()
uni.setStorageSync(storageConstant.USERINFO, userData)
showQrCodeDialog.value = false
setTimeout(() => uni.hideLoading(), 1000);
} catch (error) {
console.log("|修改用户信息|意外的错误error:", error);
uni.hideLoading()
}
}
const open = () => {
const user = uni.getStorageSync(storageConstant.USERINFO)
if (user && user.sysUser) {
form.name = user.sysUser.name
form.phone = user.sysUser.phone
form.birthday = user.sysUser.birthday
}
showQrCodeDialog.value = true
}
defineExpose({
open
})
</script>
<template>
<tm-drawer z-index="100" v-model:show="showQrCodeDialog" title="编辑个人资料" :show-close="true" position="bottom"
size="70%" @close="emit('close')">
<view v-if="warnText" class="py-20rpx">
<tm-alert status="warn" :showClose="false" :margin="[]">{{ warnText }}</tm-alert>
</view>
<view class="card p-x-30rpx p-y-40rpx">
<view class="h-30rpx" />
<tm-form ref="formRef" v-model="form" :rules="rules">
<tm-form-item name="name" label="姓名">
<tm-input v-model="form.name" disabled>
</tm-input>
</tm-form-item>
<tm-form-item name="phone" label="手机号">
<tm-input v-model="form.phone" disabled>
</tm-input>
</tm-form-item>
<tm-form-item name="birthday" label="出生日期" :show-bottom="false">
<tm-picker-date v-model="form.birthday" v-model:model-str="form.birthday" format="YYYY-MM-DD"
start="1950-1-1" end="2030-12-31">
<tm-input v-model="form.birthday" style="pointer-events: none" disabled>
</tm-input>
</tm-picker-date>
</tm-form-item>
<view class="h-30rpx" />
<tm-button block @tap="doFormSubmit">修改</tm-button>
</tm-form>
</view>
</tm-drawer>
</template>
@@ -0,0 +1,277 @@
<template>
<view class="plate-number-keyboard-box" v-if="showKeyboard" @tap.stop>
<view class="pnk-main" :animation="animationData">
<view class="pnk-main-title">
<text>请点击选择车牌号</text>
<view class="xiala" @tap="_closeKeyboard"></view>
</view>
<view class="pnk-content">
<!-- 省份面板 -->
<view class="province-plane" v-if="showProvince">
<block v-for="(item, index) in provinceList" :key="index">
<view class="line">
<view v-if="index === 'line4'" class='number-item handle-btn' @tap='_clickChangePlane'>ABC
</view>
<view class="number-item"
:class="(cursor === 0 && index === 'line4' && ind >= 2) || (cursor >= 6 && (index !== 'line4' || (index === 'line4' && ind < 2))) ? 'disabled' : ''"
v-if="!((cursor === 0 && index === 'line4' && ind > 7) || (cursor >= 6 && index === 'line4' && ind < 2))"
v-for="(it, ind) in item" :key="ind" :data-text='it'
:data-disabled="(cursor === 0 && index === 'line4' && ind >= 2) || (cursor >= 6 && (index !== 'line4' || (index === 'line4' && ind < 2)))"
@tap='_handleClick'>{{ it }}</view>
<view v-if="index === 'line4'" class='number-item handle-btn delete-btn'
@tap='_handleDelete'></view>
</view>
</block>
</view>
<!-- 字母数字面板 -->
<view class='letter-number-plane' v-if="!showProvince">
<block v-for="(item, index) in letterNumberList" :key="index">
<view class='line'>
<view v-if="index === 'line4' && cursor === 0" class='number-item handle-btn'
@tap='_clickChangePlane'>省</view>
<view v-if="index === 'line4' && cursor > 0" class="number-item handle-btn"
:class="cursor < 6 ? 'disabled' : ''" :data-disabled="cursor < 6"
@tap='_clickChangePlane'>字</view>
<view class="number-item"
:class="index === 'line2' && (it === 'I' || it === 'O') ? 'disabled' : ''"
:data-disabled="index === 'line2' && (it === 'I' || it === 'O')"
v-for="(it, ind) in item" :key="ind" :data-text='it' @tap='_handleClick'>{{ it }}</view>
<view v-if="index === 'line4'" class='number-item handle-btn delete-btn'
@tap='_handleDelete'></view>
</view>
</block>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
cursor: {
type: Number,
default: 0,
}
},
data() {
return {
showKeyboard: true, // 是否显示键盘
provinceList: {
line1: ['京', '津', '渝', '沪', '冀', '晋', '辽', '吉', '黑', '苏'],
line2: ['浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤', '琼'],
line3: ['川', '贵', '云', '陕', '甘', '青', '蒙', '桂', '宁', '新'],
line4: ['藏', '使', '领', '警', '学', '挂', '港', '澳', '试', '超', '军']
},
letterNumberList: {
line1: [1, 2, 3, 4, 5, 6, 7, 8, 9, '0'],
line2: ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
line3: ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
line4: ['Z', 'X', 'C', 'V', 'B', 'N', 'M'],
// line5: ['领', '警', '学', '挂', '港', '澳', '试', '超', '军']
},
showProvince: true, // 是否显示省份面板,控制省份面板和字符面板显示
animationData: {} // 键盘动画
};
},
watch: {
cursor(val) {
this.showProvince = (val === 0);
},
},
created() {
this.showProvince = (this.cursor === 0);
},
methods: {
show() {
this.showKeyboard = true;
const animation = uni.createAnimation({
duration: 200,
});
// 立即执行无效,延迟一段时间执行弹出动画
setTimeout(() => {
animation.translateY(0).step();
this.animationData = animation.export();
this.$emit('onShow');
}, 100);
},
hide() {
const animation = uni.createAnimation({
duration: 200
})
setTimeout(() => {
animation.translateY(300).step();
this.animationData = animation.export();
setTimeout(() => {
this.showKeyboard = false;
}, 300);
this.$emit('onHide');
}, 100);
},
/**
* 键盘面板切换操作,控制省份内容和字符内容显示
*/
_clickChangePlane: function (e) {
if (e.currentTarget.dataset.disabled) {
return;
}
this.showProvince = !this.showProvince
},
/**
* 关闭键盘,将键盘隐藏
*/
_closeKeyboard: function (e) {
// 创建动画,执行键盘面板退出动画,动画结束后隐藏整个键盘组件
this.hide();
},
/**
* 键盘主要键点击事件,将点击内容更新到plateNumber
*/
_handleClick: function (e) {
// 如果当前显示的省份面板,当即任意省份后,自动切换到字符面板,同时将结果值的第一个字符修改
if (e.currentTarget.dataset.disabled) {
return;
}
if (this.showProvince) {
this.showProvince = false;
}
if (this.cursor === 7) {
this.hide();
}
this.$emit('onInput', e.currentTarget.dataset.text);
},
_handleCursor: function (e) {
console.log(e.currentTarget.dataset.index)
this.cursor = e.currentTarget.dataset.index;
},
/**
* 删除回退点击事件
*/
_handleDelete: function () {
this.$emit('onDel');
},
},
}
</script>
<style scoped>
button {
padding: 0rpx;
}
.plate-number-keyboard-box {
position: fixed;
bottom: 0rpx;
left: 0rpx;
width: 100%;
height: 432rpx;
overflow: hidden;
background: rgba(255, 255, 255, 0);
z-index: 9999;
}
.pnk-main {
position: absolute;
bottom: 0;
left: 0rpx;
height: 432rpx;
width: 100%;
background: #d0d5d9;
transform: translateY(432rpx);
color: #333333;
}
.pnk-main .pnk-main-title {
font-size: 24rpx;
height: 50rpx;
line-height: 50rpx;
color: #333;
border-bottom: 1rpx solid #ddd;
padding: 0rpx 10rpx;
overflow: hidden;
}
.pnk-main-title text {
float: left;
}
.pnk-main-title .xiala {
position: relative;
float: right;
width: 70rpx;
height: 50rpx;
font-size: 40rpx;
text-align: center;
color: #333;
}
.pnk-content {
padding: 10rpx;
}
.line {
display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: 14rpx;
}
.line:last-of-type {
margin-bottom: 0rpx;
}
.line .number-item {
width: 60rpx;
height: 80rpx;
border-radius: 12rpx;
text-align: center;
line-height: 80rpx;
background: #fff;
font-size: 30rpx;
}
.line .number-item.disabled {
background: #ccc !important;
color: #999 !important;
}
.line .number-item:active {
background: #eee;
}
.line .handle-btn {
width: 80rpx;
background: #acb3bb;
font-size: 28rpx;
}
.xiala::after {
content: '';
position: absolute;
left: calc(50% - 14rpx);
top: calc(50% - 8rpx);
width: 28rpx;
height: 28rpx;
background-size: 100%;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAsCAMAAADFL88lAAAAclBMVEUAAAADGyYDGyYAADUDGyYAKjMAKysAIiIDGyYDGyYDGyYDGiYDGyYDGyYDGiYDGiUCGiYDGyYDGyYDGyUEGyYDGyYDGiUCGyYDGyUEGyUCGiUAGCYDGyYDGyYCGiUAGCQDHCYDGiUCHCcDHCcDGyYDHSnHUSRaAAAAJHRSTlMA/fsD+AUGB7yzubatqaSdf6+XkYvcwczGhHoK8udqDmVLb1ulZTqDAAACG0lEQVRIx6XV6WLCIAwA4AAF6rGtHvWaO9zg/V9xIVaMomuh+VWRfhBIAUA17bHdW9AwIjSYfVu1DSK69s45v/0CW+5Z+NoRUyto/FE6WfntO4ml3tZXyBx9A62TDqNYJO8dvaAI9wH0QOLOgi30dsEjUUCLLD1WfmPA5HuGvDMicYaHiFdhSXX+/troYZoHgBp/losazIYBU2xSscFRg87z9OY2RWxSNRNnACZn/dSdp0Kj5uIkQ7Q0Ge4ZaiYxVs8CxaEeTNmLUwWWDSTjQC+D1pGGncXUJHnxLy5K/zpApC4T7t0kpmP1kPgGoAZ48+srSXmoVMzwsDjiC0ycZojovTBvknSneuTi8onIPcc8/bCoZqnY7z0vNBvKQEZxdRVT75V1XNCrz8WuDoRfo9jnOe6lYfgchd9zkXtvfH7/Fq1ixS87MfWWzJtTl8Fi86i8VmdP4H50Xo84ScTEY599/yd1Lyp1463RyztGbBRFEA8kMk9grvEQMQADRXm5xkjknnQuz6NuXPxBsdyj0LfiJ4p3nkAv2Y/eveZiIPdedPUio5chLpj4DUZBc/FkPN6KRHEW4XDJV1Y5Ht+ZORN/P4Mn4vGrITtsEOVFxOg8gV4YbrQY93cZvaKshegg5mkoDBXneNauV0OxyC43+rSv3niR6m9V5KUXnOQX18gwdCVVQlTk0X6MFtfeO+dTr3wdT5vjR30alO8fxlZei+kmmyYAAAAASUVORK5CYII=);
}
.delete-btn {
position: relative;
}
.delete-btn::after {
content: '';
position: absolute;
left: calc(50% - 18rpx);
top: calc(50% - 15rpx);
width: 36rpx;
height: 36rpx;
background-size: 100%;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAvCAMAAACIYFl1AAAAk1BMVEUAAAADGyYAHCsAHh8DGyUDHCYDGyYAHykDGycDGyYDGyYAHSYEGiUAHCcDGyYCGyYEGyYAFCoCGyYDGyYCGiYDGyYDGyYEGSYFGyUDGyYDGyUDGiYCGycCGyYCGiYDGiYDGiUAHCgDGyYDGiYEGyQFGiUDGyYDGyYEGyUDGyUAHSQDGyYAGiUDGyUDGyYDGyYDHCi5/q4pAAAAL3RSTlMA+gQI/En3GeFj8RFFItl8QgzN49S+Wjs06LKSd3JrV00q3rg5L8aah1IX6x2splBDDs0AAAJiSURBVEjHrZaNdvIgDIYDxTptrbbz/3/qpvPbRu7/6j4Sh7Ht2VnpGdtRAzzkBQIEHosG2GTKNin94wgqrD4i2mYFcVOBzxgbo/jfFyW/lPJVylhlMAUpEaTsFpsUa2M8lkSP0TjWWPfnfvgvSx/++7uRq/qPji/oGnE123d+KfvRdEF+Hth5jMZgAU2KPqASWANMyLYnSKJbyaOfSgKDZ4HJ8caJVniByA+na+703VP3iWHPvvxDpTDToO8dK7TURB729Z/OUpgKO1x7yhPDIbfW4SlaE+MZtK/oIfbIEtbXCOzncMBY4SLxjuHdosUdaGF3VPMOugyTvcLYid7d2ZGlwWwqs0wNKoM4Al2FUxb9CXe4W/ASmJnXOe9zRXatwBqigvz0ByB7kYxdq8F4DtrvhWMPSVk2WUMWvS0tLYcBjxhpbyw+mC3Br5ZET3R5cV9vQp+6QDJo9PilslUclyRascBH+hQzneX6jVlFC1CFt9x0qUZUDh3L835bcgdZeoFhEJPoIhfWd6HIId/EWpxKB4GXJBo79wNRCSpjFQ+wBV2HeyxtJaJLR2H9fZWYNVk1OEOlaDvzOku+CurktoII+EPPMud9qznDB6/2OAla7fb7XI8wcwqKMIlt0za25VRtQk+VnGcaOPQ8+1uHhC9b3CRyh/Va3GHOvmZ8EV3Db09uQhL+5eGAe5uqvnhO+6AXQ7ZmQXN6zoPfKra3TriiByf4lSTkSDaOgt5niUfODLKwzEAWhHOS8ykkJxHhRXg2JK5nNjgPE5qi9JbgBWSAksZN2uSekvWOG2a9yxnAf+zPn543ZzPrAAAAAElFTkSuQmCC);
}
</style>
@@ -0,0 +1,312 @@
<template>
<view class="plate-number">
<view class="plate-number-box" :class="plate[7] && 'green'" @tap.stop>
<template v-for="(item, index) in 8" :key="`plate-${index}`">
<text v-if="index == 2" class="fz-000000 fz-weight fz-32">·</text>
<label :id="'input' + index"
:class="[(index == cursor && focus ? 'focus' : '') + (index === 7 && !plate[index] ? ' xinnengyuan' : ''), 'bglabel']"
:data-index="index" @tap='_handleCursor'>
{{ plate[index] || '' }}
</label>
</template>
</view>
<plate-keyboard ref='plateKeyboard' :cursor="cursor" @onInput="_keyboardInput" @onDel="_keyboardDel"
@onShow="plateKeyboardOnShow" @onHide="plateKeyboardOnHide" />
</view>
</template>
<script>
import PlateKeyboard from './plate-keyboard.vue';
export default {
components: {
PlateKeyboard
},
data() {
return {
plate: new Array(8),
cursor: 0,
focus: false,
}
},
created() {
// 根据IP获取到当前省份
this.getLocationByIP();
},
methods: {
setPlate: function (plateNumber) {
const plate = plateNumber.split('');
while (plate.length < 8) {
plate.push('');
}
this.plate = plate;
this.cursor = plateNumber.length;
this.focus = false;
},
getPlate: function () {
return this.plate.join('');
},
onInput: function (text) {
// 车牌号最多8位,大多数7位,新能源8位,控制不能超过8位数
if (this.cursor < 8) {
this.plate.splice(this.cursor++, 1, text);
}
},
onDel: function () {
if (this.plate[this.cursor]) {
this.plate.splice(this.cursor, 1, '');
} else if (this.cursor > 0) {
this.plate.splice(--this.cursor, 1, '');
}
},
_handleCursor: function (e) {
let index = parseInt(e.currentTarget.dataset.index);
for (; index > 0 && !this.plate[index] && !this.plate[index - 1]; index--);
this.cursor = index;
this.focus = true;
this.$refs.plateKeyboard.show();
},
hidePlateKeyboard() {
this.$refs.plateKeyboard.hide();
},
_keyboardInput(val) {
console.log('===input:' + val);
this.onInput(val);
},
_keyboardDel() {
console.log('===del')
this.onDel();
},
plateKeyboardOnShow() {
this.$emit('plateKeyboardOnShow');
},
plateKeyboardOnHide() {
this.focus = false;
this.$emit('plateKeyboardOnHide');
},
async getLocationByIP() {
if (getApp().globalData.provinceShort) {
this.setPlate(getApp().globalData.provinceShort);
} else {
if (!this.$Request) {
return;
}
const res = await this.$Request(this.$Api.general.park.getLocationByIP, {});
if (res.success) {
var prov = res.data.province;
if (prov) {
let provShort = this.provinceForShort(prov);
if (provShort) {
getApp().globalData.provinceShort = provShort;
if (!this.getPlate()) {
this.setPlate(provShort);
}
}
}
}
}
},
provinceForShort(province) {
if (province == "北京市")
return "京";
else if (province == "天津市")
return "津";
else if (province == "重庆市")
return "渝";
else if (province == "上海市")
return "沪";
else if (province == "河北省")
return "冀";
else if (province == "山西省")
return "晋";
else if (province == "辽宁省")
return "辽";
else if (province == "吉林省")
return "吉";
else if (province == "黑龙江省")
return "黑";
else if (province == "江苏省")
return "苏";
else if (province == "浙江省")
return "浙";
else if (province == "安徽省")
return "皖";
else if (province == "福建省")
return "闽";
else if (province == "江西省")
return "赣";
else if (province == "山东省")
return "鲁";
else if (province == "河南省")
return "豫";
else if (province == "湖北省")
return "鄂";
else if (province == "湖南省")
return "湘";
else if (province == "广东省")
return "粤";
else if (province == "海南省")
return "琼";
else if (province == "四川省")
return "川";
else if (province == "贵州省")
return "贵";
else if (province == "云南省")
return "云";
else if (province == "陕西省")
return "陕";
else if (province == "甘肃省")
return "甘";
else if (province == "青海省")
return "青";
else if (province == "台湾省")
return "台";
else if (province == "内蒙古自治区")
return "蒙";
else if (province == "广西壮族自治区")
return "桂";
else if (province == "宁夏回族自治区")
return "宁";
else if (province == "新疆维吾尔自治区 ")
return "新";
else if (province == "西藏自治区")
return "藏";
else if (province == "香港特别行政区")
return "港";
else if (province == "澳门特别行政区")
return "澳";
else return '';
}
},
}
</script>
<style scoped>
.plate-number {
/* display: flex; */
/* justify-content: spa; */
}
.plate-number-box {
position: relative;
/* height: 50rpx; */
padding: 24rpx 0;
box-sizing: content-box;
margin: auto;
border-radius: 10rpx;
transition: all 1s;
display: flex;
justify-content: space-between;
align-items: center;
}
/* .plate-number-box::before {
content: '';
border: 2rpx solid #fff;
position: absolute;
top: 5rpx;
left: 5rpx;
border-radius: 10rpx;
height: 86rpx;
} */
.bglabel {
background-color: #F2F5FA;
}
/* .plate-number-box::after {
content: '·';
position: absolute;
left: 122rpx;
top: 30rpx;
color: #fff;
font-size: 50rpx;
line-height: 40rpx;
} */
.plate-number-box.green {
/* background: linear-gradient(#f8f8f8 20%, #16b235); */
box-shadow: 0 0 4rpx 0 #ebebeb;
}
/* .plate-number-box.green::before {
border: 2rpx solid #000;
} */
/* .plate-number-box.green::after {
content: '';
position: absolute;
left: 120rpx;
top: 36rpx;
width: 26rpx;
height: 26rpx;
background-size: 100%;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAF6UExURUdwTH3EyJfLtffOk9mwguW7jHvftPvQofnPoVlKOYzOo4jMs5HPp5fIvqbYnOhFQfrCkJG6pIbLw4vUzYjKv5vjs9q8JvLPn4bJpJHqtYrNqnStopDh3oLJwViHcIjLv3q2qozMvmRYSYfLpovMqm6ggum9j+PMnqDSuqTQuOjOpKPOoGaZfnGqi+DCKXooHt20hqWvqW9fEMt9bKG7p8LDXnuSiKS4o////zqp4Deo4jCuMS2tLfTz8+vr7OUUW9rZ22JbUvv8/Uuy2zGxMUO2SiaZJ9DhLjdEOkpPTckubzSj2lu62VO+YDJERD9NR/f290JFQbe1tV3BbuLg4d/c3bXXLzIxJ1dVUj1OVnHF0W3IhMXCwSlMXhlAGiZOJ8zhL0ZiUVdfW7mGmU1aVqakpZiWlyNGV2mnp4KBgkpybhg9UTKh2bbWKmlnZLDcfbrcMWeofoOMGaa3IzpLVFx3GcngQ9wzaa/chHoJLK0NRGEiRWppaD1PV9NqRZ0AAAA4dFJOUwATR/X49w/6+v3hs3ByFPT09uNHzUf0+Swpy+YuL+Xw1f398P3c9/f98Pu6yMb0+vjc+vi5+NyxP0I0ygAAAWxJREFUKM910ldXwjAYBuC0KgqlVeZhKEPAhXuGKYIIKKscNsjGvff87yZNOdYLn8v3bZKTLwWAoGjGardbGQ0FpChWXU1GkOSZjZVUU9pqxOvxeL0e72X8Qjs5yDXqJMqI4/p52qwRv1dHBjEq8jCXNgtrKG1SyI+wq9MChPW0TYYKtnuIfaa/K5XKUyIIITzpsWjB2usu8v4VDGBZlMNsYpsDdBfnH3wgLICCfocGmxvjyEpJTpTxVjCU2QHzs0PYiEgewkWgpwJzC2PYqGg6RA75pwiXVWirCWRmdZggWwXRCveWXq9fXi/tETfi4W5Av8VardgjH4QS/bYOcIqXGPJQllwQdlwcAMrb62KxePdcimPCSHIZJZqVzJTy+3y+/QNBAw0xn1nEQwQGRVRoMF8zDwsJi5E8CK1I+XGF+JuhPm/RDZ7QYKpF/USDby8Zfx9dplTUUlEkde9Syv78JxzNOJxOB6PjxOAHI6di/dhTK/cAAAAASUVORK5CYII=);
} */
.plate-number-box.green label {
color: #222;
text-shadow: 0 0 2rpx #fff;
}
.plate-number-box label {
width: 60rpx;
position: relative;
vertical-align: middle;
display: inline-block;
/* width: 8%; */
color: #fff;
text-align: center;
font-size: 50rpx;
height: 86rpx;
line-height: 86rpx;
text-shadow: 0rpx 0rpx 2rpx #000;
/* margin: 0 10rpx; */
}
.plate-number-box label:nth-child(2) {
/* margin-right: 20rpx; */
}
.plate-number-box label:last-child {
content: "\e63f";
}
/* .plate-number-box label.focus::after {
content: '';
position: absolute;
bottom: -6rpx;
left: 5%;
width: 90%;
height: 4rpx;
background: #fff;
animation: cursor 1s ease-in infinite;
} */
/* .plate-number-box label.xinnengyuan::before {
content: '';
position: absolute;
left: 12%;
top: 12%;
width: 76%;
height: 76%;
background-size: 100%;
background-repeat: no-repeat;
} */
@keyframes cursor {
from {
opacity: 1;
}
to {
opacity: 0.2;
}
}
</style>
+16 -13
View File
@@ -42,18 +42,20 @@ const https = new Request({
})
https.interceptors.request.use(async (config) => {
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
const tokenStorage = uni.getStorageSync(TOKEN_KEY) as GlobalSetting.TokenStorage
const config_header: any = config.header || {}
let tokenValue = getTokenValue(tokenStorage)
// 设置租户请求头
config_header["TENANT-ID"] = "1"
if (import.meta.env.VITE_IS_TOC === "true") config_header["CLIENT-TOC"] = "Y"
if (import.meta.env.VITE_IS_TOC === "true") {
config_header["CLIENT-TOC"] = "Y"
}
// 判断是否需要设置认证头
if (!SKIP_AUTH_URLS.some((url) => config.url?.startsWith(url))) {
if (!isEmpty(tokenStorage)) {
// token存在,检查是否过期
if (isTokenExpires(tokenStorage) && !SKIP_REFRESH_TOKEN.some((url) => config.url?.startsWith(url)) && refreshTokenLock) {
debugger
// 过期,刷新token
const newTokenStorage = await refreshToken()
if (newTokenStorage) {
@@ -99,14 +101,14 @@ https.interceptors.response.use(
)
// 从tokenStorage中读取tokenValue值
const getTokenValue = (tokenStorage: any): string | null => {
return tokenStorage?.access_token ? `Bearer ${tokenStorage.access_token}` : null
const getTokenValue = (tokenStorage: GlobalSetting.TokenStorage): string | null => {
return tokenStorage?.accessToken ? `Bearer ${tokenStorage?.accessToken}` : null
}
// 检查tokenStorage中tokenValue是否过期
const isTokenExpires = (tokenStorage: any): boolean => {
const { access_token } = tokenStorage
const expiresDate = new Date(access_token?.expiresAt).getTime()
const isTokenExpires = (tokenStorage: GlobalSetting.TokenStorage): boolean => {
const { accessTokenExpiresIn } = tokenStorage
const expiresDate = new Date(accessTokenExpiresIn).getTime()
return new Date().getTime() >= expiresDate
}
@@ -114,12 +116,13 @@ async function refreshToken(): Promise<any> {
refreshTokenLock = false
try {
// 调用接口判断token是否过期
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
const { data } = await userApi.refreshToken(tokenStorage.refresh_token)
const tokenStorage = uni.getStorageSync(TOKEN_KEY) as GlobalSetting.TokenStorage
const { data } = await userApi.refreshToken(tokenStorage.refreshToken)
if (data) {
tokenStorage.access_token = data.access_token
tokenStorage.refresh_token = data.refresh_token
tokenStorage.expires_in = data.expires_in
tokenStorage.accessToken = data?.accessToken?.tokenValue
tokenStorage.refreshToken = data?.refreshToken?.tokenValue
tokenStorage.accessTokenExpiresIn = data?.accessToken?.expiresAt
tokenStorage.refreshTokenExpiresIn = data?.refreshToken?.expiresAt
uni.setStorageSync(TOKEN_KEY, tokenStorage)
}
} catch (error) {
+16
View File
@@ -0,0 +1,16 @@
<svg
t="1743075857752"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="3808"
width="12"
height="11"
>
<path
d="M512 1012.8c-253.6 0-511.2-54.4-511.2-158.4 0-92.8 198.4-131.2 283.2-143.2h3.2c12 0 22.4 8.8 24 20.8 0.8 6.4-0.8 12.8-4.8 17.6-4 4.8-9.6 8.8-16 9.6-176.8 25.6-242.4 72-242.4 96 0 44.8 180.8 110.4 463.2 110.4s463.2-65.6 463.2-110.4c0-24-66.4-70.4-244.8-96-6.4-0.8-12-4-16-9.6-4-4.8-5.6-11.2-4.8-17.6 1.6-12 12-20.8 24-20.8h3.2c85.6 12 285.6 50.4 285.6 143.2 0.8 103.2-256 158.4-509.6 158.4z m-16.8-169.6c-12-11.2-288.8-272.8-288.8-529.6 0-168 136.8-304.8 304.8-304.8S816 145.6 816 313.6c0 249.6-276.8 517.6-288.8 528.8l-16 16-16-15.2zM512 56.8c-141.6 0-256.8 115.2-256.8 256.8 0 200.8 196 416 256.8 477.6 61.6-63.2 257.6-282.4 257.6-477.6C768.8 172.8 653.6 56.8 512 56.8z m0 392.8c-80 0-144.8-64.8-144.8-144.8S432 160 512 160c80 0 144.8 64.8 144.8 144.8 0 80-64.8 144.8-144.8 144.8zM512 208c-53.6 0-96.8 43.2-96.8 96.8S458.4 401.6 512 401.6c53.6 0 96.8-43.2 96.8-96.8S564.8 208 512 208z"
fill="#707070"
p-id="3809"
></path>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750384429039" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7956" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M905.386667 278.613333c28.501333 0 42.773333 34.474667 22.613333 54.634667L533.333333 727.914667a32 32 0 0 1-45.248 0l-394.666666-394.666667c-20.16-20.16-5.888-54.613333 22.613333-54.613333h789.333333z" fill="#3496F9" p-id="7957" data-spm-anchor-id="a313x.search_index.0.i14.81863a81aL498c" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 652 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1743643959336" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2508" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M334.08 192a47.36 47.36 0 0 0 0 64l256 256-256 256a47.36 47.36 0 1 0 64 64l288.64-288.64A50.56 50.56 0 0 0 704 512a47.36 47.36 0 0 0-14.08-33.28L401.28 192a47.36 47.36 0 0 0-67.2 0z" p-id="2509" fill="#C5BFBF"></path></svg>

After

Width:  |  Height:  |  Size: 554 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744612158830" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12544" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M736 480c-12.5-12.5-32.8-12.5-45.3 0L523.3 647.4c-6.2 6.2-16.4 6.2-22.6 0L333.3 480c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L466.7 704c25 25 65.5 25 90.5 0L736 525.3c12.5-12.5 12.5-32.8 0-45.3z" p-id="12545"></path></svg>

After

Width:  |  Height:  |  Size: 558 B

+26
View File
@@ -0,0 +1,26 @@
<svg
t="1742457305987"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4499"
width="24"
height="24"
>
<path
d="M897.706667 831.146667H286.72c-8.533333 0-15.36-3.413333-20.48-10.24L39.253333 527.36c-6.826667-8.533333-6.826667-22.186667 0-30.72l226.986667-293.546667c5.12-6.826667 11.946667-10.24 20.48-10.24h610.986667c51.2 0 92.16 40.96 92.16 92.16v453.973334c0 51.2-40.96 92.16-92.16 92.16z m-599.04-51.2h599.04c22.186667 0 40.96-18.773333 40.96-40.96V285.013333c0-22.186667-18.773333-40.96-40.96-40.96H298.666667L92.16 512l206.506667 267.946667z"
fill="#333333"
p-id="4500"
></path>
<path
d="M670.72 650.24c-6.826667 0-13.653333-1.706667-18.773333-6.826667L426.666667 416.426667c-10.24-10.24-10.24-25.6 0-35.84 10.24-10.24 25.6-10.24 35.84 0l226.986666 226.986666c10.24 10.24 10.24 25.6 0 35.84-6.826667 5.12-11.946667 6.826667-18.773333 6.826667z"
fill="#333333"
p-id="4501"
></path>
<path
d="M443.733333 650.24c-6.826667 0-13.653333-1.706667-18.773333-6.826667-10.24-10.24-10.24-25.6 0-35.84l226.986667-226.986666c10.24-10.24 25.6-10.24 35.84 0 10.24 10.24 10.24 25.6 0 35.84L462.506667 643.413333c-5.12 5.12-11.946667 6.826667-18.773334 6.826667z"
fill="#333333"
p-id="4502"
></path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1743596095932" class="icon" viewBox="0 0 1042 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7038" width="65.125" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M515.657143 54.857143c-252.342857 0-457.142857 204.8-457.142857 457.142857s204.8 457.142857 457.142857 457.142857 457.142857-204.8 457.142857-457.142857S768 54.857143 515.657143 54.857143z m182.857143 442.514286h-146.285715v36.571428h146.285715v73.142857h-146.285715v109.714286h-73.142857v-109.714286h-146.285714v-73.142857h146.285714v-36.571428h-146.285714v-73.142858h91.428571l-65.828571-65.828571 51.2-51.2 104.228571 104.228571 104.228572-104.228571 51.2 51.2-65.828572 65.828571h91.428572v73.142858h3.657143z" fill="#0779FD" p-id="7039"></path></svg>

After

Width:  |  Height:  |  Size: 890 B

+16
View File
@@ -0,0 +1,16 @@
<svg
t="1743075703202"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2684"
width="12"
height="12"
>
<path
d="M575.7 647.1l69.4-69.2 13.3 2.3c1.7 0.3 4.9 0.9 9.4 1.7 7.4 1.3 15.7 2.9 24.6 4.5 25.5 4.8 50.9 9.8 74.8 14.9 17.3 3.7 33.1 7.3 47 10.7 27.6 6.8 47.3 12.9 59.1 18.6 43 20.8 60 65.5 53.4 122-9.7 81.3-97 174.3-204.4 175.4-121 1.2-291.4-87.7-411.9-214.7C165.3 560.4 81.8 406.9 98 278.3c8.5-67.1 40.2-115.9 87.8-147.2 32.4-21.3 69.4-32.5 97.9-34.6 38.2-2.8 69.6 9.6 92.5 38 9.7 12 19.7 36.1 32.2 73.3 3.6 10.9 7.4 22.8 11.3 35.4 7 23 14.1 47.6 20.9 72.2 2.4 8.6 4.5 16.6 6.4 23.7 1.1 4.3 2 7.4 2.4 9.1l3.7 14.2-80.4 81.8c44.5 89 122.6 169.8 203 202.9z m13.5 60l-15.3-5.1c-105.4-35.6-205.4-139.4-256.3-252.3l-7.3-16.2 85-86.4c-1.5-5.6-3.1-11.6-4.9-17.9-6.6-24.1-13.5-48.2-20.4-70.7-3.8-12.3-7.4-23.7-10.9-34.2-10.2-30.5-19.1-52-23.3-57.2-11.8-14.6-26.4-20.4-48.2-18.8-20.2 1.5-48.7 10.1-73.1 26.2-35.2 23.2-58.3 58.6-64.8 110.3-13.8 108.7 63.1 250.2 198.4 392.7C459.5 794.9 617 877 721.8 876c79.2-0.8 146.4-72.5 153.2-129.4 4.3-36.6-4.3-59.4-24.4-69.1-7.5-3.6-24.7-8.9-48.9-14.9-13.4-3.3-28.6-6.8-45.4-10.4-23.3-5-48.4-10-73.5-14.7-7.2-1.3-13.9-2.6-20.1-3.7l-73.5 73.3z"
fill="#666666"
p-id="2685"
></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744612190511" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12706" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M736 544c-12.5 12.5-32.8 12.5-45.3 0L523.3 376.6c-6.2-6.2-16.4-6.2-22.6 0L333.3 544c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L466.7 320c25-25 65.5-25 90.5 0L736 498.7c12.5 12.5 12.5 32.8 0 45.3z" p-id="12707"></path></svg>

After

Width:  |  Height:  |  Size: 558 B