Merge branch 'master' of https://gitea.zianxin.com/zax/biz-mobile-react-cloud
This commit is contained in:
commit
f075a42ff1
81 files changed
+6614
-1369
No files matched your search
@@ -19,16 +19,16 @@ VITE_OAUTH2_APP_CLIENT='mini:mini'
|
||||
# --------------------------- 开发环境 api地址 ----------------------------------
|
||||
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
|
||||
#VITE_APP_DEV_SERVICE = http://59.202.170.152:81/api # 信创线上
|
||||
VITE_APP_DEV_SERVICE = http://192.168.101.196:9999 # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
|
||||
# VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
|
||||
# VITE_APP_DEV_SERVICE = http://192.168.101.174:9999 # 邓-本地
|
||||
VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
|
||||
#VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
|
||||
|
||||
# --------------------------- 开发环境 图片访问基础地址 ---------------------------
|
||||
# VITE_IMAGE_DEV_PATH = https://stgl.pj.gov.cn/api # 域名线上
|
||||
#VITE_IMAGE_DEV_PATH = http://59.202.170.152:81/api # 信创线上
|
||||
VITE_IMAGE_DEV_PATH = http://192.168.101.196:9999 # 邓-本地
|
||||
# VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
|
||||
# VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
|
||||
# VITE_IMAGE_DEV_PATH = http://192.168.101.174:9999 # 邓-本地
|
||||
VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
|
||||
#VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
|
||||
|
||||
# --------------------------- 生产环境 api地址 ----------------------------------
|
||||
VITE_APP_PROD_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
|
||||
|
||||
+10
-2
@@ -61,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: ["prosperityShop"],
|
||||
auth: ["user"],
|
||||
},
|
||||
{
|
||||
pagePath: "pages/tabbar/cabinet",
|
||||
text: "智能柜",
|
||||
@@ -99,14 +107,14 @@ const tabbar: TabbarItem[] = [
|
||||
iconPath: "/static/tabbar/integrated.png",
|
||||
selectedIconPath: "/static/tabbar/integrated-select.png",
|
||||
launchParam: ["integrated"],
|
||||
auth: [],
|
||||
auth: ["user"],
|
||||
},
|
||||
{
|
||||
pagePath: "pages/tabbar/my",
|
||||
text: "我的",
|
||||
iconPath: "/static/tabbar/my.png",
|
||||
selectedIconPath: "/static/tabbar/my-select.png",
|
||||
launchParam: ["canteen", "selfMerchant", "onlineShop", "coopMerchant", "integrated"],
|
||||
launchParam: ["canteen", "selfMerchant", "onlineShop", "coopMerchant", "integrated", "prosperityShop"],
|
||||
auth: ["user", "merchant", "doorAdmin"],
|
||||
},
|
||||
]
|
||||
|
||||
+78
-13
@@ -44,6 +44,13 @@
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tabbar/prosperityShop",
|
||||
"style": {
|
||||
"navigationBarTitleText": "共富商城",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/tabbar/coopMerchant",
|
||||
"style": {
|
||||
@@ -96,33 +103,85 @@
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pages/shop",
|
||||
"root": "pages/merchant",
|
||||
"pages": [
|
||||
{
|
||||
"path": "detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarTitleText": "商户详情",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "brandDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarTitleText": "商户品牌详情",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/online-shop",
|
||||
"pages": [
|
||||
{
|
||||
"path": "cart",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarTitleText": "购物车",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "goods-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/prosperity-shop",
|
||||
"pages": [
|
||||
{
|
||||
"path": "goods-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "shop-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "超市详情",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "goods-search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品搜索",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "goods-manage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品管理",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "goods-form",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品信息管理",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "categary-manage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品分类管理",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
@@ -576,6 +635,12 @@
|
||||
"iconPath": "",
|
||||
"selectedIconPath": ""
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/tabbar/prosperityShop",
|
||||
"text": "",
|
||||
"iconPath": "",
|
||||
"selectedIconPath": ""
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/tabbar/cabinet",
|
||||
"text": "",
|
||||
@@ -614,4 +679,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,16 +59,7 @@
|
||||
</view>
|
||||
</z-paging>
|
||||
|
||||
<tm-drawer
|
||||
title=""
|
||||
:showClose="false"
|
||||
:showFooter="false"
|
||||
size="98%"
|
||||
placement="bottom"
|
||||
v-model:show="state.showDrawer"
|
||||
:duration="100"
|
||||
zIndex="100"
|
||||
>
|
||||
<tm-drawer title="" :showClose="false" :showFooter="false" size="98%" v-model:show="state.showDrawer" :duration="100" zIndex="100">
|
||||
<z-paging
|
||||
ref="pageRef"
|
||||
:fixed="true"
|
||||
|
||||
@@ -251,7 +251,7 @@ const doFood = () => {
|
||||
<view class="m-x-30rpx">
|
||||
<!-- start 简介 start -->
|
||||
<view class="h-15rpx" />
|
||||
<view flex-row card p-20rpx>
|
||||
<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">
|
||||
@@ -354,7 +354,7 @@ const doFood = () => {
|
||||
</tm-sticky>
|
||||
<!-- end 商品 end -->
|
||||
<template #main-bottom>
|
||||
<view v-if="state.classifyList.length > 0" class="car z-200 p-x-30rpx p-y-30rpx bg-white">
|
||||
<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>
|
||||
@@ -483,7 +483,7 @@ const doFood = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.car {
|
||||
.cart {
|
||||
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
@@ -79,21 +79,22 @@ const doSubmit = async () => {
|
||||
<template>
|
||||
<crud-layout :title="state.detailId ? '修改人脸' : '人脸上传'" bg="/static/page/blue-bg.png">
|
||||
<view class="h-30rpx" />
|
||||
<view class="card p-x-30rpx p-y-40rpx">
|
||||
<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">
|
||||
<view class="h-10rpx" />
|
||||
<tm-upload-photo
|
||||
:maxFileSize="1024 * 1024"
|
||||
: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!">注:图片大小不超过1M</view>
|
||||
<view class="sub-text-20 c-red!">注:图片大小不超过5M</view>
|
||||
</tm-form>
|
||||
</view>
|
||||
<view
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { onShow } from "@dcloudio/uni-app"
|
||||
|
||||
import { hooks, router } from "@@/index"
|
||||
import { faceApi } from "@@/api"
|
||||
@@ -19,7 +19,6 @@ const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.pa)
|
||||
}
|
||||
|
||||
onLoad(() => { })
|
||||
onShow(() => {
|
||||
if (pageRef.value) {
|
||||
pageRef.value.reload(false)
|
||||
@@ -49,33 +48,40 @@ 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">
|
||||
<div class="flex items-center">
|
||||
<image mode="aspectFill" class="w-100rpx h-100rpx rounded-50rpx" :src="filePath(v.faceImg)" />
|
||||
<view class="w-30rpx" />
|
||||
<text class="font-600">{{ v.username }}</text>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<!-- <text class="botder_btn" @click="doEdit(v.id)">修改</text> -->
|
||||
<view class="w-30rpx" />
|
||||
<tm-modal v-if="false" title="确定删除吗?" height="auto" @confirm="doDel(v.id)">
|
||||
<template v-slot:trigger><text class="botder_btn">删除</text></template>
|
||||
</tm-modal>
|
||||
</div>
|
||||
<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 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 py-20rpx text-center rounded-40rpx mx-30rpx my-4 sub-text-30 color-white!" @tap="doAdd">
|
||||
添加
|
||||
</view>
|
||||
<view class="h-30rpx" />
|
||||
</template>
|
||||
</z-paging>
|
||||
</crud-layout>
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
<template>
|
||||
<view class="feedback-detail">
|
||||
<view class="detail-header">
|
||||
<view class="detail-title">{{ detail.title }}</view>
|
||||
<view v-if="typeOptions.length && detail.type" class="detail-type">反馈类型:{{ getTypeLabel(detail.type) }}</view>
|
||||
</view>
|
||||
<view class="detail-content">
|
||||
<view class="content-text">{{ detail.description }}</view>
|
||||
<view v-if="imageList.length" class="image-list">
|
||||
<image
|
||||
v-for="(img, idx) in imageList"
|
||||
:key="idx"
|
||||
:src="img.startsWith('http') ? img : filePath(img)"
|
||||
class="feedback-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-if="!detail.title" class="loading-tip">加载中...</view>
|
||||
<view v-else>
|
||||
<view class="detail-header">
|
||||
<view class="detail-title">{{ detail.title || '无标题' }}</view>
|
||||
<view v-if="detail.type" class="detail-type">
|
||||
反馈类型:{{ typeOptionsLoaded ? getTypeLabel(detail.type) : '加载中...' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-content">
|
||||
<view class="content-text">{{ detail.description || '无描述' }}</view>
|
||||
<view v-if="imageList.length" class="image-list">
|
||||
<image
|
||||
v-for="(img, idx) in imageList"
|
||||
:key="idx"
|
||||
:src="img.startsWith('http') ? img : filePath(img)"
|
||||
class="feedback-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-footer">
|
||||
<text class="create-time">提交时间:{{ formatTime(detail.createTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-footer">
|
||||
<text class="create-time">提交时间:{{ formatTime(detail.createTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -25,34 +30,55 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import meetingRoomApi from '@/sheep/api/meetingRoom'
|
||||
import feedbackApi from '@/sheep/api/feedback'
|
||||
import { filePath } from '@/sheep/utils/file'
|
||||
|
||||
const detail = ref<any>({})
|
||||
const typeOptions = ref<{ value: string; label: string }[]>([])
|
||||
const typeOptionsLoaded = ref(false)
|
||||
|
||||
async function loadTypeOptions() {
|
||||
const res: any = await meetingRoomApi.getFeedbackType()
|
||||
if (res.data && Array.isArray(res.data.data)) {
|
||||
typeOptions.value = res.data.data.map(item => ({
|
||||
value: item.id,
|
||||
label: item.label
|
||||
}))
|
||||
try {
|
||||
const res: any = await feedbackApi.getFeedbackType()
|
||||
console.log('反馈类型API响应:', res)
|
||||
if (res.code === 0 && res.data && Array.isArray(res.data)) {
|
||||
typeOptions.value = res.data.map(item => ({
|
||||
value: item.id,
|
||||
label: item.label
|
||||
}))
|
||||
console.log('处理后的反馈类型选项:', typeOptions.value)
|
||||
typeOptionsLoaded.value = true
|
||||
} else {
|
||||
console.error('获取反馈类型失败:', res.msg)
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取反馈类型异常:', e)
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeLabel(type) {
|
||||
if (!typeOptions.value.length || !type) return ''
|
||||
console.log('获取类型标签 - 输入类型:', type, '类型:', typeof type)
|
||||
console.log('获取类型标签 - 类型选项:', typeOptions.value)
|
||||
if (!typeOptions.value.length || !type) {
|
||||
console.log('获取类型标签 - 无选项或无类型,返回:', type || '')
|
||||
return type || ''
|
||||
}
|
||||
const option = typeOptions.value.find(item => String(item.value) === String(type))
|
||||
return option ? option.label : type
|
||||
console.log('获取类型标签 - 找到的选项:', option)
|
||||
const result = option ? option.label : type
|
||||
console.log('获取类型标签 - 最终结果:', result)
|
||||
return result
|
||||
}
|
||||
|
||||
function getTypeClass(type: string) {
|
||||
const classMap: Record<string, string> = {
|
||||
'1': 'type-suggestion',
|
||||
'2': 'type-problem',
|
||||
'3': 'type-ui',
|
||||
'4': 'type-other'
|
||||
'投诉': 'type-complain',
|
||||
'建议': 'type-suggest',
|
||||
'bug报告': 'type-bug',
|
||||
'功能需求': 'type-feature',
|
||||
'其他': 'type-other'
|
||||
}
|
||||
return classMap[type] || 'type-other'
|
||||
}
|
||||
@@ -89,15 +115,44 @@ const imageList = computed(() => {
|
||||
})
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
await loadTypeOptions()
|
||||
const id = options.id
|
||||
if (!id) return
|
||||
console.log('页面参数:', options)
|
||||
if (!id) {
|
||||
uni.showToast({ title: '缺少反馈ID', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response: any = await meetingRoomApi.getFeedbackDetail(id)
|
||||
if (response.code === 0 && Array.isArray(response.data) && response.data.length > 0) {
|
||||
detail.value = response.data[0]
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
// 先获取反馈详情
|
||||
const response: any = await feedbackApi.getFeedbackDetail(id)
|
||||
console.log('反馈详情响应:', response)
|
||||
|
||||
if (response.code === 0) {
|
||||
// 处理不同的数据结构
|
||||
if (Array.isArray(response.data) && response.data.length > 0) {
|
||||
detail.value = response.data[0]
|
||||
} else if (response.data && typeof response.data === 'object') {
|
||||
detail.value = response.data
|
||||
} else {
|
||||
console.error('数据结构异常:', response.data)
|
||||
uni.showToast({ title: '数据格式错误', icon: 'none' })
|
||||
return
|
||||
}
|
||||
console.log('反馈详情数据:', detail.value)
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '获取详情失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 再获取类型选项
|
||||
await loadTypeOptions()
|
||||
|
||||
uni.hideLoading()
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '获取详情失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
@@ -110,6 +165,13 @@ onLoad(async (options: any) => {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.loading-tip {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
@@ -118,53 +180,53 @@ onLoad(async (options: any) => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
|
||||
.type-tag {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 26rpx;
|
||||
|
||||
|
||||
&.type-suggestion {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
|
||||
&.type-problem {
|
||||
background: #ffebee;
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
|
||||
&.type-ui {
|
||||
background: #f3e5f5;
|
||||
color: #7b1fa2;
|
||||
}
|
||||
|
||||
|
||||
&.type-other {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.status-tag {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 26rpx;
|
||||
|
||||
|
||||
&.status-pending {
|
||||
background: #fff3e0;
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
|
||||
&.status-processing {
|
||||
background: #e8f5e8;
|
||||
color: #388e3c;
|
||||
}
|
||||
|
||||
|
||||
&.status-completed {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
|
||||
&.status-closed {
|
||||
background: #f5f5f5;
|
||||
color: #999;
|
||||
@@ -177,14 +239,14 @@ onLoad(async (options: any) => {
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
|
||||
.content-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
|
||||
.contact-info {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
@@ -198,21 +260,21 @@ onLoad(async (options: any) => {
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
|
||||
.reply-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
|
||||
.reply-content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
|
||||
.reply-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
@@ -224,7 +286,7 @@ onLoad(async (options: any) => {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
|
||||
|
||||
.create-time {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
@@ -244,4 +306,4 @@ onLoad(async (options: any) => {
|
||||
background: #f7f8fa;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -15,7 +15,9 @@
|
||||
<view class="form-item">
|
||||
<view class="label">反馈类型</view>
|
||||
<picker @change="onTypeChange" :value="typeIndex" :range="typeOptions" range-key="label">
|
||||
<view class="picker">{{ typeOptions[typeIndex]?.label || '请选择类型' }}</view>
|
||||
<view class="picker">
|
||||
{{ typeOptionsLoaded ? (typeOptions[typeIndex]?.label || '请选择类型') : '加载中...' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
@@ -68,21 +70,13 @@
|
||||
@click="viewDetail(item)"
|
||||
>
|
||||
<view class="item-header">
|
||||
<view class="type-tag" :class="getTypeClass(item.type)">{{ getTypeLabel(item.type) }}</view>
|
||||
<view class="type-tag" :class="getTypeClass(item.type)">
|
||||
{{ typeOptionsLoaded ? getTypeLabel(item.type) : '加载中...' }}
|
||||
</view>
|
||||
<view class="status-tag" :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</view>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<view class="item-title">{{ item.title }}</view>
|
||||
<view class="item-desc">{{ item.description }}</view>
|
||||
<view v-if="item.picUrl" class="image-list">
|
||||
<image
|
||||
v-for="(img, idx) in item.picUrl.split(',').filter(Boolean)"
|
||||
:key="idx"
|
||||
:src="filePath(img)"
|
||||
class="item-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-footer">
|
||||
<text class="time">{{ formatTime(item.createTime) }}</text>
|
||||
@@ -97,12 +91,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watch, nextTick } from 'vue'
|
||||
import meetingRoomApi from '@/sheep/api/meetingRoom'
|
||||
import feedbackApi from '@/sheep/api/feedback'
|
||||
import { onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { filePath } from '@/sheep/utils/file'
|
||||
import { useDefaultHeader } from '@/sheep/hooks/use-token'
|
||||
|
||||
const typeOptions = ref<{ value: string; label: string }[]>([])
|
||||
const typeOptionsLoaded = ref(false)
|
||||
const typeIndex = ref(0)
|
||||
const title = ref('')
|
||||
const description = ref('')
|
||||
@@ -123,15 +118,24 @@ const canSubmit = computed(() => {
|
||||
|
||||
async function loadTypeOptions() {
|
||||
try {
|
||||
const res: any = await meetingRoomApi.getFeedbackType()
|
||||
if (res.data && Array.isArray(res.data.data)) {
|
||||
typeOptions.value = res.data.data.map(item => ({
|
||||
value: item.value,
|
||||
const res: any = await feedbackApi.getFeedbackType()
|
||||
console.log('反馈类型API响应:', res)
|
||||
if (res.code === 0 && res.data && Array.isArray(res.data)) {
|
||||
typeOptions.value = res.data.map(item => ({
|
||||
value: item.id,
|
||||
label: item.label
|
||||
}))
|
||||
console.log('处理后的反馈类型选项:', typeOptions.value)
|
||||
typeOptionsLoaded.value = true
|
||||
} else {
|
||||
console.error('获取反馈类型失败:', res.msg)
|
||||
uni.showToast({ title: res.msg || '获取反馈类型失败', icon: 'none' })
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取反馈类型异常:', e)
|
||||
uni.showToast({ title: '获取反馈类型失败', icon: 'none' })
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,16 +144,26 @@ function onTypeChange(e: any) {
|
||||
}
|
||||
|
||||
function getTypeLabel(type: string) {
|
||||
const option = typeOptions.value.find(item => item.value === type)
|
||||
return option ? option.label : type
|
||||
console.log('列表页获取类型标签 - 输入类型:', type, '类型:', typeof type)
|
||||
console.log('列表页获取类型标签 - 类型选项:', typeOptions.value)
|
||||
if (!typeOptions.value.length || !type) {
|
||||
console.log('列表页获取类型标签 - 无选项或无类型,返回:', type || '')
|
||||
return type || ''
|
||||
}
|
||||
const option = typeOptions.value.find(item => String(item.value) === String(type))
|
||||
console.log('列表页获取类型标签 - 找到的选项:', option)
|
||||
const result = option ? option.label : type
|
||||
console.log('列表页获取类型标签 - 最终结果:', result)
|
||||
return result
|
||||
}
|
||||
|
||||
function getTypeClass(type: string) {
|
||||
const classMap: Record<string, string> = {
|
||||
'投诉': 'type-complain',
|
||||
'建议': 'type-suggest',
|
||||
'BUG报告': 'type-bug',
|
||||
'功能需求': 'type-feature'
|
||||
'bug报告': 'type-bug',
|
||||
'功能需求': 'type-feature',
|
||||
'其他': 'type-other'
|
||||
}
|
||||
return classMap[type] || 'type-other'
|
||||
}
|
||||
@@ -197,7 +211,7 @@ async function submitFeedback() {
|
||||
contact: contact.value.trim(),
|
||||
picUrl: picUrlArr.join(',')
|
||||
}
|
||||
const response: any = await meetingRoomApi.submitFeedback(data)
|
||||
const response: any = await feedbackApi.submitFeedback(data)
|
||||
uni.hideLoading()
|
||||
if (response.code === 0) {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
@@ -229,7 +243,7 @@ async function loadFeedbackList(isLoadMore = false, autoFill = false) {
|
||||
loading.value = true
|
||||
error.value = false
|
||||
try {
|
||||
const response: any = await meetingRoomApi.getFeedbackList({
|
||||
const response: any = await feedbackApi.getFeedbackList({
|
||||
current: page.value,
|
||||
size: pageSize
|
||||
})
|
||||
@@ -275,14 +289,28 @@ function refreshList() {
|
||||
// 监听页面触底事件,下拉加载更多
|
||||
onReachBottom(() => {
|
||||
if (activeTab.value === 1) {
|
||||
loadFeedbackList(true, false)
|
||||
// 确保类型选项已加载完成后再加载更多
|
||||
if (typeOptionsLoaded.value) {
|
||||
loadFeedbackList(true, false)
|
||||
} else {
|
||||
loadTypeOptions().then(() => {
|
||||
loadFeedbackList(true, false)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 切换tab时重置分页并刷新
|
||||
watch(activeTab, (val) => {
|
||||
if (val === 1) {
|
||||
refreshList()
|
||||
// 确保类型选项已加载完成后再刷新列表
|
||||
if (typeOptionsLoaded.value) {
|
||||
refreshList()
|
||||
} else {
|
||||
loadTypeOptions().then(() => {
|
||||
refreshList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -290,9 +318,18 @@ onPullDownRefresh(() => {
|
||||
if (activeTab.value === 1) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadFeedbackList(false, true).then(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
// 确保类型选项已加载完成后再刷新列表
|
||||
if (typeOptionsLoaded.value) {
|
||||
loadFeedbackList(false, true).then(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
} else {
|
||||
loadTypeOptions().then(() => {
|
||||
loadFeedbackList(false, true).then(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
@@ -3,20 +3,40 @@
|
||||
<view class="room-info">
|
||||
<view class="room-name">{{ detail.roomName }}</view>
|
||||
<view class="room-meta">
|
||||
<text>容量:{{ detail.capacity }}</text>
|
||||
<text>位置:{{ detail.location }}</text>
|
||||
<text>容量:{{ detail.roomCapacity }}人</text>
|
||||
<text>位置:{{ detail.roomLocation || '未设置' }}</text>
|
||||
</view>
|
||||
<view v-if="detail.equipmentNames" class="room-equip">
|
||||
设备:{{ detail.equipmentNames }}
|
||||
<view v-if="detail.roomEquipment" class="room-equip">
|
||||
设备:{{ detail.roomEquipment }}
|
||||
</view>
|
||||
<view v-if="detail.roomStatusText" class="room-status">
|
||||
状态:{{ detail.roomStatusText }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">预约信息</view>
|
||||
<view class="info-row"><text class="label">预约人:</text>{{ detail.username }}</view>
|
||||
<view class="info-row"><text class="label">预约人:</text>{{ detail.applicantName }}</view>
|
||||
<view class="info-row"><text class="label">预约日期:</text>{{ detail.bookingDate }}</view>
|
||||
<view class="info-row"><text class="label">预约时间:</text>{{ detail.startTime }} ~ {{ detail.endTime }}</view>
|
||||
<view class="info-row"><text class="label">预约状态:</text>{{ getStatusText(detail.checkStatus) }}</view>
|
||||
<view class="info-row"><text class="label">会议时长:</text>{{ detail.duration }}分钟</view>
|
||||
<view class="info-row"><text class="label">预约状态:</text>{{ detail.checkStatusText }}</view>
|
||||
<view class="info-row" v-if="detail.checkReason"><text class="label">审核意见:</text>{{ detail.checkReason }}</view>
|
||||
<view class="info-row" v-if="detail.description"><text class="label">备注:</text>{{ detail.description }}</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">会议配置</view>
|
||||
<view class="info-row"><text class="label">会务服务:</text>{{ detail.needServiceText }}</view>
|
||||
<view class="info-row" v-if="detail.deskLayoutText"><text class="label">桌子摆放:</text>{{ detail.deskLayoutText }}</view>
|
||||
<view class="info-row" v-if="detail.description"><text class="label">备注说明:</text>{{ detail.description }}</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">系统信息</view>
|
||||
<view class="info-row"><text class="label">创建人:</text>{{ detail.createBy }}</view>
|
||||
<view class="info-row"><text class="label">创建时间:</text>{{ detail.createTime }}</view>
|
||||
<view class="info-row" v-if="detail.updateBy"><text class="label">更新人:</text>{{ detail.updateBy }}</view>
|
||||
<view class="info-row" v-if="detail.updateTime"><text class="label">更新时间:</text>{{ detail.updateTime }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -28,17 +48,6 @@ import meetingRoomApi from '@/sheep/api/meetingRoom'
|
||||
|
||||
const detail = ref<any>({})
|
||||
|
||||
function getStatusText(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
'1': '审核中',
|
||||
'2': '审核通过',
|
||||
'3': '审核拒绝',
|
||||
'4': '已取消',
|
||||
'5': '已结束',
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
const id = options.id
|
||||
if (!id) return
|
||||
@@ -73,6 +82,12 @@ onLoad(async (options: any) => {
|
||||
.room-equip {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.room-status {
|
||||
font-size: 24rpx;
|
||||
color: #ff6b35;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.section {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
>
|
||||
<view class="time-range">{{ slot.startTime }} - {{ slot.endTime }}</view>
|
||||
<view class="time-status">
|
||||
<text v-if="slot.status === 'booked'">🚫 已预约: {{ slot.bookedBy?.userName }}</text>
|
||||
<text v-if="slot.status === 'booked'">🚫 已预约</text>
|
||||
<text v-else-if="slot.status === 'disabled'">🚫 不可预约</text>
|
||||
<text v-else>✓ 可预约</text>
|
||||
</view>
|
||||
@@ -53,7 +53,57 @@
|
||||
</view>
|
||||
|
||||
<!-- 预约按钮 -->
|
||||
<view class="reserve-btn" @tap="reserveMeeting">发起会议</view>
|
||||
<view class="reserve-btn" @tap="showReserveModal">发起会议</view>
|
||||
|
||||
<!-- 预约弹窗 -->
|
||||
<view v-if="showModal" class="modal-overlay" @tap="hideModal">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">会议预约</text>
|
||||
<text class="modal-close" @tap="hideModal">×</text>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<!-- 会务服务选择 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">是否需要会务服务</text>
|
||||
<view class="service-options">
|
||||
<view
|
||||
v-for="option in serviceOptions"
|
||||
:key="option.value"
|
||||
:class="['service-option', needService === option.value ? 'active' : '']"
|
||||
@tap="selectService(option.value)"
|
||||
>
|
||||
<text class="option-text">{{ option.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 桌子摆放形态选择 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">桌子摆放形态</text>
|
||||
<view class="layout-options">
|
||||
<view
|
||||
v-for="layout in deskLayouts"
|
||||
:key="layout.id"
|
||||
:class="['layout-option', selectedLayout === layout.id ? 'active' : '']"
|
||||
@tap="selectLayout(layout.id)"
|
||||
>
|
||||
<text class="layout-name">{{ layout.layoutName }}</text>
|
||||
<text class="layout-desc">{{ layout.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-footer">
|
||||
<view class="modal-btn cancel-btn" @tap="hideModal" :class="{ disabled: isBooking }">取消</view>
|
||||
<view class="modal-btn confirm-btn" @tap="confirmReserve" :class="{ disabled: isBooking }">
|
||||
{{ isBooking ? '预约中...' : '确认预约' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -120,6 +170,19 @@ const selectedTimeText = computed(() => {
|
||||
})
|
||||
const selectedDuration = computed(() => selectedTime.value.length * 30)
|
||||
|
||||
// 弹窗相关数据
|
||||
const showModal = ref(false)
|
||||
const needService = ref('0') // 默认不需要会务服务
|
||||
const selectedLayout = ref('') // 选中的桌子摆放形态ID
|
||||
const deskLayouts = ref<any[]>([]) // 桌子摆放形态列表
|
||||
const isBooking = ref(false) // 预约中状态
|
||||
|
||||
// 会务服务选项
|
||||
const serviceOptions = [
|
||||
{ value: '0', label: '不需要' },
|
||||
{ value: '1', label: '需要' }
|
||||
]
|
||||
|
||||
// 获取日期字符串
|
||||
const getDateString = (index: number) => {
|
||||
const date = new Date()
|
||||
@@ -172,6 +235,25 @@ const fetchTimeSlots = async (date: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取桌子摆放形态数据
|
||||
const fetchDeskLayouts = async () => {
|
||||
try {
|
||||
const res = await meetingRoomApi.getMeetingDeskLayout('')
|
||||
console.log('Desk layouts response:', res)
|
||||
|
||||
const response = res as unknown as ApiResponse<any[]>
|
||||
if (response.code === 0 && response.data) {
|
||||
deskLayouts.value = response.data
|
||||
console.log('Desk layouts data:', deskLayouts.value)
|
||||
console.log('First layout item structure:', deskLayouts.value[0])
|
||||
} else {
|
||||
console.error('Failed to fetch desk layouts:', response)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API request failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 选择时间
|
||||
function selectTime(index: number) {
|
||||
const slot = timeSlots.value[index]
|
||||
@@ -200,53 +282,167 @@ function selectTime(index: number) {
|
||||
}
|
||||
}
|
||||
|
||||
// 预约会议
|
||||
async function reserveMeeting() {
|
||||
// 显示预约弹窗
|
||||
function showReserveModal() {
|
||||
if (selectedTime.value.length === 0) {
|
||||
uni.showToast({ title: '请选择时间', icon: 'none' })
|
||||
return
|
||||
}
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
// 隐藏弹窗
|
||||
function hideModal() {
|
||||
showModal.value = false
|
||||
}
|
||||
|
||||
// 选择会务服务
|
||||
function selectService(value: string) {
|
||||
needService.value = value
|
||||
}
|
||||
|
||||
// 选择桌子摆放形态
|
||||
function selectLayout(layoutId: string) {
|
||||
console.log('selectLayout called with layoutId:', layoutId)
|
||||
selectedLayout.value = layoutId
|
||||
console.log('selectedLayout.value set to:', selectedLayout.value)
|
||||
}
|
||||
|
||||
// 获取选中的桌子摆放形态名称
|
||||
const getSelectedLayoutName = () => {
|
||||
console.log('getSelectedLayoutName - selectedLayout.value:', selectedLayout.value)
|
||||
console.log('getSelectedLayoutName - deskLayouts.value:', deskLayouts.value)
|
||||
|
||||
if (!selectedLayout.value) {
|
||||
console.log('No layout selected, returning 未选择')
|
||||
return '未选择'
|
||||
}
|
||||
|
||||
const selectedLayoutItem = deskLayouts.value.find(layout => layout.id === selectedLayout.value)
|
||||
console.log('Found selected layout item:', selectedLayoutItem)
|
||||
|
||||
if (selectedLayoutItem) {
|
||||
console.log('Returning layout name:', selectedLayoutItem.layoutName)
|
||||
return selectedLayoutItem.layoutName
|
||||
} else {
|
||||
console.log('Layout item not found, returning 未选择')
|
||||
return '未选择'
|
||||
}
|
||||
}
|
||||
|
||||
// 确认预约
|
||||
async function confirmReserve() {
|
||||
if (!roomId.value) {
|
||||
uni.showToast({ title: '会议室ID不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 防止重复点击
|
||||
if (isBooking.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置预约中状态
|
||||
isBooking.value = true
|
||||
|
||||
// 显示loading
|
||||
uni.showLoading({
|
||||
title: '预约中...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '预约中...', mask: true })
|
||||
const date = getDateString(activeDate.value)
|
||||
const startSlot = timeSlots.value[selectedTime.value[0]]
|
||||
const endSlot = timeSlots.value[selectedTime.value[selectedTime.value.length - 1]]
|
||||
|
||||
const deskLayoutName = getSelectedLayoutName()
|
||||
console.log('选中的桌子摆放形态ID:', selectedLayout.value)
|
||||
console.log('桌子摆放形态名称:', deskLayoutName)
|
||||
|
||||
const res = await meetingRoomApi.bookMeetingRoom(roomId.value, {
|
||||
date,
|
||||
startTime: startSlot.startTime,
|
||||
endTime: endSlot.endTime,
|
||||
title: room.value.name || '会议',
|
||||
description: '',
|
||||
attendees: []
|
||||
attendees: [],
|
||||
need_service: needService.value,
|
||||
deskLayout: deskLayoutName
|
||||
})
|
||||
|
||||
const response = res as unknown as ApiResponse<any>
|
||||
if (response.code === 0) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '预约成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
console.log('预约接口响应:', res)
|
||||
console.log('响应类型:', typeof res)
|
||||
console.log('响应内容:', JSON.stringify(res))
|
||||
|
||||
// 处理响应数据
|
||||
const responseData = res as any
|
||||
|
||||
// 检查业务错误(HTTP 200 但 code !== 0)
|
||||
if (responseData && responseData.code !== undefined) {
|
||||
console.log('检测到响应码:', responseData.code)
|
||||
console.log('检测到响应消息:', responseData.msg)
|
||||
|
||||
if (responseData.code === 0) {
|
||||
// 成功:隐藏loading,显示成功提示
|
||||
uni.hideLoading()
|
||||
isBooking.value = false
|
||||
uni.showToast({ title: '预约成功', icon: 'success' })
|
||||
hideModal()
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
// 失败:隐藏loading,关闭弹窗,显示错误信息
|
||||
uni.hideLoading()
|
||||
isBooking.value = false
|
||||
console.log('预约失败,错误信息:', responseData.msg)
|
||||
|
||||
// 先关闭弹窗,再显示错误信息
|
||||
hideModal()
|
||||
|
||||
// 延迟显示错误信息,确保弹窗完全关闭
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: responseData.msg || '预约失败',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}, 300)
|
||||
}
|
||||
} else {
|
||||
// 响应格式异常:隐藏loading,显示错误信息
|
||||
uni.hideLoading()
|
||||
isBooking.value = false
|
||||
console.log('响应格式异常:', responseData)
|
||||
uni.showToast({
|
||||
title: response.msg || '预约失败',
|
||||
icon: 'none'
|
||||
title: '预约失败,响应格式异常',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// 确保在异常情况下也隐藏loading
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '预约失败',
|
||||
icon: 'none'
|
||||
})
|
||||
isBooking.value = false
|
||||
console.error('预约失败:', error)
|
||||
|
||||
// 处理HTTP错误(如500错误)
|
||||
const errorData = error as any
|
||||
if (errorData?.data?.msg) {
|
||||
console.log('HTTP错误信息:', errorData.data.msg)
|
||||
uni.showToast({
|
||||
title: errorData.data.msg,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '预约失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +480,9 @@ onMounted(() => {
|
||||
console.log('Fetching time slots for date:', today)
|
||||
fetchTimeSlots(today)
|
||||
|
||||
// 获取桌子摆放形态数据
|
||||
fetchDeskLayouts()
|
||||
|
||||
// 从 localStorage 获取用户信息
|
||||
try {
|
||||
const userInfoString = localStorage.getItem('user_info')
|
||||
@@ -489,4 +688,160 @@ const goBack = () => {
|
||||
padding: 24rpx 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 90%;
|
||||
max-width: 600rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.service-options {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.service-option {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.service-option.active {
|
||||
border-color: #1777FF;
|
||||
background: #e6f0ff;
|
||||
color: #1777FF;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.layout-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.layout-option {
|
||||
padding: 20rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
background: #fff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.layout-option.active {
|
||||
border-color: #1777FF;
|
||||
background: #e6f0ff;
|
||||
}
|
||||
|
||||
.layout-name {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.layout-option.active .layout-name {
|
||||
color: #1777FF;
|
||||
}
|
||||
|
||||
.layout-desc {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.layout-option.active .layout-desc {
|
||||
color: #1777FF;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.modal-btn {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
color: #666;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
color: #1777FF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-btn.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -12,8 +12,25 @@
|
||||
<text class="date-arrow">▼</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view v-if="loading" class="loading-container">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<view v-else-if="error" class="error-container">
|
||||
<text class="error-text">{{ error }}</text>
|
||||
<view class="retry-btn" @tap="fetchMeetingRooms">重试</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else-if="meetingRooms.length === 0" class="empty-container">
|
||||
<text class="empty-text">暂无会议室</text>
|
||||
</view>
|
||||
|
||||
<!-- 会议室列表 -->
|
||||
<view v-for="room in meetingRooms" :key="room.id" class="room-card" @tap="goDetail(room)">
|
||||
<view v-else>
|
||||
<view v-for="room in meetingRooms" :key="room.id" class="room-card" @tap="goDetail(room)">
|
||||
<view class="room-info">
|
||||
<view class="room-main">
|
||||
<view class="room-title">
|
||||
@@ -26,7 +43,7 @@
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<image :src="room.picUrl || '/static/meeting/default.jpg'" class="room-img" mode="aspectFill"></image>
|
||||
<image :src="room.picUrl || '/static/icon/service_4.png'" class="room-img" mode="aspectFill"></image>
|
||||
</view>
|
||||
<!-- 时间条 -->
|
||||
<view class="timeline-bar">
|
||||
@@ -36,6 +53,7 @@
|
||||
<text v-for="h in 17" :key="h" class="timeline-label">{{ h + 7 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<MeetingTabbar activeTab="home" />
|
||||
</template>
|
||||
@@ -51,6 +69,8 @@ const goBack = () => {
|
||||
|
||||
const todayText = ref("")
|
||||
const meetingRooms = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref("")
|
||||
|
||||
onMounted(() => {
|
||||
todayText.value = getTodayText()
|
||||
@@ -63,12 +83,26 @@ function getTodayText() {
|
||||
return `${d.getMonth() + 1}月${d.getDate()}日 周${week[d.getDay()]}`
|
||||
}
|
||||
|
||||
function fetchMeetingRooms() {
|
||||
meetingRoomApi.getMeetingRoomList({ current: 1, size: 10 }).then((res: any) => {
|
||||
async function fetchMeetingRooms() {
|
||||
loading.value = true
|
||||
error.value = ""
|
||||
try {
|
||||
console.log('开始获取会议室列表...')
|
||||
const res: any = await meetingRoomApi.getMeetingRoomList({ current: 1, size: 10 })
|
||||
console.log('会议室列表API响应:', res)
|
||||
if (res.code === 0) {
|
||||
meetingRooms.value = res.data.records
|
||||
meetingRooms.value = res.data.records || []
|
||||
console.log('会议室列表数据:', meetingRooms.value)
|
||||
} else {
|
||||
error.value = res.msg || '获取会议室列表失败'
|
||||
console.error('获取会议室列表失败:', res.msg)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
error.value = '网络请求失败'
|
||||
console.error('获取会议室列表异常:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goDetail(room) {
|
||||
@@ -86,7 +120,7 @@ function goDetail(room) {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url("//at.alicdn.com/t/font_1234567_lingdang.css"); /* 替换为你的iconfont地址 */
|
||||
/* 移除外部CSS引用,使用本地图标或内联样式 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: #f5f6fa;
|
||||
@@ -231,4 +265,52 @@ function goDetail(room) {
|
||||
color: #bbb;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 80rpx 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #666;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 错误状态样式 */
|
||||
.error-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80rpx 32rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #f44336;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: #1976d2;
|
||||
color: #fff;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 80rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -23,7 +23,8 @@
|
||||
<view class="card-info">
|
||||
<view>预约日期:{{ item.bookingDate }}</view>
|
||||
<view>时间:{{ item.startTime }} ~ {{ item.endTime }}</view>
|
||||
<view>申请人:{{ item.createBy }}</view>
|
||||
<view>申请人:{{ item.applicantName }}</view>
|
||||
<view>创建时间:{{ item.createTime }}</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<view class="card-btn" @tap="viewDetail(item)">查看详情</view>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<view class="repair-detail">
|
||||
<view class="detail-header">
|
||||
<view class="equipment-info">
|
||||
<text class="equipment-name">{{ detail.equipmentName }}</text>
|
||||
<text class="location">{{ detail.location }}</text>
|
||||
<text class="equipment-name">{{ detail.title || detail.equipmentName || '无标题' }}</text>
|
||||
<text class="location">{{ detail.location || '无位置' }}</text>
|
||||
</view>
|
||||
<view class="status-tag" :class="getStatusClass(detail.status)">{{ getStatusText(detail.status) }}</view>
|
||||
</view>
|
||||
@@ -11,7 +11,9 @@
|
||||
<view class="detail-content">
|
||||
<view class="info-row">
|
||||
<text class="label">维修类型:</text>
|
||||
<text class="value">{{ getTypeLabel(detail.type) }}</text>
|
||||
<text class="value">
|
||||
{{ typeOptionsLoaded ? getTypeLabel(detail.type || detail.maintainType) : '加载中...' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">紧急程度:</text>
|
||||
@@ -51,18 +53,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import meetingRoomApi from '@/sheep/api/meetingRoom'
|
||||
import maintenanceApi from '@/sheep/api/maintenance'
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
|
||||
const detail = ref<any>({})
|
||||
const typeOptionsLoaded = ref(false)
|
||||
|
||||
const typeOptions = [
|
||||
{ value: '1', label: '电气设备' },
|
||||
{ value: '2', label: '网络设备' },
|
||||
{ value: '3', label: '办公设备' },
|
||||
{ value: '4', label: '空调设备' },
|
||||
{ value: '5', label: '其他' }
|
||||
]
|
||||
const typeOptions = ref<any[]>([])
|
||||
|
||||
const urgencyOptions = [
|
||||
{ value: '1', label: '高' },
|
||||
@@ -70,8 +67,55 @@ const urgencyOptions = [
|
||||
{ value: '3', label: '低' }
|
||||
]
|
||||
|
||||
// 加载维修类型选项
|
||||
async function loadTypeOptions() {
|
||||
try {
|
||||
const response: any = await maintenanceApi.getFeedbackType()
|
||||
console.log('维修类型响应:', response)
|
||||
if (response.code === 0 && response.data) {
|
||||
// 处理API返回的数据格式
|
||||
if (Array.isArray(response.data)) {
|
||||
typeOptions.value = response.data.map((item: any) => ({
|
||||
value: String(item.value || item.id),
|
||||
label: item.label || item.name || item.text
|
||||
}))
|
||||
} else {
|
||||
// 如果返回的是对象,尝试提取选项
|
||||
typeOptions.value = Object.entries(response.data).map(([key, value]: [string, any]) => ({
|
||||
value: key,
|
||||
label: value
|
||||
}))
|
||||
}
|
||||
console.log('处理后的维修类型选项:', typeOptions.value)
|
||||
typeOptionsLoaded.value = true
|
||||
} else {
|
||||
console.error('获取维修类型失败:', response.msg)
|
||||
// 使用默认选项作为后备
|
||||
typeOptions.value = [
|
||||
{ value: '1', label: '电气设备' },
|
||||
{ value: '2', label: '网络设备' },
|
||||
{ value: '3', label: '办公设备' },
|
||||
{ value: '4', label: '空调设备' },
|
||||
{ value: '5', label: '其他' }
|
||||
]
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取维修类型失败:', error)
|
||||
// 使用默认选项作为后备
|
||||
typeOptions.value = [
|
||||
{ value: '1', label: '电气设备' },
|
||||
{ value: '2', label: '网络设备' },
|
||||
{ value: '3', label: '办公设备' },
|
||||
{ value: '4', label: '空调设备' },
|
||||
{ value: '5', label: '其他' }
|
||||
]
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeLabel(type: string) {
|
||||
const option = typeOptions.find(item => item.value === type)
|
||||
const option = typeOptions.value.find(item => item.value === String(type))
|
||||
return option ? option.label : '其他'
|
||||
}
|
||||
|
||||
@@ -113,15 +157,33 @@ const imageList = computed(() => {
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
const id = options.id
|
||||
if (!id) return
|
||||
console.log('维修详情页面参数:', options)
|
||||
if (!id) {
|
||||
uni.showToast({ title: '缺少维修ID', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response: any = await meetingRoomApi.getRepairDetail(id)
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
// 先加载维修类型选项
|
||||
await loadTypeOptions()
|
||||
|
||||
// 再加载维修详情
|
||||
const response: any = await maintenanceApi.getRepairDetail(id)
|
||||
console.log('维修详情响应:', response)
|
||||
|
||||
if (response.code === 0 && response.data) {
|
||||
detail.value = response.data
|
||||
console.log('维修详情数据:', detail.value)
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '获取详情失败', icon: 'none' })
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
} catch (error) {
|
||||
console.error('获取维修详情失败:', error)
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '获取详情失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
<view class="form-item">
|
||||
<view class="label">维修类型</view>
|
||||
<picker @change="onTypeChange" :value="typeIndex" :range="typeOptions" range-key="label">
|
||||
<view class="picker">{{ typeOptions[typeIndex].label }}</view>
|
||||
<view class="picker" :class="{ 'placeholder': typeIndex === -1 }">
|
||||
{{ typeIndex === -1 ? '请选择维修类型' : typeOptions[typeIndex].label }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
@@ -75,15 +77,16 @@
|
||||
@click="viewDetail(item)"
|
||||
>
|
||||
<view class="item-header">
|
||||
<view class="type-tag" :class="getTypeClass(item.type)">{{ getTypeLabel(item.type) }}</view>
|
||||
<view class="type-tag" :class="getTypeClass(item.type)">
|
||||
{{ typeOptionsLoaded ? getTypeLabel(item.type) : '加载中...' }}
|
||||
</view>
|
||||
<view class="status-tag" :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</view>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<view class="equipment-info">
|
||||
<text class="equipment-name">{{ item.equipmentName }}</text>
|
||||
<text class="equipment-name">{{ item.title || item.equipmentName || '无标题' }}</text>
|
||||
<text class="location">{{ item.location }}</text>
|
||||
</view>
|
||||
<view class="description">{{ item.description }}</view>
|
||||
</view>
|
||||
<view class="item-footer">
|
||||
<text class="time">{{ formatTime(item.createTime) }}</text>
|
||||
@@ -99,17 +102,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
||||
import { onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import meetingRoomApi from '@/sheep/api/meetingRoom'
|
||||
import maintenanceApi from '@/sheep/api/maintenance'
|
||||
import { hooks } from '@@/index'
|
||||
import { filePath } from '@/sheep/utils/file'
|
||||
|
||||
const typeOptions = [
|
||||
{ value: 1, label: '电气设备' },
|
||||
{ value: 2, label: '网络设备' },
|
||||
{ value: 3, label: '办公设备' },
|
||||
{ value: 4, label: '空调设备' },
|
||||
{ value: 5, label: '其他' }
|
||||
]
|
||||
const typeOptions = ref<any[]>([])
|
||||
const typeOptionsLoaded = ref(false)
|
||||
|
||||
const urgencyOptions = [
|
||||
{ value: 1, label: '高' },
|
||||
@@ -117,7 +115,7 @@ const urgencyOptions = [
|
||||
{ value: 3, label: '低' }
|
||||
]
|
||||
|
||||
const typeIndex = ref(0)
|
||||
const typeIndex = ref(-1)
|
||||
const urgencyIndex = ref(0)
|
||||
const title = ref('')
|
||||
const location = ref('')
|
||||
@@ -134,7 +132,7 @@ const repairForm = ref<any>(null)
|
||||
const header = ref({})
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
return title.value.trim() && location.value.trim() && description.value.trim() && dateFound.value
|
||||
return title.value.trim() && location.value.trim() && description.value.trim() && dateFound.value && typeIndex.value !== -1
|
||||
})
|
||||
|
||||
function onTypeChange(e: any) {
|
||||
@@ -154,8 +152,55 @@ function beforeComplete(uploadFile: any) {
|
||||
return res.code == 0
|
||||
}
|
||||
|
||||
// 加载维修类型选项
|
||||
async function loadTypeOptions() {
|
||||
try {
|
||||
const response: any = await maintenanceApi.getFeedbackType()
|
||||
console.log('维修类型响应:', response)
|
||||
if (response.code === 0 && response.data) {
|
||||
// 处理API返回的数据格式
|
||||
if (Array.isArray(response.data)) {
|
||||
typeOptions.value = response.data.map((item: any) => ({
|
||||
value: item.value || item.id,
|
||||
label: item.label || item.name || item.text
|
||||
}))
|
||||
} else {
|
||||
// 如果返回的是对象,尝试提取选项
|
||||
typeOptions.value = Object.entries(response.data).map(([key, value]: [string, any]) => ({
|
||||
value: key,
|
||||
label: value
|
||||
}))
|
||||
}
|
||||
console.log('处理后的维修类型选项:', typeOptions.value)
|
||||
typeOptionsLoaded.value = true
|
||||
} else {
|
||||
console.error('获取维修类型失败:', response.msg)
|
||||
// 使用默认选项作为后备
|
||||
typeOptions.value = [
|
||||
{ value: 1, label: '电气设备' },
|
||||
{ value: 2, label: '网络设备' },
|
||||
{ value: 3, label: '办公设备' },
|
||||
{ value: 4, label: '空调设备' },
|
||||
{ value: 5, label: '其他' }
|
||||
]
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取维修类型失败:', error)
|
||||
// 使用默认选项作为后备
|
||||
typeOptions.value = [
|
||||
{ value: 1, label: '电气设备' },
|
||||
{ value: 2, label: '网络设备' },
|
||||
{ value: 3, label: '办公设备' },
|
||||
{ value: 4, label: '空调设备' },
|
||||
{ value: 5, label: '其他' }
|
||||
]
|
||||
typeOptionsLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeLabel(type: number) {
|
||||
const option = typeOptions.find(item => item.value === type)
|
||||
const option = typeOptions.value.find(item => item.value === type)
|
||||
return option ? option.label : '其他'
|
||||
}
|
||||
|
||||
@@ -192,6 +237,13 @@ async function submitRepair() {
|
||||
uni.showToast({ title: '请填写完整信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 验证维修类型是否已选择
|
||||
if (typeIndex.value === -1 || !typeOptions.value[typeIndex.value]) {
|
||||
uni.showToast({ title: '请选择维修类型', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 收集所有上传成功图片的 url
|
||||
let picUrl: string[] = []
|
||||
if (uploadFileUrl.value.length > 0) {
|
||||
@@ -206,6 +258,7 @@ async function submitRepair() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '提交中...' })
|
||||
const data = {
|
||||
@@ -215,9 +268,11 @@ async function submitRepair() {
|
||||
dateFound: dateFound.value,
|
||||
location: location.value.trim(),
|
||||
urgency: String(urgencyOptions[urgencyIndex.value].value),
|
||||
maintainType: typeOptions[typeIndex.value].value
|
||||
maintainType: typeOptions.value[typeIndex.value].value
|
||||
}
|
||||
const response: any = await meetingRoomApi.submitRepair(data)
|
||||
console.log('提交的维修数据:', data)
|
||||
const response: any = await maintenanceApi.submitRepair(data)
|
||||
console.log('维修提交响应:', response)
|
||||
uni.hideLoading()
|
||||
if (response.code === 0) {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
@@ -227,14 +282,17 @@ async function submitRepair() {
|
||||
description.value = ''
|
||||
dateFound.value = ''
|
||||
uploadFileUrl.value = []
|
||||
typeIndex.value = 0
|
||||
typeIndex.value = -1
|
||||
urgencyIndex.value = 0
|
||||
// 刷新列表
|
||||
loadRepairList()
|
||||
if (activeTab.value === 1) {
|
||||
loadRepairList()
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '提交失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('维修提交失败:', error)
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '提交失败', icon: 'none' })
|
||||
}
|
||||
@@ -251,7 +309,7 @@ async function loadRepairList(isLoadMore = false, autoFill = false) {
|
||||
loading.value = true
|
||||
let requestSuccess = false
|
||||
try {
|
||||
const response: any = await meetingRoomApi.getRepairList({
|
||||
const response: any = await maintenanceApi.getRepairList({
|
||||
current: page.value,
|
||||
size: pageSize
|
||||
})
|
||||
@@ -315,6 +373,7 @@ watch(activeTab, (val) => {
|
||||
|
||||
onMounted(() => {
|
||||
header.value = hooks.useDefaultHeader()
|
||||
loadTypeOptions()
|
||||
if (activeTab.value === 1) {
|
||||
refreshRepairList()
|
||||
}
|
||||
@@ -378,6 +437,10 @@ onMounted(() => {
|
||||
background: #f7f8fa;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.location-input,
|
||||
|
||||
+1569
-47
File diff suppressed because it is too large.
Load diff
@@ -1,62 +1,66 @@
|
||||
<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="flex justify-between mt-1">
|
||||
<text>访客姓名:</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 mt-1">
|
||||
<text class="w-220rpx mr-6">访客姓名</text>
|
||||
<text>{{ detail.name }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>访客手机号:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">访客手机号</text>
|
||||
<text>{{ detail.mobile }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>来访公司名称:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">来访公司名称</text>
|
||||
<text>{{ detail.companyName }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>来访原因:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">来访原因</text>
|
||||
<text>{{ visitorReasonList.find((item) => item.id == detail.reasonValue)?.title || "" }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>接待人:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">接待人</text>
|
||||
<text>{{ detail.userName }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>来访人数:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">来访人数</text>
|
||||
<text>{{ detail.peopleNumber }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>是否可随行人员:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">是否可随行人员</text>
|
||||
<text>{{ detail.isOtherPeople == "1" ? "是" : "否" }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>是否可驾驶车辆:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">是否可驾驶车辆</text>
|
||||
<text>{{ detail.isDrive == "1" ? "是" : "否" }}</text>
|
||||
</view>
|
||||
</tm-sheet>
|
||||
|
||||
<tm-sheet :margin="[0, 20]" class="second-text-32">
|
||||
<tm-text font-size="32">时间</tm-text>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>预计来访时间:</text>
|
||||
<tm-sheet :margin="[0, 20]" class="second-text-30">
|
||||
<tm-text class="prefix_title" font-size="32">时间</tm-text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">预计来访时间</text>
|
||||
<text>{{ detail.vendDate }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>预计离开时间:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">预计离开时间</text>
|
||||
<text>{{ detail.vstartDate }}</text>
|
||||
</view>
|
||||
</tm-sheet>
|
||||
|
||||
<tm-sheet :margin="[0, 20]" class="second-text-32">
|
||||
<tm-text font-size="32">其他</tm-text>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>创建方式:</text>
|
||||
<tm-sheet :margin="[0, 20]" class="second-text-30">
|
||||
<tm-text class="prefix_title" font-size="32">其他</tm-text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">创建方式</text>
|
||||
<text>{{ detail.type == "1" ? "主动申请" : detail.type == "2" ? "邀约" : "后台创建" }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>提交时间:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-220rpx mr-6">提交时间</text>
|
||||
<text>{{ detail.createTime }}</text>
|
||||
</view>
|
||||
</tm-sheet>
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
// 获取拜访原因列表
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<view class="h-20rpx" />
|
||||
<tm-sheet margin="[0]">
|
||||
<view class="mx-auto mb-1 text-center text-lg">访客邀约</view>
|
||||
<tm-sheet :margin="[0]">
|
||||
<tm-alert :border="['2']" :borderColor="['#89c0ff']" :darkBorderColor="['#3b5066']">访客邀约请填写以下信息</tm-alert>
|
||||
<tm-form @submit="onsubmit" v-model="form" :rules="rules" direction="vertical">
|
||||
<!-- <tm-form-item name="name" label="访客姓名">
|
||||
@@ -19,16 +18,26 @@
|
||||
<span>拜访开始时间 </span>
|
||||
<tm-icon name="arrow-right-s-line" />
|
||||
</template>
|
||||
<tm-input :modelValue="form.vStartDate" placeholder="请选择拜访开始时间" style="pointer-events: none"
|
||||
disabled color="transparent" />
|
||||
<tm-input
|
||||
:modelValue="form.vStartDate"
|
||||
placeholder="请选择拜访开始时间"
|
||||
style="pointer-events: none"
|
||||
disabled
|
||||
color="transparent"
|
||||
/>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="vEndDate" label="拜访离开时间 >" @click="endDatePicker('vEndDate')">
|
||||
<template #label>
|
||||
<span>拜访离开时间 </span>
|
||||
<tm-icon name="arrow-right-s-line" />
|
||||
</template>
|
||||
<tm-input :modelValue="form.vEndDate" placeholder="请选择拜访离开时间" style="pointer-events: none" disabled
|
||||
color="transparent" />
|
||||
<tm-input
|
||||
:modelValue="form.vEndDate"
|
||||
placeholder="请选择拜访离开时间"
|
||||
style="pointer-events: none"
|
||||
disabled
|
||||
color="transparent"
|
||||
/>
|
||||
</tm-form-item>
|
||||
|
||||
<!-- <tm-form-item name="deptId" label="拜访部门">
|
||||
@@ -40,7 +49,7 @@
|
||||
</template>
|
||||
</tm-input>
|
||||
</template>
|
||||
<tm-text color="#999999">精致美观的对话框,可全局配置风格</tm-text>
|
||||
<tm-text color="#999999"></tm-text>
|
||||
</tm-drawer>
|
||||
</tm-form-item> -->
|
||||
|
||||
@@ -60,18 +69,39 @@
|
||||
<tm-form-item name="isDrive" label="是否可驾驶车辆" direction="horizontal" labelWidth="500">
|
||||
<tm-switch v-model="form.isDrive" />
|
||||
</tm-form-item>
|
||||
<tm-button form-type="submit" block>提交</tm-button>
|
||||
<tm-button class="mt-4" form-type="submit" block>提交</tm-button>
|
||||
</tm-form>
|
||||
</tm-sheet>
|
||||
|
||||
<tm-drawer title="请选择时间" :showClose="true" :showFooter="false" size="auto" placement="bottom"
|
||||
v-model:show="showDatePicker" :duration="100" zIndex="100">
|
||||
<tm-drawer
|
||||
title="请选择时间"
|
||||
:showClose="true"
|
||||
:showFooter="false"
|
||||
size="auto"
|
||||
v-model:show="showDatePicker"
|
||||
:duration="100"
|
||||
zIndex="100"
|
||||
>
|
||||
<time-range-picker @confirm="onDatePickerConfirm" @cancel="showDatePicker = false" />
|
||||
</tm-drawer>
|
||||
|
||||
<tm-modal v-model:show="modalShow" :overlayClick="false" title="提示" position="bottom" height="auto"
|
||||
:showCancel="true" cancelText="取消" confirmText="确定" @confirm="confirmSubmit" @cancel="modalShow = false">
|
||||
<view class="text-center"><tm-text font-size="30" color="#999999">确认提交吗?</tm-text></view>
|
||||
<tm-modal
|
||||
v-model:show="confirmModalShow"
|
||||
:overlayClick="false"
|
||||
title="确定提交吗?"
|
||||
position="bottom"
|
||||
height="auto"
|
||||
:showCancel="true"
|
||||
cancelText="取消"
|
||||
confirmText="确定"
|
||||
@confirm="confirmSubmit"
|
||||
@cancel="confirmModalShow = false"
|
||||
>
|
||||
<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>
|
||||
@@ -92,9 +122,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: "",
|
||||
@@ -106,8 +144,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(() => {
|
||||
@@ -141,20 +179,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
|
||||
}
|
||||
|
||||
@@ -162,7 +200,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
|
||||
}
|
||||
|
||||
// 确认提交
|
||||
@@ -182,15 +220,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" })
|
||||
}
|
||||
@@ -202,8 +237,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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from "vue"
|
||||
import { ref, reactive } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { merchantApi } from "@@/api"
|
||||
import { hooks, router } from "@@/index"
|
||||
@@ -54,7 +54,7 @@ const getDetail = async () => {
|
||||
const toDetail = async (v: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
router.jump("/pages/shop/detail", { id: v.id })
|
||||
router.jump("/pages/merchant/detail", { id: v.id })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -93,7 +93,7 @@
|
||||
</scroll-view>
|
||||
|
||||
<scroll-view v-if="state.goodsList.length" scroll-y :style="{ height: getHeight() }" class="flex-1 p-b-20rpx p-x-15rpx">
|
||||
<product-row
|
||||
<!-- <product-row
|
||||
v-for="goodsItem in state.goodsList"
|
||||
:key="goodsItem.id"
|
||||
:productId="goodsItem.id"
|
||||
@@ -106,7 +106,7 @@
|
||||
:retailPrice="goodsItem.retailPrice"
|
||||
:promotionPrice="goodsItem.promotionPrice"
|
||||
@onClickAddToCart="onClickAddToCart(goodsItem)"
|
||||
/>
|
||||
/> -->
|
||||
</scroll-view>
|
||||
</view>
|
||||
</tm-sticky>
|
||||
@@ -114,13 +114,13 @@
|
||||
|
||||
<template #main-bottom>
|
||||
<view v-if="false && state.classifyList.length" class="car z-200 p-x-30rpx p-y-30rpx bg-white">
|
||||
<product-total :totPrice="state.totPrice" :totCount="state.totCount" @doContainer="doVisibleCart" @submit="doBuy" />
|
||||
<!-- <product-total :totPrice="state.totPrice" :totCount="state.totCount" @doContainer="doVisibleCart" @submit="doBuy" /> -->
|
||||
</view>
|
||||
</template>
|
||||
</crud-layout>
|
||||
|
||||
<!-- 购物车弹框 -->
|
||||
<product-cart
|
||||
<!-- <product-cart
|
||||
v-model="state.showCartDialog"
|
||||
:cartList="state.cartList"
|
||||
:totPrice="state.totPrice"
|
||||
@@ -128,9 +128,9 @@
|
||||
@add="doAdd"
|
||||
@remove="doRemove"
|
||||
@submit="doBuy"
|
||||
/>
|
||||
/> -->
|
||||
<!-- 规格与数量弹框 -->
|
||||
<product-sku v-model="state.showSkuDialog" :productData="state.curProductData" @onSkuAddToCart="onSkuAddToCart" />
|
||||
<!-- <product-sku v-model="state.showSkuDialog" :productData="state.curProductData" @onSkuAddToCart="onSkuAddToCart" /> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -140,10 +140,10 @@ import { isEmpty } from "radash"
|
||||
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 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"
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<crud-layout title="购物车" mainIsScroll tabBarType="just-flot-back" bg="/static/page/blue-bg.png">
|
||||
<view v-if="cartStore.totCount > 0" class="flex-row card p-x-15rpx p-y-25rpx m-t-20rpx" v-for="(v, i) in cartStore.cart" :key="i">
|
||||
<image class="size-160rpx rounded-12rpx" :src="filePath(v.picUrl)" mode="aspectFill" />
|
||||
<view class="flex-1 m-l-20rpx">
|
||||
<view class="sub-text-28 show-line">{{ v.productName }}</view>
|
||||
<view class="second-text-26 show-line">单位:{{ v.unit }}</view>
|
||||
<crud-layout title="购物车" mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<view class="h-20rpx"></view>
|
||||
<view v-if="cartStore.totalCount > 0" class="flex-row card px-15rpx py-25rpx mb-20rpx" v-for="(v, i) in cartStore.cart" :key="i">
|
||||
<image class="size-160rpx rounded-12rpx mr-20rpx bg-#eeeeee" :src="filePath(v.productPic)" mode="aspectFill" />
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="sub-text-30 truncate">{{ v.productName }}</view>
|
||||
<view class="second-text-26 show-line">单位 {{ v.unit }}</view>
|
||||
<view class="flex-row justify-between items-center">
|
||||
<view class="second-text-30"> ¥{{ hooks.useMoney(v.productPrice) }} </view>
|
||||
<view class="second-text-30">x {{ v.productCount }}</view>
|
||||
</view>
|
||||
<view class="flex-row justify-between items-center">
|
||||
<view class="second-text-32 c-red!"> ¥{{ hooks.useMoney(v.totalPrice) }} </view>
|
||||
<view class="second-text-32 c-red!">¥{{ hooks.useMoney(v.totalPrice) }}</view>
|
||||
<view class="flex-row justify-between items-center">
|
||||
<view
|
||||
class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff"
|
||||
@@ -27,14 +28,14 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex-col justify-center items-center pt-300rpx" v-else>
|
||||
<view v-else class="flex-col justify-center items-center pt-300rpx">
|
||||
<image src="@/static/page/cart_empty.png" class="w-350rpx h-350rpx" />
|
||||
<text class="sub-text-32 font-bold">购物车空空如也</text>
|
||||
</view>
|
||||
|
||||
<template #main-bottom>
|
||||
<view class="z-200 p-x-30rpx p-y-30rpx bg-white">
|
||||
<product-total :totPrice="cartStore.totPrice" :totCount="cartStore.totCount" @doContainer="" @submit="doBuy" />
|
||||
<product-total :totPrice="cartStore.totPrice" :totCount="cartStore.totalCount" @doContainer="" @submit="doBuy" />
|
||||
</view>
|
||||
</template>
|
||||
</crud-layout>
|
||||
@@ -69,12 +70,12 @@ const submitPay = async () => {
|
||||
if (isEmpty(cartStore.cart)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
|
||||
|
||||
uni.showLoading({ title: "支付中...", mask: false })
|
||||
const { data, code }: any = await payApi.createSupermarketOrder({ carParentId: cartStore.cart[0].parentCarId, hardType: "SUPERMARKET" })
|
||||
if (code != 0) throw new Error()
|
||||
const { code, msg }: any = await payApi.createSupermarketOrder({ carParentId: cartStore.cart[0].parentCarId, hardType: "SUPERMARKET" })
|
||||
if (code != 0) throw new Error(msg)
|
||||
uni.showToast({ title: "支付成功", icon: "none" })
|
||||
router.jump("/pages/tabbar/order")
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} catch (error: any) {
|
||||
uni.showToast({ title: error.message, icon: "none" })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
+54
-54
@@ -11,66 +11,64 @@
|
||||
:duration="100"
|
||||
@close="doClose"
|
||||
>
|
||||
<scroll-view scroll-y="true" @touchmove.stop>
|
||||
<!-- 商品信息 -->
|
||||
<view class="flex-col card p-y-20rpx p-x-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">商品信息</view>
|
||||
<view class="flex-row items-center">
|
||||
<image
|
||||
class="w-160rpx h-160rpx bg-#0094ff rounded-1"
|
||||
:src="filePath(state.goodsDetail.picUrl)"
|
||||
mode="aspectFill"
|
||||
@tap.stop="state.goodsDetail.picUrl && usePreviewImage(filePath(state.goodsDetail.picUrl))"
|
||||
/>
|
||||
<view class="ml-3 flex flex-col justify-between">
|
||||
<view class="text-32 ws-nowrap text-ellipsis overflow-hidden">{{ state.goodsDetail.productName }}</view>
|
||||
<view class="flex items-center">
|
||||
<view class="second-text-30 c-red!">
|
||||
促销 ¥ {{ state.selectedSku.promotionPrice.toFixed(2) || state.selectedSku.retailPrice.toFixed(2) || 0 }}
|
||||
</view>
|
||||
<view class="m-l-20rpx second-text-28 line-through">原价 ¥ {{ state.selectedSku.retailPrice.toFixed(2) || 0 }}</view>
|
||||
</view>
|
||||
<view class="second-text-26">库存:{{ state.selectedSku.stock || 0 }}</view>
|
||||
<view class="second-text-26">
|
||||
已选择:{{ `${state.selectedSku.spec || "未选择"} / ${state.selectedSku.unit || "未选择"},数量:${state.goodsCount}` }}
|
||||
</view>
|
||||
<view class="flex-col card py-20rpx px-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">商品信息</view>
|
||||
<view class="flex items-center">
|
||||
<image
|
||||
class="size-160rpx bg-#eeeeee rounded-1 mr-2"
|
||||
:src="filePath(state.goodsDetail.picUrl)"
|
||||
mode="aspectFill"
|
||||
@tap.stop="state.goodsDetail.picUrl && usePreviewImage(filePath(state.goodsDetail.picUrl))"
|
||||
/>
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="sub-text-32 truncate">{{ state.goodsDetail.productName }}</view>
|
||||
<view v-if="state.selectedSku.promotionPrice" class="second-text-32 c-red!">
|
||||
<text> ¥{{ displayPrice(state.selectedSku.promotionPrice, "") || 0 }} </text>
|
||||
<text class="m-l-20rpx second-text-28 line-through">¥{{ displayPrice(state.selectedSku.retailPrice, "") || 0 }}</text>
|
||||
</view>
|
||||
<view v-else class="flex items-end mt-1">
|
||||
<text class="second-text-32 c-red!">¥{{ displayPrice(state.selectedSku.retailPrice, "") || 0 }}</text>
|
||||
</view>
|
||||
<view class="second-text-26">库存 {{ state.selectedSku.stock || 0 }}</view>
|
||||
<view class="second-text-26">
|
||||
已选择 {{ `${state.selectedSku.spec || "未选择"} / ${state.selectedSku.unit || "未选择"},数量 ${state.goodsCount}` }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 包装单位 -->
|
||||
<view class="flex-col card p-y-20rpx p-x-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">包装单位</view>
|
||||
<view class="flex flex-wrap">
|
||||
<view
|
||||
class="text-center rounded-2 mr-4 mb-2 py-2 px-3 text-28 bg-#F2F2F2 c-black border border-solid border-#E5E5E5"
|
||||
:class="{ 'bg-#0094ff! c-white!': state.selectedSku.id === v.id }"
|
||||
v-for="v in state.goodsDetail?.skuList"
|
||||
:key="v.id"
|
||||
@click="onSelectSku(v)"
|
||||
>
|
||||
{{ v.spec + " / " + v.unit }}
|
||||
</view>
|
||||
<!-- 包装单位 -->
|
||||
<view class="flex-col card p-y-20rpx p-x-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">包装单位</view>
|
||||
<view class="flex flex-wrap">
|
||||
<view
|
||||
class="text-center rounded-2 mr-4 mb-2 py-2 px-3 text-28 bg-#F2F2F2 c-black border border-solid border-#E5E5E5"
|
||||
:class="{ 'bg-#0094ff! c-white!': state.selectedSku.id === v.id }"
|
||||
v-for="v in state.goodsDetail?.skuList"
|
||||
:key="v.id"
|
||||
@click="onSelectSku(v)"
|
||||
>
|
||||
{{ v.spec + " / " + v.unit }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 购买数量 -->
|
||||
<view class="flex-col card p-y-20rpx p-x-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">购买数量</view>
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="second-text-28">限购:{{ state.goodsDetail.limitNum }}</view>
|
||||
<view class="flex">
|
||||
<view class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff" @tap.stop="handleNumRemove">
|
||||
<view class="iconfont icon-a--" />
|
||||
</view>
|
||||
<view class="sub-text-28 m-x-20rpx">{{ String(state.goodsCount).padStart(2, "0") }}</view>
|
||||
<view class="flex-row-center size-40rpx rounded-full bg-#0094ff" @tap.stop="handleNumAdd">
|
||||
<view class="font-size-40rpx color-white iconfont icon-a-" />
|
||||
</view>
|
||||
<!-- 购买数量 -->
|
||||
<view class="flex-col card p-y-20rpx p-x-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">购买数量</view>
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="second-text-28">限购 {{ state.goodsDetail.limitNum }}</view>
|
||||
<view class="flex">
|
||||
<view class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff" @tap.stop="handleNumRemove">
|
||||
<view class="iconfont icon-a--" />
|
||||
</view>
|
||||
<view class="sub-text-28 m-x-20rpx">{{ String(state.goodsCount).padStart(2, "0") }}</view>
|
||||
<view class="flex-row-center size-40rpx rounded-full bg-#0094ff" @tap.stop="handleNumAdd">
|
||||
<view class="font-size-40rpx color-white iconfont icon-a-" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<template #footer>
|
||||
<button
|
||||
@@ -89,17 +87,18 @@ import { filePath } from "@/sheep/utils/file"
|
||||
import { reactive, watch } from "vue"
|
||||
import { usePreviewImage } from "@/sheep/hooks/use-img"
|
||||
import { isEmpty } from "radash"
|
||||
import { displayPrice } from "@/sheep/utils/formatMoney"
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "onSkuAddToCart"])
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: boolean
|
||||
productData: any
|
||||
goodsData: any
|
||||
}>(),
|
||||
{
|
||||
modelValue: false,
|
||||
productData: {},
|
||||
goodsData: {},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -117,7 +116,7 @@ watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
if (newValue) {
|
||||
state.goodsDetail = props.productData
|
||||
state.goodsDetail = props.goodsData
|
||||
if (!isEmpty(state.goodsDetail)) {
|
||||
state.selectedSku = state.goodsDetail.skuList[0]
|
||||
}
|
||||
@@ -140,6 +139,7 @@ const handleNumAdd = () => {
|
||||
if (state.goodsCount >= state.goodsDetail.stock) return uni.showToast({ title: "库存不足", icon: "none" })
|
||||
state.goodsCount += 1
|
||||
}
|
||||
|
||||
// 减数量
|
||||
const handleNumRemove = () => {
|
||||
if (state.goodsCount <= 1) return
|
||||
@@ -148,7 +148,6 @@ const handleNumRemove = () => {
|
||||
|
||||
// 点击sku加入到购物车
|
||||
const handleSkuAddToCart = async () => {
|
||||
// 检查包装单位选择
|
||||
if (!state.selectedSku.id) return uni.showToast({ title: "请选择包装单位", icon: "none" })
|
||||
|
||||
// 检查库存
|
||||
@@ -161,6 +160,7 @@ const handleSkuAddToCart = async () => {
|
||||
// } finally {
|
||||
// uni.hideLoading()
|
||||
// }
|
||||
|
||||
// 检查库存
|
||||
if (state.goodsDetail.stock === 0 || Number(state.goodsCount) > Number(state.selectedSku.stock))
|
||||
return uni.showToast({ title: "库存不足", icon: "none" })
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view
|
||||
class="flex-row items-start card p-x-15rpx p-y-25rpx m-b-20rpx"
|
||||
@tap="props.productId && props.mode == 'default' && router.jump('/pages/shop/product-detail', { id: props.productId })"
|
||||
@tap="props.productId && props.mode == 'default' && router.jump('/pages/online-shop/goods-detail', { id: props.productId })"
|
||||
>
|
||||
<image class="size-160rpx rounded-12rpx" :src="props.picUrl" mode="aspectFill" />
|
||||
<view class="flex-1 m-l-20rpx">
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { isEmpty } from "radash"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { onlineShopApi } from "@@/api"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import ProductSku from "./components/product-sku.vue"
|
||||
import { useCartStore } from "@/sheep/stores/cart"
|
||||
import { hooks, router } from "@/sheep"
|
||||
import { displayPrice } from "@/sheep/utils/formatMoney"
|
||||
|
||||
const cartStore = useCartStore()
|
||||
|
||||
@@ -17,41 +17,38 @@ const state = reactive<any>({
|
||||
})
|
||||
|
||||
onLoad((options: any) => {
|
||||
if (!isEmpty(options.id)) {
|
||||
state.detailId = options.id
|
||||
}
|
||||
})
|
||||
onShow(() => {
|
||||
getDetail()
|
||||
const { id } = options
|
||||
if (!id) return uni.showToast({ title: "参数错误" })
|
||||
state.detailId = id
|
||||
getGoodsDetail()
|
||||
})
|
||||
|
||||
const getDetail = async () => {
|
||||
// 获取商品详情
|
||||
const getGoodsDetail = async () => {
|
||||
const { data } = await onlineShopApi.getSpuDetail(state.detailId)
|
||||
state.detail = data ?? {}
|
||||
}
|
||||
|
||||
// 加入购物车
|
||||
// 打开加入购物车
|
||||
const addToCart = () => {
|
||||
state.showSkuDialog = true
|
||||
}
|
||||
|
||||
// 选择sku加入到购物车
|
||||
// 选择sku确认加入到购物车
|
||||
const onSkuAddToCart = (sku: any) => {
|
||||
cartStore.addGoodsItemToCart({ ...state.detail, ...sku, productId: sku.skuId })
|
||||
cartStore.updateShopCart()
|
||||
state.showSkuDialog = false
|
||||
}
|
||||
|
||||
const doCart = async () => {
|
||||
const goCart = async () => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
router.jump("/pages/shop/cart")
|
||||
}
|
||||
if (isLogin) router.jump("/pages/online-shop/cart")
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<crud-layout title="订单详情" mainIsScroll tabBarType="just-flot-back" bg="/static/page/blue-bg.png">
|
||||
<crud-layout title="订单详情" mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<swiper
|
||||
class="w-100vw h-30vh"
|
||||
circular
|
||||
@@ -66,63 +63,56 @@ const doCart = async () => {
|
||||
</swiper>
|
||||
|
||||
<view class="p-15rpx">
|
||||
<view class="flex-col justify-between card p-y-20rpx p-x-30rpx">
|
||||
<view class="prefix_title mb-15rpx">商品</view>
|
||||
<view class="px-3">{{ state.detail?.productName }}</view>
|
||||
<view class="flex-row items-center justify-between m-t-15rpx px-3">
|
||||
<view class="second-text-26">限购:{{ state.detail?.limitNum || 0 }}</view>
|
||||
<view class="second-text-26">已售:{{ state.detail?.salesCount || 0 }}</view>
|
||||
<view class="flex-col justify-between card py-20rpx px-30rpx mb-2">
|
||||
<view class="prefix_title mb-15rpx">商品信息</view>
|
||||
<view class="px-3 sub-text-32 truncate w-full">{{ state.detail?.productName }}</view>
|
||||
<view class="flex-row items-center justify-end mt-15rpx px-3 gap-4 second-text-26">
|
||||
<view>限购 {{ state.detail?.limitNum || 0 }}</view>
|
||||
<view>已售 {{ state.detail?.salesCount || 0 }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="h-15rpx" />
|
||||
<view class="flex-col justify-between card p-y-20rpx p-x-30rpx">
|
||||
<view class="prefix_title mb-15rpx">包装</view>
|
||||
<view class="flex-col justify-between card py-20rpx px-30rpx mb-2">
|
||||
<view class="prefix_title mb-15rpx">包装单位</view>
|
||||
<view class="flex flex-wrap px-2">
|
||||
<view class="w-1/2 flex justify-center p-1" v-for="(item, index) in state.detail?.skuList" :key="index">
|
||||
<view class="w-1/2 p-1" v-for="(item, index) in state.detail?.skuList" :key="index">
|
||||
<view class="p-2 w-full bg-#eeeeee77 rounded-2 flex-col second-text-28">
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>单位:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-80rpx mr-1">单位</text>
|
||||
<text>{{ item.spec + " / " + item.unit }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>口味:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-80rpx mr-1">口味</text>
|
||||
<text>{{ item?.attribute || "无" }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>库存:</text>
|
||||
<view class="flex mt-1">
|
||||
<text class="w-80rpx mr-1">库存</text>
|
||||
<text>{{ item?.stock || 0 }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>原价:</text>
|
||||
<text class="line-through">¥ {{ item.retailPrice.toFixed(2) || 0 }}</text>
|
||||
<view v-if="item.promotionPrice" class="flex items-end mt-1">
|
||||
<text class="c-red! second-text-32 mr-2">¥{{ displayPrice(item.promotionPrice, "") || 0 }}</text>
|
||||
<text class="line-through">¥{{ displayPrice(item.retailPrice, "") || 0 }}</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1">
|
||||
<text>促销:</text>
|
||||
<text class="c-red!">¥ {{ item.promotionPrice.toFixed(2) || item.retailPrice.toFixed(2) || 0 }}</text>
|
||||
<view v-else class="flex items-end mt-1">
|
||||
<text class="second-text-32 c-red!">¥{{ displayPrice(item.retailPrice, "") || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="h-15rpx" />
|
||||
<view class="flex-col justify-between card p-y-20rpx p-x-30rpx">
|
||||
<view class="prefix_title mb-15rpx">评价</view>
|
||||
</view> -->
|
||||
<view class="h-15rpx" />
|
||||
<view class="flex-col justify-between card p-y-20rpx p-x-30rpx">
|
||||
<view class="prefix_title mb-15rpx">详情</view>
|
||||
<view class="flex-col justify-between card py-20rpx px-30rpx">
|
||||
<view class="prefix_title mb-15rpx">详情描述</view>
|
||||
<view class="second-text-28 px-3" v-html="state.detail?.description"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="h-80rpx" />
|
||||
<view class="h-50rpx" />
|
||||
|
||||
<template #main-bottom>
|
||||
<view class="flex-row justify-between items-center my-4 mx-12">
|
||||
<tm-badge bg-color="red" :label="cartStore.totCount">
|
||||
<view class="flex-col-center" @tap="doCart">
|
||||
<tm-badge bg-color="red" :label="cartStore.totalCount">
|
||||
<view class="flex-col-center" @tap="goCart">
|
||||
<view class="color-#0094ff! iconfont icon-biaoqianlanicon_gouwuche-xuanzhong" />
|
||||
<view class="c-#0094ff text-20rpx">购物车</view>
|
||||
</view>
|
||||
@@ -133,7 +123,7 @@ const doCart = async () => {
|
||||
</crud-layout>
|
||||
|
||||
<!-- 单位与数量选择 -->
|
||||
<product-sku v-model="state.showSkuDialog" :productData="state.detail" @onSkuAddToCart="onSkuAddToCart" />
|
||||
<product-sku v-model="state.showSkuDialog" :goodsData="state.detail" @onSkuAddToCart="onSkuAddToCart" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -96,6 +96,10 @@ const doCancelRefund = async () => {
|
||||
<view>商户</view>
|
||||
<view>{{ state.detail.merchantName }}</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>
|
||||
@@ -116,12 +120,7 @@ const doCancelRefund = async () => {
|
||||
<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"
|
||||
@tap.stop="router.jump('/pages/merchant/product-detail', { id: v.productId })"
|
||||
>
|
||||
<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">
|
||||
|
||||
@@ -46,14 +46,14 @@ import { filePath } from "@/sheep/utils/file"
|
||||
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 loading = ref(false)
|
||||
const scanPayState = reactive({
|
||||
mchId: "", // 商户id
|
||||
mchName: "", // 商户名
|
||||
orderId: "", // 订单id
|
||||
action: "",
|
||||
action: "", // action === "harQrCodeScanPay":扫收款机上生成的码支付
|
||||
})
|
||||
const walletList = ref<any>([]) // 钱包列表
|
||||
const selectWallet = ref<any>({}) // 当前选中的钱包
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<view class="h-40rpx"></view>
|
||||
<view class="card flex justify-between items-center mb-2 mx-20rpx p-4" v-for="item in categoryList" :key="item.id">
|
||||
<image :src="filePath(item.picUrl)" class="size-100rpx mr-4" />
|
||||
<view class="flex-1 flex-col">
|
||||
<view class="flex justify-between items-center">
|
||||
<text class="sub-text-30">{{ item.name }}</text>
|
||||
<text class="c-#0094ff! sub-text-24 px-2 bg-#0094ff22 rounded-2">{{ item.status == "0" ? "正常" : "禁用" }}</text>
|
||||
</view>
|
||||
<view class="flex justify-end items-center gap-2 mt-2">
|
||||
<tm-button class="px-2" size="s" :linear="['to right', '#62abff', '#0779FD99']" @click="editCategary(item)">编辑</tm-button>
|
||||
<tm-button class="px-2" size="s" :linear="['to right', '#FFC328', '#E2000099']" @click="delCategary(item.id)">删除</tm-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-100rpx"></view>
|
||||
|
||||
<view class="fixed bottom-80rpx left-40rpx">
|
||||
<tm-button class="px-2" size="m" :linear="['to right', '#0779FD', '#62abff']" @click="addCategary">新增分类</tm-button>
|
||||
</view>
|
||||
|
||||
<tm-drawer
|
||||
title="分类"
|
||||
:showClose="true"
|
||||
:showFooter="false"
|
||||
size="90%"
|
||||
v-model:show="categaryFormDrawer"
|
||||
:duration="100"
|
||||
zIndex="100"
|
||||
bgColor="#F5F5F5"
|
||||
>
|
||||
<view class="card p-4 mb-2">
|
||||
<view class="prefix_title mb-2">基本信息</view>
|
||||
<tm-form @submit="saveCategary" v-model="form" :rules="[]" direction="horizontal">
|
||||
<tm-form-item name="name" label="分类名称">
|
||||
<tm-input v-model="form.name" type="text" :showClear="true" placeholder="请输入分类名称"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="status" label="状态">
|
||||
<tm-radio-group v-model="form.status" align="flex-end">
|
||||
<tm-radio
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
v-for="(item, index) in [
|
||||
{ label: '正常', value: '0' },
|
||||
{ label: '下架', value: '1' },
|
||||
]"
|
||||
:key="index"
|
||||
></tm-radio>
|
||||
</tm-radio-group>
|
||||
</tm-form-item>
|
||||
<view class="prefix_title mb-2">父级分类</view>
|
||||
<view class="my-2">
|
||||
<node-tree v-model="selectedNode" :node-data="categoryTree" />
|
||||
</view>
|
||||
<view class="prefix_title mb-2">分类图</view>
|
||||
<tm-upload-photo
|
||||
v-model="form.picUrl"
|
||||
:maxFileSize="1024 * 1024 * 5"
|
||||
:url="'/api/admin/sys-file/upload'"
|
||||
:header="hooks.useDefaultHeader()"
|
||||
:column="1.6"
|
||||
img-height="360"
|
||||
:max-count="1"
|
||||
/>
|
||||
<view class="sub-text-20 c-red! mb-2">注:图片大小不超过5M</view>
|
||||
<tm-button form-type="submit" block>保 存</tm-button>
|
||||
</tm-form>
|
||||
</view>
|
||||
</tm-drawer>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { reactive, ref } from "vue"
|
||||
import NodeTree from "@/sheep/components/node-tree.vue"
|
||||
import { useCategaryManage } from "./use-categary-manage"
|
||||
import { hooks } from "@@/index"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
|
||||
const categaryFormDrawer = ref(false)
|
||||
const form = reactive<any>({
|
||||
id: "",
|
||||
name: "",
|
||||
status: "0", // 状态(0正常,1下架)
|
||||
parentId: "",
|
||||
picUrl: [],
|
||||
})
|
||||
const categoryTree = ref([])
|
||||
const selectedNode = ref<any>({})
|
||||
|
||||
const { categoryList, getGoodsCategory, delCategary } = useCategaryManage()
|
||||
|
||||
onLoad(() => {
|
||||
getGoodsCategory()
|
||||
})
|
||||
|
||||
const getCategaryTree = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategoryTree()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
categoryTree.value = data ?? []
|
||||
}
|
||||
|
||||
// 新增分类
|
||||
const addCategary = () => {
|
||||
getCategaryTree()
|
||||
Object.assign(form, {
|
||||
id: "",
|
||||
name: "",
|
||||
status: "0",
|
||||
parentId: "",
|
||||
picUrl: [],
|
||||
})
|
||||
selectedNode.value = {}
|
||||
categaryFormDrawer.value = true
|
||||
}
|
||||
|
||||
// 编辑分类
|
||||
const editCategary = (item) => {
|
||||
getCategaryTree()
|
||||
const picUrl = [{ path: filePath(item.picUrl), response: JSON.stringify({ data: { url: item.picUrl } }) }]
|
||||
Object.assign(form, { ...item, picUrl })
|
||||
selectedNode.value = { id: item.parentId }
|
||||
categaryFormDrawer.value = true
|
||||
}
|
||||
|
||||
// 提交保存
|
||||
const saveCategary = async () => {
|
||||
if (!form.name) return uni.showToast({ title: "请填写分类名称", icon: "none" })
|
||||
if (selectedNode.value.id === form.id) return uni.showToast({ title: "父级分类不能是自己", icon: "none" })
|
||||
try {
|
||||
const params = {
|
||||
...form,
|
||||
parentId: selectedNode.value.id,
|
||||
picUrl: JSON.parse(form.picUrl[0].response).data.url,
|
||||
}
|
||||
uni.showLoading({ title: "保存中..." })
|
||||
const { code, msg } = await (form.id ? prosperityShopApi.editCategary(params) : prosperityShopApi.addCategary(params))
|
||||
if (code != 0) return uni.showToast({ title: msg || "保存失败", icon: "none" })
|
||||
uni.showToast({ title: "保存成功", icon: "none" })
|
||||
categaryFormDrawer.value = false
|
||||
await getGoodsCategory()
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<image :src="filePath(state.detail.coverImage)" class="w-full h-500rpx bg-#eeeeee" mode="aspectFill" />
|
||||
<view v-if="state.detail.id" class="card p-20rpx mt-10rpx mx-20rpx bg-gradient-to-b from-#0094ff33 to-#fff">
|
||||
<view v-if="state.detail.priceType == '2'" class="w-full flex justify-between items-center">
|
||||
<view class="flex items-center">
|
||||
<text class="second-text-34 line-through">¥</text>
|
||||
<text class="font-bold second-text-40 line-through mr-2">{{ displayPrice(state.detail.originalPrice, "") }}</text>
|
||||
<text class="text-34rpx color-red!">¥</text>
|
||||
<text class="text-46rpx font-bold color-red!">{{ displayPrice(state.detail.spuPrice, "") }}</text>
|
||||
<text class="view-clip px-2 text-22rpx c-white bg-red ml-2 rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
<view class="main-text-32 c-red! font-italic">
|
||||
<text>约省</text>
|
||||
<text class="text-44rpx mx-1">{{ displayPrice(Number(state.detail.originalPrice) - Number(state.detail.spuPrice), "") }}</text>
|
||||
<text>元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="w-full flex justify-start items-center">
|
||||
<text class="text-34rpx color-red!">¥</text>
|
||||
<text class="text-46rpx font-bold color-red!">{{ displayPrice(state.detail.skuList[0].price, "") }}</text>
|
||||
<text class="view-clip px-2 text-22rpx c-white bg-red ml-2 rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
<view class="w-full sub-text-36 flex items-center mt-1">
|
||||
<text class="c-#0094ff! sub-text-24 px-2 bg-#0094ff22 rounded-1 mr-2 whitespace-nowrap">
|
||||
{{ state.detail.status == "0" ? "在售" : "未售" }}
|
||||
</text>
|
||||
<text class="truncate">{{ state.detail.title }}</text>
|
||||
</view>
|
||||
<view class="second-text-26 mt-1">售卖时间 {{ state.detail.saleStartTime }} ~ {{ state.detail.saleEndTime }}</view>
|
||||
<view class="second-text-26 truncate mt-1">
|
||||
<text class="mr-20rpx">售卖单位 {{ state.detail.unit }}</text>
|
||||
<text class="mr-20rpx">剩余库存 {{ state.detail.totalStock }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="state.detail.priceType == '1'" class="mt-20rpx card p-20rpx mx-20rpx">
|
||||
<view class="prefix_title">包装单位</view>
|
||||
<view class="bg-#1879ff09 rounded-1 my-2 p-3" v-for="(item, index) in state.detail.skuList" :key="index">
|
||||
<view class="sub-text-30">{{ item.skuName }}</view>
|
||||
<view class="second-text-28 mt-1 flex justify-between items-center">
|
||||
<text>售价 {{ displayPrice(item.price) }}</text>
|
||||
<text>重量 {{ item.weightG }}克</text>
|
||||
<text>库存 {{ item.stock }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-20rpx card p-20rpx mx-20rpx">
|
||||
<view class="prefix_title">商品描述</view>
|
||||
<view class="mt-20rpx overflow-auto text-26rpx!" v-html="state.detail.description"></view>
|
||||
</view>
|
||||
<view class="h-30rpx" />
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { reactive } from "vue"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { displayPrice } from "@/sheep/utils/formatMoney"
|
||||
|
||||
const state = reactive<any>({
|
||||
id: "",
|
||||
detail: {},
|
||||
})
|
||||
onLoad(async (option: any) => {
|
||||
if (!option.id) return uni.showToast({ title: "参数错误", icon: "error" })
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
state.id = option.id
|
||||
await getGoodsDetail(option.id)
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
const getGoodsDetail = async (id: string) => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsDetail(id)
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品详情获取失败", icon: "none" })
|
||||
state.detail = data
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep() {
|
||||
.layout_main > scroll-view {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
.view-clip {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18rpx;
|
||||
bottom: 0rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 0, transparent 80%, red 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<crud-layout title="" mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<view class="h-30rpx" />
|
||||
<tm-form @submit="onsubmit" v-model="form" :rules="rules" direction="horizontal">
|
||||
<view class="card p-20rpx mb-20rpx">
|
||||
<view class="prefix_title mb-2">商品基本信息</view>
|
||||
<tm-form-item name="title" label="商品标题">
|
||||
<tm-input v-model="form.title" type="text" :showClear="true" placeholder="请输入商品标题"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="unit" label="计量单位">
|
||||
<tm-input v-model="form.unit" type="text" :showClear="true" placeholder="请输入计量单位"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="categoryId" label="分类" @click="selectCategary">
|
||||
<tm-input v-model="form.categoryName" placeholder="请选择商品分类" style="pointer-events: none" disabled />
|
||||
<!-- color="transparent" -->
|
||||
</tm-form-item>
|
||||
<tm-form-item name="status" label="状态">
|
||||
<tm-radio-group v-model="form.status" align="flex-end">
|
||||
<tm-radio :value="index.toString()" :label="item" v-for="(item, index) in ['上架', '下架']" :key="index"></tm-radio>
|
||||
</tm-radio-group>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="saleStartTime" label="开售时间">
|
||||
<tm-picker-date
|
||||
v-model="form.saleStartTime"
|
||||
v-model:modelStr="_saleStartTimeStr"
|
||||
start="2024-01-01 00:00:00"
|
||||
end="2090-01-01 00:00:00"
|
||||
>
|
||||
<tm-input
|
||||
:modelValue="_saleStartTimeStr"
|
||||
placeholder="请选择开售时间"
|
||||
style="pointer-events: none"
|
||||
disabled
|
||||
align="right"
|
||||
color="transparent"
|
||||
>
|
||||
<template v-slot:rightIcon>
|
||||
<tm-icon name="arrow-right-s-line"></tm-icon>
|
||||
</template>
|
||||
</tm-input>
|
||||
</tm-picker-date>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="saleEndTime" label="停售时间">
|
||||
<tm-picker-date
|
||||
v-model="form.saleEndTime"
|
||||
v-model:modelStr="_saleEndTimeStr"
|
||||
start="2024-01-01 00:00:00"
|
||||
end="2090-01-01 00:00:00"
|
||||
>
|
||||
<tm-input
|
||||
:modelValue="_saleEndTimeStr"
|
||||
placeholder="请选择停售时间"
|
||||
style="pointer-events: none"
|
||||
disabled
|
||||
align="right"
|
||||
color="transparent"
|
||||
>
|
||||
<template v-slot:rightIcon>
|
||||
<tm-icon name="arrow-right-s-line"></tm-icon>
|
||||
</template>
|
||||
</tm-input>
|
||||
</tm-picker-date>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="coverImage" label="商品图">
|
||||
<tm-upload-photo
|
||||
v-model="form.coverImage"
|
||||
:maxFileSize="1024 * 1024 * 5"
|
||||
:url="'/api/admin/sys-file/upload'"
|
||||
:header="hooks.useDefaultHeader()"
|
||||
:column="1.2"
|
||||
img-height="360"
|
||||
:max-count="1"
|
||||
/>
|
||||
</tm-form-item>
|
||||
<view class="sub-text-20 c-red! mb-2">注:图片大小不超过5M</view>
|
||||
<tm-form-item name="description" label="详情描述" direction="vertical" required="">
|
||||
<!-- <rich-text-input v-model="form.description" placeholder="点击编辑详情描述..." label="详情描述" /> -->
|
||||
<tm-input v-model="form.description" type="textarea" :showClear="false" placeholder="请输入详情描述"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="priceType" label="定价方式">
|
||||
<tm-radio-group v-model="form.priceType" align="flex-end">
|
||||
<tm-radio
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
v-for="(item, index) in [
|
||||
{ label: '多SKU模式', value: '1' },
|
||||
{ label: '单价SPU模式', value: '2' },
|
||||
]"
|
||||
:key="index"
|
||||
></tm-radio>
|
||||
</tm-radio-group>
|
||||
</tm-form-item>
|
||||
<view v-if="form.priceType == '2'">
|
||||
<tm-form-item name="spuPrice" label="SPU售价(元)" labelWidth="200">
|
||||
<view class="flex justify-end">
|
||||
<tm-stepper :max="10000" width="300" v-model="form.spuPrice"></tm-stepper>
|
||||
</view>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="originalPrice" label="划线价(元)" labelWidth="200">
|
||||
<view class="flex justify-end">
|
||||
<tm-stepper :max="10000" width="300" v-model="form.originalPrice"></tm-stepper>
|
||||
</view>
|
||||
</tm-form-item>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="form.priceType == '1'" class="card p-20rpx mb-20rpx">
|
||||
<view class="prefix_title mb-2">商品SKU信息</view>
|
||||
<view v-for="(item, index) in form.skuList" :key="index">
|
||||
<view v-if="index !== 0" class="my-4 h-3rpx bg-#A0A0A044"></view>
|
||||
<view class="flex justify-between items-center mb-2">
|
||||
<text>编号:{{ index + 1 }}</text>
|
||||
<tm-button v-if="index !== 0" class="px-2" size="xs" skin="thin" @click.stop="delSkuListItem(index)">删 除</tm-button>
|
||||
</view>
|
||||
<tm-form-item name="skuList[index].skuCode" label="SKU编码">
|
||||
<tm-input v-model="item.skuCode" placeholder="请输入SKU编码"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="skuList[index].skuName" label="SKU名称">
|
||||
<tm-input v-model="item.skuName" placeholder="请输入SKU名称"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="skuList[index].barcode" label="条码">
|
||||
<tm-input v-model="item.barcode" placeholder="请输入条码"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="skuList[index].price" label="售价(元)">
|
||||
<view class="flex justify-end">
|
||||
<tm-stepper :max="10000" width="300" v-model="item.price"></tm-stepper>
|
||||
</view>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="skuList[index].stock" label="库存">
|
||||
<view class="flex justify-end">
|
||||
<tm-stepper :max="10000" width="300" v-model="item.stock"></tm-stepper>
|
||||
</view>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="skuList[index].weightG" label="重量(克)">
|
||||
<view class="flex justify-end">
|
||||
<tm-stepper :max="10000" width="300" v-model="item.weightG"></tm-stepper>
|
||||
</view>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="skuList[index].status" label="状态">
|
||||
<tm-radio-group v-model="item.status" align="flex-end">
|
||||
<tm-radio :value="index.toString()" :label="item" v-for="(item, index) in ['启用', '禁用']" :key="index"></tm-radio>
|
||||
</tm-radio-group>
|
||||
</tm-form-item>
|
||||
</view>
|
||||
<tm-button block size="s" skin="dashed" color="#E5F1FF" @click.stop="addSkuListItem()">添 加</tm-button>
|
||||
</view>
|
||||
|
||||
<tm-button form-type="submit" block>提 交</tm-button>
|
||||
</tm-form>
|
||||
|
||||
<tm-drawer
|
||||
title="请选择分类"
|
||||
:showClose="true"
|
||||
:showFooter="false"
|
||||
size="70%"
|
||||
v-model:show="showCategaryPicker"
|
||||
:duration="100"
|
||||
zIndex="100"
|
||||
>
|
||||
<shop-categary-picker :categoryTree="categoryTree" @confirm="onCategaryConfirm" @cancel="showCategaryPicker = false" />
|
||||
</tm-drawer>
|
||||
|
||||
<view class="h-30rpx" />
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue"
|
||||
import { hooks } from "@@/index"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import ShopCategaryPicker from "@/sheep/components/shop-categary-picker.vue"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { displayPrice, storePrice } from "@/sheep/utils/formatMoney"
|
||||
|
||||
const form = reactive<any>({
|
||||
id: "",
|
||||
title: "",
|
||||
unit: "",
|
||||
categoryId: "",
|
||||
categoryName: "",
|
||||
status: "0",
|
||||
saleStartTime: "",
|
||||
saleEndTime: "",
|
||||
coverImage: [],
|
||||
description: "",
|
||||
priceType: "1", // 定价方式(1:多SKU,2:单价SPU)
|
||||
spuPrice: 0,
|
||||
originalPrice: 0,
|
||||
skuList: [
|
||||
{
|
||||
skuCode: "",
|
||||
skuName: "",
|
||||
price: 0,
|
||||
costPrice: 0,
|
||||
stock: 0,
|
||||
barcode: "",
|
||||
weightG: 0,
|
||||
volumeCm3: 0,
|
||||
status: "0",
|
||||
},
|
||||
],
|
||||
})
|
||||
const _saleStartTimeStr = ref("")
|
||||
const _saleEndTimeStr = ref("")
|
||||
const showCategaryPicker = ref(false)
|
||||
const categoryTree = ref([])
|
||||
const categoryList = ref<any>([])
|
||||
|
||||
const rules: Record<string, TM.FORM_RULE[]> = {
|
||||
title: [{ required: true, message: "请输入商品标题" }],
|
||||
unit: [{ required: true, message: "请输入商品单位" }],
|
||||
categoryId: [{ required: true, message: "请选择商品分类" }],
|
||||
status: [{ required: true, message: "请选择状态" }],
|
||||
saleStartTime: [{ required: true, message: "请选择售卖开始时间" }],
|
||||
saleEndTime: [{ required: true, message: "请选择售卖结束时间" }],
|
||||
coverImage: [{ required: true, message: "请上传商品图" }],
|
||||
}
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
const { id = "" } = options
|
||||
if (!id) return
|
||||
await getGoodsCategory()
|
||||
await getGoodsDetail(id)
|
||||
})
|
||||
|
||||
const getGoodsDetail = async (id: string) => {
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsDetail(id)
|
||||
if (code != 0 || !data) return uni.showToast({ title: msg || "商品详情获取失败", icon: "none" })
|
||||
const skuList = data.skuList.map((item: any) => ({ ...item, status: item.status.toString(), price: displayPrice(item.price, "") }))
|
||||
const coverImage = [{ path: filePath(data.coverImage), response: JSON.stringify({ data: { url: data.coverImage } }) }]
|
||||
const categoryName = categoryList.value.find((item: any) => item.id == data.categoryId)?.name
|
||||
const saleStartTime = data.saleStartTime
|
||||
const saleEndTime = data.saleEndTime
|
||||
_saleStartTimeStr.value = saleStartTime
|
||||
_saleEndTimeStr.value = saleEndTime
|
||||
|
||||
Object.assign(form, {
|
||||
...data,
|
||||
status: data.status.toString(),
|
||||
priceType: data.priceType.toString(),
|
||||
spuPrice: displayPrice(data.spuPrice, ""),
|
||||
originalPrice: displayPrice(data.originalPrice, ""),
|
||||
coverImage,
|
||||
categoryName,
|
||||
saleStartTime,
|
||||
saleEndTime,
|
||||
skuList,
|
||||
})
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
const getGoodsCategory = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategory()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
categoryList.value = data
|
||||
}
|
||||
|
||||
const selectCategary = () => {
|
||||
getCategaryTree()
|
||||
showCategaryPicker.value = true
|
||||
}
|
||||
|
||||
const getCategaryTree = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategoryTree()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
categoryTree.value = data ?? []
|
||||
}
|
||||
|
||||
const onCategaryConfirm = (category: { categoryId: string; categoryName: string }) => {
|
||||
form.categoryId = category.categoryId
|
||||
form.categoryName = category.categoryName
|
||||
showCategaryPicker.value = false
|
||||
}
|
||||
|
||||
const delSkuListItem = (index: number) => {
|
||||
form.skuList.splice(index, 1)
|
||||
}
|
||||
|
||||
const addSkuListItem = () => {
|
||||
form.skuList.push({
|
||||
skuCode: "",
|
||||
skuName: "",
|
||||
price: 0,
|
||||
costPrice: 0,
|
||||
stock: 0,
|
||||
barcode: "",
|
||||
weightG: 0,
|
||||
volumeCm3: 0,
|
||||
status: "0",
|
||||
})
|
||||
}
|
||||
|
||||
// 保存
|
||||
const onsubmit = async (evt: TM.FORM_SUBMIT_RESULT) => {
|
||||
// console.log("校验结果:", evt)
|
||||
|
||||
const submitData = {
|
||||
...form,
|
||||
coverImage: JSON.parse(form.coverImage[0].response).data.url,
|
||||
}
|
||||
console.log("提交的数据:", submitData)
|
||||
|
||||
if (!evt.isPass) return
|
||||
|
||||
if (form.priceType == "1") {
|
||||
for (let i = 0; i < form.skuList.length; i++) {
|
||||
const skuItem = form.skuList[i]
|
||||
for (const key in skuItem) {
|
||||
if (skuItem.hasOwnProperty(key) && skuItem[key] === "") return uni.showToast({ title: "请填写完整SKU信息", icon: "none" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (form.priceType == "2") {
|
||||
form.skuList = []
|
||||
}
|
||||
|
||||
try {
|
||||
const params = {
|
||||
...form,
|
||||
spuPrice: storePrice(form.spuPrice),
|
||||
originalPrice: storePrice(form.originalPrice),
|
||||
skuList: form.skuList.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
price: storePrice(item.price),
|
||||
}
|
||||
}),
|
||||
coverImage: JSON.parse(form.coverImage[0].response).data.url,
|
||||
}
|
||||
uni.showLoading({ title: "提交中..." })
|
||||
const { code, msg } = await (form.id ? prosperityShopApi.editGoods(params) : prosperityShopApi.addGoods(params))
|
||||
if (code != 0) return uni.showToast({ title: msg || "提交失败", icon: "none" })
|
||||
uni.showToast({ title: "提交成功", icon: "success" })
|
||||
uni.navigateBack()
|
||||
} catch (error: any) {
|
||||
uni.showToast({ title: error.message || "提交失败", icon: "none" })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll 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>
|
||||
<tm-input
|
||||
class="mt-2"
|
||||
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"
|
||||
@clear="inputClear"
|
||||
>
|
||||
<template v-slot:rightIcon>
|
||||
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="search">搜 索</tm-button>
|
||||
</template>
|
||||
</tm-input>
|
||||
<scroll-view scroll-x>
|
||||
<view class="w-full flex my-2">
|
||||
<view
|
||||
class="w-300rpx mr-2 sub-text-28 py-1 px-2 rounded-1 bg-#fff border border-solid border-transparent whitespace-nowrap"
|
||||
:class="{ 'bg-#0779FD22! c-#0779FD! border-#0779FD33!': state.params.categoryId === v.id }"
|
||||
v-for="(v, i) in state.category"
|
||||
:key="i"
|
||||
@tap="doClassify(v.id)"
|
||||
>
|
||||
{{ v.name }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<view v-for="v in pageHook.list" :key="v.id" class="card mb-2 px-4 py-2">
|
||||
<view class="flex">
|
||||
<image
|
||||
:src="filePath(v.coverImage)"
|
||||
class="size-180rpx rounded-12rpx bg-#eeeeee"
|
||||
mode="aspectFill"
|
||||
@click.stop="hooks.usePreviewImage(filePath(v.coverImage))"
|
||||
/>
|
||||
<view class="flex-1 ml-2 overflow-hidden">
|
||||
<view class="truncate sub-text-32">{{ v.title }}</view>
|
||||
<view class="sub-text-24 my-6rpx">
|
||||
<text class="c-#0094ff! px-2 bg-#0094ff22 rounded-2 mr-2">
|
||||
{{ state.category.find((item) => item.id == v.categoryId)?.name || "未知分类" }}
|
||||
</text>
|
||||
<text class="c-#0094ff! px-2 bg-#0094ff22 rounded-2">{{ v.status == "0" ? "在售" : "未售" }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="second-text-26 mr-2">单位 {{ v.unit }}</text>
|
||||
<text class="second-text-26">库存 {{ v.totalStock }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<template v-if="v.priceType == '2'">
|
||||
<text class="second-text-32 line-through mr-1">
|
||||
<text class="text-30rpx">¥</text>
|
||||
<text class="font-bold">{{ displayPrice(v.originalPrice, "") }}</text>
|
||||
</text>
|
||||
<text class="color-red">
|
||||
<text class="text-30rpx">¥</text>
|
||||
<text class="text-40rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[0] }}.</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="color-red">
|
||||
<text class="text-30rpx">¥</text>
|
||||
<text class="text-40rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[0] }}.</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<text class="view-clip px-1 text-18rpx c-white bg-red ml-1 rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="my-2 h-1rpx bg-#A0A0A044"></view>
|
||||
<view class="flex gap-2 justify-end">
|
||||
<tm-button class="px-2" size="s" :linear="['to right', '#FFC328', '#E2000099']" @click.stop="deleteGoods(v.id)">
|
||||
删除商品
|
||||
</tm-button>
|
||||
<tm-button class="px-2" size="s" :linear="['to right', '#62abff', '#0779FD99']" @click.stop="editGoods(v.id)">
|
||||
编辑商品
|
||||
</tm-button>
|
||||
<tm-button class="px-2" size="s" :linear="['to right', '#00B960', '#00552C99']" @click.stop="doGoodsDetail(v.id)">
|
||||
查看详情
|
||||
</tm-button>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
<view class="fixed bottom-80rpx left-40rpx flex-col gap-2">
|
||||
<tm-button class="px-2" size="m" :linear="['to right', '#0779FD', '#62abff']" @click="addGoods">新增商品</tm-button>
|
||||
<tm-button class="px-2" size="m" :linear="['to right', '#0779FD', '#62abff']" @click="categaryManage">分类管理</tm-button>
|
||||
</view>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { hooks } from "@/sheep"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { displayPrice } from "@/sheep/utils/formatMoney"
|
||||
import { useGoodsManage } from "./use-goods-manage"
|
||||
|
||||
const state = reactive<any>({
|
||||
params: {
|
||||
keyword: "",
|
||||
categoryId: "",
|
||||
},
|
||||
category: [],
|
||||
curCategoryIndex: 0,
|
||||
})
|
||||
|
||||
const pageRef = ref<any>(null)
|
||||
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getManageGoodsList, null, "data.records")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.params)
|
||||
}
|
||||
|
||||
const { addGoods, editGoods, deleteGoods, doGoodsDetail, categaryManage } = useGoodsManage(pageRef)
|
||||
|
||||
onLoad(async () => {
|
||||
await getGoodsCategory()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
})
|
||||
|
||||
const getGoodsCategory = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategory()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
state.category = data ?? []
|
||||
state.category.unshift({ id: "", name: "全部分类" })
|
||||
}
|
||||
|
||||
// 切换分类
|
||||
const doClassify = async (categoryId: string) => {
|
||||
if (state.params.categoryId == categoryId) return
|
||||
state.params.categoryId = categoryId
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
// 点击搜索
|
||||
const search = () => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
const inputClear = () => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.view-clip {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18rpx;
|
||||
bottom: 0rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 0, transparent 80%, red 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll 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="w-full my-2">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<view v-for="v in pageHook.list" :key="v.id" class="card flex mx-20rpx mb-2 p-2" @click="doGoodsDetail(v.id)">
|
||||
<image :src="filePath(v.coverImage)" class="size-160rpx rounded-12rpx bg-#eeeeee" mode="aspectFill" />
|
||||
<view class="flex-1 ml-2 overflow-hidden">
|
||||
<view class="truncate sub-text-30">{{ v.title }}</view>
|
||||
<view>
|
||||
<text class="c-#0094ff! sub-text-24 px-2 bg-#0094ff22 rounded-2">{{ v.status == "0" ? "在售" : "未售" }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="second-text-26 mr-2">单位 {{ v.unit }}</text>
|
||||
<text class="second-text-26">库存 {{ v.totalStock }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<template v-if="v.priceType == '2'">
|
||||
<text class="second-text-28 line-through mr-1">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="font-bold">{{ displayPrice(v.originalPrice, "") }}</text>
|
||||
</text>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[0] }}.</text>
|
||||
<text class="text-30rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[0] }}.</text>
|
||||
<text class="text-30rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<text class="view-clip px-1 text-18rpx c-white bg-red ml-1 rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { hooks } from "@/sheep"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { reactive, ref } from "vue"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { displayPrice } from "@/sheep/utils/formatMoney"
|
||||
|
||||
const state = reactive<any>({
|
||||
params: {
|
||||
keyword: "",
|
||||
merchantId: "",
|
||||
},
|
||||
})
|
||||
const pageRef = ref<any>(null)
|
||||
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getGoodsList, null, "data.records")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.params)
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
const { merchantId } = options
|
||||
if (!merchantId) return uni.showToast({ title: "参数错误", icon: "none" })
|
||||
state.params.merchantId = merchantId
|
||||
})
|
||||
|
||||
// 点击搜索
|
||||
const search = () => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
const doGoodsDetail = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.navigateTo({
|
||||
url: `/pages/prosperity-shop/goods-detail?id=${id}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png" @scroll="onScroll">
|
||||
<view class="w-full h-400rpx relative">
|
||||
<image :src="filePath(state.detail.images)" class="w-full h-full bg-#eeeeee" mode="aspectFill" />
|
||||
<view class="absolute w-full h-full top-0 left-0" :style="filterStyle"></view>
|
||||
</view>
|
||||
<view
|
||||
class="fixed right-20 h-72rpx bg-#ECF4F9cc px-20rpx top-20 flex items-center flex-nowrap overflow-hidden rounded-20rpx"
|
||||
:style="searchStyle"
|
||||
@click="goSearch"
|
||||
>
|
||||
<image class="w-36rpx h-36rpx" src="@/static/page/shop-goods-search.png" />
|
||||
<text v-if="scrollDetail.scrollTop > 30" class="bg-#eeeeee44 whitespace-nowrap overflow-hidden second-text-26 ml-2">搜索商品</text>
|
||||
</view>
|
||||
<view v-if="state.detail.id" class="card p-20rpx mt-10rpx mx-20rpx">
|
||||
<view class="w-full sub-text-32 truncate">{{ state.detail.name }}</view>
|
||||
<view class="sub-text-24 mt-1">
|
||||
<text class="c-#0094ff! mr-20rpx px-2 bg-#0094ff22 rounded-2">{{ state.detail.status == "0" ? "开业中" : "未开业" }}</text>
|
||||
<text class="rounded-2 c-#0094ff px-2 bg-#0094ff22 rounded-2">{{ state.detail.businessHours }}</text>
|
||||
</view>
|
||||
<view class="second-text-24 truncate mt-1">
|
||||
<text class="mr-20rpx">营业人 {{ state.detail.contactName }}·{{ state.detail.contactPhone }}</text>
|
||||
<text class="mr-20rpx">许可 {{ state.detail.licenseNo }}</text>
|
||||
</view>
|
||||
<view class="second-text-26 mt-1">
|
||||
<image class="w-24rpx h-24rpx mr-1" src="@/static/page/address.svg" />
|
||||
<text>{{ state.detail.address }}</text>
|
||||
</view>
|
||||
<marquee class="second-text-24 mt-1">{{ state.detail.remark }}</marquee>
|
||||
</view>
|
||||
|
||||
<view class="flex mt-2">
|
||||
<scroll-view scroll-y class="menu w-200rpx mb-20rpx bg-#f6f6f6 pl-20rpx" style="height: calc(100vh - 420rpx)">
|
||||
<view
|
||||
class="menu_item flex-row-center"
|
||||
v-for="(v, i) in state.category"
|
||||
:key="v.id"
|
||||
:class="[{ menu_item_active: i == state.curCategoryIndex }]"
|
||||
@tap="doCategory(i)"
|
||||
>
|
||||
<view class="menu_item_title show-line">{{ v.name }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view
|
||||
v-if="state.goodsList.length > 0"
|
||||
scroll-y
|
||||
class="flex-1 overflow-hidden pb-20rpx flex"
|
||||
style="height: calc(100vh - 420rpx)"
|
||||
>
|
||||
<view v-for="v in state.goodsList" :key="v.id" class="card flex mx-15rpx mb-2 p-2" @click="doGoodsDetail(v.id)">
|
||||
<image :src="filePath(v.coverImage)" class="size-160rpx rounded-12rpx bg-#eeeeee" mode="aspectFill" />
|
||||
<view class="flex-1 ml-2 overflow-hidden">
|
||||
<view class="truncate sub-text-30">{{ v.title }}</view>
|
||||
<view>
|
||||
<text class="c-#0094ff! sub-text-24 px-2 bg-#0094ff22 rounded-2">{{ v.status == "0" ? "在售" : "未售" }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="second-text-26 mr-2">单位 {{ v.unit }}</text>
|
||||
<text class="second-text-26">库存 {{ v.totalStock }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<template v-if="v.priceType == '2'">
|
||||
<text class="second-text-28 line-through mr-1">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="font-bold">{{ displayPrice(v.originalPrice, "") }}</text>
|
||||
</text>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[0] }}.</text>
|
||||
<text class="text-30rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[0] }}.</text>
|
||||
<text class="text-30rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<text class="view-clip px-1 text-18rpx c-white bg-red ml-1 rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-else class="mx-auto mt-10 second-text-28">暂无商品</view>
|
||||
</view>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from "vue"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import treeToList from "@/sheep/utils/tree-to-list"
|
||||
import { displayPrice } from "@/sheep/utils/formatMoney"
|
||||
import { useShopDetailScroll } from "./use-shop-detail-scroll"
|
||||
|
||||
const state = reactive<any>({
|
||||
id: "",
|
||||
detail: {},
|
||||
category: [],
|
||||
curCategoryIndex: 0,
|
||||
goodsList: [],
|
||||
})
|
||||
const { scrollDetail, onScroll, filterStyle, searchStyle } = useShopDetailScroll()
|
||||
|
||||
onLoad(async (option: any) => {
|
||||
if (!option.id) return uni.showToast({ title: "参数错误", icon: "error" })
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
state.id = option.id
|
||||
await getShopDetail(option.id)
|
||||
await getGoodsCategoryTree(option.id)
|
||||
await getGoodsList()
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
const getShopDetail = async (id: string) => {
|
||||
const { code, data, msg } = await prosperityShopApi.getShopDetail(id)
|
||||
if (code != 0) return uni.showToast({ title: msg || "超市详情获取失败", icon: "none" })
|
||||
state.detail = data
|
||||
}
|
||||
|
||||
const getGoodsCategoryTree = async (id: string) => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategoryTree({ merchantId: id })
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
state.category = treeToList(data) ?? []
|
||||
}
|
||||
|
||||
const getGoodsList = async () => {
|
||||
const params = {
|
||||
current: 1,
|
||||
size: 99999,
|
||||
merchantId: state.id,
|
||||
categoryId: state.category[state.curCategoryIndex].id,
|
||||
}
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsList(params)
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品列表获取失败", icon: "none" })
|
||||
state.goodsList = data?.records ?? []
|
||||
}
|
||||
|
||||
const doCategory = async (index: number) => {
|
||||
if (state.curCategoryIndex == index) return
|
||||
state.curCategoryIndex = index
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
await getGoodsList()
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
const doGoodsDetail = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.navigateTo({
|
||||
url: `/pages/prosperity-shop/goods-detail?id=${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
const goSearch = () => {
|
||||
if (!state.detail.id) return uni.showToast({ title: "超市id不存在", icon: "none" })
|
||||
uni.navigateTo({ url: `/pages/prosperity-shop/goods-search?merchantId=${state.detail.id}` })
|
||||
}
|
||||
</script>
|
||||
|
||||
<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.view-clip {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18rpx;
|
||||
bottom: 0rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 0, transparent 80%, red 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { ref } from "vue"
|
||||
|
||||
export function useCategaryManage() {
|
||||
const categoryList = ref<any>([])
|
||||
|
||||
// 获取商品分类
|
||||
const getGoodsCategory = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategory()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
categoryList.value = data
|
||||
}
|
||||
// 删除分类
|
||||
const delCategary = async (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "分类id不存在", icon: "none" })
|
||||
uni.showModal({
|
||||
title: "确定删除分类吗?",
|
||||
content: "",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: "正在删除" })
|
||||
const { code, msg } = await prosperityShopApi.delCategary(id)
|
||||
if (code != 0) return uni.showToast({ title: msg || "删除失败", icon: "none" })
|
||||
uni.showToast({ title: "删除成功", icon: "none" })
|
||||
getGoodsCategory()
|
||||
uni.hideLoading()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return { categoryList, getGoodsCategory, delCategary }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
|
||||
export function useGoodsManage(pageRef) {
|
||||
// 新增商品
|
||||
const addGoods = () => {
|
||||
uni.navigateTo({ url: `/pages/prosperity-shop/goods-form` })
|
||||
}
|
||||
// 编辑商品
|
||||
const editGoods = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.navigateTo({ url: `/pages/prosperity-shop/goods-form?id=${id}` })
|
||||
}
|
||||
// 删除商品
|
||||
const deleteGoods = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.showModal({
|
||||
title: "确定删除商品吗?",
|
||||
content: "",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const { code } = await prosperityShopApi.deleteGoods(id)
|
||||
if (code != 0) return uni.showToast({ title: "删除失败", icon: "error" })
|
||||
uni.showToast({ title: "删除成功", icon: "none" })
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
// 查看详情
|
||||
const doGoodsDetail = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.navigateTo({
|
||||
url: `/pages/prosperity-shop/goods-detail?id=${id}`,
|
||||
})
|
||||
}
|
||||
// 分类管理
|
||||
const categaryManage = () => {
|
||||
uni.navigateTo({ url: "/pages/prosperity-shop/categary-manage" })
|
||||
}
|
||||
|
||||
return { addGoods, editGoods, deleteGoods, doGoodsDetail, categaryManage }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { computed, ref } from "vue"
|
||||
|
||||
interface scrollDetail {
|
||||
deltaX: number
|
||||
deltaY: number
|
||||
scrollHeight: number
|
||||
scrollLeft: number
|
||||
scrollTop: number
|
||||
scrollWidth: number
|
||||
}
|
||||
|
||||
export function useShopDetailScroll() {
|
||||
const scrollDetail = ref<scrollDetail>({} as scrollDetail)
|
||||
|
||||
const onScroll = (e: any) => {
|
||||
scrollDetail.value = e.detail
|
||||
}
|
||||
|
||||
const filterStyle = computed(() => {
|
||||
if (!scrollDetail.value.scrollTop) return {}
|
||||
const scrollY = scrollDetail.value.scrollTop
|
||||
const blur = Math.min(1, scrollY / 400) * 10
|
||||
return {
|
||||
backdropFilter: `blur(${blur}rpx)`,
|
||||
}
|
||||
})
|
||||
|
||||
const searchStyle = computed(() => {
|
||||
if (!scrollDetail.value.scrollTop) return {}
|
||||
const minWidth = 10
|
||||
const scrollY = scrollDetail.value.scrollTop
|
||||
const scrollHeight = scrollDetail.value.scrollHeight
|
||||
const scrollWidth = scrollDetail.value.scrollWidth
|
||||
const calcWidth = Math.min((scrollY / scrollHeight) * scrollWidth, scrollWidth)
|
||||
if (isNaN(calcWidth)) return { width: `${minWidth}%` }
|
||||
if (calcWidth < 2) return { width: `${calcWidth + minWidth}%` }
|
||||
return {
|
||||
width: `${calcWidth + minWidth}%`,
|
||||
border: `2rpx solid #1879ff`,
|
||||
opacity: (calcWidth / 100) * 6,
|
||||
}
|
||||
})
|
||||
|
||||
return { scrollDetail, onScroll, filterStyle, searchStyle }
|
||||
}
|
||||
@@ -54,8 +54,8 @@ const doTab = (e: any) => {
|
||||
const toDetail = async (v: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
if (v.supType && v.supType == "10") return router.jump("/pages/shop/brandDetail", { parentId: v.id })
|
||||
if (v.supType && v.supType == "20") return router.jump("/pages/shop/detail", { id: v.id })
|
||||
if (v.supType && v.supType == "10") return router.jump("/pages/merchant/brandDetail", { parentId: v.id })
|
||||
if (v.supType && v.supType == "20") return router.jump("/pages/merchant/detail", { id: v.id })
|
||||
uni.showToast({ title: "合作商家类型未知", icon: "none" })
|
||||
}
|
||||
}
|
||||
@@ -84,18 +84,18 @@ const toDetail = async (v: any) => {
|
||||
class="flex-1"
|
||||
v-model="state.pa.name"
|
||||
type="text"
|
||||
height="66"
|
||||
minHeight="66"
|
||||
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="search">搜索</tm-button>
|
||||
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="search">搜 索</tm-button>
|
||||
</template>
|
||||
</tm-input>
|
||||
</view>
|
||||
|
||||
@@ -28,6 +28,12 @@ const moduleList = [
|
||||
iconPath: "/static/tabbar/online-shop-select.png",
|
||||
launchParam: "onlineShop",
|
||||
},
|
||||
{
|
||||
pagePath: "/pages/tabbar/prosperityShop",
|
||||
text: "共富商城",
|
||||
iconPath: "/static/tabbar/prosperity-shop-select.png",
|
||||
launchParam: "prosperityShop",
|
||||
},
|
||||
{
|
||||
pagePath: "/pages/tabbar/cabinet",
|
||||
text: "智能柜",
|
||||
@@ -106,6 +112,10 @@ const jumpByParam = async () => {
|
||||
uni.setStorageSync("launchParam", launchParam)
|
||||
return uni.reLaunch({ url: "/pages/tabbar/onlineShop" })
|
||||
}
|
||||
if (launchParam === "prosperityShop") {
|
||||
uni.setStorageSync("launchParam", launchParam)
|
||||
return uni.reLaunch({ url: "/pages/tabbar/prosperityShop" })
|
||||
}
|
||||
if (launchParam === "coopMerchant") {
|
||||
uni.setStorageSync("launchParam", launchParam)
|
||||
return uni.reLaunch({ url: "/pages/tabbar/coopMerchant" })
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</view>
|
||||
</z-paging>
|
||||
|
||||
<tm-drawer title="退款" :showClose="true" size="90%" placement="bottom" bgColor="#EBF6FF" v-model:show="state.showDrawer" zIndex="999">
|
||||
<tm-drawer title="退款" :showClose="true" size="90%" bgColor="#EBF6FF" v-model:show="state.showDrawer" zIndex="999">
|
||||
<view class="bg-white rounded-16rpx px-10rpx m-b-10rpx animate-both">
|
||||
<z-tabs
|
||||
:list="['同意退款', '拒绝退款']"
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
<div class="service-container">
|
||||
<div class="service-grid">
|
||||
<div v-for="item in services" :key="item.name" class="service-item" @click="goPage(item)">
|
||||
<image :src="item.icon" class="service-icon" />
|
||||
<div class="service-icon-wrapper">
|
||||
<image :src="item.icon" class="service-icon" />
|
||||
<view v-if="item.name === 'todo' && todoCount > 0" class="badge">{{ todoCount > 99 ? "99+" : todoCount }}</view>
|
||||
</div>
|
||||
<div class="service-label">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,58 +21,90 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { ref, onMounted } from "vue"
|
||||
import integratedApi from "@/sheep/api/integrated"
|
||||
|
||||
const todoCount = ref(0)
|
||||
|
||||
const services = [
|
||||
{
|
||||
name: "todo",
|
||||
label: "工作待办",
|
||||
icon: "/src/static/icon/todo.png",
|
||||
icon: "/static/icon/todo.png",
|
||||
url: "/pages/integrated/todo/index",
|
||||
},
|
||||
{
|
||||
name: "meeting-booking",
|
||||
label: "会议预定",
|
||||
icon: "/src/static/icon/meeting-booking.png",
|
||||
icon: "/static/icon/meeting-booking.png",
|
||||
url: "/pages/integrated/meeting/index",
|
||||
},
|
||||
{
|
||||
name: "lost-and-found",
|
||||
label: "失物招领",
|
||||
icon: "/src/static/icon/lost-and-found.png",
|
||||
icon: "/static/icon/service_1.png",
|
||||
url: "/pages/integrated/lostAndFound/index",
|
||||
},
|
||||
{
|
||||
name: "feedback",
|
||||
label: "意见反馈",
|
||||
icon: "/src/static/icon/feedback.png",
|
||||
icon: "/static/icon/feedback.png",
|
||||
url: "/pages/integrated/feedback/index",
|
||||
},
|
||||
{
|
||||
name: "repair-report",
|
||||
label: "维修上报",
|
||||
icon: "/src/static/icon/repair-report.png",
|
||||
icon: "/static/icon/service_2.png",
|
||||
url: "/pages/integrated/repair/index",
|
||||
},
|
||||
{
|
||||
name: "notice",
|
||||
label: "通知公告",
|
||||
icon: "/src/static/icon/notice.png",
|
||||
icon: "/static/icon/service_3.png",
|
||||
url: "/pages/integrated/notice/index",
|
||||
},
|
||||
{
|
||||
name: "visitor",
|
||||
label: "访客邀约",
|
||||
icon: "/src/static/icon/visitor.png",
|
||||
icon: "/static/icon/visitor.png",
|
||||
url: "/pages/integrated/visitor/invitation",
|
||||
},
|
||||
]
|
||||
|
||||
// 获取待办数量
|
||||
async function fetchTodoCount() {
|
||||
try {
|
||||
const res = await integratedApi.getTodoList({ current: 1, size: 1 })
|
||||
const responseData = res as any
|
||||
if (responseData.code === 0 && responseData.data) {
|
||||
todoCount.value = responseData.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取待办数量失败:", error)
|
||||
todoCount.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
const app = getApp()
|
||||
app._initTabbar()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
fetchTodoCount()
|
||||
})
|
||||
|
||||
function goPage(item) {
|
||||
// 访客邀约功能正在开发中
|
||||
if (item.name === "visitor") {
|
||||
uni.showToast({
|
||||
title: "访客邀约功能正在开发中",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (item.url) {
|
||||
uni.navigateTo({ url: item.url })
|
||||
}
|
||||
@@ -122,15 +157,51 @@ function goPage(item) {
|
||||
transform: translateY(-6rpx) scale(1.06);
|
||||
box-shadow: 0 2rpx 12rpx rgba(25, 118, 210, 0.08);
|
||||
}
|
||||
.service-icon-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
width: 92rpx;
|
||||
height: 92rpx;
|
||||
margin-bottom: 14rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f5f7fa;
|
||||
box-shadow: 0 2rpx 8rpx rgba(25, 118, 210, 0.04);
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
background: #ff6b35;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
min-width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.4);
|
||||
border: 2rpx solid #fff;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
.service-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
+77
-170
@@ -1,61 +1,53 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<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" />
|
||||
<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>
|
||||
<!-- <text class="iconfont icon-youjiantou color-#666666 m-l-20rpx"></text> -->
|
||||
</view>
|
||||
<text class="sub-text-26">ID {{ state.userInfo?.sysUser?.userId }}</text>
|
||||
</view>
|
||||
<view v-else class="flex-row items-center m-l-30rpx" @tap="onTabLogin">
|
||||
<text class="sub-text-32">请先登录</text>
|
||||
<text class="iconfont icon-youjiantou" />
|
||||
</view>
|
||||
<template #main-top>
|
||||
<view class="flex items-center bg-#ECF4F9 py-3 px-5">
|
||||
<image
|
||||
v-if="state.userInfo?.sysUser?.avatar"
|
||||
class="size-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="ml-30rpx" v-if="state.userInfo?.sysUser?.userId">
|
||||
<view class="sub-text-30">{{ state.userInfo?.sysUser?.name }}</view>
|
||||
<view class="sub-text-26">ID {{ state.userInfo?.sysUser?.userId }}</view>
|
||||
</view>
|
||||
</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="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>
|
||||
</view>
|
||||
<view class="iconfont icon-youjiantou" />
|
||||
<view v-else class="flex items-center ml-30rpx" @tap="onTabLogin">
|
||||
<text class="sub-text-32">请先登录</text>
|
||||
<text class="iconfont icon-youjiantou" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
</view>
|
||||
<view class="h-30rpx" />
|
||||
<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 class="mt-10rpx rounded-20rpx shadow-main p-30rpx pb-40rpx bg-white">
|
||||
<view class="service_item flex items-center justify-between" v-for="(v, i) in state.menuList" :key="i" @tap="onTabMenu(v)">
|
||||
<view class="flex-row-center">
|
||||
<image class="size-40rpx" :src="v.icon" mode="aspectFill" />
|
||||
<text class="sub-text-28 m-l-20rpx">{{ v.text }}</text>
|
||||
</view>
|
||||
<view class="iconfont icon-youjiantou" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="h-100rpx" />
|
||||
|
||||
<view class="second-text-26 text-center">
|
||||
<text>联系热线:</text>
|
||||
<text @click="doPhone('18367908152')">18367908152</text>
|
||||
|
||||
<text @click="doPhone('15657370065')">15657370065</text>
|
||||
<view v-if="isDevEnv()" class="flex-col-center mt-40rpx mx-30rpx">
|
||||
<view class="w-full text-center sub-text-28 p-y-20rpx rounded-40rpx border-solid border border-#0094ff" @tap="doLogout">
|
||||
退出登录
|
||||
</view>
|
||||
<view class="h-10rpx" />
|
||||
</tm-sticky>
|
||||
</view>
|
||||
<view class="h-30rpx" />
|
||||
<view v-if="state.appInfo == APPENV.SH" class="flex-col-center mt-40rpx mx-30rpx">
|
||||
<view class="w-full text-center sub-text-28 p-y-20rpx rounded-40rpx border-solid border border-#0094ff" @tap="doChangeRole">
|
||||
切换角色
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="second-text-26 text-center mb-2 mt-10">
|
||||
<text>联系热线:</text>
|
||||
<text @click="doPhone('18367908152')">18367908152</text>
|
||||
|
||||
<text @click="doPhone('15657370065')">15657370065</text>
|
||||
</view>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
@@ -70,24 +62,15 @@ import { userApi } from "@@/api"
|
||||
import { isDevEnv } from "@/sheep/utils/app"
|
||||
import { APPENV } from "@/sheep/constant"
|
||||
|
||||
const state = reactive<{
|
||||
wallet: any[]
|
||||
userInfo: any
|
||||
menuList: {
|
||||
text: string
|
||||
icon: string
|
||||
router: string
|
||||
}[]
|
||||
appInfo: string
|
||||
}>({
|
||||
const state = reactive<any>({
|
||||
wallet: [],
|
||||
userInfo: {},
|
||||
menuList: [
|
||||
{ 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: "" },
|
||||
{ text: "扫一扫", icon: "/static/icon/scan.png", path: "" },
|
||||
{ text: "我的钱包", icon: "/static/icon/service_3.png", path: "/pages/wallet/list" },
|
||||
{ text: "我的人脸", icon: "/static/icon/service_5.png", path: "/pages/human-face/list" },
|
||||
// { text: "车辆管理", icon: "/static/icon/service_6.png", path: "/pages/car/userCar/list" },
|
||||
{ text: "清除缓存", icon: "/static/icon/clear-storage.png", path: "" },
|
||||
],
|
||||
appInfo: "",
|
||||
})
|
||||
@@ -269,105 +252,21 @@ const login = {
|
||||
},
|
||||
}
|
||||
|
||||
const scanCode = {
|
||||
// 扫一扫
|
||||
scan: async () => {
|
||||
// 获取扫码结果
|
||||
const scanResult = await scanCode.getScanResult()
|
||||
const { scanCodeAction, processUrlScanResult, processBase64ScanResult } = hooks.useScanCode()
|
||||
|
||||
if (!scanResult) return uni.showToast({ title: "扫码错误", icon: "none" })
|
||||
// 扫一扫
|
||||
const appScanCode = async () => {
|
||||
const scanResult = await scanCodeAction()
|
||||
if (!scanResult) return uni.showToast({ title: "扫码错误", icon: "none" })
|
||||
|
||||
// 判断扫码结果
|
||||
if (scanResult.startsWith("http")) {
|
||||
// 扫码结果是url链接
|
||||
scanCode.processUrlScanResult(scanResult.replace("/#", ""))
|
||||
} else {
|
||||
// 扫码结果是base64字符串
|
||||
const decodedString = scanCode.decodeBase64(scanResult)
|
||||
if (!decodedString) return
|
||||
scanCode.processBase64ScanResult(decodedString)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取扫码结果
|
||||
getScanResult: async (): Promise<string | undefined> => {
|
||||
if (isDevEnv()) {
|
||||
// 开发环境模拟扫码
|
||||
// const testQrCode = "eyJpZCI6IjE4OTYxNTcyMDc2NDQwNTM1MDYiLCJuYW1lIjoi5rWL6K+V5ZWG5oi3In0=" // 线上-测试超市-收款二维码
|
||||
// const testQrCode = "eyJoYXJkRG9vcklkIjoiMTkxNDIxNTYwODQzNzQxMTg0MSIsImNhYmluZXRObyI6IjEifQ==" // 线上-智能柜-收款二维码
|
||||
// const testQrCode = "eyJpZCI6IjE5MDU4MTY3NTEwNDM0NTI5MzAiLCJuYW1lIjoi5paw5Y6/5bqc6LaF5biCIn0=" // 邓本地-测试环境-新县府超市收款二维码
|
||||
// const testQrCode = "eyJoYXJkRG9vcklkIjoiMTkxMzE1NzA4MDQxODI5NTgwOSIsImNhYmluZXRObyI6IjEifQ==" // 邓本地-测试单门智能柜柜门二维码
|
||||
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?id=1905816751043452930&name=测试新县府超市" // 邓本地-测试环境-新县府超市收款二维码
|
||||
const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?hardDoorId=1913157080418295809&cabinetNo=1" // 测试智能柜柜门二维码
|
||||
uni.showModal({ content: "开发环境不是浙政钉和诗画环境,扫码仅供测试,收款商家为测试商户超市" })
|
||||
|
||||
return testQrCode
|
||||
} else if (state.appInfo == APPENV.ZZD) {
|
||||
// 浙政钉扫码
|
||||
const res: any = await (window as any).dd.scan({ type: "all" })
|
||||
return res.text
|
||||
} else if (state.appInfo == APPENV.SH) {
|
||||
// 诗画扫码
|
||||
const res = await (window as any).CP2.showScanQr({ needResult: 1 })
|
||||
if (res.code == 1) return res.data.qrCode
|
||||
} else {
|
||||
uni.showToast({ title: "当前环境未知", icon: "none" })
|
||||
}
|
||||
},
|
||||
|
||||
// 处理base64扫码结果
|
||||
processBase64ScanResult: (decodedString: string) => {
|
||||
try {
|
||||
const decodedJson = JSON.parse(decodedString)
|
||||
if (decodedJson.id) {
|
||||
router.jump("/pages/pay/scanPay", { id: decodedJson.id })
|
||||
} else if (decodedJson?.hardDoorId && decodedJson?.cabinetNo) {
|
||||
router.jump("/pages/cabinet/buy", { hardDoorId: decodedJson.hardDoorId, cabinetNo: decodedJson.cabinetNo })
|
||||
} else {
|
||||
uni.showToast({ title: "无效的二维码", icon: "none" })
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({ title: "无效的二维码内容", icon: "none" })
|
||||
}
|
||||
},
|
||||
|
||||
// 处理url扫码结果
|
||||
processUrlScanResult: (url: string) => {
|
||||
const searchParams = new URL(url).searchParams
|
||||
const id = searchParams.get("id")
|
||||
const name = searchParams.get("name")
|
||||
const hardDoorId = searchParams.get("hardDoorId")
|
||||
const cabinetNo = searchParams.get("cabinetNo")
|
||||
|
||||
if (id) {
|
||||
router.jump("/pages/pay/scanPay", { id, name })
|
||||
} else if (hardDoorId && cabinetNo) {
|
||||
router.jump("/pages/cabinet/buy", { hardDoorId, cabinetNo })
|
||||
} else {
|
||||
uni.showToast({ title: "无效的二维码", icon: "none" })
|
||||
}
|
||||
},
|
||||
|
||||
// 解码base64
|
||||
decodeBase64: (base64String: string): string | undefined => {
|
||||
try {
|
||||
const decodedBinaryString = atob(base64String)
|
||||
const uint8Array = new Uint8Array(decodedBinaryString.length)
|
||||
for (let i = 0; i < decodedBinaryString.length; i++) {
|
||||
uint8Array[i] = decodedBinaryString.charCodeAt(i)
|
||||
}
|
||||
const decoder = new TextDecoder("utf-8")
|
||||
const decodedString = decoder.decode(uint8Array)
|
||||
|
||||
// console.log("解码结果", decodedString)
|
||||
|
||||
return decodedString
|
||||
} catch (error) {
|
||||
console.error("Base64 解码失败:", error)
|
||||
uni.showToast({ title: "无效的二维码", icon: "none" })
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
// 判断扫码结果
|
||||
if (scanResult.startsWith("http")) {
|
||||
// 扫码结果是url链接
|
||||
processUrlScanResult(scanResult.replace("/#", ""))
|
||||
} else {
|
||||
// 扫码结果是base64字符串
|
||||
processBase64ScanResult(scanResult)
|
||||
}
|
||||
}
|
||||
|
||||
const onTabLogin = () => (isDevEnv() ? router.jump("/pages/user/login") : login.checkAppInfo())
|
||||
@@ -375,16 +274,16 @@ const onTabLogin = () => (isDevEnv() ? router.jump("/pages/user/login") : login.
|
||||
const onTabMenu = async (item: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
if (item.router == "/pages/tabbar/index") return uni.switchTab({ url: item.router })
|
||||
if (item.path == "/pages/tabbar/index") return uni.switchTab({ url: item.path })
|
||||
switch (item.text) {
|
||||
case "扫一扫":
|
||||
await scanCode.scan()
|
||||
await appScanCode()
|
||||
break
|
||||
case "清除缓存":
|
||||
storage.clearCache()
|
||||
break
|
||||
default:
|
||||
router.jump(item.router)
|
||||
router.jump(item.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,13 +302,13 @@ const doChangeRole = () => {
|
||||
const initMenuConfig = () => {
|
||||
if (isDevEnv()) {
|
||||
state.menuList.push(
|
||||
{ text: "dev-✨导航✨", icon: "/static/icon/service_1.png", router: "/pages/tabbar/index" },
|
||||
{ text: "dev-外来人员就餐", icon: "/static/icon/service_1.png", router: "/pages/apply/foreign-list" },
|
||||
{ text: "dev-跨单位就餐", icon: "/static/icon/service_1.png", router: "/pages/apply/stride-list" },
|
||||
{ text: "dev-包厢预定", icon: "/static/icon/service_4.png", router: "/pages/boxes/apply-list" },
|
||||
{ text: "dev-第三方应用", icon: "/static/icon/login.png", router: "/pages/thirdApp/happyMall" },
|
||||
{ text: "dev-登录", icon: "/static/icon/login.png", router: "/pages/user/login" },
|
||||
{ text: "dev-角色选择", icon: "/static/icon/login.png", router: "/pages/user/userRole" },
|
||||
{ text: "dev-✨导航✨", icon: "/static/icon/service_1.png", path: "/pages/tabbar/index" },
|
||||
{ text: "dev-外来人员就餐", icon: "/static/icon/service_1.png", path: "/pages/apply/foreign-list" },
|
||||
{ text: "dev-跨单位就餐", icon: "/static/icon/service_1.png", path: "/pages/apply/stride-list" },
|
||||
{ text: "dev-包厢预定", icon: "/static/icon/service_4.png", path: "/pages/boxes/apply-list" },
|
||||
{ text: "dev-第三方应用", icon: "/static/icon/login.png", path: "/pages/thirdApp/happyMall" },
|
||||
{ text: "dev-登录", icon: "/static/icon/login.png", path: "/pages/user/login" },
|
||||
{ text: "dev-角色选择", icon: "/static/icon/login.png", path: "/pages/user/userRole" },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -426,10 +325,18 @@ onLoad(async () => {
|
||||
|
||||
await userApi.userRole()
|
||||
|
||||
await isMallMchUser()
|
||||
|
||||
// 获取存储中的用户信息
|
||||
storage.getUserInfo()
|
||||
})
|
||||
|
||||
const isMallMchUser = async () => {
|
||||
const { data } = await userApi.isMallMchUser()
|
||||
if (data)
|
||||
state.menuList.unshift({ text: "共富商城商品管理", icon: "/static/icon/service_4.png", path: "/pages/prosperity-shop/goods-manage" })
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
// 获取存储中的用户信息
|
||||
storage.getUserInfo()
|
||||
|
||||
+74
-101
@@ -16,12 +16,12 @@
|
||||
:auto="false"
|
||||
>
|
||||
<template #top>
|
||||
<view class="flex justify-between items-center my-1 z-1000">
|
||||
<view class="flex justify-between items-center my-1">
|
||||
<tm-text font-size="40" color="#0779FD" class="font-bold">欢迎选购</tm-text>
|
||||
<view class="flex justify-center items-center" @tap="changeShop">
|
||||
<image class="size-40rpx" src="@/static/page/address-blue.svg" />
|
||||
<tm-text font-size="32" color="#0779FD">{{ state.curMerchant.name }}</tm-text>
|
||||
<image class="size-30rpx ml-1" src="@/static/page/arrow-down-s-fill.svg" />
|
||||
<image class="size-30rpx" src="@/static/page/arrow-down-s-fill.svg" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="w-full flex items-center">
|
||||
@@ -29,39 +29,40 @@
|
||||
class="flex-1"
|
||||
v-model="state.params.keyword"
|
||||
type="text"
|
||||
height="66"
|
||||
minHeight="66"
|
||||
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"
|
||||
@clear="inputClear"
|
||||
>
|
||||
<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">
|
||||
<tm-badge bg-color="red" :offset="[8, 4]" :label="cartStore.totalCount">
|
||||
<view class="flex-col-center size-80rpx" @tap="goCart">
|
||||
<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 whitespace-nowrap"
|
||||
:class="{ 'bg-#0779FD22! c-#0779FD! border-#0779FD33!': state.params.categoryId === v.id }"
|
||||
v-for="(v, i) in state.categaryList"
|
||||
:key="i"
|
||||
@tap="changeCategary(v.id)"
|
||||
>
|
||||
{{ v.name }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
@@ -76,21 +77,19 @@
|
||||
:hasAroundGutter="false"
|
||||
backgroundColor="#ffffff00"
|
||||
:crossOrigin="false"
|
||||
:animationCancel="true"
|
||||
:animationCancel="false"
|
||||
>
|
||||
<template #default="{ item, url, index }">
|
||||
<view class="card" @click="toDetail(item)">
|
||||
<view class="card" @click="goDetail(item)">
|
||||
<LazyImg class="rounded-2" :url="filePath(item.picUrl)" />
|
||||
<view class="p-16rpx flex-col">
|
||||
<view class="sub-text-32 show-line">{{ item.productName }}</view>
|
||||
<view class="second-text-26 flex justify-between">
|
||||
<view>{{ item.productNature == "2" ? "电子称重商品" : "普通商品" }}</view>
|
||||
<view>已售 {{ item.salesCount || 0 }}</view>
|
||||
</view>
|
||||
<view class="flex-row justify-between items-center">
|
||||
<view class="flex items-center">
|
||||
<view class="second-text-32 c-red!">¥ {{ storePrice(item.minPrice).toFixed(2) }}</view>
|
||||
<!-- <view v-if="parseFloat(item.promotionPrice) != 0" class="ml-10rpx second-text-30 line-through">
|
||||
¥{{ hooks.useMoney(item.retailPrice) || 0 }}
|
||||
</view> -->
|
||||
</view>
|
||||
<view class="second-text-26">已售:{{ item.salesCount || 0 }}</view>
|
||||
<view class="second-text-32 c-red!">¥{{ item.minPrice.toFixed(2) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -99,39 +98,33 @@
|
||||
|
||||
<tm-drawer
|
||||
title="切换超市"
|
||||
:showClose="false"
|
||||
:showClose="true"
|
||||
:showFooter="true"
|
||||
size="70%"
|
||||
placement="bottom"
|
||||
v-model:show="state.showDrawer"
|
||||
size="50%"
|
||||
v-model:show="state.shopDrawerShow"
|
||||
:duration="100"
|
||||
zIndex="1200"
|
||||
>
|
||||
<tm-radio-group v-model="state.curMerchantRadioId" direction="column">
|
||||
<view
|
||||
<tm-radio
|
||||
v-for="(item, index) in state.merchantList"
|
||||
:key="index"
|
||||
class="w-full flex items-center p-2 rounded-3"
|
||||
:class="[state.curMerchantRadioId == item.id ? 'bg-#e8e8e8' : '']"
|
||||
:value="item.id"
|
||||
>
|
||||
<tm-radio class="w-full h-full px-2" :value="item.id">
|
||||
<template v-slot:label="checked">
|
||||
<view class="flex items-center">
|
||||
<image class="w-130rpx h-130rpx rounded-2" :src="filePath(item.logoPic)" mode="aspectFill" />
|
||||
<view class="ml-2 flex-1 flex-col">
|
||||
<tm-text font-size="30">{{ item.name }}</tm-text>
|
||||
<view class="second-text-26">地址:{{ item.addressDes }}</view>
|
||||
<view class="second-text-26">手机:{{ item.phone }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</tm-radio>
|
||||
</view>
|
||||
<template v-slot:label="checked">
|
||||
<view class="flex items-center">
|
||||
<image class="size-80rpx rounded-2 mr-2" :src="filePath(item.logoPic)" mode="aspectFill" />
|
||||
<tm-text font-size="30">{{ item.name }}</tm-text>
|
||||
</view>
|
||||
</template>
|
||||
</tm-radio>
|
||||
</tm-radio-group>
|
||||
|
||||
<template #footer>
|
||||
<view class="px-20rpx pt-2 flex justify-between">
|
||||
<tm-button skin="thin" block @click="state.showDrawer = false">取消</tm-button>
|
||||
<tm-button skin="thin" block @click="state.shopDrawerShow = false">取消</tm-button>
|
||||
|
||||
<tm-button block @click="confirmRadioMerchant">确定</tm-button>
|
||||
</view>
|
||||
@@ -142,7 +135,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { ref, reactive } from "vue"
|
||||
import { LazyImg, Waterfall } from "vue-waterfall-plugin-next"
|
||||
import "vue-waterfall-plugin-next/dist/style.css"
|
||||
@@ -151,7 +144,7 @@ import { filePath } from "@@/utils/file"
|
||||
import { merchantApi, onlineShopApi } from "@@/api"
|
||||
import { hooks, router } from "@@/index"
|
||||
import { useCartStore } from "@/sheep/stores/cart"
|
||||
import { storePrice } from "@/sheep/utils/formatMoney"
|
||||
import treeToList from "@/sheep/utils/tree-to-list"
|
||||
|
||||
const cartStore = useCartStore()
|
||||
|
||||
@@ -165,9 +158,9 @@ const state = reactive<any>({
|
||||
|
||||
merchantList: [],
|
||||
curMerchant: {},
|
||||
classifyList: [],
|
||||
categaryList: [],
|
||||
|
||||
showDrawer: false,
|
||||
shopDrawerShow: false,
|
||||
curMerchantRadioId: "",
|
||||
})
|
||||
const pageRef = ref<any>(null)
|
||||
@@ -181,82 +174,66 @@ onLoad(async () => {
|
||||
await cartStore.getShopCart()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
// pageRef.value && pageRef.value.reload(false)
|
||||
})
|
||||
|
||||
// 获取所有商户
|
||||
const getAllMerchant = async () => {
|
||||
uni.showLoading({ title: "加载中...", mask: true })
|
||||
try {
|
||||
const { code, data }: any = await merchantApi.page({ current: 1, pagesize: 9999999, type: "3" })
|
||||
if (code == 0) {
|
||||
state.merchantList = data.records
|
||||
if (!isEmpty(state.merchantList)) {
|
||||
state.curMerchant = state.merchantList[0]
|
||||
state.params.merchantId = state.curMerchant.id
|
||||
cartStore.saveShopMerchantId(state.curMerchant.id)
|
||||
return
|
||||
}
|
||||
state.curMerchant = {}
|
||||
}
|
||||
if (code != 0) return uni.showToast({ title: "商户获取失败", icon: "error" })
|
||||
state.merchantList = data.records.filter((item) => item.name.includes("超市")) ?? []
|
||||
state.curMerchant = state.merchantList[0] ?? {}
|
||||
state.params.merchantId = state.curMerchant.id
|
||||
cartStore.saveShopMerchantId(state.curMerchant.id)
|
||||
} catch (error) {
|
||||
state.merchantList = []
|
||||
state.curMerchant = {}
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
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 getClassify = async () => {
|
||||
if (state.curMerchant.id) {
|
||||
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
|
||||
pageRef.value && pageRef.value.reload(true)
|
||||
}
|
||||
if (!state.curMerchant.id) return
|
||||
const { data } = await onlineShopApi.getSpuCategory()
|
||||
state.categaryList = treeToList(data) ?? []
|
||||
state.categaryList.unshift({ id: "", name: "全部分类" })
|
||||
if (!isEmpty(state.categaryList)) {
|
||||
state.params.categoryId = state.categaryList[0].id
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换超市
|
||||
// 打开切换超市抽屉
|
||||
const changeShop = () => {
|
||||
state.curMerchantRadioId = state.curMerchant.id || ""
|
||||
state.showDrawer = true
|
||||
state.shopDrawerShow = true
|
||||
}
|
||||
|
||||
// 确定切换超市
|
||||
const confirmRadioMerchant = async () => {
|
||||
state.curMerchant = state.merchantList.find((item: any) => item.id == state.curMerchantRadioId)
|
||||
state.showDrawer = false
|
||||
state.shopDrawerShow = false
|
||||
state.params.merchantId = state.curMerchant.id
|
||||
cartStore.saveShopMerchantId(state.curMerchant.id)
|
||||
await cartStore.getShopCart()
|
||||
pageRef.value && pageRef.value.reload(true)
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
// 点击搜索
|
||||
const searchProduct = () => {
|
||||
state.params.categoryId = ""
|
||||
pageRef.value && pageRef.value.reload(true)
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
// 切换分类
|
||||
const doClassify = async (categoryId: string) => {
|
||||
const changeCategary = async (categoryId: string) => {
|
||||
if (state.params.categoryId == categoryId) return
|
||||
state.params.categoryId = categoryId
|
||||
pageRef.value && pageRef.value.reload(true)
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
const inputClear = () => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
const pageHook = hooks.usePage(pageRef, onlineShopApi.getSpuList, null, "data.records")
|
||||
@@ -265,17 +242,13 @@ const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.params)
|
||||
}
|
||||
|
||||
const toDetail = async (v: any) => {
|
||||
const goDetail = async (v: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
router.jump("/pages/shop/product-detail", { id: v.id })
|
||||
}
|
||||
if (isLogin) router.jump("/pages/online-shop/goods-detail", { id: v.id })
|
||||
}
|
||||
|
||||
const doCart = async () => {
|
||||
const goCart = async () => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
router.jump("/pages/shop/cart")
|
||||
}
|
||||
if (isLogin) router.jump("/pages/online-shop/cart")
|
||||
}
|
||||
</script>
|
||||
+41
-32
@@ -100,52 +100,61 @@ const doRefund = (v: any) => {
|
||||
|
||||
<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="w-80rpx h-80rpx rounded-8rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
|
||||
<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="main-text-28">{{ v.merchantName }}</view>
|
||||
<view class="main-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="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>
|
||||
<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">
|
||||
<view class="second-text-20">
|
||||
<view class="flex-row items-center justify-between mt-5rpx second-text-24">
|
||||
<view>
|
||||
<text>下单时间</text>
|
||||
<text class="m-l-10rpx">{{ hooks.useTime(v.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</text>
|
||||
<text class="ml-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>{{ hooks.useEnum("hardwareRefundStatusEnum", v.refundStatus) }}</view>
|
||||
</view>
|
||||
<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 class="main-text-30 whitespace-nowrap color-red! text-right">¥ {{ hooks.useMoney(v.totalPrice) }}</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>
|
||||
|
||||
<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>
|
||||
<!-- <scroll-view v-if="['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)" scroll-y> -->
|
||||
<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>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view
|
||||
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-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>
|
||||
<!-- </scroll-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> -->
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<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 mb-2">
|
||||
<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="w-300rpx mr-2 sub-text-28 py-1 px-2 rounded-1 bg-#fff border border-solid border-transparent whitespace-nowrap"
|
||||
: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 bg-#eeeeee 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.stop="doPhone(v.contactPhone)">{{ v.contactPhone }}</text>
|
||||
</text>
|
||||
<text>{{ v.email }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="c-#0094ff! sub-text-26 px-2 bg-#0094ff22 rounded-2 mr-2">营业</text>
|
||||
<text class="c-#0094ff! sub-text-26 px-2 bg-#0094ff22 rounded-2">{{ 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 bg-#eeeeee" :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.priceType == "1" ? displayPrice(pV.minPrice, "") : displayPrice(pV.spuPrice, "") }}</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">
|
||||
<template v-if="pV.priceType == '2'">
|
||||
<text class="text-22rpx">¥</text>
|
||||
<text>{{ displayPrice(pV.originalPrice, "") }}</text>
|
||||
</template>
|
||||
</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 { 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"
|
||||
import { displayPrice } from "@/sheep/utils/formatMoney"
|
||||
|
||||
const state = reactive<any>({
|
||||
params: {
|
||||
keyword: "",
|
||||
},
|
||||
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()
|
||||
})
|
||||
|
||||
// 切换分类
|
||||
// const doClassify = async (categoryId: string) => {
|
||||
// if (state.params.categoryId == categoryId) return
|
||||
// state.params.categoryId = categoryId
|
||||
// pageRef.value && pageRef.value.reload(false)
|
||||
// }
|
||||
|
||||
// 点击搜索
|
||||
const search = () => {
|
||||
// state.params.categoryId = ""
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
// 联系商家
|
||||
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/prosperity-shop/shop-detail", { id: v.id })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -29,7 +29,7 @@ onShow(() => {
|
||||
const toDetail = async (v: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
router.jump("/pages/shop/detail", { id: v.id })
|
||||
router.jump("/pages/merchant/detail", { id: v.id })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<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"
|
||||
>
|
||||
<view class="h-30rpx" />
|
||||
<tm-sheet v-if="false" v-for="(item, index) in pageHook.list" :key="index" :margin="[0, 20]">
|
||||
<view class="text-36 overflow-hidden text-nowrap text-ellipsis">
|
||||
文本TmText文本TmText文本TmText文本TmText文本TmText文本TmText文本TmText文本TmText文本TmText文本TmText文本TmText文本
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<image src="@/static/page/时间.svg" mode="aspectFill" class="w-28rpx h-28rpx mr-1" />
|
||||
<tm-text font-size="26">2025-06-06</tm-text>
|
||||
</view>
|
||||
<swiper class="my-1" circular :indicator-dots="false" indicator-active-color="#0779FD" indicator-color="#ffffff" :autoplay="true">
|
||||
<swiper-item>
|
||||
<image
|
||||
class="w-full h-full"
|
||||
:src="'https://stgl.pj.gov.cn/api/admin/sys-file/oss/file?fileName=3378726f7c4b40128c238c8827d6350e.jpg'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<tm-text
|
||||
@item-click="doDetail(item.id)"
|
||||
class="line-10"
|
||||
:heightLight="['高亮显示', '正则', '邮箱']"
|
||||
label="支持多文本高亮显示,比如根据正则高亮电话号码,邮箱等,点击后打电话,发邮件。"
|
||||
/>
|
||||
</tm-sheet>
|
||||
</z-paging>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { hooks, router } from "@/sheep"
|
||||
import { doorApi } from "@/sheep/api"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
|
||||
const pageRef = ref<any>(null)
|
||||
|
||||
onLoad(() => {
|
||||
const app = getApp()
|
||||
app._initTabbar()
|
||||
})
|
||||
onShow(() => {
|
||||
if (pageRef.value) {
|
||||
pageRef.value.reload(false)
|
||||
}
|
||||
})
|
||||
|
||||
const pageHook = hooks.usePage(pageRef, doorApi.hardDoorPage, null, "data.records")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, {})
|
||||
}
|
||||
|
||||
// 公告详情
|
||||
const doDetail = (id: string) => {
|
||||
if (!id) return
|
||||
router.jump("/pages/cabinet/cabinetDetail", { id }, 1)
|
||||
}
|
||||
</script>
|
||||
@@ -1,82 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { merchantApi } from "@@/api"
|
||||
import { hooks, router } from "@@/index"
|
||||
import { filePath } from "@@/utils/file"
|
||||
|
||||
const pageRef = ref<any>(null)
|
||||
const state = reactive<any>({
|
||||
pa: {
|
||||
pagesize: 10,
|
||||
type: "3",
|
||||
},
|
||||
})
|
||||
const pageHook = hooks.usePage(pageRef, merchantApi.page, null, "data.records")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.pa)
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
const app = getApp()
|
||||
app._initTabbar()
|
||||
})
|
||||
onShow(() => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
})
|
||||
|
||||
const toDetail = async (v: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
router.jump("/pages/shop/detail", { id: v.id })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
<tm-tag size="s">{{ v.deptName }}</tm-tag>
|
||||
</view>
|
||||
<view class="m-t-10rpx">
|
||||
<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 class="second-text-24">
|
||||
<view class="second-text-28">{{ v.jobTime == ",," ? "" : "营业时间:" }}</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="second-text-28">
|
||||
<image class="w-24rpx h-24rpx" src="@/static/page/address.svg" />
|
||||
<text class="ml-1">地址:</text>
|
||||
<text class="m-l-10rpx">{{ v.addressDes }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</crud-layout>
|
||||
</template>
|
||||
@@ -27,7 +27,7 @@
|
||||
<view class="w-64 h-64 rounded-full flex-col-center mr-8">
|
||||
<image src="@/static/page/merchantRole.svg" />
|
||||
</view>
|
||||
<view class="main-text-32 mb-1">我是商户</view>
|
||||
<view class="main-text-32 mb-1">我是合作商户</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import request from "@/sheep/utils/request"
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 提交意见反馈
|
||||
*/
|
||||
submitFeedback: (data: {
|
||||
title: string;
|
||||
description: string;
|
||||
picUrl?: string;
|
||||
type: string;
|
||||
contact?: string;
|
||||
}) =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/save',
|
||||
method: 'POST',
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取我的反馈列表
|
||||
*/
|
||||
getFeedbackList: (params: { current: number; size: number }) =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取反馈详情
|
||||
*/
|
||||
getFeedbackDetail: (id: string) =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/getDetails',
|
||||
method: 'GET',
|
||||
params: { id },
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取反馈类型字典
|
||||
*/
|
||||
getFeedbackType: () =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/getFeedbackType',
|
||||
method: 'GET',
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -42,4 +42,6 @@ export { default as invitationApi } from "./invitation"
|
||||
|
||||
export { default as totalApi } from "./total"
|
||||
|
||||
export { default as userCarApi } from "./userCar"
|
||||
export { default as userCarApi } from "./userCar"
|
||||
|
||||
export { default as prosperityShopApi } from "./prosperityShop"
|
||||
@@ -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({
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import request from "@/sheep/utils/request"
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 提交维修申请
|
||||
*/
|
||||
submitRepair: (data: {
|
||||
title: string;
|
||||
picUrl?: string;
|
||||
description: string;
|
||||
dateFound: string;
|
||||
location: string;
|
||||
urgency: string;
|
||||
maintainType: number;
|
||||
}) =>
|
||||
request({
|
||||
url: '/integrated/maintenanceOrder/app/apply',
|
||||
method: 'POST',
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取我的维修记录列表
|
||||
*/
|
||||
getRepairList: (params: { current: number; size: number }) =>
|
||||
request({
|
||||
url: '/integrated/maintenanceOrder/app/my',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取维修详情
|
||||
*/
|
||||
getRepairDetail: (id: string | number) =>
|
||||
request({
|
||||
url: `/integrated/maintenanceOrder/app/my/${id}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取维修类型选项
|
||||
*/
|
||||
getFeedbackType: () =>
|
||||
request({
|
||||
url: '/integrated/maintenanceOrder/app/getFeedbackType',
|
||||
method: 'GET',
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
+11
-147
@@ -31,6 +31,8 @@ export default {
|
||||
title: string;
|
||||
description: string;
|
||||
attendees: string[];
|
||||
need_service?: string;
|
||||
desk_layout?: string;
|
||||
}) =>
|
||||
request({
|
||||
url: `/integrated/meetingRoom/${roomId}/book`,
|
||||
@@ -57,154 +59,16 @@ export default {
|
||||
method: 'GET',
|
||||
}),
|
||||
|
||||
// ========== 意见反馈相关接口 ==========
|
||||
/**
|
||||
* 提交意见反馈
|
||||
*/
|
||||
submitFeedback: (data: {
|
||||
title: string;
|
||||
description: string;
|
||||
picUrl?: string;
|
||||
type: string;
|
||||
contact?: string;
|
||||
}) =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/save',
|
||||
method: 'POST',
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取我的反馈列表
|
||||
*/
|
||||
getFeedbackList: (params: { current: number; size: number }) =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
/**
|
||||
* 获取桌子拜访形态
|
||||
*/
|
||||
getMeetingDeskLayout: (id: string) =>
|
||||
request({
|
||||
url: `/integrated/meetingDeskLayout/app/list`,
|
||||
method: 'GET',
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取反馈详情
|
||||
*/
|
||||
getFeedbackDetail: (id: string) =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/getDetails',
|
||||
method: 'GET',
|
||||
params: { id },
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取反馈类型字典
|
||||
*/
|
||||
getFeedbackType: () =>
|
||||
request({
|
||||
url: '/integrated/feedback/app/getFeedbackType',
|
||||
method: 'GET',
|
||||
}),
|
||||
|
||||
// ========== 维修上报相关接口 ==========
|
||||
/**
|
||||
* 提交维修申请
|
||||
*/
|
||||
submitRepair: (data: {
|
||||
title: string;
|
||||
picUrl?: string;
|
||||
description: string;
|
||||
dateFound: string;
|
||||
location: string;
|
||||
urgency: string;
|
||||
maintainType: number;
|
||||
}) =>
|
||||
request({
|
||||
url: '/integrated/maintenanceOrder/app/apply',
|
||||
method: 'POST',
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取我的维修记录列表
|
||||
*/
|
||||
getRepairList: (params: { current: number; size: number }) =>
|
||||
request({
|
||||
url: '/integrated/maintenanceOrder/app/my',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取维修详情
|
||||
*/
|
||||
getRepairDetail: (id: string | number) =>
|
||||
request({
|
||||
url: `/integrated/maintenanceOrder/app/my/${id}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
|
||||
// ========== 通知公告相关接口 ==========
|
||||
/**
|
||||
* 获取公告列表
|
||||
*/
|
||||
getNoticeList: (params: { current: number; size: number; type?: string }) =>
|
||||
request({
|
||||
url: '/integrated/notice/list',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取公告详情
|
||||
*/
|
||||
getNoticeDetail: (id: string) =>
|
||||
request({
|
||||
url: `/integrated/notice/detail/${id}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
|
||||
/**
|
||||
* 标记公告已读
|
||||
*/
|
||||
markNoticeRead: (id: string) =>
|
||||
request({
|
||||
url: `/integrated/notice/read/${id}`,
|
||||
method: 'POST',
|
||||
}),
|
||||
|
||||
// ========== 访客管理相关接口 ==========
|
||||
/**
|
||||
* 提交访客申请
|
||||
*/
|
||||
submitVisitor: (data: {
|
||||
visitorName: string;
|
||||
visitorPhone: string;
|
||||
purpose: string;
|
||||
visitDate: string;
|
||||
visitTime: string;
|
||||
hostName: string;
|
||||
remark?: string;
|
||||
}) =>
|
||||
request({
|
||||
url: '/integrated/visitor/submit',
|
||||
method: 'POST',
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取访客记录列表
|
||||
*/
|
||||
getVisitorList: (params: { current: number; size: number }) =>
|
||||
request({
|
||||
url: '/integrated/visitor/my-list',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取访客详情
|
||||
*/
|
||||
getVisitorDetail: (id: string) =>
|
||||
request({
|
||||
url: `/integrated/visitor/detail/${id}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
}
|
||||
@@ -20,4 +20,33 @@ export default {
|
||||
url: `/integrated/notice/app/view/${id}`,
|
||||
method: 'POST',
|
||||
}),
|
||||
|
||||
// ========== 通知公告相关接口 ==========
|
||||
/**
|
||||
* 获取公告列表
|
||||
*/
|
||||
getNoticeList: (params: { current: number; size: number; type?: string }) =>
|
||||
request({
|
||||
url: '/integrated/notice/list',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取公告详情
|
||||
*/
|
||||
getNoticeDetailById: (id: string) =>
|
||||
request({
|
||||
url: `/integrated/notice/detail/${id}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
|
||||
/**
|
||||
* 标记公告已读
|
||||
*/
|
||||
markNoticeRead: (id: string) =>
|
||||
request({
|
||||
url: `/integrated/notice/read/${id}`,
|
||||
method: 'POST',
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
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 },
|
||||
})
|
||||
},
|
||||
// 获取商品分类列表
|
||||
getGoodsCategory: (params?: any): Promise<any> => {
|
||||
return request({
|
||||
url: "/supermarket/app/mall/merchant/category/list",
|
||||
method: "GET",
|
||||
params,
|
||||
})
|
||||
},
|
||||
// 获取超市详情
|
||||
getShopDetail: (id: string): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/${id}`,
|
||||
method: "GET",
|
||||
})
|
||||
},
|
||||
// 获取超市商品分类列表树
|
||||
getGoodsCategoryTree: (params?: { merchantId: string }): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/category/tree`,
|
||||
method: "GET",
|
||||
params,
|
||||
})
|
||||
},
|
||||
// 获取商品列表
|
||||
getGoodsList: (params: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/product/page`,
|
||||
method: "GET",
|
||||
params,
|
||||
})
|
||||
},
|
||||
// 获取商品详情
|
||||
getGoodsDetail: (id: string): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/product/${id}`,
|
||||
method: "GET",
|
||||
})
|
||||
},
|
||||
// 共富商城商品管理获取自己商户的商品列表
|
||||
getManageGoodsList: (params: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/product/list`,
|
||||
method: "GET",
|
||||
params,
|
||||
})
|
||||
},
|
||||
// 删除商品
|
||||
deleteGoods: (id: string): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/product/${id}`,
|
||||
method: "DELETE",
|
||||
})
|
||||
},
|
||||
// 新增商品
|
||||
addGoods: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/product/create`,
|
||||
method: "POST",
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 修改商品
|
||||
editGoods: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/product/update`,
|
||||
method: "PUT",
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 删除商品分类
|
||||
delCategary: (id: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/category/${id}`,
|
||||
method: "DELETE",
|
||||
})
|
||||
},
|
||||
// 新增商品分类
|
||||
addCategary: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/category/create`,
|
||||
method: "POST",
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 编辑商品分类
|
||||
editCategary: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/category/update`,
|
||||
method: "PUT",
|
||||
data,
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import request from "@/sheep/utils/request"
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 查询任务详情
|
||||
*/
|
||||
queryTask: (taskId: string, view: boolean = false) =>
|
||||
request({
|
||||
url: "/task/task/queryTask",
|
||||
method: "GET",
|
||||
params: { taskId, view },
|
||||
}),
|
||||
|
||||
/**
|
||||
* 格式化开始节点显示
|
||||
*/
|
||||
formatStartNodeShow: (data: {
|
||||
flowId: string;
|
||||
processInstanceId: string;
|
||||
paramMap: any;
|
||||
taskId: string;
|
||||
}) =>
|
||||
request({
|
||||
url: "/task/process-instance/formatStartNodeShow",
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 完成任务(审批)
|
||||
*/
|
||||
completeTask: (data: {
|
||||
taskId: string;
|
||||
paramMap: any;
|
||||
taskLocalParamMap: {
|
||||
approveDesc: string;
|
||||
};
|
||||
}) =>
|
||||
request({
|
||||
url: "/task/task/completeTask",
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 转办任务
|
||||
*/
|
||||
delegateTask: (data: {
|
||||
taskId: string;
|
||||
assignee: string;
|
||||
comment?: string;
|
||||
}) =>
|
||||
request({
|
||||
url: "/task/task/delegate",
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取用户选择列表
|
||||
*/
|
||||
getUserSelectionList: () =>
|
||||
request({
|
||||
url: "/task/task/getUserSelectionList",
|
||||
method: "GET",
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取部门组织架构和用户列表
|
||||
*/
|
||||
getDeptOrgUsers: (parentDeptId: number = 0) =>
|
||||
request({
|
||||
url: "/admin/dept/org",
|
||||
method: "GET",
|
||||
params: {
|
||||
type: "user",
|
||||
parentDeptId,
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* 设置转办人员
|
||||
*/
|
||||
setAssignee: (data: {
|
||||
paramMap: any;
|
||||
taskId: string;
|
||||
targetUserIdList: string[];
|
||||
taskLocalParamMap: {
|
||||
approveDesc: string;
|
||||
};
|
||||
}) =>
|
||||
request({
|
||||
url: "/task/task/setAssignee",
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
}
|
||||
@@ -60,12 +60,18 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
// 判断是否是商户角色
|
||||
// 判断是否是合作商家商户角色
|
||||
isMerchant: (): Promise<any> =>
|
||||
request({
|
||||
url: "/admin/role/isMchUser",
|
||||
method: "GET",
|
||||
}),
|
||||
// 判断是否是共富商城商户角色
|
||||
isMallMchUser: (): Promise<any> =>
|
||||
request({
|
||||
url: "/admin/role/isMallMchUser",
|
||||
method: "GET",
|
||||
}),
|
||||
// 判断用户移动端角色
|
||||
userRole: () =>
|
||||
request({
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import request from "@/sheep/utils/request"
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 提交访客申请
|
||||
*/
|
||||
submitVisitor: (data: {
|
||||
visitorName: string;
|
||||
visitorPhone: string;
|
||||
purpose: string;
|
||||
visitDate: string;
|
||||
visitTime: string;
|
||||
hostName: string;
|
||||
remark?: string;
|
||||
}) =>
|
||||
request({
|
||||
url: '/integrated/visitor/submit',
|
||||
method: 'POST',
|
||||
data,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取访客记录列表
|
||||
*/
|
||||
getVisitorList: (params: { current: number; size: number }) =>
|
||||
request({
|
||||
url: '/integrated/visitor/my-list',
|
||||
method: 'GET',
|
||||
params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* 获取访客详情
|
||||
*/
|
||||
getVisitorDetail: (id: string) =>
|
||||
request({
|
||||
url: `/integrated/visitor/detail/${id}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
mainIsScroll: false,
|
||||
bg: "",
|
||||
})
|
||||
const emit = defineEmits(["scroll"])
|
||||
|
||||
const state = reactive({
|
||||
navBgTitleColor_: props.navBgTitleColor,
|
||||
@@ -46,10 +47,11 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
function scroll(e) {
|
||||
if (!router.hasTabbar()) return
|
||||
if (!props.scrollHideTabbar) return
|
||||
const offset = 50
|
||||
const top = e.target.scrollTop
|
||||
// if (!router.hasTabbar()) return
|
||||
// if (!props.scrollHideTabbar) return
|
||||
// const offset = 50
|
||||
// const top = e.target.scrollTop
|
||||
emit("scroll", e)
|
||||
}
|
||||
|
||||
function defaultPage() {
|
||||
@@ -67,7 +69,7 @@ function initBackStyle() {
|
||||
break
|
||||
case "just-flot-back":
|
||||
state.visble.topSafe = true
|
||||
state.visble.topNav = false
|
||||
state.visble.topNav = true
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<view class="tree-container">
|
||||
<view v-for="item in nodeData" :key="item.id">
|
||||
<view
|
||||
class="tree-item"
|
||||
:style="item.id == selectedNode.id ? { borderColor: '#1879ff' } : { color: '#000' }"
|
||||
@click.stop="toCurrentNode(item)"
|
||||
>
|
||||
<image class="tree-item-icon" :src="filePath(item.picUrl)" />
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
|
||||
<node-tree
|
||||
v-model="selectedNode"
|
||||
v-if="item.children && item.children.length"
|
||||
:node-data="item.children"
|
||||
:style="{ marginLeft: `${getLevel(item.level) * 60}rpx` }"
|
||||
@currentNode="(v) => emit('currentNode', v)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { filePath } from "../utils/file"
|
||||
import NodeTree from "./node-tree.vue"
|
||||
|
||||
withDefaults(defineProps<{ nodeData: any[] }>(), { nodeData: () => [] })
|
||||
const emit = defineEmits(["currentNode"])
|
||||
const selectedNode = defineModel<any>("modelValue")
|
||||
|
||||
const toCurrentNode = (item) => {
|
||||
emit("currentNode", item)
|
||||
selectedNode.value = item
|
||||
}
|
||||
|
||||
const getLevel = (level) => {
|
||||
return level
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.tree-item {
|
||||
border: 4rpx solid #e4e7ed77;
|
||||
margin-bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
.tree-item-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
</style>
|
||||
File renamed without changes.
@@ -0,0 +1,83 @@
|
||||
# 富文本编辑器组件
|
||||
|
||||
这是一个简单的富文本编辑器组件,专为移动端设计,支持Markdown语法。
|
||||
|
||||
## 组件文件
|
||||
|
||||
- `rich-text-editor.vue` - 核心编辑器组件
|
||||
- `rich-text-input.vue` - 表单输入组件
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 在表单中使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<tm-form-item name="description" label="详情描述" direction="vertical" required="">
|
||||
<rich-text-input
|
||||
v-model="form.description"
|
||||
placeholder="点击编辑详情描述..."
|
||||
label="详情描述"
|
||||
/>
|
||||
</tm-form-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import RichTextInput from '@/sheep/components/rich-text-input/rich-text-input.vue'
|
||||
|
||||
const form = reactive({
|
||||
description: ''
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 直接使用编辑器
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<rich-text-editor
|
||||
v-model="content"
|
||||
placeholder="请输入内容..."
|
||||
@change="onContentChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import RichTextEditor from '@/sheep/components/rich-text-editor/rich-text-editor.vue'
|
||||
|
||||
const content = ref('')
|
||||
|
||||
const onContentChange = (newContent) => {
|
||||
console.log('内容变化:', newContent)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持Markdown语法(标题、粗体、斜体、列表、表格、链接等)
|
||||
- ✅ 实时预览功能
|
||||
- ✅ 模板插入功能
|
||||
- ✅ 移动端优化界面
|
||||
- ✅ 弹窗编辑模式
|
||||
- ✅ 表单集成
|
||||
|
||||
## 支持的Markdown语法
|
||||
|
||||
- `# 标题` - 一级标题
|
||||
- `## 标题` - 二级标题
|
||||
- `### 标题` - 三级标题
|
||||
- `**粗体**` - 粗体文字
|
||||
- `*斜体*` - 斜体文字
|
||||
- `- 列表项` - 无序列表
|
||||
- `> 引用` - 引用块
|
||||
- `[链接文字](URL)` - 链接
|
||||
- `` - 图片
|
||||
- `| 表格 |` - 表格
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 组件使用tm-ui的Modal组件,确保项目中已安装tm-ui
|
||||
2. 图片上传功能需要配合项目的上传接口
|
||||
3. 组件样式可以根据项目需求进行调整
|
||||
4. 没有修改任何配置文件,只创建了组件文件
|
||||
@@ -0,0 +1,724 @@
|
||||
<template>
|
||||
<view class="rich-text-editor">
|
||||
<!-- 工具栏 -->
|
||||
<view class="toolbar">
|
||||
<view class="toolbar-row">
|
||||
<view class="tool-item" @click="insertBold">
|
||||
<text class="tool-text">粗体</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="insertItalic">
|
||||
<text class="tool-text">斜体</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="insertTitle">
|
||||
<text class="tool-text">标题</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="insertList">
|
||||
<text class="tool-text">列表</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="toolbar-row">
|
||||
<view class="tool-item" @click="insertLink">
|
||||
<text class="tool-text">链接</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="insertImage">
|
||||
<text class="tool-text">图片</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="insertTable">
|
||||
<text class="tool-text">表格</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="clearFormat">
|
||||
<text class="tool-text">清空</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 编辑区域 -->
|
||||
<view class="editor-container">
|
||||
<textarea
|
||||
v-model="content"
|
||||
class="editor-textarea"
|
||||
:placeholder="placeholder"
|
||||
@input="onContentChange"
|
||||
></textarea>
|
||||
</view>
|
||||
|
||||
<!-- 预览区域 -->
|
||||
<view v-if="showPreview" class="preview-container">
|
||||
<view class="preview-title">预览效果</view>
|
||||
<view class="preview-content">
|
||||
<rich-text :nodes="previewNodes"></rich-text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-actions">
|
||||
<tm-button size="s" skin="thin" @click="togglePreview">
|
||||
{{ showPreview ? '编辑' : '预览' }}
|
||||
</tm-button>
|
||||
<tm-button size="s" skin="thin" @click="insertTemplate">模板</tm-button>
|
||||
</view>
|
||||
|
||||
<!-- 链接输入弹窗 -->
|
||||
<tm-modal v-model:show="showLinkModal" title="插入链接" :showClose="true" size="small" :zIndex="1001">
|
||||
<view class="link-modal-content">
|
||||
<tm-input v-model="linkUrl" placeholder="请输入链接地址" class="link-input"></tm-input>
|
||||
</view>
|
||||
<template #footer>
|
||||
<view class="modal-footer">
|
||||
<tm-button size="s" skin="thin" @click="showLinkModal = false">取消</tm-button>
|
||||
<tm-button size="s" color="primary" @click="confirmLink">确定</tm-button>
|
||||
</view>
|
||||
</template>
|
||||
</tm-modal>
|
||||
|
||||
<!-- 模板选择弹窗 -->
|
||||
<tm-modal v-model:show="showTemplateModal" title="选择模板" :showClose="true" size="large" :zIndex="1001">
|
||||
<view class="template-modal-content">
|
||||
<view class="template-list">
|
||||
<view
|
||||
v-for="(template, index) in templates"
|
||||
:key="index"
|
||||
class="template-item"
|
||||
@click="selectTemplate(template)"
|
||||
>
|
||||
<text class="template-title">{{ template.title }}</text>
|
||||
<text class="template-desc">{{ template.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template #footer>
|
||||
<view class="modal-footer">
|
||||
<tm-button size="s" skin="thin" @click="showTemplateModal = false">取消</tm-button>
|
||||
</view>
|
||||
</template>
|
||||
</tm-modal>
|
||||
|
||||
<!-- 图片描述输入弹窗 -->
|
||||
<tm-modal v-model:show="showImageModal" title="插入图片" :showClose="true" size="small" :zIndex="1001">
|
||||
<view class="image-modal-content">
|
||||
<tm-input v-model="imageDescription" placeholder="请输入图片描述" class="image-input"></tm-input>
|
||||
</view>
|
||||
<template #footer>
|
||||
<view class="modal-footer">
|
||||
<tm-button size="s" skin="thin" @click="showImageModal = false">取消</tm-button>
|
||||
<tm-button size="s" color="primary" @click="confirmImage">确定</tm-button>
|
||||
</view>
|
||||
</template>
|
||||
</tm-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { hooks } from '@@/index'
|
||||
|
||||
interface Props {
|
||||
modelValue?: string
|
||||
placeholder?: string
|
||||
height?: string
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
(e: 'change', value: string): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
placeholder: '请输入内容...',
|
||||
height: '300px'
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const content = ref(props.modelValue)
|
||||
const showPreview = ref(false)
|
||||
const showLinkModal = ref(false)
|
||||
const showTemplateModal = ref(false)
|
||||
const showImageModal = ref(false)
|
||||
const linkUrl = ref('')
|
||||
const imageDescription = ref('')
|
||||
const pendingImageUrl = ref('')
|
||||
|
||||
// 模板数据
|
||||
const templates = ref([
|
||||
{
|
||||
title: '商品介绍模板',
|
||||
desc: '包含产品特点、规格、使用方法等',
|
||||
content: `# 商品介绍
|
||||
|
||||
## 产品特点
|
||||
- 特点1:详细描述
|
||||
- 特点2:详细描述
|
||||
- 特点3:详细描述
|
||||
|
||||
## 产品规格
|
||||
| 规格 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| 尺寸 | 长x宽x高 | 具体尺寸 |
|
||||
| 重量 | 重量 | 具体重量 |
|
||||
| 材质 | 材质 | 具体材质 |
|
||||
|
||||
## 使用方法
|
||||
1. **步骤一**:详细说明
|
||||
2. **步骤二**:详细说明
|
||||
3. **步骤三**:详细说明
|
||||
|
||||
## 注意事项
|
||||
> 请仔细阅读以下注意事项:
|
||||
> - 注意事项1
|
||||
> - 注意事项2
|
||||
> - 注意事项3`
|
||||
},
|
||||
{
|
||||
title: '活动说明模板',
|
||||
desc: '包含活动时间、规则、奖品设置等',
|
||||
content: `# 活动说明
|
||||
|
||||
## 活动时间
|
||||
**活动开始:** 2024年1月1日 00:00
|
||||
**活动结束:** 2024年1月31日 23:59
|
||||
|
||||
## 活动规则
|
||||
1. **参与条件**:详细说明参与条件
|
||||
2. **活动流程**:详细说明活动流程
|
||||
3. **奖励机制**:详细说明奖励机制
|
||||
|
||||
## 奖品设置
|
||||
| 奖项 | 奖品名称 | 数量 | 价值 |
|
||||
|------|----------|------|------|
|
||||
| 一等奖 | 奖品1 | 1个 | ¥1000 |
|
||||
| 二等奖 | 奖品2 | 3个 | ¥500 |
|
||||
| 三等奖 | 奖品3 | 10个 | ¥100`
|
||||
},
|
||||
{
|
||||
title: '使用说明模板',
|
||||
desc: '包含安装步骤、功能介绍、常见问题等',
|
||||
content: `# 使用说明
|
||||
|
||||
## 安装步骤
|
||||
1. **下载安装包**
|
||||
- 访问官网下载最新版本
|
||||
- 确保系统兼容性
|
||||
|
||||
2. **运行安装程序**
|
||||
- 双击安装包
|
||||
- 按照提示完成安装
|
||||
|
||||
3. **首次配置**
|
||||
- 启动程序
|
||||
- 完成基本设置
|
||||
|
||||
## 功能介绍
|
||||
- **功能一**:详细功能说明
|
||||
- **功能二**:详细功能说明
|
||||
- **功能三**:详细功能说明
|
||||
|
||||
## 常见问题
|
||||
**Q: 问题一?**
|
||||
A: 详细解答
|
||||
|
||||
**Q: 问题二?**
|
||||
A: 详细解答
|
||||
|
||||
**Q: 问题三?**
|
||||
A: 详细解答`
|
||||
}
|
||||
])
|
||||
|
||||
// 预览节点
|
||||
const previewNodes = computed(() => {
|
||||
if (!content.value) return []
|
||||
return parseToRichText(content.value)
|
||||
})
|
||||
|
||||
// 内容变化
|
||||
const onContentChange = () => {
|
||||
emit('update:modelValue', content.value)
|
||||
emit('change', content.value)
|
||||
}
|
||||
|
||||
// 插入粗体
|
||||
const insertBold = () => {
|
||||
insertText('**粗体文字**')
|
||||
}
|
||||
|
||||
// 插入斜体
|
||||
const insertItalic = () => {
|
||||
insertText('*斜体文字*')
|
||||
}
|
||||
|
||||
// 插入标题
|
||||
const insertTitle = () => {
|
||||
insertText('# 标题文字')
|
||||
}
|
||||
|
||||
// 插入列表
|
||||
const insertList = () => {
|
||||
insertText('- 列表项1\n- 列表项2\n- 列表项3')
|
||||
}
|
||||
|
||||
// 插入链接
|
||||
const insertLink = () => {
|
||||
linkUrl.value = ''
|
||||
showLinkModal.value = true
|
||||
}
|
||||
|
||||
// 确认插入链接
|
||||
const confirmLink = () => {
|
||||
if (linkUrl.value.trim()) {
|
||||
insertText(`[链接文字](${linkUrl.value})`)
|
||||
showLinkModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 插入图片
|
||||
const insertImage = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
zIndex: 1001,
|
||||
success: (res) => {
|
||||
// 先上传图片
|
||||
uploadImage(res.tempFilePaths[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 上传图片
|
||||
const uploadImage = (filePath: string) => {
|
||||
uni.showLoading({ title: '上传中...' })
|
||||
|
||||
// 使用项目中的hooks获取请求头
|
||||
const headers = hooks.useDefaultHeader()
|
||||
|
||||
uni.uploadFile({
|
||||
url: '/api/admin/sys-file/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
header: headers,
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
if (data.code === 0 && data.data && data.data.url) {
|
||||
// 修复URL格式问题,确保fileName参数格式正确
|
||||
let imageUrl = data.data.url
|
||||
// 如果URL中包含fileName参数但没有等号,则修复它
|
||||
if (imageUrl.includes('fileName ') && !imageUrl.includes('fileName=')) {
|
||||
imageUrl = imageUrl.replace('fileName ', 'fileName=')
|
||||
}
|
||||
|
||||
// 存储时保持相对路径,不拼接完整URL
|
||||
// 只在显示时才拼接完整URL
|
||||
|
||||
// 上传成功,显示描述输入弹窗
|
||||
// 确保存储的是相对路径
|
||||
pendingImageUrl.value = imageUrl
|
||||
imageDescription.value = ''
|
||||
showImageModal.value = true
|
||||
uni.showToast({ title: '上传成功', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: data.msg || '上传失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' })
|
||||
},
|
||||
complete: () => {
|
||||
uni.hideLoading()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 确认插入图片
|
||||
const confirmImage = () => {
|
||||
if (pendingImageUrl.value) {
|
||||
const desc = imageDescription.value.trim() || '图片'
|
||||
// 确保存储的是相对路径,移除可能存在的完整URL前缀
|
||||
let imageUrl = pendingImageUrl.value
|
||||
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
|
||||
// 如果是完整URL,提取相对路径部分
|
||||
const url = new URL(imageUrl)
|
||||
imageUrl = url.pathname + url.search
|
||||
}
|
||||
insertText(``)
|
||||
showImageModal.value = false
|
||||
pendingImageUrl.value = ''
|
||||
imageDescription.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 插入表格
|
||||
const insertTable = () => {
|
||||
const tableText = `| 列1 | 列2 | 列3 |
|
||||
|-----|-----|-----|
|
||||
| 内容1 | 内容2 | 内容3 |`
|
||||
insertText(tableText)
|
||||
}
|
||||
|
||||
// 清空格式
|
||||
const clearFormat = () => {
|
||||
content.value = content.value
|
||||
.replace(/\*\*(.*?)\*\*/g, '$1')
|
||||
.replace(/\*(.*?)\*/g, '$1')
|
||||
}
|
||||
|
||||
// 切换预览
|
||||
const togglePreview = () => {
|
||||
showPreview.value = !showPreview.value
|
||||
}
|
||||
|
||||
// 插入模板
|
||||
const insertTemplate = () => {
|
||||
showTemplateModal.value = true
|
||||
}
|
||||
|
||||
// 选择模板
|
||||
const selectTemplate = (template: any) => {
|
||||
content.value = template.content
|
||||
onContentChange()
|
||||
showTemplateModal.value = false
|
||||
}
|
||||
|
||||
// 插入文本
|
||||
const insertText = (text: string) => {
|
||||
content.value += '\n' + text + '\n'
|
||||
onContentChange()
|
||||
}
|
||||
|
||||
// 解析为富文本节点
|
||||
const parseToRichText = (text: string) => {
|
||||
if (!text) return []
|
||||
|
||||
const nodes = []
|
||||
const lines = text.split('\n')
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
|
||||
if (line.startsWith('# ')) {
|
||||
nodes.push({
|
||||
name: 'h1',
|
||||
attrs: { style: 'font-size: 18px; font-weight: bold; margin: 8px 0; color: #333;' },
|
||||
children: [{ type: 'text', text: line.substring(2) }]
|
||||
})
|
||||
} else if (line.startsWith('## ')) {
|
||||
nodes.push({
|
||||
name: 'h2',
|
||||
attrs: { style: 'font-size: 16px; font-weight: bold; margin: 6px 0; color: #333;' },
|
||||
children: [{ type: 'text', text: line.substring(3) }]
|
||||
})
|
||||
} else if (line.startsWith('### ')) {
|
||||
nodes.push({
|
||||
name: 'h3',
|
||||
attrs: { style: 'font-size: 14px; font-weight: bold; margin: 4px 0; color: #333;' },
|
||||
children: [{ type: 'text', text: line.substring(4) }]
|
||||
})
|
||||
} else if (line.startsWith('- ')) {
|
||||
nodes.push({
|
||||
name: 'li',
|
||||
attrs: { style: 'margin: 2px 0; padding-left: 16px; color: #666;' },
|
||||
children: [{ type: 'text', text: '• ' + line.substring(2) }]
|
||||
})
|
||||
} else if (line.startsWith('> ')) {
|
||||
nodes.push({
|
||||
name: 'blockquote',
|
||||
attrs: { style: 'border-left: 3px solid #ddd; padding-left: 8px; margin: 4px 0; color: #666; font-style: italic;' },
|
||||
children: [{ type: 'text', text: line.substring(2) }]
|
||||
})
|
||||
} else if (line.startsWith('---')) {
|
||||
nodes.push({
|
||||
name: 'hr',
|
||||
attrs: { style: 'border: none; border-top: 1px solid #eee; margin: 8px 0;' }
|
||||
})
|
||||
} else if (line.trim()) {
|
||||
const formattedText = formatText(line)
|
||||
nodes.push({
|
||||
name: 'p',
|
||||
attrs: { style: 'margin: 2px 0; line-height: 1.4; color: #666;' },
|
||||
children: formattedText
|
||||
})
|
||||
} else {
|
||||
nodes.push({
|
||||
name: 'br'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// 格式化文本
|
||||
const formatText = (text: string) => {
|
||||
const children = []
|
||||
let remaining = text
|
||||
|
||||
while (remaining.length > 0) {
|
||||
const boldMatch = remaining.match(/\*\*(.*?)\*\*/)
|
||||
const italicMatch = remaining.match(/\*(.*?)\*/)
|
||||
const linkMatch = remaining.match(/\[([^\]]+)\]\(([^)]+)\)/)
|
||||
const imageMatch = remaining.match(/!\[([^\]]*)\]\(([^)]+)\)/)
|
||||
|
||||
let match = null
|
||||
let type = ''
|
||||
|
||||
if (boldMatch && (!match || boldMatch.index < match.index)) {
|
||||
match = boldMatch
|
||||
type = 'bold'
|
||||
}
|
||||
if (italicMatch && (!match || italicMatch.index < match.index)) {
|
||||
match = italicMatch
|
||||
type = 'italic'
|
||||
}
|
||||
if (linkMatch && (!match || linkMatch.index < match.index)) {
|
||||
match = linkMatch
|
||||
type = 'link'
|
||||
}
|
||||
if (imageMatch && (!match || imageMatch.index < match.index)) {
|
||||
match = imageMatch
|
||||
type = 'image'
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if (match.index > 0) {
|
||||
children.push({
|
||||
type: 'text',
|
||||
text: remaining.substring(0, match.index)
|
||||
})
|
||||
}
|
||||
|
||||
if (type === 'bold') {
|
||||
children.push({
|
||||
name: 'strong',
|
||||
children: [{ type: 'text', text: match[1] }]
|
||||
})
|
||||
} else if (type === 'italic') {
|
||||
children.push({
|
||||
name: 'em',
|
||||
children: [{ type: 'text', text: match[1] }]
|
||||
})
|
||||
} else if (type === 'link') {
|
||||
children.push({
|
||||
name: 'a',
|
||||
attrs: { href: match[2], style: 'color: #007bff; text-decoration: underline;' },
|
||||
children: [{ type: 'text', text: match[1] }]
|
||||
})
|
||||
} else if (type === 'image') {
|
||||
// 处理图片URL,确保能正确显示
|
||||
let imageUrl = match[2]
|
||||
console.log('富文本编辑器 - 原始图片URL:', imageUrl)
|
||||
// 如果是相对路径,添加基础URL
|
||||
if (imageUrl.startsWith('/api/') || imageUrl.startsWith('/admin/')) {
|
||||
// 使用环境变量获取图片基础路径
|
||||
const isDev = import.meta.env.DEV
|
||||
let baseUrl = ''
|
||||
|
||||
if (isDev) {
|
||||
// 开发环境
|
||||
baseUrl = import.meta.env.VITE_IMAGE_DEV_PATH || 'http://127.0.0.1:9999'
|
||||
} else {
|
||||
// 生产环境
|
||||
baseUrl = import.meta.env.VITE_APP_PROD_IMAGE || window.location.origin
|
||||
}
|
||||
|
||||
console.log('富文本编辑器 - 环境:', isDev ? '开发' : '生产')
|
||||
console.log('富文本编辑器 - 基础URL:', baseUrl)
|
||||
console.log('富文本编辑器 - 原始图片URL:', imageUrl)
|
||||
|
||||
// 拼接URL
|
||||
imageUrl = baseUrl + imageUrl
|
||||
console.log('富文本编辑器 - 拼接后URL:', imageUrl)
|
||||
}
|
||||
|
||||
children.push({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: imageUrl,
|
||||
alt: match[1] || '图片',
|
||||
style: 'max-width: 100%; height: auto; display: block; margin: 8px 0; border-radius: 4px;'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
remaining = remaining.substring(match.index + match[0].length)
|
||||
} else {
|
||||
children.push({
|
||||
type: 'text',
|
||||
text: remaining
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return children.length > 0 ? children : [{ type: 'text', text: text }]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rich-text-editor {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e9ecef;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.toolbar-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tool-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex: 1;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.tool-item:active {
|
||||
background: #e9ecef;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.tool-text {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.editor-textarea {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
border-top: 1px solid #e9ecef;
|
||||
padding: 12px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e9ecef;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.link-modal-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.link-input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.image-modal-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.image-input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.template-modal-content {
|
||||
padding: 16px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
padding: 16px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.template-item:active {
|
||||
background: #f8f9fa;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.template-title {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.template-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<view class="rich-text-input">
|
||||
<tm-form-item :name="name" :label="label" :required="required" :direction="direction">
|
||||
<view class="input-container" @click="openEditor">
|
||||
<view v-if="!modelValue || modelValue.trim() === ''" class="placeholder">
|
||||
{{ placeholder }}
|
||||
</view>
|
||||
<view v-else class="content-preview">
|
||||
<rich-text :nodes="previewNodes" class="preview-text"></rich-text>
|
||||
</view>
|
||||
<view class="input-actions">
|
||||
<tm-icon name="edit" size="16" color="#999"></tm-icon>
|
||||
</view>
|
||||
</view>
|
||||
</tm-form-item>
|
||||
|
||||
<!-- 弹窗编辑器 -->
|
||||
<tm-modal v-model:show="showEditor" title="富文本编辑" :showClose="true" size="large" :height="'80vh'" :zIndex="1000">
|
||||
<view class="editor-modal-content">
|
||||
<rich-text-editor
|
||||
v-model="editorContent"
|
||||
:placeholder="placeholder"
|
||||
@change="onEditorChange"
|
||||
/>
|
||||
</view>
|
||||
<template #footer>
|
||||
<view class="modal-footer">
|
||||
<tm-button size="s" skin="thin" @click="showEditor = false">取消</tm-button>
|
||||
<tm-button size="s" color="primary" @click="saveContent">保存</tm-button>
|
||||
</view>
|
||||
</template>
|
||||
</tm-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import RichTextEditor from '../rich-text-editor/rich-text-editor.vue'
|
||||
|
||||
interface Props {
|
||||
modelValue?: string
|
||||
label?: string
|
||||
name?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
direction?: 'horizontal' | 'vertical'
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
(e: 'change', value: string): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
label: '内容',
|
||||
placeholder: '点击编辑内容...',
|
||||
required: false,
|
||||
direction: 'horizontal'
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const showEditor = ref(false)
|
||||
const editorContent = ref('')
|
||||
|
||||
// 预览节点
|
||||
const previewNodes = computed(() => {
|
||||
if (!props.modelValue || props.modelValue.trim() === '') return []
|
||||
return parseToRichText(props.modelValue)
|
||||
})
|
||||
|
||||
// 监听内容变化
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
editorContent.value = newVal || ''
|
||||
})
|
||||
|
||||
// 打开编辑器
|
||||
const openEditor = () => {
|
||||
editorContent.value = props.modelValue || ''
|
||||
showEditor.value = true
|
||||
}
|
||||
|
||||
// 编辑器内容变化
|
||||
const onEditorChange = (content: string) => {
|
||||
editorContent.value = content
|
||||
}
|
||||
|
||||
// 保存内容
|
||||
const saveContent = () => {
|
||||
emit('update:modelValue', editorContent.value)
|
||||
emit('change', editorContent.value)
|
||||
showEditor.value = false
|
||||
}
|
||||
|
||||
// HTML转Markdown函数
|
||||
const convertHtmlToMarkdown = (html: string): string => {
|
||||
if (!html) return ''
|
||||
|
||||
// 移除HTML标签,保留文本内容
|
||||
let markdown = html
|
||||
// 处理标题
|
||||
.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n')
|
||||
.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n')
|
||||
.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n')
|
||||
.replace(/<h4[^>]*>(.*?)<\/h4>/gi, '#### $1\n')
|
||||
.replace(/<h5[^>]*>(.*?)<\/h5>/gi, '##### $1\n')
|
||||
.replace(/<h6[^>]*>(.*?)<\/h6>/gi, '###### $1\n')
|
||||
// 处理段落
|
||||
.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n')
|
||||
// 处理换行
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
// 处理粗体
|
||||
.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**')
|
||||
.replace(/<b[^>]*>(.*?)<\/b>/gi, '**$1**')
|
||||
// 处理斜体
|
||||
.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*')
|
||||
.replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*')
|
||||
// 处理下划线
|
||||
.replace(/<u[^>]*>(.*?)<\/u>/gi, '__$1__')
|
||||
// 处理链接
|
||||
.replace(/<a[^>]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>/gi, '[$2]($1)')
|
||||
// 处理图片 - 更全面的图片处理
|
||||
.replace(/<img[^>]*src=["']([^"']*)["'][^>]*alt=["']([^"']*)["'][^>]*>/gi, (match, src, alt) => {
|
||||
// 直接使用原始URL,不添加额外前缀
|
||||
console.log('rich-text-input HTML图片转换 - 原始:', match)
|
||||
console.log('rich-text-input HTML图片转换 - src:', src)
|
||||
console.log('rich-text-input HTML图片转换 - alt:', alt)
|
||||
return ``
|
||||
})
|
||||
.replace(/<img[^>]*src=["']([^"']*)["'][^>]*>/gi, (match, src) => {
|
||||
// 直接使用原始URL,不添加额外前缀
|
||||
console.log('rich-text-input HTML图片转换 - 原始:', match)
|
||||
console.log('rich-text-input HTML图片转换 - src:', src)
|
||||
return ``
|
||||
})
|
||||
// 处理列表
|
||||
.replace(/<ul[^>]*>(.*?)<\/ul>/gis, (match, content) => {
|
||||
return content.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n')
|
||||
})
|
||||
.replace(/<ol[^>]*>(.*?)<\/ol>/gis, (match, content) => {
|
||||
let index = 1
|
||||
return content.replace(/<li[^>]*>(.*?)<\/li>/gi, () => `${index++}. $1\n`)
|
||||
})
|
||||
// 处理引用
|
||||
.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, (match, content) => {
|
||||
return content.replace(/\n/g, '\n> ') + '\n'
|
||||
})
|
||||
// 处理代码
|
||||
.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`')
|
||||
.replace(/<pre[^>]*>(.*?)<\/pre>/gis, '```\n$1\n```')
|
||||
// 处理分割线
|
||||
.replace(/<hr[^>]*>/gi, '---\n')
|
||||
// 处理表格(简单处理)
|
||||
.replace(/<table[^>]*>(.*?)<\/table>/gis, (match, content) => {
|
||||
return content.replace(/<tr[^>]*>(.*?)<\/tr>/gi, (trMatch, trContent) => {
|
||||
const cells = trContent.match(/<t[hd][^>]*>(.*?)<\/t[hd]>/gi)
|
||||
if (cells) {
|
||||
return '| ' + cells.map(cell => cell.replace(/<[^>]*>/g, '')).join(' | ') + ' |\n'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
})
|
||||
// 移除剩余的HTML标签
|
||||
.replace(/<[^>]*>/g, '')
|
||||
// 清理多余的换行
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim()
|
||||
|
||||
return markdown
|
||||
}
|
||||
|
||||
// 解析为富文本节点
|
||||
const parseToRichText = (text: string) => {
|
||||
if (!text) return []
|
||||
|
||||
// 如果包含HTML标签,先转换为Markdown
|
||||
const markdownText = text.includes('<') ? convertHtmlToMarkdown(text) : text
|
||||
|
||||
const nodes = []
|
||||
const lines = markdownText.split('\n')
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
|
||||
if (line.startsWith('# ')) {
|
||||
nodes.push({
|
||||
name: 'h1',
|
||||
attrs: { style: 'font-size: 16px; font-weight: bold; margin: 6px 0; color: #333;' },
|
||||
children: [{ type: 'text', text: line.substring(2) }]
|
||||
})
|
||||
} else if (line.startsWith('## ')) {
|
||||
nodes.push({
|
||||
name: 'h2',
|
||||
attrs: { style: 'font-size: 14px; font-weight: bold; margin: 4px 0; color: #333;' },
|
||||
children: [{ type: 'text', text: line.substring(3) }]
|
||||
})
|
||||
} else if (line.startsWith('### ')) {
|
||||
nodes.push({
|
||||
name: 'h3',
|
||||
attrs: { style: 'font-size: 13px; font-weight: bold; margin: 3px 0; color: #333;' },
|
||||
children: [{ type: 'text', text: line.substring(4) }]
|
||||
})
|
||||
} else if (line.startsWith('- ')) {
|
||||
nodes.push({
|
||||
name: 'li',
|
||||
attrs: { style: 'margin: 1px 0; padding-left: 12px; color: #666;' },
|
||||
children: [{ type: 'text', text: '• ' + line.substring(2) }]
|
||||
})
|
||||
} else if (line.startsWith('> ')) {
|
||||
nodes.push({
|
||||
name: 'blockquote',
|
||||
attrs: { style: 'border-left: 2px solid #ddd; padding-left: 6px; margin: 2px 0; color: #666; font-style: italic;' },
|
||||
children: [{ type: 'text', text: line.substring(2) }]
|
||||
})
|
||||
} else if (line.startsWith('---')) {
|
||||
nodes.push({
|
||||
name: 'hr',
|
||||
attrs: { style: 'border: none; border-top: 1px solid #eee; margin: 6px 0;' }
|
||||
})
|
||||
} else if (line.trim()) {
|
||||
const formattedText = formatText(line)
|
||||
nodes.push({
|
||||
name: 'p',
|
||||
attrs: { style: 'margin: 1px 0; line-height: 1.3; color: #666;' },
|
||||
children: formattedText
|
||||
})
|
||||
} else {
|
||||
nodes.push({
|
||||
name: 'br'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// 格式化文本
|
||||
const formatText = (text: string) => {
|
||||
const children = []
|
||||
let remaining = text
|
||||
|
||||
while (remaining.length > 0) {
|
||||
const boldMatch = remaining.match(/\*\*(.*?)\*\*/)
|
||||
const italicMatch = remaining.match(/\*(.*?)\*/)
|
||||
const linkMatch = remaining.match(/\[([^\]]+)\]\(([^)]+)\)/)
|
||||
const imageMatch = remaining.match(/!\[([^\]]*)\]\(([^)]+)\)/)
|
||||
|
||||
let match = null
|
||||
let type = ''
|
||||
|
||||
if (boldMatch && (!match || boldMatch.index < match.index)) {
|
||||
match = boldMatch
|
||||
type = 'bold'
|
||||
}
|
||||
if (italicMatch && (!match || italicMatch.index < match.index)) {
|
||||
match = italicMatch
|
||||
type = 'italic'
|
||||
}
|
||||
if (linkMatch && (!match || linkMatch.index < match.index)) {
|
||||
match = linkMatch
|
||||
type = 'link'
|
||||
}
|
||||
if (imageMatch && (!match || imageMatch.index < match.index)) {
|
||||
match = imageMatch
|
||||
type = 'image'
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if (match.index > 0) {
|
||||
children.push({
|
||||
type: 'text',
|
||||
text: remaining.substring(0, match.index)
|
||||
})
|
||||
}
|
||||
|
||||
if (type === 'bold') {
|
||||
children.push({
|
||||
name: 'strong',
|
||||
children: [{ type: 'text', text: match[1] }]
|
||||
})
|
||||
} else if (type === 'italic') {
|
||||
children.push({
|
||||
name: 'em',
|
||||
children: [{ type: 'text', text: match[1] }]
|
||||
})
|
||||
} else if (type === 'link') {
|
||||
children.push({
|
||||
name: 'a',
|
||||
attrs: { href: match[2], style: 'color: #007bff; text-decoration: underline;' },
|
||||
children: [{ type: 'text', text: match[1] }]
|
||||
})
|
||||
} else if (type === 'image') {
|
||||
// 处理图片URL,确保能正确显示
|
||||
let imageUrl = match[2]
|
||||
console.log('原始图片URL:', imageUrl)
|
||||
// 如果是相对路径,添加基础URL
|
||||
if (imageUrl.startsWith('/api/') || imageUrl.startsWith('/admin/')) {
|
||||
// 使用环境变量获取图片基础路径
|
||||
const isDev = import.meta.env.DEV
|
||||
let baseUrl = ''
|
||||
|
||||
if (isDev) {
|
||||
// 开发环境
|
||||
baseUrl = import.meta.env.VITE_IMAGE_DEV_PATH || 'http://127.0.0.1:9999'
|
||||
} else {
|
||||
// 生产环境
|
||||
baseUrl = import.meta.env.VITE_APP_PROD_IMAGE || window.location.origin
|
||||
}
|
||||
|
||||
console.log('环境:', isDev ? '开发' : '生产')
|
||||
console.log('基础URL:', baseUrl)
|
||||
console.log('原始图片URL:', imageUrl)
|
||||
|
||||
// 拼接URL
|
||||
imageUrl = baseUrl + imageUrl
|
||||
console.log('拼接后URL:', imageUrl)
|
||||
}
|
||||
|
||||
children.push({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: imageUrl,
|
||||
alt: match[1] || '图片',
|
||||
style: 'max-width: 100%; height: auto; display: block; margin: 8px 0; border-radius: 4px;'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
remaining = remaining.substring(match.index + match[0].length)
|
||||
} else {
|
||||
children.push({
|
||||
type: 'text',
|
||||
text: remaining
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return children.length > 0 ? children : [{ type: 'text', text: text }]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rich-text-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
min-height: 44px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.input-container:active {
|
||||
border-color: #007bff;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.content-preview {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
max-height: 80px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.input-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.editor-modal-content {
|
||||
height: 60vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<view class="mb-20rpx">
|
||||
<node-tree v-model="selectedNode" :node-data="categoryTree" />
|
||||
<view v-if="categoryTree.length === 0" class="text-center text-#888 py-40rpx">暂无数据</view>
|
||||
</view>
|
||||
<view class="px-20rpx py-2 flex justify-between sticky bottom-0 bg-white">
|
||||
<tm-button skin="thin" block @click="emit('cancel')">取消</tm-button>
|
||||
|
||||
<tm-button block :disabled="!selectedNode" @click="onConfirm">确定</tm-button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import NodeTree from "@/sheep/components/node-tree.vue"
|
||||
|
||||
defineProps({
|
||||
categoryTree: {
|
||||
type: Array<any>,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(["confirm", "cancel"])
|
||||
|
||||
const selectedNode = ref<any>({})
|
||||
|
||||
function onConfirm() {
|
||||
if (!selectedNode.value) {
|
||||
return uni.showToast({ title: "请选择分类", icon: "none" })
|
||||
}
|
||||
emit("confirm", { ...selectedNode, categoryId: selectedNode.value.id, categoryName: selectedNode.value.name })
|
||||
}
|
||||
</script>
|
||||
File renamed without changes.
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
export * from "./use-copy";
|
||||
export * from "./use-copy"
|
||||
|
||||
export * from "./use-enum";
|
||||
export * from "./use-enum"
|
||||
|
||||
export * from "./use-img";
|
||||
export * from "./use-img"
|
||||
|
||||
export * from "./use-page";
|
||||
export * from "./use-page"
|
||||
|
||||
export * from "./use-phone";
|
||||
export * from "./use-phone"
|
||||
|
||||
export * from "./use-unit";
|
||||
export * from "./use-unit"
|
||||
|
||||
export * from "./use-token";
|
||||
export * from "./use-token"
|
||||
|
||||
export * from "./use-scan-code"
|
||||
Whitespace-only changes.
@@ -0,0 +1,105 @@
|
||||
import { router } from ".."
|
||||
import { APPENV } from "../constant"
|
||||
import { getAppInfo, isDevEnv } from "../utils/app"
|
||||
|
||||
export function useScanCode() {
|
||||
async function scanCodeAction(): Promise<string | undefined> {
|
||||
const appInfo = await getAppInfo()
|
||||
|
||||
if (isDevEnv()) {
|
||||
// 开发环境模拟扫码
|
||||
// const testQrCode = "eyJpZCI6IjE5MDU4ODg4NjQ0NTQ1MzMxMjEiLCJuYW1lIjoi6ICB5Y6/5bqc6aOf5aCCIn0=" // 测试线上-老县府食堂-收款二维码
|
||||
// const testQrCode = "eyJoYXJkRG9vcklkIjoiMTkxNDIxNTYwODQzNzQxMTg0MSIsImNhYmluZXRObyI6IjEifQ==" // 测试线上-智能柜-收款二维码
|
||||
// const testQrCode = "eyJpZCI6IjE5MDU4MTY3NTEwNDM0NTI5MzAiLCJuYW1lIjoi5paw5Y6/5bqc6LaF5biCIn0=" // 邓本地-测试环境-新县府超市收款二维码
|
||||
// const testQrCode = "eyJoYXJkRG9vcklkIjoiMTkxMzE1NzA4MDQxODI5NTgwOSIsImNhYmluZXRObyI6IjEifQ==" // 邓本地-测试单门智能柜柜门二维码
|
||||
const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?id=1905816751043452930&name=测试新县府超市" // 邓本地-测试环境-新县府超市收款二维码
|
||||
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?hardDoorId=1913157080418295809&cabinetNo=1" // 测试智能柜柜门二维码
|
||||
uni.showModal({ content: "开发环境不是浙政钉和诗画环境,扫码仅供测试" })
|
||||
return Promise.resolve(testQrCode)
|
||||
}
|
||||
|
||||
if (appInfo == APPENV.ZZD) {
|
||||
// 浙政钉扫码
|
||||
const res: any = await (window as any).dd.scan({ type: "all" })
|
||||
return res.text || ""
|
||||
}
|
||||
|
||||
if (appInfo == APPENV.SH) {
|
||||
// 诗画扫码
|
||||
const res = await (window as any).CP2.showScanQr({ needResult: 1 })
|
||||
if (res.code == 1) return res.data.qrCode || ""
|
||||
return ""
|
||||
}
|
||||
uni.showToast({ title: "当前环境未知", icon: "none" })
|
||||
}
|
||||
|
||||
// 处理url扫码结果
|
||||
function processUrlScanResult(url: string) {
|
||||
if (!url) return
|
||||
const searchParams = new URL(url).searchParams
|
||||
const paramsObj: Record<string, string> = {}
|
||||
searchParams.forEach((value, key) => (paramsObj[key] = value))
|
||||
const { hardDoorId, cabinetNo, action, id, name } = paramsObj
|
||||
|
||||
// 智能柜扫码
|
||||
if (hardDoorId && cabinetNo) {
|
||||
router.jump("/pages/cabinet/buy", { hardDoorId, cabinetNo })
|
||||
return
|
||||
}
|
||||
// 扫收款机上生成的码支付
|
||||
if (action === "harQrCodeScanPay") {
|
||||
const { mchId, orderId } = paramsObj
|
||||
router.jump("/pages/pay/scanPay", { action, mchId, orderId })
|
||||
return
|
||||
}
|
||||
// 扫商户url收款码
|
||||
if (id) {
|
||||
router.jump("/pages/pay/scanPay", { id, name })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: "无效的二维码", icon: "none" })
|
||||
}
|
||||
|
||||
// 处理base64扫码结果
|
||||
function processBase64ScanResult(scanResult: string) {
|
||||
const decodedString = decodeBase64(scanResult)
|
||||
if (!decodedString) return
|
||||
|
||||
try {
|
||||
const decodedJson = JSON.parse(decodedString)
|
||||
if (decodedJson.id) {
|
||||
router.jump("/pages/pay/scanPay", { id: decodedJson.id })
|
||||
return
|
||||
}
|
||||
if (decodedJson?.hardDoorId && decodedJson?.cabinetNo) {
|
||||
router.jump("/pages/cabinet/buy", { hardDoorId: decodedJson.hardDoorId, cabinetNo: decodedJson.cabinetNo })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: "无效的二维码", icon: "none" })
|
||||
} catch (error) {
|
||||
uni.showToast({ title: "无效的二维码内容", icon: "none" })
|
||||
}
|
||||
}
|
||||
|
||||
// 解码base64
|
||||
function decodeBase64(base64String: string): string | undefined {
|
||||
try {
|
||||
const decodedBinaryString = atob(base64String)
|
||||
const uint8Array = new Uint8Array(decodedBinaryString.length)
|
||||
for (let i = 0; i < decodedBinaryString.length; i++) {
|
||||
uint8Array[i] = decodedBinaryString.charCodeAt(i)
|
||||
}
|
||||
const decoder = new TextDecoder("utf-8")
|
||||
const decodedString = decoder.decode(uint8Array)
|
||||
|
||||
// console.log("解码结果", decodedString)
|
||||
|
||||
return decodedString
|
||||
} catch (error) {
|
||||
console.error("Base64 解码失败:", error)
|
||||
uni.showToast({ title: "无效的二维码", icon: "none" })
|
||||
}
|
||||
}
|
||||
|
||||
return { scanCodeAction, processUrlScanResult, processBase64ScanResult }
|
||||
}
|
||||
@@ -3,11 +3,11 @@ import { foodApi } from "../api"
|
||||
|
||||
export const useCartStore = defineStore("cartStore", {
|
||||
state: () => ({
|
||||
cart: JSON.parse(uni.getStorageSync("cart") || "[]"),
|
||||
shopMerchantId: uni.getStorageSync("shopMerchantId") || "",
|
||||
cart: JSON.parse(uni.getStorageSync("onlineShopCart") || "[]"),
|
||||
shopMerchantId: uni.getStorageSync("onlineShopMerchantId") || "",
|
||||
}),
|
||||
getters: {
|
||||
totCount(state) {
|
||||
totalCount(state) {
|
||||
return state.cart.reduce((prev: string, cur: any) => {
|
||||
return prev + cur.productCount
|
||||
}, 0)
|
||||
@@ -22,14 +22,11 @@ export const useCartStore = defineStore("cartStore", {
|
||||
actions: {
|
||||
saveShopMerchantId(shopMerchantId: string) {
|
||||
this.shopMerchantId = shopMerchantId
|
||||
uni.setStorageSync("shopMerchantId", this.shopMerchantId)
|
||||
uni.setStorageSync("onlineShopMerchantId", this.shopMerchantId)
|
||||
},
|
||||
saveToStorage() {
|
||||
uni.setStorageSync("cart", JSON.stringify(this.cart))
|
||||
},
|
||||
saveShoppingCart(cartList: any) {
|
||||
saveShopCart(cartList: any) {
|
||||
this.cart = cartList
|
||||
this.saveToStorage()
|
||||
uni.setStorageSync("onlineShopCart", JSON.stringify(this.cart))
|
||||
},
|
||||
addGoodsItemToCart(goodsItem: any) {
|
||||
const findResult = this.cart.find((item: any) => {
|
||||
@@ -40,7 +37,7 @@ export const useCartStore = defineStore("cartStore", {
|
||||
} else {
|
||||
findResult.productCount++
|
||||
}
|
||||
this.saveToStorage()
|
||||
uni.setStorageSync("onlineShopCart", JSON.stringify(this.cart))
|
||||
},
|
||||
async addProductCount(productId: string) {
|
||||
this.cart.forEach((item: any) => {
|
||||
@@ -62,7 +59,7 @@ export const useCartStore = defineStore("cartStore", {
|
||||
},
|
||||
async getShopCart() {
|
||||
const { data } = await foodApi.getShopCar({ merchantId: this.shopMerchantId, hardType: "SUPERMARKET" })
|
||||
this.saveShoppingCart(data ?? [])
|
||||
this.saveShopCart(data ?? [])
|
||||
},
|
||||
async updateShopCart() {
|
||||
const list = this.cart
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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
|
||||
}
|
||||
|
||||
export default treeToList
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
@@ -1,9 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch, type PropType, getCurrentInstance, onUpdated, nextTick } from 'vue';
|
||||
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
|
||||
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
|
||||
import { useTmConfig } from "../../libs/config";
|
||||
import { onPageScroll } from '@dcloudio/uni-app';
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch, type PropType, getCurrentInstance, onUpdated, nextTick } from "vue"
|
||||
import {
|
||||
arrayNumberValid,
|
||||
arrayNumberValidByStyleMP,
|
||||
covetUniNumber,
|
||||
arrayNumberValidByStyleBorderColor,
|
||||
linearValid,
|
||||
getUnit,
|
||||
getUid,
|
||||
} from "../../libs/tool"
|
||||
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors"
|
||||
import { useTmConfig } from "../../libs/config"
|
||||
import { onPageScroll } from "@dcloudio/uni-app"
|
||||
|
||||
/**
|
||||
* @displayName 抽屉
|
||||
@@ -15,7 +23,7 @@ import { onPageScroll } from '@dcloudio/uni-app';
|
||||
| --- | --- | --- | --- |
|
||||
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
|
||||
*/
|
||||
defineOptions({ name: 'TmDrawer' });
|
||||
defineOptions({ name: "TmDrawer" })
|
||||
const { config } = useTmConfig()
|
||||
|
||||
const props = defineProps({
|
||||
@@ -24,84 +32,84 @@ const props = defineProps({
|
||||
*/
|
||||
customStyle: {
|
||||
type: String,
|
||||
default: ""
|
||||
default: "",
|
||||
},
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: "标题"
|
||||
default: "标题",
|
||||
},
|
||||
/**
|
||||
* 显示底部操作栏
|
||||
*/
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 是否显示标题
|
||||
*/
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 是否显示底部关闭按钮
|
||||
*/
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 遮罩是否允许点击被关闭
|
||||
*/
|
||||
overlayClick: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 显示可v-model:show双向绑定
|
||||
*/
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 显示取消按钮
|
||||
*/
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 取消按钮的文本
|
||||
*/
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: "取消"
|
||||
default: "取消",
|
||||
},
|
||||
/**
|
||||
* 确认按钮的文本
|
||||
*/
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: "确认"
|
||||
default: "确认",
|
||||
},
|
||||
/**
|
||||
* 动画时间
|
||||
*/
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
default: 300,
|
||||
},
|
||||
/**
|
||||
* 打开方向。
|
||||
*/
|
||||
position: {
|
||||
type: String as PropType<"top" | "bottom" | "left" | "right">,
|
||||
default: "bottom"
|
||||
default: "bottom",
|
||||
},
|
||||
/**
|
||||
* 打开方向为上和下时的圆角
|
||||
@@ -109,7 +117,7 @@ const props = defineProps({
|
||||
*/
|
||||
round: {
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
default: "",
|
||||
},
|
||||
/**
|
||||
* 左右时为内容宽,
|
||||
@@ -118,7 +126,7 @@ const props = defineProps({
|
||||
*/
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: "50%"
|
||||
default: "50%",
|
||||
},
|
||||
/**
|
||||
* 弹层最大的高度值,默认为屏幕的可视高
|
||||
@@ -126,21 +134,21 @@ const props = defineProps({
|
||||
*/
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: ""
|
||||
default: "",
|
||||
},
|
||||
/**
|
||||
* 背景颜色
|
||||
*/
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'white'
|
||||
default: "white",
|
||||
},
|
||||
/**
|
||||
* 暗黑背景颜色,如果不提供默认读取全局的sheet配置
|
||||
*/
|
||||
darkBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: "",
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -150,14 +158,14 @@ const props = defineProps({
|
||||
*/
|
||||
disabledScroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 内容区域左右和下的边距。
|
||||
*/
|
||||
contentMargin: {
|
||||
type: String,
|
||||
default: '24'
|
||||
default: "24",
|
||||
},
|
||||
/**
|
||||
* 宽屏时是否让内容剧中显示
|
||||
@@ -166,21 +174,21 @@ const props = defineProps({
|
||||
*/
|
||||
widthCoverCenter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 距离顶部的偏移量
|
||||
*/
|
||||
offsetTop: {
|
||||
type: [String, Number],
|
||||
default: '0'
|
||||
default: "0",
|
||||
},
|
||||
/**
|
||||
* 弹层的层级
|
||||
*/
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 1100
|
||||
default: 1100,
|
||||
},
|
||||
/**
|
||||
* 懒加载
|
||||
@@ -189,50 +197,50 @@ const props = defineProps({
|
||||
*/
|
||||
lazy: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 关闭前执行,可以是异步函数,只有当用户点击confirm时才会触发。如果返回false将阻止关闭。true允许关闭。
|
||||
*/
|
||||
beforeClose: {
|
||||
type: [Function, Boolean] as PropType<() => Promise<boolean>>,
|
||||
default: true
|
||||
}
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
/**
|
||||
* 点击遮罩事件
|
||||
*/
|
||||
'click',
|
||||
"click",
|
||||
/**
|
||||
* 关闭是触发
|
||||
*/
|
||||
'close',
|
||||
"close",
|
||||
/**
|
||||
* 打开时触发
|
||||
*/
|
||||
'open',
|
||||
"open",
|
||||
/**
|
||||
* 打开前执行
|
||||
*/
|
||||
'beforeOpen',
|
||||
"beforeOpen",
|
||||
/**
|
||||
* 关闭前执行
|
||||
*/
|
||||
'beforeClose',
|
||||
"beforeClose",
|
||||
/**
|
||||
* 等同v-model:show
|
||||
*/
|
||||
'update:show',
|
||||
"update:show",
|
||||
/**
|
||||
* 取消时触发
|
||||
*/
|
||||
'cancel',
|
||||
"cancel",
|
||||
/**
|
||||
* 确认时触发
|
||||
*/
|
||||
'confirm'
|
||||
"confirm",
|
||||
])
|
||||
|
||||
const isLoading = ref(false)
|
||||
@@ -251,13 +259,16 @@ const windtopReal = ref(0)
|
||||
const tantiaoTrue = ref(false)
|
||||
const safeFooterHeight = ref(0)
|
||||
const lezyShowModal = ref(props.lazy ? false : true)
|
||||
watch(() => props.show, (newVal) => {
|
||||
if (newVal) {
|
||||
showAlert()
|
||||
} else {
|
||||
closeAlert()
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => props.show,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
showAlert()
|
||||
} else {
|
||||
closeAlert()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const _lazy = computed(() => props.lazy)
|
||||
const _customStyle = computed(() => props.customStyle)
|
||||
@@ -276,10 +287,10 @@ const _round = computed(() => {
|
||||
let radius = covetUniNumber(round, config.unit)
|
||||
|
||||
let _r = "none"
|
||||
if (props.position == 'top') {
|
||||
if (props.position == "top") {
|
||||
_r = `0px 0px ${radius} ${radius}`
|
||||
}
|
||||
if (props.position == 'bottom') {
|
||||
if (props.position == "bottom") {
|
||||
_r = `${radius} ${radius} 0px 0px`
|
||||
}
|
||||
return _r
|
||||
@@ -288,7 +299,7 @@ const _offset = computed(() => {
|
||||
let offset = covetUniNumber(props.offsetTop, config.unit)
|
||||
let x = parseFloat(offset)
|
||||
let unit = getUnit(offset)
|
||||
if (unit == 'rpx') {
|
||||
if (unit == "rpx") {
|
||||
x = uni.upx2px(x)
|
||||
}
|
||||
return x
|
||||
@@ -298,7 +309,7 @@ const _contentMargin = computed(() => covetUniNumber(props.contentMargin, config
|
||||
const _showFooter = computed(() => props.showFooter)
|
||||
const _maxHeight = computed(() => {
|
||||
if (props.maxHeight == "") return ""
|
||||
if (props.position == 'left' || props.position == 'right') return ""
|
||||
if (props.position == "left" || props.position == "right") return ""
|
||||
|
||||
return covetUniNumber(props.maxHeight, config.unit)
|
||||
})
|
||||
@@ -308,8 +319,8 @@ const _confirmText = computed(() => props.confirmText)
|
||||
const _animationFun = computed(() => config.animation)
|
||||
const _bgColor = computed(() => {
|
||||
let bgcolor = props.bgColor
|
||||
if (config.mode == 'dark') {
|
||||
if (props.darkBgColor != '') {
|
||||
if (config.mode == "dark") {
|
||||
if (props.darkBgColor != "") {
|
||||
bgcolor = props.darkBgColor
|
||||
} else {
|
||||
bgcolor = config.sheetDarkColor
|
||||
@@ -318,19 +329,19 @@ const _bgColor = computed(() => {
|
||||
return getDefaultColor(bgcolor)
|
||||
})
|
||||
const __height = computed(() => {
|
||||
let h = '100%'
|
||||
let h = "100%"
|
||||
// #ifdef WEB
|
||||
h = `calc(100% - ${windtopReal.value}px)`
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
if (_offset.value > 0) {
|
||||
h = _height.value + 'px'
|
||||
h = _height.value + "px"
|
||||
}
|
||||
// #endif
|
||||
return h
|
||||
})
|
||||
const _titleFontSize = computed(() => (config.fontSizeScale * 16).toString() + 'px')
|
||||
const _isDark = computed(() => config.mode == 'dark')
|
||||
const _titleFontSize = computed(() => (config.fontSizeScale * 16).toString() + "px")
|
||||
const _isDark = computed(() => config.mode == "dark")
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
@@ -340,8 +351,8 @@ onMounted(() => {
|
||||
_width.value = sys.windowWidth
|
||||
_height.value = sys.windowHeight
|
||||
|
||||
windtop.value = sys.windowTop + (_position.value == 'bottom' ? 0 : _offset.value)
|
||||
windtopReal.value = _position.value == 'bottom' ? (sys.windowTop + _offset.value) : sys.windowTop
|
||||
windtop.value = sys.windowTop + (_position.value == "bottom" ? 0 : _offset.value)
|
||||
windtopReal.value = _position.value == "bottom" ? sys.windowTop + _offset.value : sys.windowTop
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
_width.value = sys.windowWidth
|
||||
@@ -360,26 +371,25 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
|
||||
const cancelEvt = () => {
|
||||
emit('cancel')
|
||||
emit("cancel")
|
||||
closeAlert()
|
||||
}
|
||||
|
||||
const confirmEvt = async () => {
|
||||
emit('confirm')
|
||||
let isPass = true;
|
||||
if (typeof props.beforeClose == 'function') {
|
||||
isLoading.value = true;
|
||||
emit("confirm")
|
||||
let isPass = true
|
||||
if (typeof props.beforeClose == "function") {
|
||||
isLoading.value = true
|
||||
// @ts-ignore
|
||||
isPass = await props.beforeClose();
|
||||
isPass = await props.beforeClose()
|
||||
}
|
||||
isLoading.value = false;
|
||||
isLoading.value = false
|
||||
if (!isPass) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
closeAlert()
|
||||
}
|
||||
|
||||
|
||||
const onClickOverflowy = (evt: Event) => {
|
||||
evt.stopPropagation()
|
||||
/**
|
||||
@@ -392,22 +402,21 @@ const onClickOverflowy = (evt: Event) => {
|
||||
tantiaoTrue.value = true
|
||||
tid2.value = setTimeout(() => {
|
||||
tantiaoTrue.value = false
|
||||
}, 200);
|
||||
return;
|
||||
}, 200)
|
||||
return
|
||||
}
|
||||
closeAlert()
|
||||
|
||||
}
|
||||
|
||||
const closeAlert = () => {
|
||||
if (actinon.value || isLoading.value) return
|
||||
if (status.value == 'close') return
|
||||
if (status.value == "close") return
|
||||
actinon.value = true
|
||||
status.value = 'close'
|
||||
status.value = "close"
|
||||
/**
|
||||
* 关闭前执行
|
||||
*/
|
||||
emit('beforeClose')
|
||||
emit("beforeClose")
|
||||
|
||||
clearTimeout(tid.value)
|
||||
tid.value = setTimeout(() => {
|
||||
@@ -417,17 +426,17 @@ const closeAlert = () => {
|
||||
|
||||
const showAlert = () => {
|
||||
if (actinon.value) return
|
||||
if (status.value == 'open') return
|
||||
if (status.value == "open") return
|
||||
showOverflay.value = true
|
||||
actinon.value = true
|
||||
|
||||
/**
|
||||
* 打开前执行
|
||||
*/
|
||||
emit('beforeOpen')
|
||||
emit("beforeOpen")
|
||||
clearTimeout(tid.value)
|
||||
tid.value = setTimeout(() => {
|
||||
status.value = 'open'
|
||||
status.value = "open"
|
||||
tid.value = setTimeout(() => {
|
||||
onEnd()
|
||||
}, _duration.value)
|
||||
@@ -435,35 +444,30 @@ const showAlert = () => {
|
||||
}
|
||||
|
||||
function openDrawer() {
|
||||
showAlert();
|
||||
showAlert()
|
||||
}
|
||||
|
||||
function onEnd() {
|
||||
actinon.value = false;
|
||||
actinon.value = false
|
||||
|
||||
if (status.value === 'close') {
|
||||
showOverflay.value = false;
|
||||
emit('close');
|
||||
emit('update:show', false);
|
||||
if (status.value === "close") {
|
||||
showOverflay.value = false
|
||||
emit("close")
|
||||
emit("update:show", false)
|
||||
if (_lazy.value) {
|
||||
lezyShowModal.value = false;
|
||||
lezyShowModal.value = false
|
||||
}
|
||||
} else {
|
||||
emit('open');
|
||||
emit("open")
|
||||
if (_lazy.value) {
|
||||
lezyShowModal.value = true;
|
||||
lezyShowModal.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function maskerMove(evt: TouchEvent) {
|
||||
evt.preventDefault();
|
||||
evt.preventDefault()
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
@@ -473,7 +477,7 @@ export default {
|
||||
addGlobalClass: true,
|
||||
multipleSlots: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<view>
|
||||
@@ -490,33 +494,49 @@ export default {
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<root-portal>
|
||||
<!-- #endif -->
|
||||
<view @click="onClickOverflowy" @touchmove="maskerMove" v-if="showOverflay" :id="id" ref="tmDrawerWrap"
|
||||
class="tmDrawerWrap" :class="[
|
||||
status == 'open' ? 'tmModalWrap_on' : 'tmModalWrap_off',
|
||||
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidthWrapDir' : '',
|
||||
'tmDrawerWrap_' + _position
|
||||
]" :style="[{
|
||||
width: '100%',
|
||||
top: (windtop + 'px'),
|
||||
height: __height,
|
||||
zIndex: zIndex,
|
||||
'transition-timing-function': _animationFun
|
||||
}, _customStyle]">
|
||||
|
||||
<view @click.stop="" ref="tmDrawerWrapContent" class="tmDrawerWrapContent" :class="[
|
||||
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidth' : '',
|
||||
'tmDrawerWrapContent_' + _position + (status == 'open' ? '_on' : ''),
|
||||
tantiaoTrue ? 'tmModalWrapContent_Tantiao' : ''
|
||||
]" :id="wrapId" :style="{
|
||||
width: _position == 'left' || _position == 'right' ? _size : '100%',
|
||||
height: _position == 'left' || _position == 'right' ? '100%' : _size,
|
||||
borderRadius: _round,
|
||||
maxHeight: _maxHeight != '' ? _maxHeight : '100%',
|
||||
minHeight: _size == 'auto' ? '350rpx' : '0px',
|
||||
'transition-timing-function': _animationFun,
|
||||
backgroundColor: _bgColor
|
||||
}">
|
||||
|
||||
<view
|
||||
@click="onClickOverflowy"
|
||||
@touchmove="maskerMove"
|
||||
v-if="showOverflay"
|
||||
:id="id"
|
||||
ref="tmDrawerWrap"
|
||||
class="tmDrawerWrap"
|
||||
:class="[
|
||||
status == 'open' ? 'tmModalWrap_on' : 'tmModalWrap_off',
|
||||
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidthWrapDir' : '',
|
||||
'tmDrawerWrap_' + _position,
|
||||
]"
|
||||
:style="[
|
||||
{
|
||||
width: '100%',
|
||||
top: windtop + 'px',
|
||||
height: __height,
|
||||
zIndex: zIndex,
|
||||
'transition-timing-function': _animationFun,
|
||||
},
|
||||
_customStyle,
|
||||
]"
|
||||
>
|
||||
<view
|
||||
@click.stop=""
|
||||
ref="tmDrawerWrapContent"
|
||||
class="tmDrawerWrapContent"
|
||||
:class="[
|
||||
(_position == 'top' || _position == 'bottom') && _widthCoverCenter ? 'tmDrawerWrapContentMinwidth' : '',
|
||||
'tmDrawerWrapContent_' + _position + (status == 'open' ? '_on' : ''),
|
||||
tantiaoTrue ? 'tmModalWrapContent_Tantiao' : '',
|
||||
]"
|
||||
:id="wrapId"
|
||||
:style="{
|
||||
width: _position == 'left' || _position == 'right' ? _size : '100%',
|
||||
height: _position == 'left' || _position == 'right' ? '100%' : _size,
|
||||
borderRadius: _round,
|
||||
maxHeight: _maxHeight != '' ? _maxHeight : '100%',
|
||||
minHeight: _size == 'auto' ? '350rpx' : '0px',
|
||||
'transition-timing-function': _animationFun,
|
||||
backgroundColor: _bgColor,
|
||||
}"
|
||||
>
|
||||
<view v-if="_showClose" class="tmDrawerXclose">
|
||||
<tm-icon @click="closeAlert" color="#dcdcdc" size="52" name="close-circle-fill"></tm-icon>
|
||||
</view>
|
||||
@@ -529,14 +549,15 @@ export default {
|
||||
<slot name="title" :show="show">
|
||||
<text
|
||||
:style="{ fontSize: _titleFontSize, color: _isDark ? 'white' : 'black', opacity: '0.64' }"
|
||||
class="tmDrawertitleBox">{{ _title }}</text>
|
||||
class="tmDrawertitleBox"
|
||||
>{{ _title }}</text
|
||||
>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!disabledScroll && _size != 'auto'" style="flex: 1;" class="tmModalWrapBoxContent">
|
||||
<scroll-view style="height:100%;position:absolute;width:100%;" :scroll-y="true"
|
||||
:rebound="false">
|
||||
<view v-if="!disabledScroll && _size != 'auto'" style="flex: 1" class="tmModalWrapBoxContent">
|
||||
<scroll-view style="height: 100%; position: absolute; width: 100%" :scroll-y="true" :rebound="false">
|
||||
<view :style="{ padding: `0px ${_contentMargin} 0px ${_contentMargin}` }">
|
||||
<!--
|
||||
@slot 默认插槽
|
||||
@@ -545,36 +566,36 @@ export default {
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-else
|
||||
:style="{ height: '100%', padding: `0px ${_contentMargin} 0px ${_contentMargin}` }">
|
||||
<view v-else :style="{ height: '100%', padding: `0px ${_contentMargin} 0px ${_contentMargin}` }">
|
||||
<!--
|
||||
@slot 默认插槽
|
||||
-->
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
|
||||
<view v-if="showFooter && lezyShowModal" class="tmDrawerFooter"
|
||||
:style="{ backgroundColor: _bgColor }">
|
||||
<view v-if="showFooter && lezyShowModal" class="tmDrawerFooter" :style="{ backgroundColor: _bgColor }">
|
||||
<view :style="{ height: _contentMargin }"></view>
|
||||
|
||||
<!--
|
||||
@slot 底部操作栏
|
||||
-->
|
||||
<slot name="footer">
|
||||
<view
|
||||
style="flex-direction: row;align-items: center;justify-content: center;display:flex;">
|
||||
<tm-button :loading="isLoading" width="0px" @click="cancelEvt" v-if="_showCancel"
|
||||
skin="thin" style="margin-right: 16px;flex:1">{{ _cancelText }}</tm-button>
|
||||
<tm-button :loading="isLoading" width="0px" @click="confirmEvt" style="flex:1">{{
|
||||
_confirmText
|
||||
}}</tm-button>
|
||||
<view style="flex-direction: row; align-items: center; justify-content: center; display: flex; margin: 0 20rpx">
|
||||
<tm-button
|
||||
:loading="isLoading"
|
||||
width="0px"
|
||||
@click="cancelEvt"
|
||||
v-if="_showCancel"
|
||||
skin="thin"
|
||||
style="margin-right: 16px; flex: 1"
|
||||
>{{ _cancelText }}</tm-button
|
||||
>
|
||||
<tm-button :loading="isLoading" width="0px" @click="confirmEvt" style="flex: 1">{{ _confirmText }}</tm-button>
|
||||
</view>
|
||||
</slot>
|
||||
<view :style="{ height: safeFooterHeight + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
</root-portal>
|
||||
@@ -613,7 +634,6 @@ export default {
|
||||
line-clamp: 1;
|
||||
display: -webkit-box;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
}
|
||||
|
||||
.tmDrawerWrap_bottom {
|
||||
@@ -661,13 +681,12 @@ export default {
|
||||
}
|
||||
|
||||
@keyframes bottomAnimate3d {
|
||||
|
||||
0% {
|
||||
transform: translate(0%, 100%) translate3d(0%, 20px, 0px) rotateX(45deg)
|
||||
transform: translate(0%, 100%) translate3d(0%, 20px, 0px) rotateX(45deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(0%, 0%) translate3d(0, 0, 0px) rotateX(0deg)
|
||||
transform: translate(0%, 0%) translate3d(0, 0, 0px) rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,7 +755,6 @@ export default {
|
||||
75% {
|
||||
transform: scale(0.97);
|
||||
opacity: 0.9;
|
||||
|
||||
}
|
||||
|
||||
85% {
|
||||
@@ -746,11 +764,10 @@ export default {
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.tmModalWrapContent_Tantiao {
|
||||
animation: tantiao 0.15s linear;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,54 +1,72 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch, type PropType, getCurrentInstance, type ComponentInstance, onUpdated, nextTick, VueElement, inject } from 'vue';
|
||||
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
|
||||
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
|
||||
import { useTmConfig } from "../../libs/config";
|
||||
import pickerItem from './picker-item.vue';
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
watch,
|
||||
type PropType,
|
||||
getCurrentInstance,
|
||||
type ComponentInstance,
|
||||
onUpdated,
|
||||
nextTick,
|
||||
VueElement,
|
||||
inject,
|
||||
} from "vue"
|
||||
import {
|
||||
arrayNumberValid,
|
||||
arrayNumberValidByStyleMP,
|
||||
covetUniNumber,
|
||||
arrayNumberValidByStyleBorderColor,
|
||||
linearValid,
|
||||
getUnit,
|
||||
getUid,
|
||||
} from "../../libs/tool"
|
||||
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors"
|
||||
import { useTmConfig } from "../../libs/config"
|
||||
import pickerItem from "./picker-item.vue"
|
||||
|
||||
const { config } = useTmConfig()
|
||||
type TM_PICKER_X_ITEM = Record<string, any>
|
||||
|
||||
|
||||
const boxHeight = ref(0)
|
||||
const id = ('tmPickerItem-' + getUid())
|
||||
const id = "tmPickerItem-" + getUid()
|
||||
const nowCurrentIndex = ref([0])
|
||||
let tid = 0
|
||||
let tid2 = 1
|
||||
|
||||
const emits = defineEmits(['changeDeep'])
|
||||
const emits = defineEmits(["changeDeep"])
|
||||
const props = defineProps({
|
||||
|
||||
list: {
|
||||
type: Array as PropType<TM_PICKER_X_ITEM[][]>,
|
||||
default: (): TM_PICKER_X_ITEM[] => [] as TM_PICKER_X_ITEM[]
|
||||
default: (): TM_PICKER_X_ITEM[] => [] as TM_PICKER_X_ITEM[],
|
||||
},
|
||||
|
||||
|
||||
selectedIndex: {
|
||||
type: Array as PropType<number[]>,
|
||||
default: (): number[] => [] as number[]
|
||||
default: (): number[] => [] as number[],
|
||||
},
|
||||
cellUnits: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: (): string[] => [] as string[]
|
||||
default: (): string[] => [] as string[],
|
||||
},
|
||||
fontSize: {
|
||||
type: String,
|
||||
default: "30"
|
||||
default: "30",
|
||||
},
|
||||
/**
|
||||
* 自定义标识id
|
||||
*/
|
||||
rangKey: {
|
||||
type: String,
|
||||
default: "id"
|
||||
default: "id",
|
||||
},
|
||||
/**
|
||||
* 自定义标识文本字段名
|
||||
*/
|
||||
rangText: {
|
||||
type: String,
|
||||
default: "title"
|
||||
default: "title",
|
||||
},
|
||||
})
|
||||
const _fontSize = computed((): string => {
|
||||
@@ -62,34 +80,28 @@ const _fontSize = computed((): string => {
|
||||
return (sizeNumber * config.fontSizeScale).toString() + getUnit(props.fontSize)
|
||||
})
|
||||
|
||||
|
||||
|
||||
const refreshKey = ref(12)
|
||||
|
||||
const _cellUnits = computed((): string[] => props.cellUnits as string[])
|
||||
|
||||
const _list = computed((): TM_PICKER_X_ITEM[][] => props.list.slice(0))
|
||||
|
||||
|
||||
const _isDark = computed((): boolean => config.mode == 'dark')
|
||||
const _isDark = computed((): boolean => config.mode == "dark")
|
||||
|
||||
const _maskStyle = computed((): string => {
|
||||
if (_isDark.value) {
|
||||
return 'background-image:linear-gradient(180deg,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0)),linear-gradient(0deg, rgba(0, 0, 0, 0),rgba(0, 0, 0, 0))'
|
||||
return "background-image:linear-gradient(180deg,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0)),linear-gradient(0deg, rgba(0, 0, 0, 0),rgba(0, 0, 0, 0))"
|
||||
}
|
||||
return 'background-image:linear-gradient(180deg,rgba(255,255,255,0),rgba(255,255,255,0)),linear-gradient(0deg, rgba(255,255,255,0),rgba(255,255,255,0))'
|
||||
return "background-image:linear-gradient(180deg,rgba(255,255,255,0),rgba(255,255,255,0)),linear-gradient(0deg, rgba(255,255,255,0),rgba(255,255,255,0))"
|
||||
})
|
||||
const _selectedMaskStyle = computed((): string => {
|
||||
return _isDark.value ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'
|
||||
return _isDark.value ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)"
|
||||
})
|
||||
|
||||
|
||||
|
||||
const onChange = (event: any) => {
|
||||
|
||||
if (event.detail.value.length == 0 || _list.value.length == 0) return;
|
||||
let indexs = event.detail.value!;
|
||||
if (indexs.join('') == nowCurrentIndex.value.join('')) return;
|
||||
if (event.detail.value.length == 0 || _list.value.length == 0) return
|
||||
let indexs = event.detail.value!
|
||||
if (indexs.join("") == nowCurrentIndex.value.join("")) return
|
||||
nowCurrentIndex.value = indexs
|
||||
emits("changeDeep", indexs)
|
||||
}
|
||||
@@ -102,18 +114,21 @@ onBeforeUnmount(() => {
|
||||
clearTimeout(tid2)
|
||||
})
|
||||
|
||||
watch(() => props.selectedIndex, (newvalue: number[]) => {
|
||||
if (newvalue.join('') == nowCurrentIndex.value.join('')) return;
|
||||
nowCurrentIndex.value = newvalue.slice(0)
|
||||
}, { deep: true })
|
||||
watch(_list, (newvalue) => {
|
||||
refreshKey.value += 1;
|
||||
|
||||
}, { deep: true })
|
||||
|
||||
|
||||
|
||||
|
||||
watch(
|
||||
() => props.selectedIndex,
|
||||
(newvalue: number[]) => {
|
||||
if (newvalue.join("") == nowCurrentIndex.value.join("")) return
|
||||
nowCurrentIndex.value = newvalue.slice(0)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
watch(
|
||||
_list,
|
||||
(newvalue) => {
|
||||
refreshKey.value += 1
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
@@ -123,29 +138,41 @@ export default {
|
||||
addGlobalClass: true,
|
||||
multipleSlots: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<view>
|
||||
<view class="tmPickerViewUnit" v-if="_cellUnits.length > 0">
|
||||
<view v-for="(item, index) in _cellUnits" :key="index" style="flex: 1;text-align: center;">
|
||||
<view v-for="(item, index) in _cellUnits" :key="index" style="flex: 1; text-align: center">
|
||||
<tm-text font-size="24" class="tmPickerViewUnitText">{{ item }}</tm-text>
|
||||
</view>
|
||||
</view>
|
||||
<picker-view :key="refreshKey" :immediate-change="true" :value="nowCurrentIndex" @change="onChange"
|
||||
:style="{ height: '250px', width: '100%' }" :mask-style="`${_maskStyle}`"
|
||||
:indicator-style="`height:50px;border:none;border-radius:10px`">
|
||||
<picker-view
|
||||
:key="refreshKey"
|
||||
:immediate-change="true"
|
||||
:value="nowCurrentIndex"
|
||||
@change="onChange"
|
||||
:style="{ height: '250px', width: '100%' }"
|
||||
:mask-style="`${_maskStyle}`"
|
||||
:indicator-style="`height:50px;border:none;border-radius:10px`"
|
||||
>
|
||||
<picker-view-column v-for="(item2, index2) in _list" :key="index2">
|
||||
<view
|
||||
:style="{ background: nowCurrentIndex[index2] == index ? _selectedMaskStyle : 'none', margin: '0 4px' }"
|
||||
v-for="(item, index) in item2" :key="index" class="tmPickerViewWrapCoumn">
|
||||
<text class="tmPickerViewWrapCoumnText" :style="{
|
||||
fontSize: _fontSize,
|
||||
lineHeight: 1,
|
||||
fontWeight: nowCurrentIndex[index2] == index ? 'bold' : 'inherit',
|
||||
opacity: item.disabled ? 0.4 : (nowCurrentIndex[index2] == index ? 1 : 0.6),
|
||||
color: _isDark ? 'rgba(255,255,255,0.8)' : '#000000'
|
||||
}">
|
||||
v-for="(item, index) in item2"
|
||||
:key="index"
|
||||
class="tmPickerViewWrapCoumn"
|
||||
>
|
||||
<text
|
||||
class="tmPickerViewWrapCoumnText"
|
||||
:style="{
|
||||
fontSize: _fontSize,
|
||||
lineHeight: 1,
|
||||
fontWeight: nowCurrentIndex[index2] == index ? 'bold' : 'inherit',
|
||||
opacity: item.disabled ? 0.4 : nowCurrentIndex[index2] == index ? 1 : 0.6,
|
||||
color: _isDark ? 'rgba(255,255,255,0.8)' : '#000000',
|
||||
}"
|
||||
>
|
||||
{{ item[rangText] || "" }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -183,7 +210,6 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
|
||||
}
|
||||
|
||||
.tmPickerView {
|
||||
@@ -221,7 +247,6 @@ export default {
|
||||
border-radius: 10px;
|
||||
margin: 0 3px;
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
|
||||
.tmPickerContent {
|
||||
@@ -239,4 +264,4 @@ export default {
|
||||
border-radius: 10px;
|
||||
/* background-color: #f5f5f5; */
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
Reference in new issue
Block a user