# Conflicts:
#	.env
This commit is contained in:
zjf committed 2025-09-09 11:21:35 +08:00
commit 41d239e574
44 files changed
+1147 -614

No files matched your search

+11 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import { getLaunchParam } from "@/sheep/utils/app"
import { getAppInfo } from "@@/utils/app"
import { APPENV } from "./sheep/constant"
interface TabbarItem {
pagePath: string
@@ -60,6 +61,14 @@ const tabbar: TabbarItem[] = [
launchParam: ["onlineShop"],
auth: ["user"],
},
{
pagePath: "pages/tabbar/prosperityShop",
text: "共富商城",
iconPath: "/static/tabbar/prosperity-shop.png",
selectedIconPath: "/static/tabbar/prosperity-shop-select.png",
launchParam: ["onlineShop"],
auth: ["user"],
},
{
pagePath: "pages/tabbar/cabinet",
text: "智能柜",
@@ -152,7 +161,7 @@ export default {
const appInfo = await getAppInfo()
// 浙政钉环境
if (appInfo == "zzd") {
if (appInfo == APPENV.ZZD) {
if (!launchParam) return uni.hideTabBar()
setTabbar(launchParam)
@@ -161,7 +170,7 @@ export default {
}
// 诗画环境
if (appInfo == "sh") {
if (appInfo == APPENV.SH) {
if (!launchParam) return uni.hideTabBar()
const userRole = uni.getStorageSync("userRole")
+30
View File
@@ -44,6 +44,13 @@
"disableScroll": true
}
},
{
"path": "pages/tabbar/prosperityShop",
"style": {
"navigationBarTitleText": "共富商城",
"disableScroll": true
}
},
{
"path": "pages/tabbar/coopMerchant",
"style": {
@@ -512,6 +519,23 @@
}
}
]
},
{
"root": "pages/car",
"pages": [
{
"path": "userCar/apply",
"style": {
"navigationBarTitleText": "申请车辆"
}
},
{
"path": "userCar/list",
"style": {
"navigationBarTitleText": "我的车辆"
}
}
]
}
],
"globalStyle": {
@@ -559,6 +583,12 @@
"iconPath": "",
"selectedIconPath": ""
},
{
"pagePath": "pages/tabbar/prosperityShop",
"text": "",
"iconPath": "",
"selectedIconPath": ""
},
{
"pagePath": "pages/tabbar/cabinet",
"text": "",
+12 -27
View File
@@ -35,7 +35,7 @@ onLoad((options: any) => {
getDetail()
}
})
onShow(() => {})
onShow(() => { })
const getDetail = async () => {
if (isEmpty(state.detailId)) return
@@ -88,18 +88,14 @@ const doSubmit = async () => {
<view class="card p-x-30rpx p-y-40rpx">
<tm-form ref="applyFormRef" v-model="applyForm" :rules="applyFormRules" direction="vertical">
<tm-form-item name="displayTimeRangeStr" label="使用时间" :showBottom="false">
<tm-between-time
start="2020-1-1"
end="2030-1-1"
:quickDate="[]"
v-model="applyForm.displayTimeRange"
v-model:model-str="applyForm.displayTimeRangeStr"
format="YYYY-MM-DD"
>
<tm-between-time start="2020-1-1" end="2030-1-1" :quickDate="[]"
v-model="applyForm.displayTimeRange" v-model:model-str="applyForm.displayTimeRangeStr"
format="YYYY-MM-DD">
<view :style="{ pointerEvents: state.detailType == 'view' ? 'auto' : 'none' }">
<tm-input v-model="applyForm.displayTimeRangeStr" disabled placeholder="请选择使用时间..">
<template v-slot:rightIcon>
<text v-if="state.detailType != 'view'" class="p-0.5rem color-#b1b1b1 iconfont icon-xiangxiajiantouxiao"></text>
<text v-if="state.detailType != 'view'"
class="p-0.5rem color-#b1b1b1 iconfont icon-xiangxiajiantouxiao"></text>
</template>
</tm-input>
</view>
@@ -107,31 +103,20 @@ const doSubmit = async () => {
</tm-form-item>
<view class="h-30rpx" />
<tm-form-item name="count" label="申请数量" :showBottom="false">
<tm-input
v-model="applyForm.count"
type="number"
:disabled="state.detailType == 'view'"
:showClear="state.detailType != 'view'"
placeholder="请输入申请数量.."
/>
<tm-input v-model="applyForm.count" type="number" :disabled="state.detailType == 'view'"
:showClear="state.detailType != 'view'" placeholder="请输入申请数量.." />
</tm-form-item>
<view class="h-30rpx" />
<tm-form-item name="remark" label="备注" :required="false" :showBottom="false">
<tm-input
v-model="applyForm.remark"
type="textarea"
:disabled="state.detailType == 'view'"
<tm-input v-model="applyForm.remark" type="textarea" :disabled="state.detailType == 'view'"
:showClear="state.detailType != 'view'"
:placeholder="state.detailType == 'view' ? '' : '请输入备注..'"
></tm-input>
:placeholder="state.detailType == 'view' ? '' : '请输入备注..'"></tm-input>
</tm-form-item>
</tm-form>
</view>
<view
v-if="state.detailType != '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"
>
@tap="doSubmit">
提交
</view>
</crud-layout>
+9 -17
View File
@@ -27,7 +27,7 @@ const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
onLoad(() => {})
onLoad(() => { })
onShow(() => {
if (pageRef.value) {
pageRef.value.reload(false)
@@ -55,20 +55,9 @@ const doCard = (v: any) => {
<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"
>
<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">
<template #top>
<view class="h-30rpx" />
<view class="bg-white rounded-16rpx px-20rpx">
@@ -76,7 +65,8 @@ const doCard = (v: any) => {
</view>
</template>
<view class="h-20rpx" />
<view class="flex-col card p-x-20rpx p-y-30rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i" @tap="doCard(v)">
<view class="flex-col card p-x-20rpx p-y-30rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i"
@tap="doCard(v)">
<view class="flex-row items-center justify-between">
<view class="sub-text-30">电子餐券</view>
<view class="flex-row items-center">
@@ -127,7 +117,9 @@ const doCard = (v: any) => {
</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
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" />
+101
View File
@@ -0,0 +1,101 @@
<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"
const { carTypeEnum } = serviceEnum
const applyFormRef = 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 () => {
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">
<tm-input v-model="applyForm.carNumber" placeholder="请输入" show-char-count></tm-input>
</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/userCar/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>
+7 -16
View File
@@ -19,7 +19,7 @@ const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
onLoad(() => {})
onLoad(() => { })
onShow(() => {
if (pageRef.value) {
pageRef.value.reload(false)
@@ -49,20 +49,9 @@ const doDel = (id: string) => {
<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"
>
<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-col card p-x-20rpx p-y-30rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="v.id">
<view class="flex items-center justify-between second-text-26">
@@ -81,7 +70,9 @@ const doDel = (id: string) => {
</view>
</view>
<template #bottom>
<view v-if="false" class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-60rpx sub-text-30 color-white!" @tap="doAdd">
<view v-if="false"
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-60rpx sub-text-30 color-white!"
@tap="doAdd">
添加
</view>
<view class="h-30rpx" />
+169 -168
View File
@@ -1,206 +1,207 @@
<template>
<view class="notice-detail">
<view class="detail-header">
<view class="title">
<text v-if="detail.isTop === '1'" class="top-tag">置顶</text>
{{ detail.title }}
</view>
<view class="meta">
<text class="type-tag" :class="getTypeClass(detail.type)">{{ getTypeLabel(detail.type) }}</text>
<text class="time">{{ formatTime(detail.createTime) }}</text>
<text class="view-count">浏览 {{ detail.viewCount }}</text>
</view>
</view>
<view class="detail-content">
<rich-text :nodes="detail.description || ''"></rich-text>
</view>
<view v-if="detail.attachments && detail.attachments.length > 0" class="attachments">
<view class="attachment-title">附件</view>
<view
v-for="(file, index) in detail.attachments"
:key="index"
class="attachment-item"
@click="downloadFile(file)"
>
<text class="file-name">{{ file.name }}</text>
<text class="file-size">{{ formatFileSize(file.size) }}</text>
</view>
</view>
</view>
<view class="notice-detail">
<view class="detail-header">
<view class="title">
<text v-if="detail.isTop === '1'" class="top-tag">置顶</text>
{{ detail.title }}
</view>
<view class="meta">
<text class="type-tag" :class="getTypeClass(detail.type)">{{ getTypeLabel(detail.type) }}</text>
<text class="time">{{ formatTime(detail.createTime) }}</text>
<text class="view-count">浏览 {{ detail.viewCount }}</text>
</view>
</view>
<view class="detail-content">
<rich-text :nodes="nodes || ''"></rich-text>
</view>
<view v-if="detail.attachments && detail.attachments.length > 0" class="attachments">
<view class="attachment-title">附件</view>
<view v-for="(file, index) in detail.attachments" :key="index" class="attachment-item" @click="downloadFile(file)">
<text class="file-name">{{ file.name }}</text>
<text class="file-size">{{ formatFileSize(file.size) }}</text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { noticeApi } from '@/sheep/api'
import { computed, ref } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { noticeApi } from "@/sheep/api"
const detail = ref<any>({})
const nodes = computed(() => {
return detail.value.description?.replaceAll(`<img src="/api`, `<img src="https://stgl.pj.gov.cn/api`)
})
function getTypeLabel(type: string) {
const typeMap: Record<string, string> = {
'1': '重要',
'2': '一般',
'3': '活动'
}
return typeMap[type] || '一般'
const typeMap: Record<string, string> = {
"1": "重要",
"2": "一般",
"3": "活动",
}
return typeMap[type] || "一般"
}
function getTypeClass(type: string) {
const classMap: Record<string, string> = {
'1': 'type-important',
'2': 'type-general',
'3': 'type-activity'
}
return classMap[type] || 'type-general'
const classMap: Record<string, string> = {
"1": "type-important",
"2": "type-general",
"3": "type-activity",
}
return classMap[type] || "type-general"
}
function formatTime(time: string) {
if (!time) return ''
return new Date(time).toLocaleString()
if (!time) return ""
return new Date(time).toLocaleString()
}
function formatFileSize(size: number) {
if (size < 1024) {
return size + 'B'
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(1) + 'KB'
} else {
return (size / (1024 * 1024)).toFixed(1) + 'MB'
}
if (size < 1024) {
return size + "B"
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(1) + "KB"
} else {
return (size / (1024 * 1024)).toFixed(1) + "MB"
}
}
function downloadFile(file: any) {
uni.downloadFile({
url: file.url,
success: (res) => {
if (res.statusCode === 200) {
uni.openDocument({
filePath: res.tempFilePath,
success: () => {
console.log('打开文档成功')
},
fail: () => {
uni.showToast({ title: '无法打开此文件', icon: 'none' })
}
})
}
},
fail: () => {
uni.showToast({ title: '下载失败', icon: 'none' })
}
})
uni.downloadFile({
url: file.url,
success: (res) => {
if (res.statusCode === 200) {
uni.openDocument({
filePath: res.tempFilePath,
success: () => {
console.log("打开文档成功")
},
fail: () => {
uni.showToast({ title: "无法打开此文件", icon: "none" })
},
})
}
},
fail: () => {
uni.showToast({ title: "下载失败", icon: "none" })
},
})
}
onLoad(async (options: any) => {
const id = options.id
if (!id) return
try {
// 增加浏览量
await noticeApi.addNoticeView(id)
// 获取详情
const response: any = await noticeApi.getNoticeDetail(id)
if (response.code === 0 && response.data) {
detail.value = response.data
}
} catch (error) {
console.error('获取公告详情失败:', error)
uni.showToast({ title: '获取详情失败', icon: 'none' })
}
const id = options.id
if (!id) return
try {
// 增加浏览量
await noticeApi.addNoticeView(id)
// 获取详情
const response: any = await noticeApi.getNoticeDetail(id)
if (response.code === 0 && response.data) {
detail.value = response.data
}
} catch (error) {
console.error("获取公告详情失败:", error)
uni.showToast({ title: "获取详情失败", icon: "none" })
}
})
</script>
<style lang="scss" scoped>
.notice-detail {
padding: 32rpx;
background: #f5f5f5;
min-height: 100vh;
padding: 32rpx;
background: #f5f5f5;
min-height: 100vh;
}
.detail-header {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
line-height: 1.4;
margin-bottom: 8rpx;
display: flex;
align-items: center;
}
.top-tag {
background: #ffebee;
color: #d32f2f;
font-size: 22rpx;
border-radius: 8rpx;
padding: 2rpx 12rpx;
margin-right: 12rpx;
}
.meta {
display: flex;
justify-content: space-between;
color: #999;
font-size: 24rpx;
margin-bottom: 16rpx;
}
.view-count {
margin-left: 24rpx;
}
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
line-height: 1.4;
margin-bottom: 8rpx;
display: flex;
align-items: center;
}
.top-tag {
background: #ffebee;
color: #d32f2f;
font-size: 22rpx;
border-radius: 8rpx;
padding: 12rpx 12rpx;
margin-right: 12rpx;
white-space: nowrap;
}
.meta {
display: flex;
justify-content: space-between;
color: #999;
font-size: 24rpx;
margin-bottom: 16rpx;
}
.view-count {
margin-left: 24rpx;
}
}
.detail-content {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
font-size: 30rpx;
color: #333;
line-height: 1.8;
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
font-size: 30rpx;
color: #333;
line-height: 1.8;
overflow: auto;
}
.attachments {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
.attachment-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
}
.attachment-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.file-name {
font-size: 28rpx;
color: #007aff;
flex: 1;
margin-right: 16rpx;
}
.file-size {
font-size: 24rpx;
color: #999;
}
}
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
.attachment-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
}
.attachment-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.file-name {
font-size: 28rpx;
color: #007aff;
flex: 1;
margin-right: 16rpx;
}
.file-size {
font-size: 24rpx;
color: #999;
}
}
}
</style>
</style>
+24 -15
View File
@@ -1,8 +1,12 @@
<template>
<crud-layout title="详情" mainIsScroll bg="/static/page/blue-bg.png">
<view v-if="detail.id">
<tm-sheet :margin="[0, 20]" class="second-text-32">
<tm-text font-size="32">访客信息</tm-text>
<view class="h-20rpx" />
<tm-alert v-if="alertList[detail.checkStatus]" :showClose="false" skin="normal" :color="alertList[detail.checkStatus].color">
{{ alertList[detail.checkStatus].text }}
</tm-alert>
<tm-sheet :margin="[0, 20]" class="second-text-30">
<tm-text class="prefix_title" font-size="32">访客信息</tm-text>
<view class="flex justify-between mt-1">
<text>访客姓名:</text>
<text>{{ detail.name }}</text>
@@ -37,8 +41,8 @@
</view>
</tm-sheet>
<tm-sheet :margin="[0, 20]" class="second-text-32">
<tm-text font-size="32">时间</tm-text>
<tm-sheet :margin="[0, 20]" class="second-text-30">
<tm-text class="prefix_title" font-size="32">时间</tm-text>
<view class="flex justify-between mt-1">
<text>预计来访时间:</text>
<text>{{ detail.vendDate }}</text>
@@ -49,8 +53,8 @@
</view>
</tm-sheet>
<tm-sheet :margin="[0, 20]" class="second-text-32">
<tm-text font-size="32">其他</tm-text>
<tm-sheet :margin="[0, 20]" class="second-text-30">
<tm-text class="prefix_title" font-size="32">其他</tm-text>
<view class="flex justify-between mt-1">
<text>创建方式:</text>
<text>{{ detail.type == "1" ? "主动申请" : detail.type == "2" ? "邀约" : "后台创建" }}</text>
@@ -67,10 +71,17 @@
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue"
import { ref } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { invitationApi } from "@/sheep/api"
const alertList = {
1: { color: "#f5a623", text: "访客未接受邀约" },
2: { color: "#2db7f5", text: "访客待到访" },
3: { color: "#52c41a", text: "访客拒绝了邀约" },
5: { color: "#f5222d", text: "访客取消了邀约" },
}
const detail = ref<any>({})
const visitorReasonList = ref<{ title: string; id: string }[]>([])
@@ -78,19 +89,17 @@ onLoad(async (options: any) => {
const id = options.id
try {
uni.showLoading({ title: "加载中..." })
const { code, data, msg } = await invitationApi.getInvitationDetail(id)
if (code == 0) {
detail.value = data
return
}
uni.showToast({ title: msg || "获取详情失败", icon: "none" })
if (code != 0) return uni.showToast({ title: msg || "获取详情失败", icon: "none" })
detail.value = data
} catch (error: any) {
uni.showToast({ title: error.message, icon: "none" })
} finally {
uni.hideLoading()
}
})
onMounted(() => {
getVisitorReasonList()
await getVisitorReasonList()
})
// 获取拜访原因列表
+35 -26
View File
@@ -50,7 +50,7 @@
</template>
</tm-input>
</template>
<tm-text color="#999999">精致美观的对话框,可全局配置风格</tm-text>
<tm-text color="#999999"></tm-text>
</tm-drawer>
</tm-form-item> -->
@@ -88,18 +88,22 @@
</tm-drawer>
<tm-modal
v-model:show="modalShow"
v-model:show="confirmModalShow"
:overlayClick="false"
title="提示"
title="确定提交吗?"
position="bottom"
height="auto"
:showCancel="true"
cancelText="取消"
confirmText="确定"
@confirm="confirmSubmit"
@cancel="modalShow = false"
@cancel="confirmModalShow = false"
>
<view class="text-center"><tm-text font-size="30" color="#999999">确认提交吗?</tm-text></view>
<view class="text-center">
<tm-text font-size="30" color="#999999">提交后系统将会向手机号</tm-text>
<tm-text font-size="30" color="#0779FD">{{ form.mobile || "" }}</tm-text>
<tm-text font-size="30" color="#999999">发送邀约短信</tm-text>
</view>
</tm-modal>
<template #main-bottom>
@@ -120,9 +124,17 @@
<script setup lang="ts">
import { reactive, ref } from "vue"
import { invitationApi } from "@/sheep/api/index"
import TimeRangePicker from "@/sheep/components/TimePointPicker.vue"
import TimeRangePicker from "@/sheep/components/time-range-picker.vue"
const form = reactive<any>({
interface FormData {
mobile: string
vStartDate: string
vEndDate: string
isOtherPeople: boolean | string
isDrive: boolean | string
}
const form = reactive<FormData>({
// name: "",
mobile: "",
// companyName: "",
@@ -134,8 +146,8 @@ const form = reactive<any>({
isDrive: true,
})
const showDatePicker = ref(false)
const curSelectedInputName = ref("")
const modalShow = ref(false)
const curDateType = ref("")
const confirmModalShow = ref(false)
// const visitorReasonList = ref<{ title: string; id: string }[]>([])
// onLoad(() => {
@@ -169,20 +181,20 @@ const rules: Record<string, TM.FORM_RULE[]> = {
}
// 开始日期选择
const startDatePicker = (key: string) => {
const startDatePicker = (dateType: string) => {
showDatePicker.value = true
curSelectedInputName.value = key
curDateType.value = dateType
}
// 离开日期选择
const endDatePicker = (key: string) => {
const endDatePicker = (dateType: string) => {
showDatePicker.value = true
curSelectedInputName.value = key
curDateType.value = dateType
}
// 日期选择器确认
const onDatePickerConfirm = (dateValue: any, timeValue: string) => {
form[curSelectedInputName.value] = `${dateValue.fullDate} ${timeValue}`
form[curDateType.value] = `${dateValue.fullDate} ${timeValue}`
showDatePicker.value = false
}
@@ -190,7 +202,7 @@ const onDatePickerConfirm = (dateValue: any, timeValue: string) => {
const onsubmit = (evt: TM.FORM_SUBMIT_RESULT) => {
// console.log("校验结果:", evt)
if (!evt.isPass) return
modalShow.value = true
confirmModalShow.value = true
}
// 确认提交
@@ -210,15 +222,12 @@ const confirmSubmit = async () => {
form.mobile = form.mobile.trim().replace(/\s/g, "")
try {
const { code, data, msg } = await invitationApi.saveInvitation(form)
if (code == 0) {
uni.showToast({ title: "提交成功", icon: "success" })
resetForm()
modalShow.value = false
uni.navigateTo({ url: "/pages/integrated/visitor/invitationRecord" })
return
}
uni.showToast({ title: msg || "提交失败", icon: "none" })
const { code, msg } = await invitationApi.saveInvitation(form)
if (code != 0) return uni.showToast({ title: msg || "提交失败", icon: "error" })
uni.showToast({ title: "提交成功", icon: "success" })
resetForm()
confirmModalShow.value = false
uni.navigateTo({ url: "/pages/integrated/visitor/invitationRecord" })
} catch (error: any) {
uni.showToast({ title: error.message || "提交失败", icon: "none" })
}
@@ -230,8 +239,8 @@ const resetForm = () => {
// name: "",
mobile: "",
// companyName: "",
vStartDate: "",
vEndDate: "",
vstartDate: "",
vendDate: "",
// reasonValue: "",
// reasonText: "",
isOtherPeople: true,
@@ -54,26 +54,21 @@
</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 justify-between items-center">
<view v-if="v.checkStatus == '1'" class="p-1 c-white! sub-text-26 rounded-1 bg-#f5a623">待接受</view>
<view v-if="v.checkStatus == '2'" class="p-1 c-white! sub-text-26 rounded-1 bg-#2db7f5">待到访</view>
<view v-if="v.checkStatus == '3'" class="p-1 c-white! sub-text-26 rounded-1 bg-#52c41a">已拒绝</view>
<view v-if="v.checkStatus == '5'" class="p-1 c-white! sub-text-26 rounded-1 bg-#f5222d">已取消</view>
<view v-if="v.checkStatus == '1'" class="p-1 c-white! sub-text-22 rounded-1 bg-#f5a623">待接受</view>
<view v-if="v.checkStatus == '2'" class="p-1 c-white! sub-text-22 rounded-1 bg-#2db7f5">待到访</view>
<view v-if="v.checkStatus == '3'" class="p-1 c-white! sub-text-22 rounded-1 bg-#52c41a">已拒绝</view>
<view v-if="v.checkStatus == '5'" class="p-1 c-white! sub-text-22 rounded-1 bg-#f5222d">已取消</view>
<view class="sub-text-26">{{ v.vstartDate + " ~ " + v.vendDate }}</view>
</view>
<view class="flex-col my-1 px-4 py-2 bg-#ECF4F9 rounded-4">
<view class="flex justify-start sub-text-30 whitespace-nowrap">
<view>{{ v.name }}</view>
<view class="second-text-28 ml-2">{{ v.mobile }}</view>
</view>
<view class="flex justify-center sub-text-28 font-italic whitespace-nowrap my-2">
<span v-if="v.reasonValue">拜访原因:{{ visitorReasonList.find((item) => item.id == v.reasonValue)?.title || "" }}</span>
</view>
<!-- <view class="flex justify-end second-text-28 whitespace-nowrap">
<span v-if="state.params.type == 1">接待人:{{ v.userName }}</span>
</view> -->
<view class="m-2 h-1rpx bg-#A0A0A044"></view>
<view class="flex justify-between sub-text-26 whitespace-nowrap">
<!-- <view>{{ v.name }}</view> -->
<view>访客手机号:{{ v.mobile }}</view>
<!-- <view v-if="v.reasonValue">拜访原因:{{ visitorReasonList.find((item) => item.id == v.reasonValue)?.title || "" }}</view> -->
<tm-button class="px-1" size="xs" @click.stop="goDetail(v)">查看详情</tm-button>
</view>
<view class="flex justify-end items-center gap-2">
<!-- <view v-if="v.checkStatus == 1" class="flex items-center gap-2">
<!-- <view class="flex justify-end items-center gap-2"> -->
<!-- <view v-if="v.checkStatus == 1" class="flex items-center gap-2">
<tm-modal title="确定审核通过吗?" height="auto" @confirm="visitorCheck(v.id, 2)">
<template v-slot:trigger><tm-button class="px-1" size="s">审核通过</tm-button></template>
</tm-modal>
@@ -81,8 +76,7 @@
<template v-slot:trigger><tm-button class="px-1" size="s" color="danger">审核拒绝</tm-button></template>
</tm-modal>
</view> -->
<tm-button v-if="v.checkStatus == '2'" class="px-1" size="s" color="success" @click.stop="goDetail(v)">查看详情</tm-button>
</view>
<!-- </view> -->
</view>
</z-paging>
@@ -102,7 +96,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
import { ref, reactive } from "vue"
import { onShow } from "@dcloudio/uni-app"
import { hooks, router } from "@@/index"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
@@ -123,30 +117,12 @@ const state = reactive<any>({
},
})
const pageRef = ref<any>(null)
const visitorReasonList = ref<{ title: string; id: string }[]>([])
onShow(async () => {
const isLogin = await hooks.useCheckLogin()
if (!isLogin) return
if (pageRef.value) {
pageRef.value.reload(false)
}
})
onMounted(() => {
getVisitorReasonList()
})
// 获取拜访原因列表
const getVisitorReasonList = async () => {
const { code, data } = await invitationApi.getDictListByType("1")
if (code == 0 && data) {
visitorReasonList.value = data.map((item: any) => ({ title: item.label, id: item.value }))
return
}
visitorReasonList.value = []
}
const doTab = (e: any) => {
const target = tabs[e]
state.params.checkStatus = target.value
+5 -5
View File
@@ -24,13 +24,13 @@
</view>
<view class="flex-col-center m-b-60rpx">
<text class="text-90 c-#333333!">¥{{ payData.customPrice }}</text>
<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">{{ payData.name }}</text>
<text class="sub-text-30">{{ scanPayData.mchName }}</text>
</view>
<hr />
<view class="flex-row justify-between m-15rpx">
@@ -50,11 +50,11 @@ import { ref } from "vue"
import { onShow } from "@dcloudio/uni-app"
import { usePayStore } from "@/sheep/stores/pay"
const payData = ref<{ id?: string; name?: string; customPrice?: number }>({})
const scanPayData = ref<any>({})
onShow(() => {
if (!usePayStore().payData.id) return uni.showToast({ title: "支付信息不存在", icon: "none" })
payData.value = usePayStore().payData
if (!usePayStore().scanPayData.mchId) return uni.showToast({ title: "支付信息不存在", icon: "none" })
scanPayData.value = usePayStore().scanPayData
})
const goBack = () => {
+102 -75
View File
@@ -1,36 +1,36 @@
<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">{{ scanCodeData.name }}</view>
<view class="mx-auto text-34 py-2 h-80rpx">{{ scanPayState.mchName }}</view>
<tm-sheet class="flex flex-row">
<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" class="flex flex-col">
<view class="text-gray-700 font-bold text-sm mb-1">选择支付钱包:</view>
<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="card relative border-4 border-solid my-1 duration-200 border-#efefef flex justify-between items-center p-3"
:class="{ 'border-4 border-solid border-blue!': curWalletId == v.id }"
@click="curWalletId = v.id"
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="main-text-32">{{ v.walletTypeName }}</view>
<view class="text-32">{{ v.walletTypeName }}</view>
</view>
<view class="main-text-28 flex items-center">
<view>余额:{{ hooks.useMoney(v.balance) }}</view>
<text>元</text>
<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="disabled ? '' : handlePay()" :disabled="disabled">支 付</tm-button>
<tm-button class="mt-1" :block="true" @click="handlePay()" :disabled="loading">支 付</tm-button>
<numberKeyboard v-model="amount" />
<number-keyboard v-if="!scanPayState.action" v-model="amount" />
</view>
</crud-layout>
</template>
@@ -39,79 +39,96 @@
import { ref, computed, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import payApi from "@@/api/pay"
import { merchantApi, walletApi } from "@/sheep/api"
import { merchantApi, orderApi, walletApi } from "@/sheep/api"
import { hooks } from "@@/index"
import { router } from "@/sheep"
import { filePath } from "@/sheep/utils/file"
import { storePrice } from "@/sheep/utils/formatMoney"
import { displayPrice, storePrice } from "@/sheep/utils/formatMoney"
import { usePayStore } from "@/sheep/stores/pay"
import { isEmpty } from "radash"
import numberKeyboard from "@/sheep/components/numberKeyboard.vue"
import NumberKeyboard from "@/sheep/components/number-keyboard.vue"
const scanCodeData = reactive({
id: "",
name: "",
const loading = ref(false)
const scanPayState = reactive({
mchId: "", // 商户id
mchName: "", // 商户名
orderId: "", // 订单id
action: "",
})
const walletList = ref<any>([])
const curWalletId = ref("")
const disabled = ref(false)
const amount = ref("")
const walletList = ref<any>([]) // 钱包列表
const selectWallet = ref<any>({}) // 当前选中的钱包
const orderInfo = ref<any>({}) // 订单信息
const amount = ref("") // 键盘输入的金额
onLoad((options: any) => {
if (!isEmpty(options.id)) {
scanCodeData.id = options.id
} else {
uni.showToast({ title: "参数不存在", icon: "none" })
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
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 () => {
uni.showLoading({ title: "加载中...", mask: true })
if (scanCodeData.id) {
const { data } = await merchantApi.detail({ id: scanCodeData.id })
if (data) {
scanCodeData.name = data.name
disabled.value = false
} else {
disabled.value = true
return uni.showToast({ title: "商户id错误", icon: "none" })
}
} else {
uni.showToast({ title: "参数不存在", icon: "none" })
disabled.value = true
try {
await getWalletList()
} finally {
uni.hideLoading()
}
const res = await walletApi.all()
if (res.code == 0) {
walletList.value = res.data.records
if (walletList.value.length > 0) {
curWalletId.value = walletList.value.find((v: any) => v.walletTypeUniqueCode == "1").id
}
}
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()
// .toFixed(2)
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
)
return amount.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
})
// 点击支付
const handlePay = () => {
if (!scanCodeData.id || !scanCodeData.name) return uni.showToast({ title: "扫码参数不存在", icon: "none" })
if (!amount.value) {
uni.showToast({ title: "请输入金额", icon: "none" })
return
}
if (Number(amount.value) === 0) {
uni.showToast({ title: "金额不能为0", icon: "none" })
return
}
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) => {
@@ -126,18 +143,28 @@ const handlePay = () => {
const pay = async () => {
try {
uni.showLoading({ title: "支付中...", mask: true })
const res = await payApi.createQrCodeOrder({
merchantId: scanCodeData.id,
customPrice: storePrice(Number(amount.value)),
walletId: curWalletId.value,
})
if (res.code != 0) {
uni.showToast({ title: res.msg || "支付失败", icon: "error" })
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 {
uni.hideLoading()
usePayStore().setPayData({ id: scanCodeData.id, name: scanCodeData.name, customPrice: Number(amount.value).toFixed(2) })
router.jump("/pages/pay/payComplete", {}, 2)
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()
+1 -1
View File
@@ -68,7 +68,7 @@
<script setup lang="ts">
import { ref, computed, reactive } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import NumberKeyboard from "@/sheep/components/numberKeyboard.vue"
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"
+4 -2
View File
@@ -96,13 +96,15 @@ const toDetail = async (v: any) => {
<view class="sub-text-34">{{ v.name }}</view>
<view class="m-t-10rpx">
<view class="flex justify-between items-center">
<view>
<view class="flex-1">
<view v-if="v.phone" class="flex items-center">
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
<view class="second-text-28 ml-1">{{ v.phone }}</view>
</view>
</view>
<tm-tag size="xs">{{ v.deptName.length > 5 ? v.deptName.substring(0, 5) + "..." : v.deptName }}</tm-tag>
<tm-tag class="max-w-160rpx" size="xs">
{{ v.deptName.length > 5 ? v.deptName.substring(0, 5) + "..." : v.deptName }}
</tm-tag>
</view>
<view class="second-text-24">
<view v-if="v.jobTime != ',,'" class="second-text-28">营业时间:</view>
+24 -13
View File
@@ -24,19 +24,24 @@
<view class="sub-text-34">{{ state.detail.name }}</view>
<view class="m-t-10rpx">
<view class="flex justify-between items-center">
<view>
<view v-if="state.detail.phone" class="flex items-center">
<view class="flex-1">
<view
v-if="state.detail.phone"
v-for="(item, index) in state.detail.phone.split('/')"
:key="index"
class="flex items-center"
>
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
<view class="second-text-28 ml-1">{{ state.detail.phone?.slice(0, 11) }}</view>
<view class="second-text-28 ml-1">{{ item.trim() }}</view>
<view
class="second-text-20 bg-#f8f8f8 ml-1 p-0.3 flex items-center rounded-1 border border-solid border-b-coolGray"
@tap="doPhone"
@tap="doPhone(item)"
>
联系商家
</view>
</view>
</view>
<tm-tag size="xs">
<tm-tag class="max-w-160rpx" size="xs">
{{ state.detail.deptName?.length > 5 ? state.detail.deptName.substring(0, 5) + "..." : state.detail.deptName }}
</tm-tag>
</view>
@@ -132,13 +137,15 @@
import { reactive, nextTick } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { router } from "@@/index"
import { hooks, router } from "@@/index"
import { foodApi, merchantApi, payApi, shopApi } 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"
import ProductSku from "./components/product-sku.vue"
import { getAppInfo } from "@/sheep/utils/app"
import { APPENV } from "@/sheep/constant"
// 商品详情相关数据
const state = reactive<any>({
@@ -331,15 +338,19 @@ const submitPay = async () => {
}
// 联系商家
const doPhone = () => {
if (isEmpty(state.detail.phone)) {
uni.showToast({ title: "该商户没有预留电话", icon: "none" })
const doPhone = async (item: string) => {
if (isEmpty(item)) return uni.showToast({ title: "该商户没有预留电话", icon: "none" })
if ((await getAppInfo()) == APPENV.ZZD) {
;(window as any).dd
.callPhone({ phoneNum: item.trim() })
.then((res) => {})
.catch((err) => {})
return
}
if ((await getAppInfo()) == APPENV.SH) {
hooks.useMakePhoneCall(item.trim())
return
}
;(window as any).dd
.callPhone({ phoneNum: state.detail.phone.slice(0, 11) })
.then((res) => {})
.catch((err) => {})
}
</script>
+82 -68
View File
@@ -1,83 +1,97 @@
<template>
<crud-layout title="详情" mainIsScroll bg="/static/page/blue-bg.png">
<tm-sheet :margin="[0, 20]">
<view class="prefix_title mb-15rpx">菜品分类购买数量占比图</view>
<view class="w-full h-600rpx">
<qiun-data-charts type="pie" :opts="pieChartOpts" :chartData="pieChartData" tooltipFormat="canteenPieTooltip" />
</view>
</tm-sheet>
<tm-sheet :margin="[0, 20]">
<view class="prefix_title mb-15rpx">近一周各食堂购买数量趋势图</view>
<view class="w-full h-600rpx">
<!-- tooltipFormat="canteenAreaTooltip" -->
<qiun-data-charts type="area" :opts="areaChartOpts" :chartData="areaChartData" />
</view>
</tm-sheet>
<tm-sheet :margin="[0, 20]">
<view class="prefix_title mb-15rpx">个人菜品喜好排行</view>
<view v-if="productLikeTopList.length > 0" class="rounded-16rpx bg-white overflow-hidden card">
<view class="bg-gradient-to-r from-blue-100 to-blue-100 px-20rpx py-16rpx flex items-center border-b border-gray-100">
<view class="w-80rpx text-center text-24rpx font-bold text-gray-600">排名</view>
<view class="flex-1 px-16rpx text-24rpx font-bold text-gray-600">菜品名称</view>
<view class="w-120rpx text-center text-24rpx font-bold text-gray-600">分类</view>
<view class="w-160rpx text-center text-24rpx font-bold text-gray-600">购买数量</view>
<crud-layout title="喜好分析" bg="/static/page/blue-bg.png">
<z-paging
ref="pageRef"
height="100%"
:fixed="false"
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"
hide-empty-view
>
<tm-sheet :margin="[0, 20]">
<view class="prefix_title mb-15rpx">菜品分类购买数量占比图</view>
<view class="w-full h-600rpx">
<qiun-data-charts type="pie" :opts="pieChartOpts" :chartData="pieChartData" tooltipFormat="canteenPieTooltip" />
</view>
</tm-sheet>
<view
v-for="(item, index) in productLikeTopList"
:key="item.productId"
class="flex items-center px-20rpx py-16rpx border-b border-gray-50 hover:bg-gray-50 transition-colors"
:class="{
'bg-gradient-to-r from-yellow-50 to-orange-50': index === 0,
'bg-gradient-to-r from-gray-50 to-gray-100': index === 1,
'bg-gradient-to-r from-orange-50 to-red-50': index === 2,
}"
>
<view class="w-80rpx flex justify-center items-center">
<view
v-if="index < 3"
class="w-40rpx h-40rpx rounded-1 flex items-center justify-center text-20rpx font-bold text-white"
:class="{
'bg-gradient-to-r from-yellow-400 to-yellow-500': index === 0,
'bg-gradient-to-r from-gray-400 to-gray-500': index === 1,
'bg-gradient-to-r from-orange-400 to-orange-500': index === 2,
}"
>
{{ index + 1 }}
<tm-sheet :margin="[0, 20]">
<view class="prefix_title mb-15rpx">近一周各食堂购买数量趋势图</view>
<view class="w-full h-600rpx">
<!-- tooltipFormat="canteenAreaTooltip" -->
<qiun-data-charts type="area" :opts="areaChartOpts" :chartData="areaChartData" />
</view>
</tm-sheet>
<tm-sheet :margin="[0, 20]">
<view class="prefix_title mb-15rpx">个人菜品喜好排行</view>
<view v-if="productLikeTopList.length > 0" class="rounded-16rpx bg-white overflow-hidden card">
<view class="bg-gradient-to-r from-blue-100 to-blue-100 px-20rpx py-16rpx flex items-center border-b border-gray-100">
<view class="w-80rpx text-center text-24rpx font-bold text-gray-600">排名</view>
<view class="flex-1 px-16rpx text-24rpx font-bold text-gray-600">菜品名称</view>
<view class="w-120rpx text-center text-24rpx font-bold text-gray-600">分类</view>
<view class="w-160rpx text-center text-24rpx font-bold text-gray-600">购买数量</view>
</view>
<view
v-for="(item, index) in productLikeTopList"
:key="item.productId"
class="flex items-center px-20rpx py-16rpx border-b border-gray-50 hover:bg-gray-50 transition-colors"
:class="{
'bg-gradient-to-r from-yellow-50 to-orange-50': index === 0,
'bg-gradient-to-r from-gray-50 to-gray-100': index === 1,
'bg-gradient-to-r from-orange-50 to-red-50': index === 2,
}"
>
<view class="w-80rpx flex justify-center items-center">
<view
v-if="index < 3"
class="w-40rpx h-40rpx rounded-1 flex items-center justify-center text-20rpx font-bold text-white"
:class="{
'bg-gradient-to-r from-yellow-400 to-yellow-500': index === 0,
'bg-gradient-to-r from-gray-400 to-gray-500': index === 1,
'bg-gradient-to-r from-orange-400 to-orange-500': index === 2,
}"
>
{{ index + 1 }}
</view>
<view v-else class="text-24rpx font-medium text-gray-500">{{ index + 1 }}</view>
</view>
<view v-else class="text-24rpx font-medium text-gray-500">{{ index + 1 }}</view>
</view>
<view class="flex-1 px-16rpx">
<view class="text-26rpx font-medium text-gray-800 truncate">{{ item.productName || "未知菜品" }}</view>
</view>
<view class="flex-1 px-16rpx">
<view class="text-26rpx font-medium text-gray-800 truncate">{{ item.productName || "未知菜品" }}</view>
</view>
<view class="w-120rpx text-center">
<view class="text-22rpx text-gray-600 truncate">{{ item.classifyName || "未分类" }}</view>
</view>
<view class="w-120rpx text-center">
<view class="text-22rpx text-gray-600 truncate">{{ item.classifyName || "未分类" }}</view>
</view>
<view class="w-160rpx text-center">
<view class="px-12rpx py-6rpx bg-blue-100 text-blue-600 text-22rpx rounded-1 whitespace-nowrap">{{ item.payCount }}次</view>
<view class="w-160rpx text-center">
<view class="px-12rpx py-6rpx bg-blue-100 text-blue-600 text-22rpx rounded-1 whitespace-nowrap">{{ item.payCount }}次</view>
</view>
</view>
</view>
</view>
<view v-else class="bg-white rounded-16rpx p-60rpx flex flex-col items-center justify-center">
<view class="text-28rpx text-gray-500">暂无菜品喜好数据</view>
</view>
</tm-sheet>
<view v-else class="bg-white rounded-16rpx p-60rpx flex flex-col items-center justify-center">
<view class="text-28rpx text-gray-500">暂无菜品喜好数据</view>
</view>
</tm-sheet>
<view class="h-80rpx" />
<view class="h-80rpx" />
</z-paging>
</crud-layout>
</template>
<script setup lang="ts">
import { onLoad, onShow } from "@dcloudio/uni-app"
import { onLoad } from "@dcloudio/uni-app"
import { ref } from "vue"
import qiunDataCharts from "@/components/qiun-data-charts/qiun-data-charts.vue"
import { totalApi } from "@/sheep/api"
import { hooks } from "@/sheep"
const pieChartData = ref({})
const areaChartData = ref({})
@@ -152,16 +166,16 @@ const areaChartOpts = {
onLoad(() => {
const app = getApp()
app._initTabbar()
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, () => [], null, "")
const query = () => {
getPieChartData()
getAreaChartData()
getProductLikeTop()
})
onShow(() => {
getPieChartData()
getAreaChartData()
getProductLikeTop()
})
pageHook.startPage()
}
const getPieChartData = async () => {
const { code, data, msg } = await totalApi.productByClassify({})
+28 -4
View File
@@ -11,6 +11,7 @@ const state = reactive<any>({
pa: {
pagesize: 10,
type: "2",
name: "",
catgoryId: null,
},
tabs: [{ id: null, name: "全部", disabled: false }],
@@ -40,6 +41,10 @@ onShow(() => {
pageRef.value && pageRef.value.reload(false)
})
const search = () => {
pageRef.value && pageRef.value.reload(true)
}
const doTab = (e: any) => {
const target = state.tabs[e]
state.pa.catgoryId = target.id
@@ -74,12 +79,31 @@ const toDetail = async (v: any) => {
>
<template #top>
<view class="h-30rpx" />
<view class="bg-white rounded-16rpx px-20rpx">
<view class="flex w-full">
<tm-input
class="flex-1"
v-model="state.pa.name"
type="text"
height="66"
minHeight="66"
fontSize="28"
:showClear="true"
:focus="false"
placeholder="请输入商家名称"
style="border: 2rpx solid #0779fd; border-radius: 14rpx"
:focus-highlight="false"
color="#ECF4F9"
>
<template v-slot:rightIcon>
<tm-button class="mr-1 py-3" size="xs" @click="search">搜索</tm-button>
</template>
</tm-input>
</view>
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
<z-tabs :list="state.tabs" @change="doTab" />
</view>
</template>
<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 my-auto" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
@@ -89,13 +113,13 @@ const toDetail = async (v: any) => {
</view>
<view class="m-t-10rpx">
<view class="flex justify-between items-center">
<view>
<view class="flex-1">
<view v-if="v.phone" class="flex items-center">
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
<view class="second-text-28 ml-1">{{ v.phone }}</view>
</view>
</view>
<tm-tag size="xs">{{ v.deptName.length > 5 ? v.deptName.substring(0, 5) + "..." : v.deptName }}</tm-tag>
<tm-tag class="max-w-160rpx" size="xs">{{ v.deptName.length > 5 ? v.deptName.substring(0, 5) + "..." : v.deptName }}</tm-tag>
</view>
<view class="second-text-24">
<view v-if="v.jobTime != ',,'" class="second-text-28">营业时间:</view>
+3 -2
View File
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { APPENV } from "@/sheep/constant"
import { getAppInfo, getLaunchParam, isDevEnv } from "@/sheep/utils/app"
import { onShow } from "@dcloudio/uni-app"
@@ -57,7 +58,7 @@ const jumpByParam = async () => {
const launchParam = getLaunchParam() // 获取启动参数
// 浙政钉环境
if (appInfo == "zzd") {
if (appInfo == APPENV.ZZD) {
switch (launchParam) {
case "canteen": // 食堂
uni.setStorageSync("launchParam", launchParam)
@@ -87,7 +88,7 @@ const jumpByParam = async () => {
}
// 诗画环境
if (appInfo == "sh") {
if (appInfo == APPENV.SH) {
const userRole = uni.getStorageSync("userRole") // 获取用户角色
if (!launchParam) return uni.hideTabBar()
+4 -1
View File
@@ -27,7 +27,10 @@
:quickDate="[]"
drawerSize="900"
>
<view class="w-100rpx text-center second-text-28 text-30 mb-4rpx">筛选</view>
<view class="mb-6rpx flex items-center gap-1">
<image class="w-[30rpx] h-[30rpx]" src="@/static/page/filter.svg" />
<view class="text-center second-text-28 text-30 whitespace-nowrap">筛选</view>
</view>
</tm-between-time>
</view>
</template>
+15 -13
View File
@@ -3,13 +3,10 @@
<tm-sticky>
<tm-sticky-header>
<view class="flex-row items-center bg-#ECF4F9 py-3">
<image
v-if="state.userInfo?.sysUser?.avatar"
class="w-100rpx h-100rpx rounded-full"
:src="filePath(state.userInfo.sysUser.avatar)"
mode="aspectFill"
/>
<image v-else class="w-100rpx h-100rpx rounded-full" src="/static/icon/default-avatar.png" mode="aspectFill" />
<image v-if="state.userInfo?.sysUser?.avatar" class="w-100rpx h-100rpx rounded-full"
:src="filePath(state.userInfo.sysUser.avatar)" mode="aspectFill" />
<image v-else class="w-100rpx h-100rpx rounded-full" src="/static/icon/default-avatar.png"
mode="aspectFill" />
<view class="m-l-30rpx" v-if="state.userInfo?.sysUser?.userId">
<view class="flex-row items-center">
<text class="sub-text-30">{{ state.userInfo?.sysUser?.name }}</text>
@@ -25,7 +22,8 @@
</tm-sticky-header>
<view class="m-t-10rpx rounded-20rpx shadow-main p-30rpx p-b-40rpx bg-white!">
<view class="service_item flex-row items-center justify-between" v-for="(v, i) in state.menuList" :key="i" @tap="onTabMenu(v)">
<view class="service_item flex-row items-center justify-between" v-for="(v, i) in state.menuList"
:key="i" @tap="onTabMenu(v)">
<view class="flex-row-center">
<image class="w-40rpx h-40rpx" :src="v.icon" mode="aspectFill" />
<text class="sub-text-28 m-l-20rpx">{{ v.text }}</text>
@@ -35,13 +33,15 @@
</view>
<view v-if="isDevEnv()" class="flex-col-center m-t-40rpx m-x-30rpx">
<view class="w-full text-center sub-text-28 p-y-20rpx rounded-40rpx border-solid border border-#0094ff" @tap="doLogout">
<view class="w-full text-center sub-text-28 p-y-20rpx rounded-40rpx border-solid border border-#0094ff"
@tap="doLogout">
退出登录
</view>
</view>
<view class="h-30rpx" />
<view v-if="state.appInfo == 'sh'" class="flex-col-center m-t-40rpx m-x-30rpx">
<view class="w-full text-center sub-text-28 p-y-20rpx rounded-40rpx border-solid border border-#0094ff" @tap="doChangeRole">
<view v-if="state.appInfo == APPENV.SH" class="flex-col-center m-t-40rpx m-x-30rpx">
<view class="w-full text-center sub-text-28 p-y-20rpx rounded-40rpx border-solid border border-#0094ff"
@tap="doChangeRole">
切换角色
</view>
</view>
@@ -68,6 +68,7 @@ import { getAppInfo } from "@@/utils/app"
import { hooks, router, constant } from "@@/index"
import { userApi } from "@@/api"
import { isDevEnv } from "@/sheep/utils/app"
import { APPENV } from "@/sheep/constant"
const state = reactive<{
wallet: any[]
@@ -85,6 +86,7 @@ const state = reactive<{
{ text: "扫一扫", icon: "/static/icon/scan.png", router: "" },
{ text: "钱包管理", icon: "/static/icon/service_3.png", router: "/pages/wallet/list" },
{ text: "人脸管理", icon: "/static/icon/service_5.png", router: "/pages/human-face/list" },
// { text: "车辆管理", icon: "/static/icon/service_6.png", router: "/pages/car/userCar/list" },
{ text: "清除缓存", icon: "/static/icon/clear-storage.png", router: "" },
],
appInfo: "",
@@ -300,11 +302,11 @@ const scanCode = {
uni.showModal({ content: "开发环境不是浙政钉和诗画环境,扫码仅供测试,收款商家为测试商户超市" })
return testQrCode
} else if (state.appInfo == "zzd") {
} else if (state.appInfo == APPENV.ZZD) {
// 浙政钉扫码
const res: any = await (window as any).dd.scan({ type: "all" })
return res.text
} else if (state.appInfo == "sh") {
} else if (state.appInfo == APPENV.SH) {
// 诗画扫码
const res = await (window as any).CP2.showScanQr({ needResult: 1 })
if (res.code == 1) return res.data.qrCode
+19 -19
View File
@@ -29,39 +29,39 @@
class="flex-1"
v-model="state.params.keyword"
type="text"
:height="66"
:minHeight="66"
:fontSize="28"
height="72"
minHeight="72"
fontSize="28"
:showClear="true"
:focus="false"
placeholder="请输入商品名称"
style="border: 2rpx solid #0779fd; border-radius: 14rpx"
style="border: 4rpx solid #0779fd; border-radius: 14rpx"
:focus-highlight="false"
color="#ECF4F9"
>
<template v-slot:rightIcon>
<tm-button class="mr-1 py-3" size="xs" @click="searchProduct">搜索</tm-button>
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="searchProduct">搜 索</tm-button>
</template>
</tm-input>
<tm-badge bg-color="red" :offset="[8, 4]" :label="cartStore.totCount">
<view class="flex-col-center size-80rpx" @tap="doCart">
<view class="color-#0094ff! iconfont icon-biaoqianlanicon_gouwuche-xuanzhong" />
<view class="c-#0094ff text-20rpx">购物车</view>
<view class="c-#0094ff text-22rpx">购物车</view>
</view>
</tm-badge>
</view>
<scroll-view scroll-x class="whitespace-nowrap my-2">
<tm-tag
class="mr-2"
size="s"
:skin="state.params.categoryId == v.id ? 'normal' : 'outlined'"
v-for="(v, i) in state.classifyList"
:key="i"
@tap="doClassify(v.id)"
>
{{ v.name }}
</tm-tag>
<scroll-view scroll-x>
<view class="w-full flex my-2">
<view
class="mr-2 sub-text-28 py-1 px-2 rounded-1 bg-#fff border border-solid border-transparent"
:class="{ 'bg-#0779FD22! c-#0779FD! border-#0779FD33!': state.params.categoryId === v.id }"
v-for="(v, i) in state.classifyList"
:key="i"
@tap="doClassify(v.id)"
>
{{ v.name }}
</view>
</view>
</scroll-view>
</template>
@@ -76,7 +76,7 @@
:hasAroundGutter="false"
backgroundColor="#ffffff00"
:crossOrigin="false"
:animationCancel="true"
:animationCancel="false"
>
<template #default="{ item, url, index }">
<view class="card" @click="toDetail(item)">
+30 -28
View File
@@ -93,61 +93,63 @@ const doRefund = (v: any) => {
@scroll="pageHook.scroll"
>
<template #top>
<view class="h-30rpx" />
<view class="bg-white rounded-16rpx px-20rpx">
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
<z-tabs :list="data.tabs" @change="doTab" />
</view>
</template>
<view class="h-20rpx" />
<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 items-center">
<image class="w-110rpx h-110rpx rounded-8rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 m-l-20rpx">
<view class="flex-row">
<image class="w-80rpx h-80rpx rounded-8rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 ml-20rpx">
<view class="flex-row items-center justify-between">
<view class="flex-row items-center">
<view class="main-text-28">{{ v.merchantName }}</view>
<!-- <view class="m-l-15rpx color-#999 font-size-26rpx! iconfont icon-youjiantou" /> -->
</view>
<view class="main-text-28">{{ 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="xs">未支付</tm-tag>
<tm-tag v-if="v.orderStatus == 5" color="success" size="xs">已支付</tm-tag>
<tm-tag v-if="v.orderStatus == 10" color="error" size="xs">售后</tm-tag>
<tm-tag v-if="v.orderStatus == 20" size="xs">已核销</tm-tag>
<tm-tag v-if="v.orderStatus == 30" color="info" size="xs">已取消(智能柜)</tm-tag>
</view>
<view class="flex-row items-center justify-between m-t-5rpx">
<view class="flex-row items-center justify-between mt-5rpx">
<view class="second-text-20">
<text>下单时间</text>
<text class="m-l-10rpx">{{ hooks.useTime(v.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</text>
</view>
<view class="second-text-22">{{ hooks.useEnum("hardwareRefundStatusEnum", v.refundStatus) }}</view>
</view>
<view class="flex-row items-center justify-between m-t-5rpx">
<view class="sub-text-24">
<view class="flex-row items-center justify-end mt-5rpx">
<!-- <view class="sub-text-24">
<text v-if="v.payType == '9'">{{ v.productDesc?.replaceAll(";", "\;\n") }}</text>
</view>
</view> -->
<view class="main-text-30 whitespace-nowrap color-red! text-right">¥ {{ hooks.useMoney(v.totalPrice) }}</view>
</view>
</view>
</view>
<view class="flex items-center ml-[120rpx]">
<scroll-view v-if="['5', '20'].includes(v.orderStatus) && v.orderType == 2" scroll-x class="flex-row-center whitespace-nowrap">
<view class="flex-row items-center">
<view class="inline-flex! flex-col items-center m-r-20rpx" v-for="(pV, pI) in v.carList" :key="pI">
<image class="w-60rpx h-60rpx rounded-8rpx" :src="filePath(pV.productPic)" mode="aspectFill" />
<view class="w-80rpx m-t-10rpx sub-text-24 show-line text-center">{{ pV.productName }}</view>
<view class="flex items-center ml-[20rpx] overflow-hidden gap-2">
<view class="w-4/5">
<scroll-view v-if="['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)" scroll-x>
<view class="flex-row">
<view class="flex-col justify-start items-center mr-20rpx" v-for="(pV, pI) in v.carList" :key="pI">
<image v-if="v.payType == '9'" class="w-80rpx h-80rpx rounded-8rpx" :src="pV.productPic" mode="aspectFill" />
<image v-else class="w-80rpx h-80rpx rounded-8rpx" :src="filePath(pV.productPic)" mode="aspectFill" />
<view class="mt-10rpx sub-text-24 text-center min-w-120rpx max-w-200rpx">{{ pV.productName }}</view>
</view>
</view>
</view>
</scroll-view>
</scroll-view>
</view>
<view
v-if="v.carList?.length && ['5', '20'].includes(v.orderStatus) && v.orderType == 2"
class="w-200rpx m-x-10rpx second-text-26"
v-if="v.carList?.length && ['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)"
class="w-1/5 second-text-26 truncate text-center"
>
共 {{ v.carList.length }} 件
</view>
<view v-if="['5', '10'].includes(v.orderStatus)" class="w-500rpx flex-row items-center justify-end">
<!-- <view v-if="v.orderStatus == 5 && v.orderType == 2" class="botder_btn" @tap.stop="doRefund(v)">退款</view> -->
<view v-if="v.orderStatus == 5 && v.orderType == 2" class="m-l-20rpx botder_btn" @tap.stop="doToDetail(v)">核销码</view>
</view>
</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 && v.orderType == 2" class="m-l-20rpx botder_btn" @tap.stop="doToDetail(v)">核销码</view>
</view>
</view>
</z-paging>
+194
View File
@@ -0,0 +1,194 @@
<template>
<crud-layout 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"
>
<template #top>
<view class="flex justify-between items-center my-1">
<tm-text font-size="40" color="#0779FD" class="font-bold">共富商城</tm-text>
<tm-text font-size="30" color="#0779FD">优质商户,品质商品,欢迎联系购买</tm-text>
</view>
<view class="w-full flex items-center">
<tm-input
class="flex-1"
v-model="state.params.keyword"
type="text"
height="72"
minHeight="72"
fontSize="28"
:showClear="true"
:focus="false"
placeholder="请输入超市名称"
style="border: 4rpx solid #0779fd; border-radius: 14rpx"
:focus-highlight="false"
color="#ECF4F9"
>
<template v-slot:rightIcon>
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="search">搜 索</tm-button>
</template>
</tm-input>
</view>
<scroll-view scroll-x>
<view class="w-full flex my-2">
<view
class="mr-2 sub-text-28 py-1 px-2 rounded-1 bg-#fff border border-solid border-transparent"
:class="{ 'bg-#0779FD22! c-#0779FD! border-#0779FD33!': state.params.categoryId === v.id }"
v-for="(v, i) in state.classifyList"
:key="i"
@tap="doClassify(v.id)"
>
{{ v.name }}
</view>
</view>
</scroll-view>
</template>
<view class="card p-20rpx mb-20rpx" v-for="(v, i) in pageHook.list" :key="i" @tap="toDetail(v)">
<view class="flex">
<image class="w-150rpx h-150rpx rounded-12rpx" :src="filePath(v.images)" mode="aspectFill" />
<view class="ml-20rpx flex-1 overflow-hidden">
<view class="sub-text-30 truncate">{{ v.name }}</view>
<view class="second-text-26 truncate">
<text class="mr-20rpx">{{ v.contactName }}</text>
<text class="mr-20rpx">
<image class="w-24rpx h-24rpx mr-1" src="@/static/page/phone.svg" />
<text @tap="doPhone(v.contactPhone)">{{ v.contactPhone }}</text>
</text>
<text>{{ v.email }}</text>
</view>
<view class="sub-text-26 truncate">
<text class="bg-#0779FD11 py-1 rounded-2 c-#0094ff">营业:{{ v.businessHours }}</text>
</view>
<view class="second-text-26">
<image class="w-24rpx h-24rpx mr-1" src="@/static/page/address.svg" />
<text>{{ v.address }}</text>
</view>
</view>
</view>
<scroll-view scroll-x>
<view class="flex mt-2">
<view class="w-160rpx flex-col items-center mr-20rpx" v-for="(pV, pI) in v.featuredProducts" :key="pI">
<image class="w-160rpx h-160rpx rounded-12rpx" :src="filePath(pV.coverImage)" mode="aspectFill" />
<view class="w-full mt-4rpx sub-text-24 truncate">{{ pV.title }}</view>
<view class="w-full flex justify-between pr-1">
<text class="main-text-26 color-red!">
<text class="text-22rpx">¥</text>
<text>{{ pV.spuPrice.toFixed(2) }}</text>
</text>
<text class="sub-text-24">{{ pV.unit || "" }}</text>
</view>
<view class="w-full flex justify-between pr-1">
<text class="second-text-24 line-through">
<text class="text-22rpx">¥</text>
<text>{{ pV.originalPrice.toFixed(2) }}</text>
</text>
<text class="second-text-24">库存{{ pV.totalStock }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</z-paging>
</crud-layout>
</template>
<script setup lang="ts">
import { hooks, router } from "@/sheep"
import { onlineShopApi, prosperityShopApi } from "@/sheep/api"
import { onLoad } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { reactive, ref } from "vue"
import { filePath } from "@@/utils/file"
import { getAppInfo } from "@/sheep/utils/app"
import { APPENV } from "@/sheep/constant"
const state = reactive<any>({
params: {
categoryId: "",
keyword: "",
},
classifyList: [],
shopList: [],
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getShopList, null, "data")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.params)
}
onLoad(async () => {
const app = getApp()
app._initTabbar()
await getClassify()
})
// 获取分类
const getClassify = async () => {
const { data } = await onlineShopApi.getSpuCategory()
state.classifyList = treeToList(data) ?? []
state.classifyList.unshift({ id: "", name: "全部分类" })
if (!isEmpty(state.classifyList)) {
state.params.categoryId = state.classifyList[0].id
}
}
const treeToList = <T extends { children?: T[] }>(tree: T[], result: T[] = []): T[] => {
for (const node of tree) {
result.push(node)
if (node?.children?.length) {
treeToList(node.children, result)
}
}
return result
}
// 切换分类
const doClassify = async (categoryId: string) => {
if (state.params.categoryId == categoryId) return
state.params.categoryId = categoryId
pageRef.value && pageRef.value.reload(true)
}
// 点击搜索
const search = () => {
state.params.categoryId = ""
pageRef.value && pageRef.value.reload(true)
}
// 联系商家
const doPhone = async (item: string) => {
if (isEmpty(item)) return uni.showToast({ title: "该商户没有预留电话", icon: "none" })
if ((await getAppInfo()) == APPENV.ZZD) {
;(window as any).dd
.callPhone({ phoneNum: item.trim() })
.then((res) => {})
.catch((err) => {})
return
}
if ((await getAppInfo()) == APPENV.SH) {
hooks.useMakePhoneCall(item.trim())
return
}
}
// 超市详情
const toDetail = async (v: any) => {
const isLogin = await hooks.useCheckLogin()
if (isLogin) {
// router.jump("/pages/shop/detail", { id: v.id })
}
}
</script>
+20 -56
View File
@@ -29,7 +29,7 @@ const freezeForm = reactive<any>({
remark: "",
})
onLoad(() => {})
onLoad(() => { })
onShow(async () => {
const isLogin = await hooks.useCheckLogin()
if (!isLogin) return
@@ -140,35 +140,22 @@ const doTransferMoney = (v: any) => {
<template>
<crud-layout bg="/static/page/blue-bg.png">
<z-paging
ref="pageRef"
height="100%"
:fixed="false"
v-model="pageHook.list"
:default-page-size="5"
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"
>
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="5"
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-30rpx" />
<view
class="card flex-col rounded-20rpx p-x-30rpx p-y-20rpx m-b-30rpx"
v-for="(v, i) in pageHook.list"
:key="i"
@tap="router.jump('/pages/wallet/detail', { id: v.id })"
>
<view class="card flex-col rounded-20rpx p-x-30rpx p-y-20rpx m-b-30rpx" v-for="(v, i) in pageHook.list"
:key="i" @tap="router.jump('/pages/wallet/detail', { id: v.id })">
<view class="flex-col">
<view class="flex-row items-center justify-between">
<view class="flex-row-center">
<text v-if="v.cardNumber" class="second-text-28">卡号: {{ v.cardNumber }}</text>
</view>
<view class="second-text-50">
<text v-if="v.cardNumber" class="iconfont icon-fuzhi" @tap.stop="hooks.useCopy(v.cardNumber)" />
<text v-if="v.walletTypeUniqueCode != '2'" class="m-l-20rpx iconfont icon-erweima" @tap.stop="doPreviewQrCode(v)" />
<text v-if="v.cardNumber" class="iconfont icon-fuzhi"
@tap.stop="hooks.useCopy(v.cardNumber)" />
<text v-if="v.walletTypeUniqueCode != '2'" class="m-l-20rpx iconfont icon-erweima"
@tap.stop="doPreviewQrCode(v)" />
</view>
</view>
<view v-if="v.cardNumber" class="h-1px m-t-10rpx bg-#999 bg-opacity-5" />
@@ -191,14 +178,12 @@ const doTransferMoney = (v: any) => {
<view class="flex-row items-center justify-end space-x-20rpx m-t-10rpx">
<!-- <view v-if="v.walletTypeUniqueCode == 1 && !v.cardNumber" class="botder_btn" @tap.stop="openBindDialog(v)">绑定</view> -->
<view v-if="v.isRule == '1'" class="botder_btn" @tap.stop="doTransferMoney(v)">转账</view>
<view v-if="v.walletTypeUniqueCode == 1 && v.cardNumber && v.isFreeze != 1" class="botder_btn" @tap.stop="openFreezeDialog(v)">
<view v-if="v.walletTypeUniqueCode == 1 && v.cardNumber && v.isFreeze != 1" class="botder_btn"
@tap.stop="openFreezeDialog(v)">
申请冻结
</view>
<view
v-if="false && v.walletTypeUniqueCode == 2"
class="botder_btn"
@tap.stop="router.jump('/pages/wallet/recharge', { id: v.id })"
>
<view v-if="false && v.walletTypeUniqueCode == 2" class="botder_btn"
@tap.stop="router.jump('/pages/wallet/recharge', { id: v.id })">
去充值
</view>
</view>
@@ -206,15 +191,8 @@ const doTransferMoney = (v: any) => {
</z-paging>
</crud-layout>
<tm-drawer
z-index="100"
v-model:show="state.showBindDialog"
:overlayClick="false"
title="绑定饭卡"
:show-close="true"
position="bottom"
size="40%"
>
<tm-drawer z-index="100" v-model:show="state.showBindDialog" :overlayClick="false" title="绑定饭卡" :show-close="true"
position="bottom" size="40%">
<view class="h-50rpx" />
<tm-form ref="bindFormRef" v-model="bindForm" :rules="bindFormRules">
<tm-form-item name="cardNumber" :showLabel="false" :showBottom="false">
@@ -224,15 +202,8 @@ const doTransferMoney = (v: any) => {
<view class="h-50rpx" />
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doBind">绑定</view>
</tm-drawer>
<tm-drawer
z-index="100"
v-model:show="state.showFreezeDialog"
:overlayClick="false"
title="申请冻结"
:show-close="true"
position="bottom"
size="40%"
>
<tm-drawer z-index="100" v-model:show="state.showFreezeDialog" :overlayClick="false" title="申请冻结" :show-close="true"
position="bottom" size="40%">
<view class="h-50rpx" />
<tm-form ref="bindFormRef" v-model="freezeForm">
<tm-form-item name="remark" :showLabel="false" :showBottom="false">
@@ -242,15 +213,8 @@ const doTransferMoney = (v: any) => {
<view class="h-50rpx" />
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doFreeze">冻结</view>
</tm-drawer>
<tm-drawer
z-index="100"
v-model:show="state.showQrCodeDialog"
:overlayClick="false"
title="钱包二维码"
:show-close="true"
position="bottom"
size="50%"
>
<tm-drawer z-index="100" v-model:show="state.showQrCodeDialog" :overlayClick="false" title="钱包二维码"
:show-close="true" position="bottom" size="50%">
<view class="flex-col-center">
<view class="h-80rpx" />
<canvas canvas-id="qrcodeId" class="size-200px" />
+5 -1
View File
@@ -38,4 +38,8 @@ export { default as noticeApi } from "./notice"
export { default as invitationApi } from "./invitation"
export { default as totalApi } from "./total"
export { default as totalApi } from "./total"
export { default as userCarApi } from "./userCar"
export { default as prosperityShopApi } from "./prosperityShop"
+1 -1
View File
@@ -16,7 +16,7 @@ export default {
data,
}),
// 获取访客邀约列表
// 获取访客邀约记录列表
// type为1:主动申请 type为2:邀约 type为3:后台创建
getInvitationRecord: (params: { current: number; size: number; checkStatus?: string; type?: number }): Promise<any> =>
request({
+11
View File
@@ -33,4 +33,15 @@ export default {
url: "/hard/hard-appt/app/list",
method: "GET",
}),
/**
* 用户确认设备二维码扫一扫订单
* @param orderId 订单id
* @param walletTypeId 钱包类型ID
*/
doneHardQrCodeScanOrder: (data: any) =>
request({
url: "/hard/hard-api/ad/done-hard-qr-code-order",
method: "POST",
data,
}),
}
+12
View File
@@ -0,0 +1,12 @@
import request from "@/sheep/utils/request"
export default {
// 获取超市列表
getShopList: (params: { productLimit: number }): Promise<any> => {
return request({
url: "/supermarket/app/mall/merchant/list",
method: "GET",
params: { productLimit: 5 },
})
},
}
+22
View File
@@ -0,0 +1,22 @@
import request from "@/sheep/utils/request"
export default {
/**
* 申请车
*/
apply: (data: any) =>
request({
url: `/admin/sysUserCar/app/create`,
method: "POST",
data,
}),
/**
* 车列表
*/
page: (data: any) =>
request({
url: `/admin/sysUserCar/app/page`,
method: "POST",
data,
}),
}
@@ -36,7 +36,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed, onUnmounted } from "vue"
import { ref, reactive, watch, onUnmounted } from "vue"
import { userApi } from "@@/api"
const props = withDefaults(
@@ -1,5 +1,5 @@
import { h, render } from "vue"
import VerifyDeviceImpModel from "./verifyDeviceImpModel.vue"
import VerifyDeviceImpModel from "./verify-device-imp-model.vue"
let VerifyDeviceImpModelInstance: boolean | null = null
+7
View File
@@ -0,0 +1,7 @@
/**
* ZZD:浙政钉 | SH:诗画
*/
export default {
ZZD: "zzd",
SH: "sh",
}
+2
View File
@@ -1,3 +1,5 @@
export { default as serviceEnum } from "./service-enum"
export { default as storageConstant } from "./storage-constant"
export { default as APPENV } from "./app-env"
+8
View File
@@ -36,6 +36,13 @@ const hardwareRefundStatusEnum = {
30: "售后失败",
}
const carTypeEnum = {
1: "私家车",
2: "公车",
3: "军车",
4: "警车",
}
const boxesBxStatusEnum = {
"0": "今日空闲",
"1": "今日部分空闲",
@@ -49,4 +56,5 @@ export default {
boxesBxStatusEnum,
hardwareSaleStatusEnum,
hardwareRefundStatusEnum,
carTypeEnum,
}
+17 -4
View File
@@ -1,16 +1,29 @@
import { defineStore } from "pinia"
type State = {
scanPayData: {
mchId: string
mchName: string
payPrice: number | string
}
sourceWalletData: any
}
export const usePayStore = defineStore("pay", {
state: (): { payData: { id?: string; name?: string; customPrice?: number }; sourceWalletData: any } => {
state: (): State => {
return {
payData: {},
scanPayData: {
mchId: "",
mchName: "",
payPrice: 0,
},
sourceWalletData: {},
}
},
actions: {
// 支付
setPayData(data: any) {
this.payData = data
setScanPayData(data: any) {
this.scanPayData = data
},
// 转账
setSourceWalletData(data: any) {
+5 -2
View File
@@ -1,4 +1,7 @@
// app运行环境
import { APPENV } from "../constant"
// export default () => import.meta.env.VITE_DEV_ENV === "dev"
export const isDevEnv = (): boolean => process.env.NODE_ENV === "development"
@@ -14,11 +17,11 @@ export const getAppInfo = async (): Promise<string> => {
// 浙政钉环境
if (res.code === 0) {
console.log("环境:浙政钉-zzd")
return "zzd"
return APPENV.ZZD
} else {
// 诗画环境
console.log("环境:诗画浦江-sh")
return "sh"
return APPENV.SH
}
} catch (error) {
return "unknown"
+23 -8
View File
@@ -4,6 +4,7 @@ import { isEmpty } from "radash"
import { getAppInfo, isDevEnv } from "@@/utils/app"
import { userApi } from "../api"
import showVerifyDeviceImpModel from "../components/verifyDeviceImpModel/verifyDeviceImpModel"
import { APPENV } from "../constant"
const TOKEN_KEY = constant.storageConstant.TOKEN
const USER_INFO_KEY = constant.storageConstant.USERINFO
@@ -27,8 +28,8 @@ const processQueue = (error: any, token: any = null) => {
}
}
// 创建http请求实例
const http = new Request({
// 创建https请求实例
const https = new Request({
baseURL: import.meta.env.VITE_API_URL,
timeout: 15000,
method: "GET",
@@ -47,7 +48,21 @@ const http = new Request({
},
})
http.interceptors.request.use(async (config) => {
// 生产环境协议安全检查
const validateProductionSecurity = () => {
if (isDevEnv()) return
const apiUrl = import.meta.env.VITE_API_URL
if (apiUrl && !apiUrl.startsWith("https://")) {
uni.showToast({ title: "生产环境安全错误: 必须使用 HTTPS 协议", icon: "error" })
throw new Error("生产环境禁止使用 HTTP 协议")
}
}
https.interceptors.request.use(async (config) => {
// 生产环境协议检查
// validateProductionSecurity()
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
const config_header: any = config.header || {}
@@ -84,7 +99,7 @@ http.interceptors.request.use(async (config) => {
}, Promise.reject)
// 响应拦截器
http.interceptors.response.use(
https.interceptors.response.use(
async (response) => {
// 账号密码登录响应结果特殊处理
if (response.config.url === "/auth/oauth2/token") {
@@ -113,7 +128,7 @@ http.interceptors.response.use(
const config = error.config
const tokenValue = getTokenValue(newTokenStorage)
if (tokenValue && config.header) config.header.Authorization = tokenValue
return http.request(config)
return https.request(config)
}
} catch (err) {
return Promise.reject(err)
@@ -196,9 +211,9 @@ async function refreshToken(): Promise<any> {
const autoLogin = async (): Promise<boolean> => {
const appInfo = await getAppInfo()
// 浙政钉环境
if (appInfo == "zzd") return await loginByZzd()
if (appInfo == APPENV.ZZD) return await loginByZzd()
// 诗画环境
if (appInfo == "sh") return await loginBySh()
if (appInfo == APPENV.SH) return await loginBySh()
uni.showToast({ title: "app环境错误", icon: "none" })
return false
@@ -284,7 +299,7 @@ async function loginBySh(): Promise<boolean> {
// 请求函数
const request = (config: any) => {
return http.middleware(config)
return https.middleware(config)
}
export default request
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

+8
View File
@@ -9,6 +9,12 @@ import { useRouter } from "./src/sheep"
export default defineConfig(({ mode }) => {
const { NODE_ENV, VITE_APP_DEV_SERVICE, VITE_APP_PROD_SERVICE, VITE_IMAGE_DEV_PATH } = loadEnv(mode, process.cwd(), "")
const baseUrl = NODE_ENV == "development" ? VITE_APP_DEV_SERVICE : VITE_APP_PROD_SERVICE
// 生产环境强制 HTTPS 检查
if (NODE_ENV === "production" && baseUrl && !baseUrl.startsWith("https://")) {
throw new Error("生产环境必须使用 HTTPS 协议!请检查 VITE_APP_PROD_SERVICE 配置")
}
return {
build: {
target: "es6",
@@ -40,6 +46,8 @@ export default defineConfig(({ mode }) => {
ws: true,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
// 生产环境强制 HTTPS 验证
...(NODE_ENV === "production" && { secure: true }),
},
},
},