海关直通车

This commit is contained in:
陈善美 2025-08-28 18:25:09 +08:00
parent 635e28bb4b
commit 13b27ce641
10 changed files with 1328 additions and 75 deletions

715
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

37
src/api/customs.ts Normal file
View File

@ -0,0 +1,37 @@
import type { PageParams } from '@/types/global'
import { request } from '@/utils/request'
export const postConfigApi = (data?: PageParams) => {
return request(
{
method: 'POST',
url: 'customs/CustomsConfig/config',
data,
apiName:true
},
false,
)
}
export const postDataMainApi = (data?: PageParams) => {
return request({
method: 'POST',
url: 'customs/Index/dataMain',
data,
apiName:false
},
false,
)
}
export const postDataSmartApi = (data) => {
return request({
method: 'POST',
url: 'customsServices/CustomsServicesConfig/config',
data,
apiName:true
},
false,
)
}

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import { ref } from 'vue'
import '@/styles/common.scss'
const props = defineProps<{
title: string
color?: string
}>()
const emits = defineEmits<{
(event: 'titleClick'): void
}>()
const handleTitleClick = () => {
emits('titleClick')
}
</script>
<template>
<view class="section-title flex-row align-center floor-margin-top-40" @click="handleTitleClick">
<view class="title-indicator" :style="{ backgroundColor: color || '#4D7EEC' }"></view>
<text class="title-text floor-text-primary floor-text-bold floor-font-32">{{ title }}</text>
</view>
</template>
<style scoped lang="scss">
.section-title {
// margin: 0 0 30rpx 0;
.title-indicator {
width: 6rpx;
height: 31rpx;
border-radius: 3px;
margin-right: 8rpx;
}
}
</style>

View File

@ -0,0 +1,10 @@
import { useBackground } from './common/useBackground'
export function useCustomsExpress() {
const { backgroundStyle, topGradientStyle } = useBackground()
return {
backgroundStyle,
topGradientStyle,
}
}

View File

@ -125,12 +125,29 @@
{
"root": "pagesCustomer",
"pages": [{
"path": "pages/customer/customer",
"path": "pages/customerManagement/index",
"style": {
"navigationBarTitleText": "客户管理",
"navigationStyle": "custom"
}
}]
},
{
"path": "pages/customsExpress/index",
"style": {
"navigationBarTitleText": "",
// #ifdef WEB
"navigationStyle": "custom"
// #endif
}
},
{
"path": "pages/customsService/index",
"style": {
"navigationBarTitleText": "智慧关务",
"navigationBarBackgroundColor": "#fff"
}
}
]
},
// shop
{

View File

@ -0,0 +1,266 @@
<template>
<view class="customs-service-grid" v-if="configData">
<!-- 顶部统计栏 -->
<view class="stats-bar customs-service-grid-nav">
<view class="stat-item" v-for="(item,index) in configData.board" :key="index">
<text class="stat-number">{{item.value}}</text>
<text class="stat-label">{{item.title}}</text>
</view>
</view>
<view class="service-section-container">
<!-- 我要办 -->
<view class="service-section">
<view class="section-header">
<section-title title="我要办" />
</view>
<view class="service-grid">
<view v-for="(service, index) in configData.menusDo" :key="index" class="service-item"
@click="handleServiceClick(service.url)">
<view class="service-icon">
<image :src="service.img" class="img"></image>
</view>
<text class="service-title">{{ service.name }}</text>
</view>
</view>
</view>
<!-- 我要查 -->
<view class="service-section">
<view class="section-header">
<section-title title="我要查" />
</view>
<view class="service-grid">
<view v-for="(service, index) in configData.menusSearch" :key="index" class="service-item"
@click="handleServiceClick(service.url)">
<view class="service-icon">
<image :src="service.img" class="img"></image>
</view>
<text class="service-title">{{ service.name }}</text>
</view>
</view>
</view>
<!-- 公共服务 -->
<view class="service-section">
<view class="section-header">
<section-title title="电子账册" @title-click="handleTitleClick" />
</view>
<view class="service-grid">
<view
v-for="(service, index) in configData.menusCustomsBook"
:key="index"
class="service-item"
@click="handleServiceClick(service)"
>
<view class="service-icon" >
<image :src="service.img" class="img"></image>
</view>
<text class="service-title">{{ service.name }}</text>
</view>
</view>
</view>
<!-- 展转贸 -->
<view class="service-section">
<view class="section-header">
<section-title title="展转贸" />
</view>
<view class="service-grid">
<view v-for="(service, index) in configData.menusExpoToTrade" :key="index" class="service-item"
@click="handleServiceClick(service.url)">
<view class="service-icon">
<image :src="service.img" class="img"></image>
</view>
<text class="service-title">{{ service.name }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { postConfigApi } from '@/api/customs'
interface ServiceItem {
id : string
title : string
icon : string
color : string
route ?: string
}
const handleServiceClick = (url : string) => {
console.log('点击服务:', url)
if (url && url != '#') {
uni.navigateTo({
url,
fail: () => {
uni.reLaunch({
url
})
}
})
}
//
// uni.showToast({
// title: `${service.title}`,
// icon: 'none',
// })
}
const configData = ref(null)
const getList = () => {
postConfigApi({
page: 1,
page_size: 10
}).then(res => {
if (res.code == 1) {
configData.value = res.data
}
})
}
getList()
</script>
<style lang="scss" scoped>
.customs-service-grid {
position: relative;
z-index: 2;
padding: 0 32rpx;
}
.customs-service-grid-nav {
display: flex;
justify-content: space-around;
align-items: center;
}
//
.stats-bar {
position: relative;
z-index: 2;
background: #ffffff;
border-radius: 20rpx;
padding: 40rpx 32rpx;
// margin-bottom: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.stat-number {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666;
text-align: center;
}
}
}
.service-section-container {
background: #ffffff;
position: absolute;
left: 0;
width: 100%;
// border-radius: 80rpx 80rpx 0 0;
&::before {
content: '';
position: absolute;
z-index: 1;
top: -100rpx;
left: 0;
right: 0;
height: 100rpx;
background: #ffffff;
border-radius: 80rpx 80rpx 0 0;
}
}
//
.service-section {
padding: 32rpx;
// margin-bottom: 32rpx;
.section-header {
display: flex;
align-items: center;
margin-bottom: 32rpx;
.section-bar {
width: 8rpx;
height: 32rpx;
background: #4a90e2;
border-radius: 4rpx;
margin-right: 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
.service-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 24rpx;
.service-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
.service-icon {
width: 80rpx;
height: 80rpx;
// border: 3rpx dashed;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
.img {
width: 50rpx;
height: 50rpx;
}
.icon-text {
font-size: 32rpx;
}
}
.service-title {
font-size: 24rpx;
color: #333;
text-align: center;
line-height: 1.2;
max-width: 100%;
word-break: break-all;
}
&:active .service-icon {
transform: scale(0.95);
opacity: 0.8;
}
}
}
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<view class="customs-express-page" :style="backgroundStyle">
<!-- 顶部渐变背景 -->
<view :style="topGradientStyle"></view>
<!-- 状态栏占位 -->
<view class="status-bar"></view>
<!-- 服务网格 -->
<customs-service-grid />
</view>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { useCustomsExpress } from '@/composables/useCustomsExpress'
import CustomsServiceGrid from './components/customs-service-grid.vue'
// 使
const { backgroundStyle, topGradientStyle } = useCustomsExpress()
//
onMounted(() => {
//
uni.setNavigationBarTitle({
title: '海关服务',
})
})
onUnmounted(() => {})
</script>
<style lang="scss" scoped>
.customs-express-page {
position: relative;
min-height: 100vh;
padding: 0;
padding-bottom: 60rpx;
}
.status-bar {
height: 88rpx;
background: transparent;
position: relative;
z-index: 2;
}
.page-header {
position: relative;
z-index: 2;
text-align: center;
padding: 40rpx 32rpx 60rpx;
.page-title {
display: block;
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
}
.page-subtitle {
display: block;
font-size: 28rpx;
color: #666;
}
}
</style>

View File

@ -0,0 +1,226 @@
<template>
<view class="smart_body_box" v-if="dataConfig">
<view class="header_card_box">
<view class="item_num_box flex-col" v-for="(item,index) in dataConfig.board" :key="index">
<text class="num_ti">{{item.value}}</text>
<view class="lab_ti">{{item.title}}</view>
</view>
</view>
<view class="menu_body_box">
<view class="menu_label_box">
<text class="la_ti">海保服务</text>
<view class="more_box" @click="changeMore('menusHbsm')" v-if="dataConfig.menusHbsm.length > 4">
更多
<wd-icon name="arrow-down" size="12px" color="#3F8C8B" class="ml-3 " :class="showOption.menusHbsm ?'more_up' : 'more_down'"></wd-icon>
</view>
</view>
<view class="menu_icon_box" :style="{height: showOption.menusHbsm ? 'auto': '100px'}">
<view class="menu_item_box flex-col" v-for="(item,index) in dataConfig.menusHbsm"
@click="jumpUrl(item.url)">
<image :src="item.img" class="menu_img"></image>
<view class="title_box">{{item.name}}</view>
</view>
</view>
</view>
<view class="menu_body_box">
<view class="menu_label_box">
<text class="la_ti">物流跟踪</text>
<view class="more_box" @click="changeMore('menusLogisticsTracking')" v-if="dataConfig.menusLogisticsTracking.length > 4">
更多
<wd-icon name="arrow-down" size="12px" color="#3F8C8B" class="ml-3" :class="showOption.menusLogisticsTracking ?'more_up' : 'more_down'"></wd-icon>
</view>
</view>
<view class="menu_icon_box" :style="{height: showOption.menusLogisticsTracking ? 'auto': '100px'}">
<view class="menu_item_box flex-col" @click="jumpUrl(item.url)" v-for="(item,index) in dataConfig.menusLogisticsTracking">
<image :src="item.img" class="menu_img"></image>
<view class="title_box">{{item.name}}</view>
</view>
</view>
</view>
<view class="menu_body_box">
<view class="menu_label_box">
<text class="la_ti">查询工具</text>
<view class="more_box" @click="changeMore('menusSearchTool')" v-if="dataConfig.menusSearchTool.length > 4">
更多
<wd-icon :class="showOption.menusSearchTool ?'more_up' : 'more_down'" name="arrow-down" size="12px" color="#3F8C8B" class="ml-3"></wd-icon>
</view>
</view>
<view class="menu_icon_box" :style="{height: showOption.menusSearchTool ? 'auto': '100px'}">
<view class="menu_item_box flex-col" @click="jumpUrl(item.url)" v-for="(item,index) in dataConfig.menusSearchTool">
<image :src="item.img" class="menu_img"></image>
<view class="title_box">{{item.name}}</view>
</view>
</view>
</view>
<view class="menu_body_box">
<view class="menu_label_box">
<text class="la_ti">增值服务</text>
<view class="more_box" @click="changeMore('menusAddedServices')" v-if="dataConfig.menusAddedServices.length > 4">
更多
<wd-icon :class="showOption.menusAddedServices ?'more_up' : 'more_down'" name="arrow-down" size="12px" color="#3F8C8B" class="ml-3"></wd-icon>
</view>
</view>
<view class="menu_icon_box" :style="{height: showOption.menusAddedServices ? 'auto': '100px'}">
<view class="menu_item_box flex-col" @click="jumpUrl(item.url)" v-for="(item,index) in dataConfig.menusAddedServices">
<image :src="item.img" class="menu_img"></image>
<view class="title_box">{{item.name}}</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { postDataSmartApi }from '@/api/customs'
const dataConfig = ref(null)
const showOption = reactive({
menusHbsm:false,
menusLogisticsTracking:false,
menusSearchTool:false,
menusAddedServices:false
})
const getData = ()=>{
postDataSmartApi({
page:1,
page_size:10,
}).then(res=>{
// console.log(res)
if(res.code == 1){
dataConfig.value = res.data
}
})
}
const jumpUrl = (url)=>{
if(url && url !='#'){
uni.navigateTo({
url,
fail: () => {
uni.reLaunch({
url
})
}
})
}
}
const changeMore = (data)=>{
showOption[data] = !showOption[data]
}
getData()
console.log(111)
</script>
<style>
page{
height: 100vh;
background: #F9FAFB;
}
</style>
<style lang="scss" scoped>
.smart_body_box{
width: 100%;
padding: 0 16px;
box-sizing: border-box;
.menu_body_box{
width: 100%;
background: #FFFFFF;
padding-top: 18px;
padding-bottom: 18px;
box-sizing: border-box;
margin-top: 14px;
border-radius: 6px;
.menu_icon_box{
width: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
overflow: hidden;
.menu_item_box{
width: calc(100% / 4);
display: flex;
align-items: center;
justify-content: center;
margin-top: 18px;
.title_box{
width: 56px;
height: 38px;
text-align: center;
margin-top: 6px;
font-size: 14px;
color: #333333;
}
.menu_img{
width: 48px;
height: 48px;
}
}
}
.menu_label_box{
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 18px;
box-sizing: border-box;
.more_box{
display: flex;
align-items: center;
font-size: 12px;
color: #3F8C8B;
transition: all .3s linear;
.more_up{
transform: rotate(-180deg);
transition: all .3s linear;
}
.more_down{
transform: rotate(0deg);
transition: all .3s linear;
}
}
.la_ti{
font-size: 16px;
color: #333333;
font-weight: 700!important;
}
}
}
.header_card_box{
width: 100%;
margin-top: 16px;
background: linear-gradient( 47deg, #45908F 0%, #338382 100%);
border-radius: 6px;
padding: 11px 30px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
.item_num_box{
width: 90px;
height: 102px;
background: rgba(255,255,255,0.1);
box-shadow: 0px 0px 4px 0px rgba(35,83,83,0.2);
border-radius: 4px;
border: 1px solid;
border-image: linear-gradient(137deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)) 1 1;
display: flex;
align-items: center;
justify-content: center;
.lab_ti{
width: 48px;
height: 38px;
text-align: center;
color: #D2EDED;
font-size: 12px;
font-weight: 500;
margin-top: 7px;
}
.num_ti{
font-size: 30px;
color: #FFFFFF;
font-weight: 700!important;
}
}
}
}
</style>

View File

@ -8,16 +8,17 @@ import type ShopSubmitButton from '@/components/shop-submit-button/shop-submit-b
import type ShopUserAvatar from '@/components/shop-user-avatar/shop-user-avatar.vue'
import type ShopGoodsSearch from '@/components/shop-goods-search/shop-goods-search.vue'
import type ShopSwiper from '@/components/shop-swiper/shop-swiper.vue'
import type SectionTitle from '@/components/section-title/section-title.vue'
declare module 'vue' {
export interface GlobalComponents {
shopSwiper: typeof ShopSwiper
shopGoodsList: typeof shopGoodsList
ShopGoodsSearch: typeof ShopGoodsSearch
shopSubmitButton: typeof ShopSubmitButton
shopUserAvatar: typeof ShopUserAvatar
shopCoupon: typeof ShopCoupon
shopPaginateList: typeof shopPaginateList
shopSwiper : typeof ShopSwiper
shopGoodsList : typeof shopGoodsList
ShopGoodsSearch : typeof ShopGoodsSearch
shopSubmitButton : typeof ShopSubmitButton
shopUserAvatar : typeof ShopUserAvatar
shopCoupon : typeof ShopCoupon
shopPaginateList : typeof shopPaginateList
sectionTitle : typeof SectionTitle
}
}