Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
cf792b854a | |||
45c0f1e05b | |||
13b27ce641 | |||
635e28bb4b | |||
6c969df959 | |||
860ce21b52 | |||
fe61729cb4 | |||
6bf2a1ada9 | |||
65c88c24eb | |||
0d17baa9c9 | |||
25341584fa |
16
.hbuilderx/launch.json
Normal file
@ -0,0 +1,16 @@
|
||||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version": "0.0",
|
||||
"configurations": [{
|
||||
"default" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"mp-weixin" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
@ -62,6 +62,11 @@ https:://apis.haibao.shop
|
||||
[腾讯的] (https://www.tapd.cn/tapd_fe/55592674/storywall)
|
||||
```
|
||||
|
||||
```shell
|
||||
12. 分包命名
|
||||
注意:分包命名不能与主包相同,主包为`pages`,分包为`pagesXxx`,xxx为分包名称,例如商城相关分包命名为`pagesShop`
|
||||
```
|
||||
|
||||
```shell
|
||||
12. PC端首页 https://www.haibao.shop
|
||||
13. 后台地址 https://apis.haibao.shop/index.html#/admin
|
||||
|
976
package-lock.json
generated
@ -59,11 +59,14 @@
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-alpha-3081220230802001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3081220230802001",
|
||||
"@dcloudio/uni-ui": "^1.4.28",
|
||||
"@tailwindcss/cli": "^4.1.12",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "2.0.27",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"vue": "^3.2.47",
|
||||
"vue-i18n": "^9.2.2"
|
||||
"vue-i18n": "^9.2.2",
|
||||
"wot-design-uni": "^1.12.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.3.3",
|
||||
|
715
pnpm-lock.yaml
generated
28
project.config.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"appid": "wxfd7d85cf2619b249",
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "3.9.2",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"setting": {
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"enhance": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
}
|
||||
},
|
||||
"condition": {},
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 4
|
||||
}
|
||||
}
|
7
project.private.config.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
|
||||
"projectname": "hbsmUniapp",
|
||||
"setting": {
|
||||
"compileHotReLoad": true
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import { request } from '@/utils/request'
|
||||
export const postAddressApi = (data: addressParams) => {
|
||||
return request<addressItem>({
|
||||
method: 'POST',
|
||||
url: '/userAddress/add',
|
||||
url: 'shop/userAddress/add',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@ -18,7 +18,7 @@ export const postAddressApi = (data: addressParams) => {
|
||||
export const getAddressApi = (data?: PageParams) => {
|
||||
return request<PageResult<addressItem>>({
|
||||
method: 'GET',
|
||||
url: '/userAddress',
|
||||
url: 'shop/userAddress',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@ -26,7 +26,7 @@ export const getAddressApi = (data?: PageParams) => {
|
||||
export const getAddressByIdApi = (id: string) => {
|
||||
return request<addressItem>({
|
||||
method: 'GET',
|
||||
url: `/userAddress/detail`,
|
||||
url: `shop/userAddress/detail`,
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
@ -36,7 +36,7 @@ export const getAddressByIdApi = (id: string) => {
|
||||
export const putAddressApi = (data: addressParams) => {
|
||||
return request<addressItem>({
|
||||
method: 'POST',
|
||||
url: `/userAddress/edit`,
|
||||
url: `shop/userAddress/edit`,
|
||||
data,
|
||||
})
|
||||
}
|
||||
@ -44,7 +44,7 @@ export const putAddressApi = (data: addressParams) => {
|
||||
export const deleteAddressApi = (id: string | number) => {
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: `/userAddress/delete`,
|
||||
url: `shop/userAddress/delete`,
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
|
@ -6,7 +6,7 @@ import { request } from '@/utils/request'
|
||||
export const getSettingApi = () => {
|
||||
return request<settingItem>({
|
||||
method: 'GET',
|
||||
url: '/shop/common/setting',
|
||||
url: 'shop/common/setting',
|
||||
})
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ export const uploadApi = (filePath: string): Promise<AnyObject> => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `/shop/common/upload?driver=${uploadSetting.mode}`,
|
||||
url: `shop/common/upload?driver=${uploadSetting.mode}`,
|
||||
filePath,
|
||||
name: 'file',
|
||||
success: (res) => {
|
||||
@ -55,7 +55,7 @@ export const uploadApi = (filePath: string): Promise<AnyObject> => {
|
||||
|
||||
export const getCaptchaData = (id: string) => {
|
||||
return request({
|
||||
url: '/api/common/clickCaptcha',
|
||||
url: 'api/common/clickCaptcha',
|
||||
method: 'GET',
|
||||
data: {
|
||||
id,
|
||||
@ -65,7 +65,7 @@ export const getCaptchaData = (id: string) => {
|
||||
|
||||
export const getCaptchaCodeApi = (id: string) => {
|
||||
return request({
|
||||
url: '/api/common/captcha',
|
||||
url: 'api/common/captcha',
|
||||
method: 'GET',
|
||||
data: {
|
||||
id,
|
||||
@ -76,7 +76,7 @@ export const getCaptchaCodeApi = (id: string) => {
|
||||
|
||||
export const checkClickCaptcha = (id: string, info: string, unset: boolean) => {
|
||||
return request({
|
||||
url: '/api/common/checkClickCaptcha',
|
||||
url: 'api/common/checkClickCaptcha',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id,
|
||||
|
37
src/api/customs.ts
Normal 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,
|
||||
)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { request } from '@/utils/request'
|
||||
export const getGoodsListApi = (data?: PageParams & goodsListParams) => {
|
||||
return request<PageResult<goodsListItem>>({
|
||||
method: 'GET',
|
||||
url: '/shop/goods',
|
||||
url: 'shop/goods',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@ -19,7 +19,7 @@ export const getGoodsListApi = (data?: PageParams & goodsListParams) => {
|
||||
export const getGoodsByIdApi = (id: number) => {
|
||||
return request<goodsResult>({
|
||||
method: 'GET',
|
||||
url: '/shop/goods/detail',
|
||||
url: 'shop/goods/detail',
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
@ -29,7 +29,7 @@ export const getGoodsByIdApi = (id: number) => {
|
||||
export const getGoodsServiceApi = (service_ids: string) => {
|
||||
return request<goodsServiceItem[]>({
|
||||
method: 'GET',
|
||||
url: '/shop/goods/service',
|
||||
url: 'shop/goods/service',
|
||||
data: {
|
||||
service_ids,
|
||||
},
|
||||
@ -39,7 +39,7 @@ export const getGoodsServiceApi = (service_ids: string) => {
|
||||
export const getShareInfoApi = (id: number) => {
|
||||
return request<goodsShareResult>({
|
||||
method: 'GET',
|
||||
url: '/shop/goods/share',
|
||||
url: 'shop/goods/share',
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
|
@ -6,10 +6,10 @@ import { request } from '@/utils/request'
|
||||
* 获取轮播图
|
||||
* @returns
|
||||
*/
|
||||
export const getBannerApi = (type: number = 1) => {
|
||||
export const getBannerApi = (type : number = 1) => {
|
||||
return request<bannerItem[]>({
|
||||
method: 'GET',
|
||||
url: '/shop/advert/banner',
|
||||
url: 'shop/advert/banner',
|
||||
data: {
|
||||
type,
|
||||
},
|
||||
@ -21,10 +21,10 @@ export const getBannerApi = (type: number = 1) => {
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getGoodsListApi = (data?: PageParams) => {
|
||||
export const getGoodsListApi = (data ?: PageParams) => {
|
||||
return request<PageResult<goodsListItem>>({
|
||||
method: 'GET',
|
||||
url: '/shop/goods',
|
||||
url: 'shop/goods',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@ -37,13 +37,29 @@ export const getGoodsListApi = (data?: PageParams) => {
|
||||
export const getNoticeBartApi = () => {
|
||||
return request<noticeBarItem[]>({
|
||||
method: 'GET',
|
||||
url: '/shop/advert/noticeBar',
|
||||
url: 'shop/advert/noticeBar',
|
||||
})
|
||||
}
|
||||
|
||||
export const getHotRecommendApi = () => {
|
||||
return request<hotRecommendItem[]>({
|
||||
method: 'GET',
|
||||
url: '/shop/advert/hotRecommend',
|
||||
url: 'shop/advert/hotRecommend',
|
||||
})
|
||||
}
|
||||
// 移动端-首页接口
|
||||
export const configIndex = (data : { page : number, page_size : number }) => {
|
||||
return request({
|
||||
url: 'api/index/config',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
// 移动端-首页接口-搜索接口
|
||||
export const configSearch = (data:Record<string, any>) => {
|
||||
return request({
|
||||
url: 'api/index/search',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
10
src/api/other.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
// 富文本案例介绍
|
||||
export const editorApi = (data : { key : string }) => {
|
||||
return request({
|
||||
method: 'POST',
|
||||
url: 'api/index/editor',
|
||||
data,
|
||||
})
|
||||
}
|
47
src/api/portrait.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { PageParams } from '@/types/global'
|
||||
import type { RootName } from '@/types/userCenter'
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
|
||||
// 企业画像
|
||||
export const postCompanyDataThirdDataViewApi = (data : PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'company/CompanyDataThird/dataView',
|
||||
})
|
||||
}
|
||||
// 出口数据
|
||||
export const postDataViewExportApi = (data : PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'company/CompanyDataThird/dataViewExport',
|
||||
})
|
||||
}
|
||||
// 进口数据
|
||||
export const postDataViewImportApi = (data : PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'company/CompanyDataThird/dataViewImport',
|
||||
})
|
||||
}
|
||||
// 贸易伙伴
|
||||
export const postDataViewPartnersApi = (data : PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'company/CompanyDataThird/dataViewPartners',
|
||||
})
|
||||
}
|
||||
// 海关编码统计
|
||||
export const postDataViewHscodeApi = (data : PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'company/CompanyDataThird/dataViewHscode',
|
||||
})
|
||||
}
|
||||
// 海贸区域
|
||||
export const postDataViewAreaApi = (data : PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'company/CompanyDataThird/dataViewArea',
|
||||
})
|
||||
}
|
18
src/api/userCenter.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { PageParams } from '@/types/global'
|
||||
import type { RootName } from '@/types/userCenter'
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
// 个人中心按钮素材配置
|
||||
export const postCenterConfigApi = (data:PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'shop/user/centerConfig',
|
||||
})
|
||||
}
|
||||
// 个人中心数据
|
||||
export const postUserInfoApi = (data:PageParams) => {
|
||||
return request<RootName>({
|
||||
method: 'POST',
|
||||
url: 'shop/user/userInfo',
|
||||
})
|
||||
}
|
113
src/components/notice-bar.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<view class="notice-bar">
|
||||
<wd-notice-bar
|
||||
:text="displayTexts"
|
||||
color="#333"
|
||||
background-color="#fff"
|
||||
@click="handleNoticeClick"
|
||||
>
|
||||
<template #prefix>
|
||||
<view class="notice-bar-left flex-row align-center">
|
||||
<view class="notice-icon" v-if="props.prefix">
|
||||
<wd-icon
|
||||
class="prefix"
|
||||
:name="props.noticeData.notice_img"
|
||||
size="28rpx"
|
||||
v-if="props.noticeData.notice_img"
|
||||
></wd-icon>
|
||||
</view>
|
||||
<view class="header-text flex-row align-center">
|
||||
<text class="smart-text floor-font-28">{{ props.firstText }}</text>
|
||||
<text class="announcement-text floor-font-28">{{ props.secondText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<view class="arrow-right" v-if="props.suffix">
|
||||
<wd-icon name="arrow-right" size="24rpx"></wd-icon>
|
||||
</view>
|
||||
</template>
|
||||
</wd-notice-bar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
prefix?: boolean
|
||||
suffix?: boolean
|
||||
firstText?: string
|
||||
secondText?: string
|
||||
noticeData: {
|
||||
notice_img?: string
|
||||
list: Array<{
|
||||
title: string
|
||||
content: string
|
||||
url: string
|
||||
}>
|
||||
}
|
||||
}>(),
|
||||
{
|
||||
prefix: false,
|
||||
suffix: false,
|
||||
firstText: '智慧',
|
||||
secondText: '公告'
|
||||
}
|
||||
)
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'noticeClick', index: number): void
|
||||
}>()
|
||||
|
||||
// 计算属性:提取content作为展示文本
|
||||
const displayTexts = computed(() => {
|
||||
// 从noticeData.list中提取所有content组成数组
|
||||
return props.noticeData?.list?.map(item => item.content) || []
|
||||
})
|
||||
|
||||
const handleNoticeClick = ({ index }: { index: number }) => {
|
||||
if (props.noticeData?.list?.[index]?.url) {
|
||||
uni.navigateTo({
|
||||
url: props.noticeData.list[index].url,
|
||||
fail: () => {
|
||||
uni.reLaunch({
|
||||
url: props.noticeData.list[index].url
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
emits('noticeClick', index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/common.scss';
|
||||
|
||||
// 穿透修改通知栏内容样式
|
||||
:deep(.wd-notice-bar__content) {
|
||||
font-size: 24rpx;
|
||||
color: #666; // 内容文本颜色区分于标题
|
||||
}
|
||||
|
||||
.notice-bar {
|
||||
.header-text {
|
||||
flex-wrap: wrap;
|
||||
font-size: 28rpx;
|
||||
margin-left: 4rpx;
|
||||
|
||||
.smart-text {
|
||||
color: $else-highlight-color;
|
||||
font-family: $font-family-bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.announcement-text {
|
||||
color: #333;
|
||||
font-family: $font-family-bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
183
src/components/search-bar/search-bar.vue
Normal file
@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<view class="search-icon flex-row align-center">
|
||||
<image src="/static/images/search.png" mode="aspectFit" class="search-icon-img"></image>
|
||||
</view>
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="localSearchKeyword"
|
||||
:placeholder="placeholder"
|
||||
placeholder-class="placeholder"
|
||||
@input="handleInput"
|
||||
@confirm="handleSearch"
|
||||
/>
|
||||
<!-- 清除图标 -->
|
||||
<view v-if="localSearchKeyword" class="clear-icon" @click="clearSearch">
|
||||
<uni-icons type="clear" size="18" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="showSearchButton" class="search-btn" @click="handleSearch">搜索</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useSearch } from '@/composables/common/useSearch'
|
||||
|
||||
// 接收组件值
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入搜索关键字',
|
||||
},
|
||||
showSearchButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showSearchText: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
debounceTime: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
minLength: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
maxHistoryCount: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search', 'input', 'clear'])
|
||||
|
||||
// 本地搜索关键词状态
|
||||
const localSearchKeyword = ref('')
|
||||
|
||||
// 使用搜索逻辑
|
||||
const {
|
||||
isSearching,
|
||||
searchHistory,
|
||||
handleSearch: performSearch,
|
||||
debounceSearch,
|
||||
} = useSearch({
|
||||
debounceTime: props.debounceTime,
|
||||
minLength: props.minLength,
|
||||
maxHistoryCount: props.maxHistoryCount,
|
||||
})
|
||||
|
||||
// 处理输入
|
||||
const handleInput = (e) => {
|
||||
const value = e.detail.value
|
||||
emit('input', value)
|
||||
|
||||
// 防抖搜索
|
||||
debounceSearch(() => {
|
||||
if (value.trim().length >= props.minLength) {
|
||||
emit('search', value.trim())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
const keyword = localSearchKeyword.value.trim()
|
||||
if (keyword.length >= props.minLength) {
|
||||
performSearch(keyword)
|
||||
emit('search', keyword)
|
||||
}
|
||||
}
|
||||
|
||||
// 清空搜索
|
||||
const clearSearch = () => {
|
||||
localSearchKeyword.value = ''
|
||||
emit('clear')
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
searchKeyword: localSearchKeyword,
|
||||
isSearching,
|
||||
searchHistory,
|
||||
clearSearch,
|
||||
handleSearch,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/common.scss';
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 24rpx;
|
||||
position: relative; // 添加相对定位
|
||||
|
||||
.search-icon {
|
||||
margin-right: 16rpx;
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
// background: red;
|
||||
.search-icon-img {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
padding-right: 40rpx; // 为清除图标留出空间
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 8rpx;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
color: $secondary-color;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
37
src/components/section-title/section-title.vue
Normal 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>
|
63
src/composables/common/useBackground.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
export function useBackground() {
|
||||
// 默认背景色
|
||||
const defaultBackgroundColor = '#f1f1f1'
|
||||
// 渐变色起始色
|
||||
const gradientStartColor = '#C8E3FF'
|
||||
// 渐变色结束色
|
||||
const gradientEndColor = '#E8F4FF'
|
||||
|
||||
// 多端背景样式
|
||||
const backgroundStyle = computed(() => {
|
||||
// 默认样式
|
||||
return {
|
||||
background: defaultBackgroundColor,
|
||||
minHeight: '100vh',
|
||||
position: 'relative',
|
||||
}
|
||||
})
|
||||
|
||||
// 顶部渐变背景样式
|
||||
const topGradientStyle = computed(() => ({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '470rpx',
|
||||
background: getGradientBackground(),
|
||||
zIndex: 1,
|
||||
}))
|
||||
const setTopGradientStyle = (startColor: string, endColor: string) => ({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '470rpx',
|
||||
background: setGradientBackground(startColor, endColor),
|
||||
zIndex: 1,
|
||||
})
|
||||
|
||||
// 获取背景色值
|
||||
const getBackgroundColor = () => defaultBackgroundColor
|
||||
|
||||
// 获取渐变背景色值
|
||||
const getGradientBackground = () =>
|
||||
`linear-gradient(180deg,
|
||||
#C8E3FF 0%,
|
||||
#E3ECF6 50%,
|
||||
#f1f1f1 100%)`
|
||||
//设置渐变背景色
|
||||
const setGradientBackground = (startColor: string, endColor: string) =>
|
||||
`linear-gradient(180deg,
|
||||
${startColor} 0%,
|
||||
${endColor} 50%,
|
||||
#f1f1f1 100%)`
|
||||
return {
|
||||
backgroundStyle,
|
||||
topGradientStyle,
|
||||
getBackgroundColor,
|
||||
getGradientBackground,
|
||||
setTopGradientStyle
|
||||
}
|
||||
}
|
124
src/composables/common/useDataFetch.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { ref, computed, type Ref } from 'vue'
|
||||
|
||||
export interface FetchOptions<T = any> {
|
||||
// 是否立即执行
|
||||
immediate?: boolean
|
||||
// 是否显示加载状态
|
||||
showLoading?: boolean
|
||||
// 加载提示文字
|
||||
loadingText?: string
|
||||
// 是否显示错误提示
|
||||
showError?: boolean
|
||||
// 错误提示文字
|
||||
errorText?: string
|
||||
// 重试次数
|
||||
retryCount?: number
|
||||
// 重试延迟
|
||||
retryDelay?: number
|
||||
}
|
||||
|
||||
export interface FetchResult<T = any> {
|
||||
data: Ref<T | null>
|
||||
loading: Ref<boolean>
|
||||
error: Ref<string | null>
|
||||
execute: (...args: any[]) => Promise<T>
|
||||
refresh: () => Promise<T>
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export function useDataFetch<T = any>(
|
||||
fetchFn: (...args: any[]) => Promise<T>,
|
||||
options: FetchOptions<T> = {},
|
||||
): FetchResult<T> {
|
||||
const {
|
||||
immediate = false,
|
||||
showLoading = true,
|
||||
loadingText = '加载中...',
|
||||
showError = true,
|
||||
errorText = '加载失败',
|
||||
retryCount = 0,
|
||||
retryDelay = 1000,
|
||||
} = options
|
||||
|
||||
const data = ref<T | null>(null) as Ref<T | null>
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const retryTimes = ref(0)
|
||||
|
||||
// 是否正在重试
|
||||
const isRetrying = computed(() => retryTimes.value > 0)
|
||||
|
||||
// 执行数据获取
|
||||
const execute = async (...args: any[]): Promise<T> => {
|
||||
if (loading.value) return data.value as T
|
||||
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
if (showLoading) {
|
||||
uni.showLoading({
|
||||
title: loadingText,
|
||||
mask: true,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fetchFn(...args)
|
||||
data.value = result
|
||||
retryTimes.value = 0
|
||||
return result
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
error.value = errorMessage
|
||||
|
||||
if (showError) {
|
||||
uni.showToast({
|
||||
title: errorText,
|
||||
icon: 'error',
|
||||
duration: 2000,
|
||||
})
|
||||
}
|
||||
|
||||
// 重试逻辑
|
||||
if (retryTimes.value < retryCount) {
|
||||
retryTimes.value++
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay))
|
||||
return execute(...args)
|
||||
}
|
||||
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (showLoading) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
const refresh = async (): Promise<T> => {
|
||||
return execute()
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
const reset = () => {
|
||||
data.value = null
|
||||
loading.value = false
|
||||
error.value = null
|
||||
retryTimes.value = 0
|
||||
}
|
||||
|
||||
// 立即执行
|
||||
if (immediate) {
|
||||
execute()
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
execute,
|
||||
refresh,
|
||||
reset,
|
||||
}
|
||||
}
|
113
src/composables/common/useRefreshLoad.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
|
||||
export interface RefreshLoadOptions {
|
||||
// 刷新成功提示
|
||||
showRefreshSuccess?: boolean
|
||||
// 加载失败提示
|
||||
showLoadError?: boolean
|
||||
}
|
||||
|
||||
export function useRefreshLoad(options: RefreshLoadOptions = {}) {
|
||||
const { showRefreshSuccess = true, showLoadError = true } = options
|
||||
|
||||
// 响应式状态
|
||||
const isRefreshing = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const currentPage = ref(1)
|
||||
|
||||
// 下拉刷新处理
|
||||
const handleRefresh = async (refreshCallback?: () => Promise<void>) => {
|
||||
if (isRefreshing.value) return
|
||||
|
||||
isRefreshing.value = true
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
|
||||
try {
|
||||
if (refreshCallback) {
|
||||
await refreshCallback()
|
||||
}
|
||||
|
||||
if (showRefreshSuccess) {
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
duration: 1500,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新失败:', error)
|
||||
if (showLoadError) {
|
||||
uni.showToast({
|
||||
title: '刷新失败',
|
||||
icon: 'error',
|
||||
duration: 1500,
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
isRefreshing.value = false
|
||||
// 停止下拉刷新动画
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
// 上拉加载处理
|
||||
const handleLoadMore = async (loadMoreCallback?: () => Promise<void>) => {
|
||||
if (isLoading.value || !hasMore.value) return
|
||||
|
||||
isLoading.value = true
|
||||
currentPage.value++
|
||||
|
||||
try {
|
||||
if (loadMoreCallback) {
|
||||
await loadMoreCallback()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载失败:', error)
|
||||
currentPage.value-- // 回退页码
|
||||
if (showLoadError) {
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'error',
|
||||
duration: 1500,
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
const resetState = () => {
|
||||
isRefreshing.value = false
|
||||
isLoading.value = false
|
||||
hasMore.value = true
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
// 设置加载状态
|
||||
const setLoadingState = (loading: boolean) => {
|
||||
isLoading.value = loading
|
||||
}
|
||||
|
||||
// 设置是否有更多数据
|
||||
const setHasMore = (hasMoreData: boolean) => {
|
||||
hasMore.value = hasMoreData
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
isRefreshing,
|
||||
isLoading,
|
||||
hasMore,
|
||||
currentPage,
|
||||
|
||||
// 方法
|
||||
handleRefresh,
|
||||
handleLoadMore,
|
||||
resetState,
|
||||
setLoadingState,
|
||||
setHasMore,
|
||||
}
|
||||
}
|
142
src/composables/common/useSearch.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface SearchOptions {
|
||||
debounceTime?: number
|
||||
minLength?: number
|
||||
maxHistoryCount?: number
|
||||
}
|
||||
|
||||
export function useSearch(options: SearchOptions = {}) {
|
||||
const { debounceTime = 300, minLength = 1, maxHistoryCount = 10 } = options
|
||||
|
||||
const isSearching = ref(false)
|
||||
const searchHistory = ref<string[]>([])
|
||||
const searchTimer = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = (keyword: string) => {
|
||||
const searchText = keyword.trim()
|
||||
|
||||
if (searchText.length < minLength) {
|
||||
uni.showToast({
|
||||
title: `请输入至少${minLength}个字符`,
|
||||
icon: 'none',
|
||||
duration: 1500,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到搜索历史
|
||||
addToHistory(searchText)
|
||||
|
||||
// 执行搜索
|
||||
performSearch(searchText)
|
||||
}
|
||||
|
||||
// 执行搜索
|
||||
const performSearch = async (keyword: string) => {
|
||||
isSearching.value = true
|
||||
|
||||
try {
|
||||
// 这里可以调用搜索API
|
||||
console.log('执行搜索:', keyword)
|
||||
|
||||
// 模拟搜索延迟
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
// 搜索成功回调
|
||||
// uni.showToast({
|
||||
// title: '搜索完成',
|
||||
// icon: 'success',
|
||||
// duration: 1000,
|
||||
// })
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
uni.showToast({
|
||||
title: '搜索失败',
|
||||
icon: 'error',
|
||||
duration: 1500,
|
||||
})
|
||||
} finally {
|
||||
isSearching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到搜索历史
|
||||
const addToHistory = (keyword: string) => {
|
||||
console.log('addToHistory', keyword)
|
||||
if (!keyword.trim()) return
|
||||
|
||||
// 移除重复项
|
||||
const index = searchHistory.value.indexOf(keyword)
|
||||
if (index > -1) {
|
||||
searchHistory.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 添加到开头
|
||||
searchHistory.value.unshift(keyword)
|
||||
|
||||
// 限制历史记录数量
|
||||
if (searchHistory.value.length > maxHistoryCount) {
|
||||
searchHistory.value = searchHistory.value.slice(0, maxHistoryCount)
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
saveSearchHistory()
|
||||
}
|
||||
|
||||
// 清空搜索历史
|
||||
const clearSearchHistory = () => {
|
||||
searchHistory.value = []
|
||||
saveSearchHistory()
|
||||
}
|
||||
|
||||
// 保存搜索历史到本地
|
||||
const saveSearchHistory = () => {
|
||||
try {
|
||||
uni.setStorageSync('searchHistory', searchHistory.value)
|
||||
} catch (error) {
|
||||
console.error('保存搜索历史失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 从本地加载搜索历史
|
||||
const loadSearchHistory = () => {
|
||||
try {
|
||||
const history = uni.getStorageSync('searchHistory')
|
||||
if (history && Array.isArray(history)) {
|
||||
searchHistory.value = history
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载搜索历史失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖搜索
|
||||
const debounceSearch = (callback: () => void) => {
|
||||
if (searchTimer.value) {
|
||||
clearTimeout(searchTimer.value)
|
||||
}
|
||||
|
||||
searchTimer.value = setTimeout(() => {
|
||||
callback()
|
||||
}, debounceTime)
|
||||
}
|
||||
|
||||
// 初始化时加载搜索历史
|
||||
loadSearchHistory()
|
||||
|
||||
return {
|
||||
// 状态
|
||||
isSearching,
|
||||
searchHistory,
|
||||
|
||||
// 方法
|
||||
handleSearch,
|
||||
performSearch,
|
||||
addToHistory,
|
||||
clearSearchHistory,
|
||||
debounceSearch,
|
||||
loadSearchHistory,
|
||||
}
|
||||
}
|
10
src/composables/useCustomsExpress.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useBackground } from './common/useBackground'
|
||||
|
||||
export function useCustomsExpress() {
|
||||
const { backgroundStyle, topGradientStyle } = useBackground()
|
||||
|
||||
return {
|
||||
backgroundStyle,
|
||||
topGradientStyle,
|
||||
}
|
||||
}
|
143
src/composables/useHomePage.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { ref } from 'vue'
|
||||
import { useBackground } from './common/useBackground'
|
||||
import { useRefreshLoad } from './common/useRefreshLoad'
|
||||
import { useDataFetch } from './common/useDataFetch'
|
||||
import { getBannerApi, getHotRecommendApi, getNoticeBartApi, configIndex } from '@/api/home'
|
||||
import { getTopCategoryApi } from '@/api/catogory'
|
||||
import type { bannerItem, noticeBarItem } from '@/types/home'
|
||||
|
||||
export function useHomePage() {
|
||||
// ==================== 使用其他 composables ====================
|
||||
const { backgroundStyle, topGradientStyle } = useBackground()
|
||||
|
||||
const {
|
||||
isRefreshing,
|
||||
isLoading,
|
||||
hasMore,
|
||||
currentPage,
|
||||
handleRefresh,
|
||||
handleLoadMore,
|
||||
resetState,
|
||||
} = useRefreshLoad({
|
||||
showRefreshSuccess: true,
|
||||
showLoadError: true,
|
||||
})
|
||||
const homeData = ref({})
|
||||
// ==================== 页面数据 ====================
|
||||
const bannerList = ref<bannerItem[]>([
|
||||
{
|
||||
id: 1,
|
||||
image: '/static/images/home/banner1.png',
|
||||
jump: true,
|
||||
goods_id: '1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
image: '/static/images/home/banner1.png',
|
||||
jump: true,
|
||||
goods_id: '2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
image: '/static/images/home/banner1.png',
|
||||
jump: true,
|
||||
goods_id: '3',
|
||||
},
|
||||
])
|
||||
|
||||
const noticeBarList = ref<string[]>([
|
||||
'公告公告公告公告公告1111111',
|
||||
'智慧关务系统升级通知',
|
||||
'保税物流服务优化公告',
|
||||
'海关政策更新提醒',
|
||||
'新功能上线通知',
|
||||
])
|
||||
const categoryList = ref<any[]>([])
|
||||
const hotRecommendList = ref<any[]>([])
|
||||
|
||||
// ==================== 数据获取 ====================
|
||||
const { execute: fetchHomeIndex } = useDataFetch(configIndex, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
const { execute: fetchBanners } = useDataFetch(getBannerApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
const { execute: fetchNotices } = useDataFetch(getNoticeBartApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
const { execute: fetchCategories } = useDataFetch(getTopCategoryApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
const { execute: fetchHotRecommend } = useDataFetch(getHotRecommendApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
//页面初始化
|
||||
const initPage = async () => {
|
||||
try {
|
||||
await Promise.all([fetchHomeIndex().then(res => {
|
||||
if (res.code == 1) {
|
||||
homeData.value = res.data
|
||||
}
|
||||
})])
|
||||
// 在这里赋值或者获取接口返回的数据
|
||||
} catch (error) {
|
||||
console.error('页面初始化失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新处理
|
||||
const onRefresh = async () => {
|
||||
await handleRefresh(async () => {
|
||||
await initPage()
|
||||
})
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const onLoadMore = async () => {
|
||||
await handleLoadMore(async () => {
|
||||
// 这里可以加载更多数据
|
||||
console.log('加载更多数据')
|
||||
})
|
||||
}
|
||||
|
||||
// 返回数据和方法
|
||||
return {
|
||||
// 背景样式
|
||||
backgroundStyle,
|
||||
topGradientStyle,
|
||||
|
||||
// 页面状态
|
||||
isRefreshing,
|
||||
isLoading,
|
||||
hasMore,
|
||||
currentPage,
|
||||
|
||||
// 页面数据
|
||||
bannerList,
|
||||
noticeBarList,
|
||||
categoryList,
|
||||
hotRecommendList,
|
||||
|
||||
// 页面方法
|
||||
initPage,
|
||||
onRefresh,
|
||||
onLoadMore,
|
||||
resetState,
|
||||
homeData
|
||||
}
|
||||
}
|
293
src/composables/useTodoPage.ts
Normal file
@ -0,0 +1,293 @@
|
||||
import { ref, unref, computed, nextTick } from 'vue'
|
||||
import { useBackground } from './common/useBackground'
|
||||
import { useRefreshLoad } from './common/useRefreshLoad'
|
||||
import { useDataFetch } from './common/useDataFetch'
|
||||
import { getBannerApi, getHotRecommendApi, getNoticeBartApi, configIndex, configSearch } from '@/api/home'
|
||||
import { getTopCategoryApi } from '@/api/catogory'
|
||||
import type { bannerItem, noticeBarItem } from '@/types/home'
|
||||
|
||||
|
||||
export function useTodoPage() {
|
||||
// ==================== 使用其他 composables ====================
|
||||
const { backgroundStyle } = useBackground()
|
||||
const topGradientStyle = useBackground().setTopGradientStyle('#FCDC9D', '#F2EFE7');
|
||||
const {
|
||||
isRefreshing,
|
||||
isLoading,
|
||||
hasMore,
|
||||
currentPage,
|
||||
handleRefresh,
|
||||
handleLoadMore,
|
||||
resetState,
|
||||
} = useRefreshLoad({
|
||||
showRefreshSuccess: true,
|
||||
showLoadError: true,
|
||||
})
|
||||
|
||||
// ==================== 页面数据 ====================
|
||||
const bannerList = ref<bannerItem[]>([])
|
||||
const noticeBarList = ref<string[]>([])
|
||||
const categoryList = ref<any[]>([])
|
||||
const hotRecommendList = ref<any[]>([])
|
||||
|
||||
// 新增接口返回的数据
|
||||
const menuList = ref<any[]>([])
|
||||
const switchList = ref<any[]>([])
|
||||
const systemInfo = ref<any>({})
|
||||
const noticeInfo = ref<any>({})
|
||||
const hbsmNotice = ref<any>({})
|
||||
const blackInfo = ref<any>({})
|
||||
|
||||
|
||||
// ==================== Tab切换相关 ====================
|
||||
const activeTab = ref(0) // 当前激活的tab索引
|
||||
const activeTabKey = ref('goods') // 当前激活的tab key
|
||||
const tabContentList = ref<any[]>([]) // tab对应的内容列表
|
||||
const tabLoading = ref(false) // tab内容加载状态
|
||||
const tabCurrentPage = ref(1) // tab内容当前页
|
||||
const tabHasMore = ref(true) // tab内容是否还有更多
|
||||
|
||||
// ==================== 数据获取 ====================
|
||||
const { execute: fetchBanners } = useDataFetch(getBannerApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
const { execute: fetchNotices } = useDataFetch(getNoticeBartApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
const { execute: fetchCategories } = useDataFetch(getTopCategoryApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
const { execute: fetchHotRecommend } = useDataFetch(getHotRecommendApi, {
|
||||
immediate: false,
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
})
|
||||
|
||||
// ==================== 页面初始化 ====================
|
||||
const initPage = async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchBanners(),
|
||||
fetchNotices(),
|
||||
fetchCategories(),
|
||||
fetchHotRecommend(),
|
||||
configIndexApi()
|
||||
])
|
||||
|
||||
// 初始化完成后,加载默认tab的内容
|
||||
if (switchList.value.length > 0) {
|
||||
activeTabKey.value = switchList.value[0].keys
|
||||
await loadTabContent(activeTabKey.value, 1, true)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('页面初始化失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 刷新处理 ====================
|
||||
const onRefresh = async () => {
|
||||
await handleRefresh(async () => {
|
||||
await initPage()
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 加载更多处理 ====================
|
||||
const onLoadMore = async () => {
|
||||
if (tabHasMore.value && !tabLoading.value) {
|
||||
await loadTabContent(activeTabKey.value, tabCurrentPage.value + 1, false)
|
||||
}
|
||||
}
|
||||
|
||||
const configIndexApi = async () => {
|
||||
try {
|
||||
const res = await configIndex({ page: 1, page_size: 10 })
|
||||
console.log(res, '-----------------configIndex')
|
||||
|
||||
if (res.code === 1 && res.data) {
|
||||
const { data } = res
|
||||
|
||||
// 处理菜单数据
|
||||
if (data.menu && Array.isArray(data.menu)) {
|
||||
menuList.value = data.menu
|
||||
}
|
||||
bannerList.value = data.adv
|
||||
|
||||
// [
|
||||
// { title: '推荐', route: '/pages/home/index' },
|
||||
// { title: '合作伙伴', route: '/pagesMember/pages/profile/profile' },
|
||||
// { title: '商机', route: '/pagesForeignTrade/pages/trade/trade' },
|
||||
// { title: '关于我们', route: '/pagesOther/pages/blank/rich-text' },
|
||||
// { title: '找买家', route: '/pagesMember/pages/profile/profile' },
|
||||
// { title: '找卖家', route: '/pagesMember/pages/profile/profile' },
|
||||
// ]
|
||||
// 处理切换列表数据
|
||||
if (data?.switchList && Array.isArray(data?.switchList)) {
|
||||
switchList.value = data?.switchList
|
||||
// 设置默认激活的tab
|
||||
if (data.switchList.length > 0) {
|
||||
activeTabKey.value = data.switchList[0].keys
|
||||
}
|
||||
}
|
||||
|
||||
// 处理系统信息
|
||||
if (data.system) {
|
||||
systemInfo.value = data.system
|
||||
}
|
||||
|
||||
// 处理通知栏数据
|
||||
if (data.notice) {
|
||||
noticeInfo.value = data.notice
|
||||
// 将通知列表转换为字符串数组格式
|
||||
if (data.notice.list && Array.isArray(data.notice.list)) {
|
||||
noticeBarList.value = data.notice.list.map((item : any) => item.content || item.title)
|
||||
}
|
||||
}
|
||||
// 处理海保世贸通知
|
||||
if (data.hbsmNotice) {
|
||||
hbsmNotice.value = data.hbsmNotice;
|
||||
//弹出系统维护窗口
|
||||
//status 系统提示状态,1=弹出系统提示, 弹出hbsm_notice.title,和hbsm_notice.content ; 0不用管
|
||||
if (data.hbsmNotice.status == 1)
|
||||
uni.showModal({
|
||||
showCancel: false,
|
||||
title: data.hbsmNotice.title,
|
||||
content: data.hbsmNotice.content
|
||||
})
|
||||
}
|
||||
|
||||
// 处理黑名单信息
|
||||
if (data.black) {
|
||||
blackInfo.value = data.black
|
||||
//status 黑名单状态,1=已被拉黑,弹窗弹窗,不能关闭,或者跳到新页面无法返回
|
||||
if (data.black.status == 1) {
|
||||
uni.setStorage({
|
||||
key: 'blackInfo',
|
||||
data: data.black,
|
||||
success: () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/black/index'
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取首页配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Tab切换和内容加载 ====================
|
||||
/**
|
||||
* 切换Tab
|
||||
* @param index tab索引
|
||||
* @param tabKey tab的key值
|
||||
*/
|
||||
const switchTab = async (index : number, tabKey : string) => {
|
||||
if (activeTab.value === index) return
|
||||
|
||||
activeTab.value = index
|
||||
activeTabKey.value = tabKey
|
||||
|
||||
// 切换tab时重新加载内容
|
||||
await loadTabContent(tabKey, 1, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Tab内容
|
||||
* @param type tab类型
|
||||
* @param page 页码
|
||||
* @param isRefresh 是否刷新(清空原有数据)
|
||||
* @param keywords 搜索关键词
|
||||
*/
|
||||
const loadTabContent = async (type : string, page : number = 1, isRefresh : boolean = false, keywords : string = '') => {
|
||||
try {
|
||||
tabLoading.value = true
|
||||
|
||||
const res = await configSearch({
|
||||
page,
|
||||
page_size: 10,
|
||||
keywords,
|
||||
type
|
||||
})
|
||||
|
||||
console.log(res, '-----------------configSearch')
|
||||
|
||||
if (res.code === 1 && res.data) {
|
||||
const { data } = res
|
||||
|
||||
if (isRefresh) {
|
||||
// 刷新时清空原有数据
|
||||
tabContentList.value = data.list || []
|
||||
tabCurrentPage.value = 1
|
||||
} else {
|
||||
// 加载更多时追加数据
|
||||
tabContentList.value = [...tabContentList.value, ...(data.list || [])]
|
||||
tabCurrentPage.value = page
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
tabHasMore.value = (data.list && data.list.length === 10) || false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载tab内容失败:', error)
|
||||
} finally {
|
||||
tabLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 返回数据和方法 ====================
|
||||
return {
|
||||
// 背景样式
|
||||
backgroundStyle,
|
||||
topGradientStyle,
|
||||
|
||||
// 页面状态
|
||||
isRefreshing,
|
||||
isLoading,
|
||||
hasMore,
|
||||
currentPage,
|
||||
|
||||
// 页面数据
|
||||
bannerList,
|
||||
noticeBarList,
|
||||
categoryList,
|
||||
hotRecommendList,
|
||||
|
||||
// 新增的接口数据
|
||||
menuList,
|
||||
switchList,
|
||||
systemInfo,
|
||||
noticeInfo,
|
||||
hbsmNotice,
|
||||
blackInfo,
|
||||
|
||||
// Tab相关数据和状态
|
||||
activeTab,
|
||||
activeTabKey,
|
||||
tabContentList,
|
||||
tabLoading,
|
||||
tabCurrentPage,
|
||||
tabHasMore,
|
||||
|
||||
// 页面方法
|
||||
initPage,
|
||||
onRefresh,
|
||||
onLoadMore,
|
||||
resetState,
|
||||
|
||||
// Tab相关方法
|
||||
switchTab,
|
||||
loadTabContent,
|
||||
}
|
||||
}
|
332
src/pages.json
@ -3,12 +3,12 @@
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
|
||||
"^shop-(.*)": "@/components/shop$1.vue"
|
||||
"^shop-(.*)": "@/components/shop$1.vue",
|
||||
"^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue"
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"pages": [{
|
||||
"path": "pages/home/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
// #ifdef WEB
|
||||
@ -17,29 +17,43 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/my/my",
|
||||
"path": "pages/assistant/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "我的"
|
||||
"navigationBarTitleText": "海保助手"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/cart/cart",
|
||||
"path": "pages/my/userCenter",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购物车",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "个人中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/category/category",
|
||||
"path": "pages/black/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "分类",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "账户状态"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/todo/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "待办事项"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/service/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "客服"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -51,81 +65,95 @@
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#333",
|
||||
"selectedColor": "#ff5f3c",
|
||||
"fontSize": "20rpx",
|
||||
"iconWidth": "36rpx",
|
||||
"selectedColor": "#4d7eec",
|
||||
"backgroundColor": "#fff",
|
||||
"borderStyle": "white",
|
||||
"list": [
|
||||
{
|
||||
"list": [{
|
||||
"text": "首页",
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "/static/tabs/home_default.png",
|
||||
"selectedIconPath": "/static/tabs/home_selected.png"
|
||||
"pagePath": "pages/home/index",
|
||||
"iconPath": "/static/tabs/home.png",
|
||||
"selectedIconPath": "/static/tabs/home_active.png"
|
||||
},
|
||||
{
|
||||
"text": "分类",
|
||||
"pagePath": "pages/category/category",
|
||||
"iconPath": "/static/tabs/category_default.png",
|
||||
"selectedIconPath": "/static/tabs/category_selected.png"
|
||||
"text": "海保助手",
|
||||
"pagePath": "pages/assistant/index",
|
||||
"iconPath": "/static/tabs/assistant.png",
|
||||
"selectedIconPath": "/static/tabs/assistant_active.png"
|
||||
},
|
||||
{
|
||||
"text": "购物车",
|
||||
"pagePath": "pages/cart/cart",
|
||||
"iconPath": "/static/tabs/cart_default.png",
|
||||
"selectedIconPath": "/static/tabs/cart_selected.png"
|
||||
"text": "待办事项",
|
||||
"pagePath": "pages/todo/index",
|
||||
"iconPath": "/static/tabs/todo.png",
|
||||
"selectedIconPath": "/static/tabs/todo_active.png"
|
||||
},
|
||||
{
|
||||
"text": "我的",
|
||||
"pagePath": "pages/my/my",
|
||||
"iconPath": "/static/tabs/user_default.png",
|
||||
"selectedIconPath": "/static/tabs/user_selected.png"
|
||||
"text": "个人中心",
|
||||
"pagePath": "pages/my/userCenter",
|
||||
"iconPath": "/static/tabs/my.png",
|
||||
"selectedIconPath": "/static/tabs/my_active.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subPackages": [
|
||||
"subPackages": [{
|
||||
"root": "pagesOther",
|
||||
"pages": [{
|
||||
"path": "pages/blank/rich-text",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
}]
|
||||
},
|
||||
// company企业相关分包
|
||||
{
|
||||
"root": "pagesGoods",
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/goods/goods",
|
||||
"root": "pagesCompany",
|
||||
"pages": [{
|
||||
"path": "pages/companyPortrait/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "企业画像",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
}]
|
||||
},
|
||||
// custome客户相关分包
|
||||
{
|
||||
"root": "pagesCustomer",
|
||||
"pages": [{
|
||||
"path": "pages/customerManagement/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"navigationBarTitleText": "客户管理",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/customsExpress/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/list/list",
|
||||
"path": "pages/customsService/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品列表",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/evaluate/evaluate",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品评价",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/search/search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "搜索商品",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
"navigationBarTitleText": "智慧关务",
|
||||
"navigationBarBackgroundColor": "#fff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// shop 商城相关分包
|
||||
{
|
||||
"root": "pagesMember",
|
||||
"root": "pagesShop",
|
||||
"pages": [
|
||||
// 登录
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
@ -135,15 +163,6 @@
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/settings/settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/profile",
|
||||
"style": {
|
||||
@ -152,6 +171,15 @@
|
||||
"navigationBarTitleText": "个人信息"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/settings/settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/address/address",
|
||||
"style": {
|
||||
@ -214,162 +242,212 @@
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pagesOrder",
|
||||
"pages": [
|
||||
},
|
||||
// 商城相关
|
||||
{
|
||||
"path": "pages/create/create",
|
||||
"path": "pages/shopMall/index",
|
||||
|
||||
"style": {
|
||||
"navigationBarTitleText": "大贸商城",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/shopGoods/goods/goods",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/shopGoods/list/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品列表",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/shopGoods/evaluate/evaluate",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品评价",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/shopGoods/search/search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "搜索商品",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
// 购物车相关
|
||||
{
|
||||
"path": "pages/shopCart/cart/cart",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购物车",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
// 分类相关
|
||||
{
|
||||
"path": "pages/shopCategory/category/category",
|
||||
"style": {
|
||||
"navigationBarTitleText": "分类",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
// 订单相关
|
||||
{
|
||||
"path": "pages/shopCreate/create",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单创建",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/detail/detail",
|
||||
"path": "pages/shopDetail/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/payment/pay",
|
||||
"path": "pages/shopPayment/pay",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单支付",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/payment/payment",
|
||||
"path": "pages/shopPayment/payment",
|
||||
"style": {
|
||||
"navigationBarTitleText": "支付结果",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/list/list",
|
||||
"path": "pages/shopList/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单列表",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/receive/receive",
|
||||
"path": "pages/shopReceive/receive",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收货结果",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/evaluate/evaluate",
|
||||
"path": "pages/shopEvaluate/evaluate",
|
||||
"style": {
|
||||
"navigationBarTitleText": "评价订单",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/refund/refund",
|
||||
"path": "pages/shopRefund/refund",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请售后",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/refund/list",
|
||||
"path": "pages/shopRefund/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "售后列表",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/refund/detail",
|
||||
"path": "pages/shopRefund/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "售后详情",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/delivery/delivery",
|
||||
"path": "pages/shopDelivery/delivery",
|
||||
"style": {
|
||||
"navigationBarTitleText": "物流详情",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/recharge/list",
|
||||
"path": "pages/shopRecharge/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "充值订单",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/center/center",
|
||||
"path": "pages/shopCenter/center",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单中心",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pagesOther",
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/blank/rich-text",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
// #ifdef WEB
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "default"
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
],
|
||||
"preloadRule": {
|
||||
"pages/index/index": {
|
||||
"pages/home/index": {
|
||||
"network": "all",
|
||||
"packages": ["pagesGoods"]
|
||||
"packages": ["pagesShop"]
|
||||
},
|
||||
"pages/category/category": {
|
||||
"pages/assistant/index": {
|
||||
"network": "all",
|
||||
"packages": ["pagesGoods"]
|
||||
"packages": ["pagesShop"]
|
||||
},
|
||||
"pages/cart/cart": {
|
||||
"pages/todo/index": {
|
||||
"network": "all",
|
||||
"packages": ["pagesGoods"]
|
||||
"packages": ["pagesShop"]
|
||||
},
|
||||
"pages/my/my": {
|
||||
"pages/my/userCenter": {
|
||||
"network": "all",
|
||||
"packages": ["pagesMember", "pagesOrder"]
|
||||
"packages": ["pagesShop", "pagesCompany"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
src/pages/assistant/index.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<view>
|
||||
<text>海保助手</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
297
src/pages/black/index.vue
Normal file
@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<!-- 黑名单页面 -->
|
||||
<view class="page-container">
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<wd-navbar title="账户状态" safeAreaInsetTop :bordered="false"></wd-navbar>
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="content-wrapper">
|
||||
<!-- 状态图标 -->
|
||||
<view class="status-icon-wrapper">
|
||||
<view class="icon-bg">
|
||||
<wd-icon name="warning" size="100rpx" color="#fff"></wd-icon>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 标题 -->
|
||||
<view class="title-text">账户已被限制</view>
|
||||
<!-- 描述信息 -->
|
||||
<view class="description-text">
|
||||
<text>{{blackInfo.content}}</text>
|
||||
</view>
|
||||
<!-- 联系客服卡片 -->
|
||||
<view class="contact-card-new">
|
||||
<!-- <view class="contact-header">
|
||||
<uv-icon name="server-man" size="36rpx" color="#389054"></uv-icon>
|
||||
<text class="header-title">联系我们</text>
|
||||
</view> -->
|
||||
<view class="contact-body">
|
||||
<view class="contact-item-new">
|
||||
<image class="item-img" :src="blackInfo.qrCode" mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="contact-footer">
|
||||
<wd-icon name="time" size="28rpx" color="#999"></wd-icon>
|
||||
<text class="footer-text">服务时间:{{blackInfo.date}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<view class="footer-tips">
|
||||
<view class="tip-card">
|
||||
<uv-icon name="info-circle" size="32rpx" color="#f9ae3d"></uv-icon>
|
||||
<view class="tip-content">
|
||||
<view class="tip-title">温馨提示</view>
|
||||
<view class="tip-text">账户异常可能由违规操作、安全风险等原因导致,请您理解与配合。</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref,
|
||||
onMounted
|
||||
} from "vue";
|
||||
|
||||
interface blackObj {
|
||||
//商品ID
|
||||
status : number
|
||||
content : string
|
||||
//客服二维码路径
|
||||
qrCode : string
|
||||
//服务时间
|
||||
date : string
|
||||
}
|
||||
const blackInfo = ref<blackObj>({});
|
||||
onMounted(() => {
|
||||
uni.getStorage({
|
||||
key: 'blackInfo',
|
||||
success: (res) => {
|
||||
console.log(res.data);
|
||||
blackInfo.value = res.data;
|
||||
}
|
||||
})
|
||||
})
|
||||
// // 客服电话
|
||||
// const phoneValue = ref("");
|
||||
|
||||
// // 拨打客服电话
|
||||
// const makePhoneCall = () => {
|
||||
// // 检查电话号码是否存在
|
||||
// if (!phoneValue.value) {
|
||||
// uni.showToast({
|
||||
// title: "暂未获取到客服电话",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// uni.makePhoneCall({
|
||||
// phoneNumber: phoneValue.value,
|
||||
// success: () => {
|
||||
// console.log("成功发起拨打电话");
|
||||
// },
|
||||
// fail: (err) => {
|
||||
// console.error("拨打电话失败:", err);
|
||||
// uni.showToast({
|
||||
// title: "拨打电话功能暂不可用",
|
||||
// icon: "none",
|
||||
// });
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
|
||||
// // 处理联系客服操作
|
||||
// const handleContactService = () => {
|
||||
// // 添加触觉反馈,提升用户体验
|
||||
// uni.vibrateShort && uni.vibrateShort();
|
||||
// makePhoneCall();
|
||||
// };
|
||||
|
||||
// // 获取远程设置,例如客服电话
|
||||
// const getSetting = async () => {
|
||||
// try {
|
||||
// const { kefu } = await useHttp("/api/setting");
|
||||
// if (kefu && kefu.kefu_mobile) {
|
||||
// phoneValue.value = kefu.kefu_mobile;
|
||||
// } else {
|
||||
// console.warn("未获取到客服电话");
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error("获取配置失败", e);
|
||||
// }
|
||||
// };
|
||||
|
||||
// // 页面加载时获取设置
|
||||
// getSetting();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #f0f8f2 0%, #ffffff 40%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
// flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.status-icon-wrapper {
|
||||
margin-top: 60rpx;
|
||||
margin-bottom: 40rpx;
|
||||
position: relative;
|
||||
|
||||
.icon-bg {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(145deg, #ff7575, #dd524d);
|
||||
box-shadow: 0 10rpx 30rpx rgba(221, 82, 77, 0.4);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
font-size: 30rpx;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
margin-bottom: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.contact-card-new {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.08);
|
||||
// margin-bottom: 60rpx;
|
||||
|
||||
.contact-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.contact-item-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
background-color: #f7f8fa;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
background-color: #f2f3f5;
|
||||
}
|
||||
|
||||
.item-image {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.contact-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 24rpx;
|
||||
margin-top: 24rpx;
|
||||
border-top: 1rpx solid #f2f3f5;
|
||||
|
||||
.footer-text {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-tips {
|
||||
padding: 0 40rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.tip-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
background-color: #fff7e8;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
|
||||
.tip-content {
|
||||
margin-left: 16rpx;
|
||||
|
||||
.tip-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #f29100;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 26rpx;
|
||||
color: #b87a28;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 10rpx 30rpx rgba(221, 82, 77, 0.4);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 15rpx 40rpx rgba(221, 82, 77, 0.6);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 10rpx 30rpx rgba(221, 82, 77, 0.4);
|
||||
}
|
||||
}
|
||||
</style>
|
97
src/pages/home/components/service-grid.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<view class="service-grid">
|
||||
<view class="grid-container">
|
||||
<view
|
||||
v-for="(service, index) in props.list"
|
||||
:key="index"
|
||||
class="service-item"
|
||||
@click="handleServiceClick(service)"
|
||||
>
|
||||
<view class="service-icon flex-row align-center justify-center">
|
||||
<image
|
||||
:src="service.img"
|
||||
mode="aspectFit"
|
||||
:class="getIconClass(1)"
|
||||
></image>
|
||||
</view>
|
||||
<text class="service-title">{{ service.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { pageUrl } from '@/utils/constants'
|
||||
import type { menuItem } from '@/types/home'
|
||||
interface ServiceItem {
|
||||
title: string
|
||||
iconPath: string
|
||||
route?: string
|
||||
floor?: number
|
||||
}
|
||||
//接收组件值
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
list: menuItem[]
|
||||
}>(),
|
||||
{
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
const handleServiceClick = (service: ServiceItem) => {
|
||||
if (service.url && service.url != "#") {
|
||||
uni.navigateTo({
|
||||
url: service.url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getIconClass = (floor: number | undefined) => {
|
||||
return floor === 1 ? 'icon-size-1' : floor === 2 ? 'icon-size-2' : 'icon-size-3'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.service-grid {
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 40rpx;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
.service-icon {
|
||||
.icon-size-1 {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
.icon-size-2 {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
.icon-size-3 {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
881
src/pages/home/components/tab-content.vue
Normal file
@ -0,0 +1,881 @@
|
||||
<template>
|
||||
<view class="tab-content">
|
||||
<!-- 推荐 tab - 商品展示 -->
|
||||
<view v-if="activeTab === 'goods'" class="recommend-content">
|
||||
<view class="goods-list flex-col">
|
||||
<view
|
||||
v-for="(item,index) in dataList"
|
||||
:key="index"
|
||||
class="goods-item"
|
||||
@click="jumpUrl(item.url)"
|
||||
>
|
||||
<view class="goods-image">
|
||||
<image :src="item.cover" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="goods-info flex-col">
|
||||
<text class="goods-title">{{ item.name }}</text>
|
||||
<view class="goods-tags">
|
||||
<text v-for="tag in item.tags" :key="tag" class="goods-tag">{{ tag }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-price align-end">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">{{ item.min_price}}</text>
|
||||
<text class="price-unit">/起</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 合作伙伴 tab -->
|
||||
<view v-else-if="activeTab === 1" class="partners-content">
|
||||
<view class="partners-list">
|
||||
<view class="partner-item" v-for="partner in partners" :key="partner.id">
|
||||
<image class="partner-logo" :src="partner.logo" mode="aspectFit"></image>
|
||||
<text class="partner-name">{{ partner.name }}</text>
|
||||
<text class="partner-desc">{{ partner.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商机 tab -->
|
||||
<view v-else-if="activeTab === 'opportunity'" class="opportunities-content"
|
||||
>
|
||||
<view class="opportunities-list">
|
||||
<view class="opportunity-item" v-for="(opp,index) in dataList" :key="index" @click="jumpUrl(opp.url)">
|
||||
<view class="opp-header">
|
||||
<text class="opp-title">{{ opp.name }}</text>
|
||||
<view class="opp-status active" >{{ opp.service_fee }}</view>
|
||||
</view>
|
||||
<view class="opp-desc">{{ opp.desc }}</view>
|
||||
<view class="opp-meta">
|
||||
<text class="opp-location">{{ opp.cate_name }}</text>
|
||||
<text class="opp-date">{{ opp.create_time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关于我们 tab -->
|
||||
<view v-else-if="activeTab === 3" class="about-content">
|
||||
<view class="about-info">
|
||||
<text class="about-text">
|
||||
我们是一家专注于国际贸易和物流服务的综合性企业,致力于为客户提供优质、高效、便捷的服务。
|
||||
</text>
|
||||
<view class="company-stats">
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">10+</text>
|
||||
<text class="stat-label">年行业经验</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">1000+</text>
|
||||
<text class="stat-label">服务客户</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">50+</text>
|
||||
<text class="stat-label">合作国家</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 找买家 tab -->
|
||||
<view v-else-if="activeTab === 'buy'" class="buyers-content">
|
||||
<!-- <view class="buyers-search">
|
||||
<search-bar
|
||||
ref="buyersSearchBarRef"
|
||||
:placeholder="'请输入搜索关键字'"
|
||||
:showSearchButton="false"
|
||||
@search="handleSearch"
|
||||
@input="handleSearchInput"
|
||||
@clear="handleClearSearch"
|
||||
/>
|
||||
</view> -->
|
||||
<view class="buyers-list">
|
||||
<view class="buyer-card flex-col" @click="jumpUrl(buyer.url)" v-for="(buyer,index) in dataList" :key="buyer.id">
|
||||
<!-- 标题行 -->
|
||||
<view class="card-header flex-row justify-between align-center">
|
||||
<text class="product-title">{{ buyer.name }}</text>
|
||||
<view class="view-more">
|
||||
<text class="view-more-text">查看更多</text>
|
||||
<wd-icon name="arrow-right" size="14" color="#666666"></wd-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 海关编码 -->
|
||||
<!-- <view class="customs-code">
|
||||
<text class="code-label">海关编码</text>
|
||||
<text class="code-value">{{ buyer.customsCode }}</text>
|
||||
</view> -->
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 主要数据 -->
|
||||
<view class="data-section">
|
||||
<view class="data-items-combined">
|
||||
<view class="data-item">
|
||||
<text class="data-label1">类型</text>
|
||||
<text class="data-value">{{ buyer.cate_name }}</text>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<text class="data-label1">贸易伙伴</text>
|
||||
<text class="data-value">{{ buyer.partner }}</text>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<text class="data-label1">邮箱</text>
|
||||
<text class="data-value">{{ buyer.email }}</text>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<text class="data-label2">日期</text>
|
||||
<text class="data-value">{{ buyer.date }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="product-info">
|
||||
<view class="data-item">
|
||||
<text class="data-label1">描述</text>
|
||||
<view class="data-value">{{ buyer.remarks }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 进出口商信息 -->
|
||||
<view class="trade-info flex-row justify-between">
|
||||
<view class="exporter-info flex-row">
|
||||
<view class="exporter-item">
|
||||
<view class="info-label">主营业务</view>
|
||||
<view class="info-value">{{ buyer.business }}</view>
|
||||
|
||||
</view>
|
||||
<!-- <view class="exporter-item flex-col">
|
||||
<image :src="buyer.logo" mode="aspectFit" class="logo"></image>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 竖向分隔线 -->
|
||||
<view class="vertical-divider"></view>
|
||||
|
||||
<view class="importer-info flex-row">
|
||||
<view class="importer-item">
|
||||
<view class="info-label">是否有联系方式</view>
|
||||
<text class="info-value">{{ buyer.is_contact == 1 ? '有' :'无' }}</text>
|
||||
|
||||
</view>
|
||||
<!-- <view class="importer-item flex-col">
|
||||
<text class="info-value">{{ buyer.importerCountry }}</text>
|
||||
<view class="info-label">所在国</view>
|
||||
</view> -->
|
||||
<view class="importer-item flex-col">
|
||||
<image :src="buyer.logo" class="logo"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 找卖家 tab -->
|
||||
<view v-else-if="activeTab === 'sell'" class="sellers-content">
|
||||
<view class="sellers-list">
|
||||
<view class="seller-item" @click="jumpUrl(seller.url)" v-for="(seller,index) in dataList" :key="index">
|
||||
<view class="seller-avatar">
|
||||
<image :src="seller.logo" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="seller-info">
|
||||
<view class="seller-name">{{ seller.name }}</view>
|
||||
<text class="seller-company">{{ seller.cate_name }}</text>
|
||||
<view class="seller-products">{{ seller.remarks }}</view>
|
||||
</view>
|
||||
<view class="seller-action">
|
||||
<text class="contact-btn">联系</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 保税生活 -->
|
||||
<view v-else-if="activeTab === 'life'" class="sellers-content">
|
||||
<view class="sellers-list">
|
||||
<view class="seller-item" @click="jumpUrl(seller.url)" v-for="(seller,index) in dataList" :key="index">
|
||||
<view class="seller-avatar">
|
||||
<image :src="seller.logo" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="seller-info">
|
||||
<view class="seller-name">{{ seller.name }}</view>
|
||||
<view class="tags_box">
|
||||
<view class="tag_item" v-for="(t,ti) in seller.tag" :key="index">
|
||||
{{t}}
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="seller-company">{{ setTag(seller.tag)}}</view> -->
|
||||
<view class="seller-products">{{ seller.remarks }}</view>
|
||||
</view>
|
||||
<view class="seller-action">
|
||||
<text class="contact-btn">联系</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 默认内容 -->
|
||||
<view v-if="!dataList.length" class="default-content">
|
||||
<text class="default-text">暂无内容</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive, watch } from 'vue'
|
||||
import { configSearch } from '@/api/home'
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
activeTab: {
|
||||
type: [String,Number],
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
const dataList = ref([])
|
||||
const hasNextPage = ref(false)
|
||||
watch(() => props.activeTab, (newValue, oldValue) => {
|
||||
queryParams.page = 1
|
||||
dataList.value = []
|
||||
getList()
|
||||
});
|
||||
// 推荐商品数据
|
||||
const recommendGoods = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '劳斯莱斯',
|
||||
image: '/static/images/home/goods1.png',
|
||||
tags: ['拍卖', '进口', '展会清仓', '海关处置', '零售'],
|
||||
priceType: '¥',
|
||||
price: '500',
|
||||
unit: 'CBM',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '优质商品B',
|
||||
image: '/static/images/home/goods2.png',
|
||||
tags: ['拍卖', '进口', '展会清仓', '海关处置', '零售'],
|
||||
priceType: '¥',
|
||||
price: '500',
|
||||
unit: 'CBM',
|
||||
},
|
||||
])
|
||||
|
||||
// 合作伙伴数据
|
||||
const partners = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '合作伙伴A',
|
||||
logo: '/static/images/home/partner1.png',
|
||||
description: '长期合作伙伴,提供优质服务',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '合作伙伴B',
|
||||
logo: '/static/images/home/partner2.png',
|
||||
description: '战略合作伙伴,共同发展',
|
||||
},
|
||||
])
|
||||
|
||||
// 商机数据
|
||||
const opportunities = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '国际贸易合作机会',
|
||||
description: '寻找长期稳定的贸易合作伙伴,共同开拓国际市场',
|
||||
status: 'active',
|
||||
statusText: '进行中',
|
||||
location: '上海',
|
||||
date: '2024-01-15',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '物流服务招标',
|
||||
description: '大型物流项目招标,诚邀有实力的物流企业参与',
|
||||
status: 'pending',
|
||||
statusText: '待审核',
|
||||
location: '深圳',
|
||||
date: '2024-01-20',
|
||||
},
|
||||
])
|
||||
|
||||
// 买家数据
|
||||
const buyers = ref([
|
||||
{
|
||||
id: 1,
|
||||
productName: '优质商品A',
|
||||
customsCode: '12345678901234567890',
|
||||
quantity: '1000',
|
||||
amount: '100000',
|
||||
description: '这是一款高质量的商品,适合长期合作。',
|
||||
metricTons: '10',
|
||||
date: '2023-12-30',
|
||||
exporter: 'ABC贸易公司',
|
||||
exporterCountry: '中国',
|
||||
importer: 'XYZ进出口公司',
|
||||
importerCountry: '美国',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
productName: '商品B',
|
||||
customsCode: '98765432109876543210',
|
||||
quantity: '500',
|
||||
amount: '50000',
|
||||
description: '这是一款价格合理的商品,需求量大。',
|
||||
metricTons: '5',
|
||||
date: '2024-01-10',
|
||||
exporter: 'DEF进出口公司',
|
||||
exporterCountry: '英国',
|
||||
importer: 'GHI贸易公司',
|
||||
importerCountry: '德国',
|
||||
},
|
||||
])
|
||||
|
||||
// 搜索栏组件引用
|
||||
const buyersSearchBarRef = ref()
|
||||
const queryParams = reactive({
|
||||
page:1,
|
||||
page_size:10,
|
||||
keywords:''
|
||||
})
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = (keyword) => {
|
||||
console.log('找买家调用api搜索:', keyword)
|
||||
// 这里可以添加搜索逻辑,比如跳转到搜索页面
|
||||
}
|
||||
|
||||
// 处理搜索输入
|
||||
const handleSearchInput = (keyword) => {
|
||||
console.log('搜索输入:', keyword)
|
||||
}
|
||||
const setTag = (list)=>{
|
||||
if(list && list.length){
|
||||
return list.join(",")
|
||||
|
||||
}else{
|
||||
return ""
|
||||
}
|
||||
}
|
||||
// 处理清空搜索
|
||||
const handleClearSearch = () => {
|
||||
console.log('清空搜索')
|
||||
}
|
||||
|
||||
// 卖家数据
|
||||
const sellers = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '王先生',
|
||||
company: '优质供应商A',
|
||||
avatar: '/static/images/home/avatar3.png',
|
||||
products: '提供各类商品,质量保证',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '赵女士',
|
||||
company: '优质供应商B',
|
||||
avatar: '/static/images/home/avatar4.png',
|
||||
products: '专业生产,价格优惠',
|
||||
},
|
||||
])
|
||||
|
||||
// 处理查看更多
|
||||
const handleViewMore = () => {
|
||||
console.log('查看更多推荐商品')
|
||||
// 这里可以跳转到商品列表页面
|
||||
}
|
||||
|
||||
// 处理商品点击
|
||||
const handleGoodsClick = (goods) => {
|
||||
// console.log('点击商品:', goods)
|
||||
uni.navigateTo({
|
||||
url:goods.url
|
||||
})
|
||||
// 这里可以跳转到商品详情页面
|
||||
}
|
||||
const jumpOppo = (data)=>{
|
||||
console.log('点击商品:', data)
|
||||
}
|
||||
const jumpBuy = (data)=>{
|
||||
console.log('点击商品:', data)
|
||||
}
|
||||
const jumpUrl=(url)=>{
|
||||
uni.navigateTo({
|
||||
url:'/'+url,
|
||||
fail: () => {
|
||||
uni.reLaunch({
|
||||
url:'/'+url,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const getList = (page = 1 )=>{
|
||||
queryParams.page = page
|
||||
configSearch({
|
||||
...queryParams,
|
||||
key:props.activeTab
|
||||
}).then(res=>{
|
||||
if(res.code == 1){
|
||||
if(queryParams.page == 1){
|
||||
dataList.value = res.data.list
|
||||
}else{
|
||||
dataList.value = [...dataList.value,...res.data.list]
|
||||
}
|
||||
hasNextPage.vaue = dataList.value.length >= res.data.count ? false : true
|
||||
}
|
||||
})
|
||||
}
|
||||
const getMoreData = ()=>{
|
||||
if(!hasNextPage.value)return
|
||||
queryParams.page++
|
||||
getList()
|
||||
|
||||
}
|
||||
getList()
|
||||
defineExpose({
|
||||
getList,
|
||||
queryParams,
|
||||
getMoreData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/common.scss';
|
||||
.tab-content {
|
||||
// 推荐内容样式
|
||||
.recommend-content {
|
||||
.goods-list {
|
||||
gap: 20rpx;
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 30rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: $border-radius;
|
||||
overflow: hidden;
|
||||
|
||||
.goods-image {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #697efe;
|
||||
image{
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
padding: 9rpx 0;
|
||||
gap: 20rpx;
|
||||
|
||||
.goods-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.goods-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
|
||||
.goods-tag {
|
||||
height: 30rpx;
|
||||
line-height: 30rpx;
|
||||
padding: 0 10rpx;
|
||||
background: #e7e7e7;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 400;
|
||||
font-size: 20rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods-price {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
color: #ff8500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 合作伙伴样式
|
||||
.partners-content {
|
||||
.partners-list {
|
||||
.partner-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.partner-logo {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.partner-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.partner-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 商机样式
|
||||
.opportunities-content {
|
||||
.opportunities-list {
|
||||
.opportunity-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.opp-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.opp-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.opp-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 22rpx;
|
||||
|
||||
&.active {
|
||||
background: #e8f5e8;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.pending {
|
||||
background: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.opp-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: -webkit-box; /* 设置为WebKit内核的弹性盒子模型 */
|
||||
margin-bottom: 16rpx;
|
||||
-webkit-box-orient: vertical; /* 垂直排列 */
|
||||
-webkit-line-clamp: 2; /* 限制显示两行 */
|
||||
overflow: hidden; /* 隐藏超出范围的内容 */
|
||||
text-overflow: ellipsis; /* 使用省略号 */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.opp-meta {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
// margin-top: 10px;
|
||||
.opp-location,
|
||||
.opp-date {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关于我们样式
|
||||
.about-content {
|
||||
.about-info {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.about-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 30rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.company-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
|
||||
.stat-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #0478f4;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 找买家样式
|
||||
.buyers-content {
|
||||
.buyers-search {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.buyers-list {
|
||||
.buyer-card {
|
||||
background: #fff;
|
||||
border-radius: $border-radius;
|
||||
padding: 24rpx 29rpx;
|
||||
padding-bottom: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.card-header {
|
||||
margin-bottom: 20rpx;
|
||||
.product-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.view-more {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.customs-code {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background: #eee;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.data-section {
|
||||
.data-items-combined {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
row-gap: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
.product-info {
|
||||
margin-bottom: 38rpx;
|
||||
}
|
||||
.data-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
|
||||
.data-label1 {
|
||||
width: 100rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
.data-label2 {
|
||||
width: 60rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
// width: 200px;
|
||||
display: -webkit-box; /* 设置为WebKit内核的弹性盒子模型 */
|
||||
-webkit-box-orient: vertical; /* 垂直排列 */
|
||||
-webkit-line-clamp: 3; /* 限制显示三行 */
|
||||
overflow: hidden; /* 隐藏超出范围的内容 */
|
||||
text-overflow: ellipsis; /* 使用省略号 */
|
||||
font-size: 24rpx;
|
||||
font-weight: 540;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trade-info {
|
||||
.exporter-info,
|
||||
.importer-info {
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
.exporter-item,
|
||||
.importer-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8rpx;
|
||||
.logo{
|
||||
width: 30px;
|
||||
|
||||
height: 30px;
|
||||
}
|
||||
.info-value {
|
||||
width: 120px;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 22rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
.exporter-info {
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
.importer-info {
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
.vertical-divider {
|
||||
width: 1rpx;
|
||||
height: 80rpx;
|
||||
background: #e5e5e5;
|
||||
margin: 0 20rpx;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 找卖家样式
|
||||
.sellers-content {
|
||||
.sellers-list {
|
||||
.seller-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.seller-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.seller-info {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.tags_box{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
.tag_item{
|
||||
height: 30rpx;
|
||||
line-height: 30rpx;
|
||||
padding: 0 10rpx;
|
||||
background: #e7e7e7;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 400;
|
||||
font-size: 20rpx;
|
||||
color: #999999;
|
||||
// margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.seller-name {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.seller-company {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.seller-products {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.seller-action {
|
||||
.contact-btn {
|
||||
background: #0478f4;
|
||||
color: #fff;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 默认内容样式
|
||||
.default-content {
|
||||
text-align: center;
|
||||
padding: 100rpx 0;
|
||||
|
||||
.default-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
115
src/pages/home/components/tab-navigation.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<view class="tab-navigation">
|
||||
<scroll-view class="tab-scroll" scroll-x="true" show-scrollbar="false">
|
||||
<view class="tab-list">
|
||||
<view
|
||||
v-for="(tab, index) in switchList"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === index }"
|
||||
@click="handleTabClick(index, tab)"
|
||||
>
|
||||
<text class="tab-text">{{ tab.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 定义TabItem接口,匹配接口返回的switchList数据结构
|
||||
interface TabItem {
|
||||
name: string
|
||||
keys: string
|
||||
route: string
|
||||
}
|
||||
|
||||
// 定义props,接收来自父组件的switchList和activeTab
|
||||
const props = defineProps({
|
||||
switchList: {
|
||||
type: Array as () => TabItem[],
|
||||
default: () => []
|
||||
},
|
||||
activeTab: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const handleTabClick = (index: number, tab: TabItem) => {
|
||||
// 发射事件给父组件,让父组件控制其他组件的内容
|
||||
emit('tabChange', { index, tab })
|
||||
}
|
||||
|
||||
// 定义事件发射器
|
||||
const emit = defineEmits<{
|
||||
tabChange: [{ index: number; tab: TabItem }]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tab-navigation {
|
||||
.tab-scroll {
|
||||
white-space: nowrap;
|
||||
overflow: hidden; // 完全隐藏滚动条
|
||||
|
||||
// 隐藏滚动条 - Uni-app 兼容方式
|
||||
::v-deep .uni-scroll-view::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep .uni-scroll-view {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
gap: 50rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex-shrink: 0;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8rpx;
|
||||
|
||||
&.active {
|
||||
.tab-text {
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// 添加圆形渐变背景
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: linear-gradient(0deg, #0478f4 0%, rgba(4, 120, 244, 0) 100%);
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
192
src/pages/home/index.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<view class="home-page" :style="backgroundStyle">
|
||||
<!-- 顶部渐变背景 -->
|
||||
<view :style="topGradientStyle"></view>
|
||||
<!-- logo -->
|
||||
<!-- <image src="/static/images/logo.png" class="home-page-logo"></image> -->
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-section">
|
||||
<search-bar ref="searchBarRef" :placeholder="'请输入搜索关键字'" :showSearchButton="true" @search="handleSearch"
|
||||
@input="handleSearchInput" @clear="handleClearSearch" />
|
||||
</view>
|
||||
|
||||
<!-- 轮播横幅 -->
|
||||
<view class="banner-section">
|
||||
<shop-swiper v-if="homeData.adv" :isFullUrl="true" :height="250" :list="homeData.adv" />
|
||||
</view>
|
||||
|
||||
<!-- 服务网格 -->
|
||||
<view class="service-grid-section">
|
||||
<service-grid v-if="homeData.menu" :list="homeData.menu" />
|
||||
</view>
|
||||
|
||||
<!-- 公告栏 -->
|
||||
<view class="notice-section">
|
||||
<notice-bar v-if="homeData.notice" :noticeData="homeData.notice" :prefix="true" :suffix="true" />
|
||||
</view>
|
||||
|
||||
<!-- 导航标签 -->
|
||||
<view class="tab-section">
|
||||
<tab-navigation v-if="homeData.switchList" :activeTab="activeTab" :switchList="homeData.switchList"
|
||||
@tabChange="handleTabChange" />
|
||||
</view>
|
||||
|
||||
<!-- 热门推荐 -->
|
||||
<view class="hot-recommend-section">
|
||||
<tab-content :activeTab="activeKey" ref="tabContentRef" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app"
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useHomePage } from '@/composables/useHomePage'
|
||||
import SearchBar from '@/components/search-bar/search-bar.vue'
|
||||
import NoticeBar from '@/components/notice-bar.vue'
|
||||
import ServiceGrid from './components/service-grid.vue'
|
||||
import TabNavigation from './components/tab-navigation.vue'
|
||||
import TabContent from './components/tab-content.vue'
|
||||
|
||||
|
||||
// 使用首页逻辑
|
||||
const {
|
||||
backgroundStyle,
|
||||
topGradientStyle,
|
||||
isRefreshing,
|
||||
isLoading,
|
||||
hasMore,
|
||||
currentPage,
|
||||
bannerList,
|
||||
noticeBarList,
|
||||
categoryList,
|
||||
hotRecommendList,
|
||||
initPage,
|
||||
onRefresh,
|
||||
onLoadMore,
|
||||
resetState,
|
||||
homeData
|
||||
} = useHomePage()
|
||||
|
||||
// 搜索栏组件引用
|
||||
const searchBarRef = ref()
|
||||
const tabContentRef = ref(null)
|
||||
|
||||
// 当前激活的 tab
|
||||
const activeTab = ref(0)
|
||||
const activeKey = ref('goods')
|
||||
// 处理 tab 变化
|
||||
const handleTabChange = ({ index, tab } : { index : number; tab : any }) => {
|
||||
activeTab.value = index
|
||||
activeKey.value = tab.keys
|
||||
// 这里可以根据不同的 tab 来加载不同的数据或切换组件
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = (keyword : string) => {
|
||||
// 这里可以添加搜索逻辑,比如跳转到搜索页
|
||||
}
|
||||
|
||||
// 处理搜索输入
|
||||
const handleSearchInput = (keyword : string) => {
|
||||
tabContentRef.value.queryParams.keywords = keyword
|
||||
tabContentRef.value.getList()
|
||||
}
|
||||
|
||||
// 处理清空搜索
|
||||
const handleClearSearch = () => {
|
||||
tabContentRef.value.queryParams.keywords = ''
|
||||
tabContentRef.value.getList()
|
||||
console.log('清空搜索')
|
||||
}
|
||||
|
||||
// 页面生命周期
|
||||
onMounted(async () => {
|
||||
await initPage()
|
||||
// 判断调整封控页面
|
||||
if (homeData.value.black.status == 1) {
|
||||
uni.redirectTo({
|
||||
url: '/pages/black/index'
|
||||
})
|
||||
return
|
||||
}
|
||||
// 判断是否弹出公共
|
||||
if (homeData.value.hbsm_notice.status == 1) {
|
||||
uni.showModal({
|
||||
title: homeData.value.hbsm_notice.title,
|
||||
content: homeData.value.hbsm_notice.content,
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
resetState()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
console.log("下拉刷新")
|
||||
})
|
||||
onReachBottom(() => {
|
||||
tabContentRef.value.getMoreData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/common.scss';
|
||||
|
||||
.home-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding: 30rpx 32rpx;
|
||||
// 底部状态栏一般建议预留60rpx-100rpx,具体根据实际底部栏高度调整
|
||||
padding-bottom: 60rpx;
|
||||
|
||||
&-logo {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
height: 88rpx;
|
||||
background: transparent;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.capsule-menu-section {
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin: 0 0 20rpx;
|
||||
}
|
||||
|
||||
.banner-section {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-bottom: 22rpx;
|
||||
}
|
||||
|
||||
.service-grid-section {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.notice-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.tab-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.hot-recommend-section {
|
||||
margin-bottom: 44rpx;
|
||||
}
|
||||
</style>
|
@ -1,109 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { getBannerApi, getHotRecommendApi, getNoticeBartApi } from '@/api/home'
|
||||
import type { bannerItem, noticeBarItem } from '@/types/home'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import categoryGroup from '@/pages/index/components/categoryGroup.vue'
|
||||
import noticeBar from '@/pages/index/components/notice-bar.vue'
|
||||
import { getTopCategoryApi } from '@/api/catogory'
|
||||
import { useCartStore, useMemberStore } from '@/stores'
|
||||
import { getCartTotalNumApi } from '@/api/cart'
|
||||
import hotRecommend from '@/pages/index/components/hotRecommend.vue'
|
||||
|
||||
//轮播图
|
||||
const bannerList = ref<bannerItem[]>([])
|
||||
const getBannerListData = async () => {
|
||||
const res = await getBannerApi()
|
||||
bannerList.value = res.result
|
||||
}
|
||||
//公告
|
||||
const noticeBarList = ref<noticeBarItem[]>([])
|
||||
const getNoticeBarData = async () => {
|
||||
const res = await getNoticeBartApi()
|
||||
noticeBarList.value = res.result
|
||||
}
|
||||
//分类
|
||||
const categoryList = ref()
|
||||
const getTopCategoryListData = async () => {
|
||||
const res = await getTopCategoryApi()
|
||||
categoryList.value = res.result
|
||||
}
|
||||
//热卖推荐
|
||||
const hotRecommendList = ref()
|
||||
const getHotRecommend = async () => {
|
||||
const res = await getHotRecommendApi()
|
||||
hotRecommendList.value = res.result
|
||||
}
|
||||
//商品列表
|
||||
const shopGoodsListRef = ref()
|
||||
|
||||
const getIndexData = () => {
|
||||
return Promise.all([
|
||||
getBannerListData(),
|
||||
getNoticeBarData(),
|
||||
getTopCategoryListData(),
|
||||
getHotRecommend(),
|
||||
])
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
getIndexData()
|
||||
// 首页分享
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menus: ['shareAppMessage'],
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
nextTick(() => {
|
||||
shopGoodsListRef.value.refresh()
|
||||
})
|
||||
|
||||
const cartStore = useCartStore()
|
||||
//首页初始化设置购物车角标
|
||||
if (useMemberStore().profile) {
|
||||
getCartTotalNumApi().then((res) => {
|
||||
cartStore.setCartTotalNum(res.result)
|
||||
})
|
||||
} else {
|
||||
cartStore.setCartTotalNum(0)
|
||||
}
|
||||
cartStore.setCartTabBadge()
|
||||
})
|
||||
|
||||
const handleRefresh = async (status: number) => {
|
||||
//触发刷新状态
|
||||
if (status === 2) {
|
||||
getIndexData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<shop-goods-list
|
||||
ref="shopGoodsListRef"
|
||||
@refresher-status-changed="handleRefresh"
|
||||
:options="{
|
||||
auto: false,
|
||||
}"
|
||||
>
|
||||
<template #top>
|
||||
<shop-goods-search />
|
||||
</template>
|
||||
<template #middle>
|
||||
<shop-swiper :list="bannerList" v-if="bannerList" />
|
||||
<noticeBar :list="noticeBarList" />
|
||||
<category-group :list="categoryList" v-if="categoryList" />
|
||||
<hot-recommend :list="hotRecommendList" />
|
||||
<uni-section title="商品列表" type="line"></uni-section>
|
||||
</template>
|
||||
</shop-goods-list>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
</style>
|
@ -1,61 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: {
|
||||
name: string
|
||||
url: string
|
||||
icon: string
|
||||
iconPath: string
|
||||
count: string
|
||||
}[]
|
||||
perRow?: number
|
||||
bigSpace?: boolean
|
||||
}>(),
|
||||
{
|
||||
data: () => [],
|
||||
perRow: 4,
|
||||
bigSpace: false,
|
||||
},
|
||||
)
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data : {
|
||||
name : string
|
||||
url : string
|
||||
icon : string
|
||||
iconPath : string
|
||||
count : string
|
||||
}[]
|
||||
perRow ?: number
|
||||
bigSpace ?: boolean
|
||||
}>(),
|
||||
{
|
||||
data: () => [],
|
||||
perRow: 4,
|
||||
bigSpace: false,
|
||||
},
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<view class="block">
|
||||
<slot name="header"></slot>
|
||||
<view class="section">
|
||||
<view
|
||||
class="section-item"
|
||||
v-for="(item, index) in props.data"
|
||||
:key="index"
|
||||
hover-class="none"
|
||||
:class="[
|
||||
<view class="section-item" v-for="(item, index) in props.data" :key="index" hover-class="none" :class="[
|
||||
{ 'no-padding-bottom': index === props.data.length - 1 },
|
||||
props.perRow > 4 ? 'quintet' : '',
|
||||
props.bigSpace ? 'big-space' : '',
|
||||
]"
|
||||
>
|
||||
]">
|
||||
<uni-badge :text="item.count" absolute="rightTop">
|
||||
<navigator
|
||||
v-if="item.iconPath"
|
||||
:url="item.url"
|
||||
hover-class="none"
|
||||
class="navigator icon-container"
|
||||
:class="props.perRow > 4 ? 'quintet' : ''"
|
||||
>
|
||||
<!-- #ifdef WEB -->
|
||||
<img :src="`.${item.iconPath}`" alt="icon" style="width: 60rpx; height: 60rpx" />
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<img :src="`${item.iconPath}`" alt="icon" style="width: 60rpx; height: 60rpx" />
|
||||
<!-- #endif -->
|
||||
<navigator v-if="item.img" :url="item.url" hover-class="none" class="navigator icon-container"
|
||||
:class="props.perRow > 4 ? 'quintet' : ''">
|
||||
<image :src="item.img" alt="icon" style="width: 50rpx; height: 50rpx" />
|
||||
</navigator>
|
||||
<navigator
|
||||
v-else
|
||||
:url="item.url"
|
||||
hover-class="none"
|
||||
class="navigator icon-container"
|
||||
:class="[item.icon, props.perRow > 4 ? 'quintet' : '']"
|
||||
/>
|
||||
</uni-badge>
|
||||
<view class="section-item-text">{{ item.name }}</view>
|
||||
</view>
|
||||
@ -64,78 +40,86 @@ const props = withDefaults(
|
||||
</view>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.block {
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
padding: 10rpx;
|
||||
margin: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 6rpx rgba(240, 240, 240, 0.6);
|
||||
.block {
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
|
||||
.section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
padding-top: 30rpx;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
.navigator {
|
||||
.section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
&::before {
|
||||
display: block;
|
||||
font-size: 60rpx;
|
||||
color: #ff9545;
|
||||
}
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
padding: 0 30rpx;
|
||||
|
||||
.icon-container {
|
||||
.navigator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
font-size: 60rpx;
|
||||
color: #ff9545;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
font-size: 60rpx;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
color: #ff9545;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
&-item {
|
||||
width: 100%;
|
||||
flex-basis: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
font-size: 60rpx;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
color: #ff9545;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.section-item-text {
|
||||
width: 100rpx;
|
||||
text-align: center;
|
||||
padding-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
/* 强制文字不换行 */
|
||||
overflow: hidden;
|
||||
/* 超出容器部分隐藏 */
|
||||
text-overflow: ellipsis;
|
||||
/* 溢出部分显示省略号 */
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
flex-basis: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.section-item-text {
|
||||
padding-top: 10rpx;
|
||||
font-size: 28rpx;
|
||||
.quintet {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.quintet {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
.big-space {
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
.big-space {
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
&:nth-last-child(-n + 4) {
|
||||
padding-bottom: 20rpx;
|
||||
&:nth-last-child(-n + 4) {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,442 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { pageUrl } from '@/utils/constants'
|
||||
import { getDashboardApi } from '@/api/member'
|
||||
import { useAddressStore, useMemberStore, useSettingStore } from '@/stores'
|
||||
import { computed, ref } from 'vue'
|
||||
import type ZPagingInstance from '@/uni_modules/z-paging/global'
|
||||
import { onLoad, onPullDownRefresh, onShow } from '@dcloudio/uni-app'
|
||||
import { onShowRefreshData } from '@/utils/common'
|
||||
import { reactive } from 'vue'
|
||||
import blockIcon from '@/pages/my/components/blockIcon.vue'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const addressStore = useAddressStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
addressStore.changeScene('list')
|
||||
|
||||
const dashboard = reactive({
|
||||
rights: {
|
||||
balance: '0',
|
||||
coupon_num: '0',
|
||||
favorite_num: '0',
|
||||
},
|
||||
order_bar: {
|
||||
to_pay: '',
|
||||
to_ship: '',
|
||||
to_receive: '',
|
||||
to_evaluate: '',
|
||||
refund: '',
|
||||
},
|
||||
coupon: {
|
||||
eligibility: true,
|
||||
},
|
||||
})
|
||||
|
||||
const orderSection = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: '待付款',
|
||||
icon: 'icon-to-pay',
|
||||
count: dashboard.order_bar.to_pay,
|
||||
url: `${pageUrl['order-list']}?status=1`,
|
||||
},
|
||||
{
|
||||
name: '待发货',
|
||||
icon: 'icon-to-ship',
|
||||
count: dashboard.order_bar.to_ship,
|
||||
url: `${pageUrl['order-list']}?status=2`,
|
||||
},
|
||||
{
|
||||
name: '待收货',
|
||||
icon: 'icon-to-receive',
|
||||
count: dashboard.order_bar.to_receive,
|
||||
url: `${pageUrl['order-list']}?status=3`,
|
||||
},
|
||||
{
|
||||
name: '待评价',
|
||||
icon: 'icon-to-evaluate',
|
||||
count: dashboard.order_bar.to_evaluate,
|
||||
url: `${pageUrl['order-list']}?status=4`,
|
||||
},
|
||||
{
|
||||
name: '退款/售后',
|
||||
icon: 'icon-refund',
|
||||
count: dashboard.order_bar.refund,
|
||||
url: `${pageUrl['order-refund-list']}?status=1`,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const couponSection = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: '待使用',
|
||||
icon: 'icon-coupon-to-use',
|
||||
count: '',
|
||||
url: `${pageUrl['coupon']}?status=1`,
|
||||
},
|
||||
{
|
||||
name: '已使用',
|
||||
icon: 'icon-coupon-used',
|
||||
count: '',
|
||||
url: `${pageUrl['coupon']}?status=2`,
|
||||
},
|
||||
{
|
||||
name: '已过期',
|
||||
icon: 'icon-coupon-expired',
|
||||
count: '',
|
||||
url: `${pageUrl['coupon']}?status=3`,
|
||||
},
|
||||
{
|
||||
name: '领券中心',
|
||||
icon: 'icon-coupon-center',
|
||||
count: dashboard.coupon.eligibility ? 'New' : '',
|
||||
url: `${pageUrl['coupon-center']}`,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const agreements = settingStore.data.agreement
|
||||
const serviceSection = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: '我的地址',
|
||||
iconPath: '/static/icons/my-address.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['address']}`,
|
||||
},
|
||||
{
|
||||
name: '充值中心',
|
||||
iconPath: '/static/icons/balance.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['balance-recharge']}`,
|
||||
},
|
||||
{
|
||||
name: '订单中心',
|
||||
iconPath: '/static/icons/order-center.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['order-center']}`,
|
||||
},
|
||||
{
|
||||
name: '设置',
|
||||
iconPath: '/static/icons/setting.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['setting']}`,
|
||||
},
|
||||
{
|
||||
name: '我的收藏',
|
||||
icon: 'icon-my_favorite',
|
||||
iconPath: '/static/icons/my-favorite.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['favorite']}`,
|
||||
},
|
||||
{
|
||||
name: '用户协议',
|
||||
iconPath: '/static/icons/user-agreement.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['rich-text']}?title=用户协议&content=${encodeURIComponent(
|
||||
agreements.user_agreement,
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
name: '隐私协议',
|
||||
iconPath: '/static/icons/privacy-agreement.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['rich-text']}?title=隐私协议&content=${encodeURIComponent(
|
||||
agreements.privacy_agreement,
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
name: '关于我们',
|
||||
iconPath: '/static/icons/about-us.svg',
|
||||
count: '',
|
||||
url: `${pageUrl['rich-text']}?title=关于我们&content=${encodeURIComponent(
|
||||
agreements.about_us_agreement,
|
||||
)}`,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const getData = () => {
|
||||
return new Promise(() => {
|
||||
if (memberStore.profile?.token) {
|
||||
getDashboardApi().then((res) => {
|
||||
Object.assign(dashboard, res.result)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onShowRefreshData(() => {
|
||||
getData()
|
||||
}, true)
|
||||
|
||||
const paging = ref<ZPagingInstance>()
|
||||
const onRefresh = () => {
|
||||
getData()
|
||||
setTimeout(() => {
|
||||
paging.value?.complete()
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="viewport">
|
||||
<z-paging ref="paging" refresher-only @onRefresh="onRefresh" safe-area-inset-bottom>
|
||||
<template #top>
|
||||
<!-- 已登录 -->
|
||||
<template v-if="memberStore.profile">
|
||||
<view class="profile">
|
||||
<view class="overview">
|
||||
<navigator :url="`${pageUrl['profile']}`" hover-class="none">
|
||||
<shop-user-avatar :width="120" :url="memberStore.profile.avatar" />
|
||||
</navigator>
|
||||
<view class="meta">
|
||||
<view class="nickname">
|
||||
{{ memberStore.profile.nickname || memberStore.profile.username }}
|
||||
</view>
|
||||
<navigator class="extra" :url="`${pageUrl['profile']}`" hover-class="none">
|
||||
<text class="update">更新个人信息</text>
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 未登录 -->
|
||||
<template v-else>
|
||||
<view class="profile">
|
||||
<view class="overview">
|
||||
<navigator :url="`${pageUrl['login']}`" hover-class="none">
|
||||
<shop-user-avatar :width="120" url="" />
|
||||
</navigator>
|
||||
<view class="meta">
|
||||
<navigator :url="`${pageUrl['login']}`" hover-class="none" class="nickname">
|
||||
未登录
|
||||
</navigator>
|
||||
<view class="extra">
|
||||
<text class="tips">点击登录账号</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 权益栏 -->
|
||||
<view class="rights">
|
||||
<navigator
|
||||
:url="`${pageUrl['balance']}`"
|
||||
open-type="navigate"
|
||||
hover-class="navigator-hover"
|
||||
class="rights-item"
|
||||
>
|
||||
<text class="rights-item-num">{{ dashboard.rights.balance }}</text>
|
||||
<text>账户余额</text>
|
||||
</navigator>
|
||||
<navigator
|
||||
:url="`${pageUrl['favorite']}`"
|
||||
open-type="navigate"
|
||||
hover-class="navigator-hover"
|
||||
class="rights-item"
|
||||
>
|
||||
<text class="rights-item-num">{{ dashboard.rights.favorite_num }}</text>
|
||||
<text>收藏</text>
|
||||
</navigator>
|
||||
<navigator
|
||||
:url="`${pageUrl['coupon']}`"
|
||||
open-type="navigate"
|
||||
hover-class="navigator-hover"
|
||||
class="rights-item"
|
||||
>
|
||||
<text class="rights-item-num">{{ dashboard.rights.coupon_num }}</text>
|
||||
<text>优惠券</text>
|
||||
</navigator>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 订单栏 -->
|
||||
<blockIcon :data="orderSection" :perRow="5">
|
||||
<template #header>
|
||||
<view class="title">
|
||||
我的订单
|
||||
<navigator class="navigator" :url="`${pageUrl['order-list']}`" hover-class="none">
|
||||
<text>全部</text><text class="icon-right"></text>
|
||||
</navigator>
|
||||
</view>
|
||||
</template>
|
||||
</blockIcon>
|
||||
<!-- 优惠券栏 -->
|
||||
<blockIcon :data="couponSection" />
|
||||
<!-- 服务栏 -->
|
||||
<blockIcon :data="serviceSection" big-space>
|
||||
<template #footer>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="section-item" hover-class="none">
|
||||
<button hover-class="none" class="contact-btn" open-type="contact">
|
||||
<image src="@/static/icons/contact.svg" style="width: 60rpx; height: 60rpx" />
|
||||
</button>
|
||||
<view class="section-item-text">{{ '联系客服' }}</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
</blockIcon>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f7f7f8;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('~@/static/images/bg.png');
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
||||
/* 用户信息 */
|
||||
.profile {
|
||||
margin-top: 50rpx;
|
||||
position: relative;
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
padding-top: 25px;
|
||||
/* #endif */
|
||||
|
||||
.overview {
|
||||
display: flex;
|
||||
height: 120rpx;
|
||||
padding: 0 36rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.gray {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
line-height: 30rpx;
|
||||
padding: 16rpx 0;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
max-width: 350rpx;
|
||||
padding-bottom: 20rpx;
|
||||
font-size: 30rpx;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.extra {
|
||||
display: flex;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.update {
|
||||
padding: 3rpx 10rpx 1rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.8);
|
||||
margin-right: 10rpx;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.settings {
|
||||
position: absolute;
|
||||
bottom: -20rpx;
|
||||
right: 40rpx;
|
||||
font-size: 45rpx;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.rights {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 35rpx 40rpx;
|
||||
margin: 45rpx 20rpx 0;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 6rpx rgba(240, 240, 240, 0.6);
|
||||
|
||||
/* #ifdef WEB */
|
||||
padding-top: 25px;
|
||||
/* #endif */
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #75787d;
|
||||
padding-bottom: 10rpx;
|
||||
|
||||
&-num {
|
||||
font-size: 40rpx;
|
||||
color: #ff9545;
|
||||
padding-bottom: 25rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1e1e1e;
|
||||
padding: 10rpx 15rpx 50rpx;
|
||||
font-weight: bold;
|
||||
|
||||
.navigator {
|
||||
font-size: 24rpx;
|
||||
color: #939393;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.section-item {
|
||||
flex-basis: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.section-item-text {
|
||||
padding-top: 10rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.contact-btn {
|
||||
display: flex;
|
||||
width: 60rpx;
|
||||
height: 68rpx;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
463
src/pages/my/userCenter.vue
Normal file
@ -0,0 +1,463 @@
|
||||
<template>
|
||||
<view class="viewport">
|
||||
<z-paging ref="paging" refresher-only @onRefresh="onRefresh" safe-area-inset-bottom>
|
||||
<template #top>
|
||||
<view class="header">
|
||||
<view class="title">个人中心</view>
|
||||
</view>
|
||||
<!-- 已登录 -->
|
||||
<template v-if="memberStore.profile">
|
||||
<view class="profile">
|
||||
<view class="overview">
|
||||
<navigator :url="`${pageUrl['profile']}`" hover-class="none">
|
||||
<shop-user-avatar :width="120" :url="otersImgs.default_avatar" />
|
||||
</navigator>
|
||||
<view class="meta">
|
||||
<view class="nickname">
|
||||
{{ memberStore.profile.nickname || memberStore.profile.username }}
|
||||
</view>
|
||||
<navigator class="extra" :url="`${pageUrl['profile']}`" hover-class="none">
|
||||
<text class="update">暂无公司</text>
|
||||
<image class="edit-icon" src="/static/images/userCenter/edit.png" />
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
<image class="qr-icon" :src="otersImgs.saoyisao" />
|
||||
</view>
|
||||
</template>
|
||||
<!-- 未登录 -->
|
||||
<template v-else>
|
||||
<view class="profile">
|
||||
<view class="overview">
|
||||
<navigator :url="`${pageUrl['login']}`" hover-class="none">
|
||||
<shop-user-avatar :width="120" url="otersImgs.default_avatar" />
|
||||
</navigator>
|
||||
<view class="meta">
|
||||
<navigator :url="`${pageUrl['login']}`" hover-class="none" class="nickname">
|
||||
未登录
|
||||
</navigator>
|
||||
<view class="extra">
|
||||
<text class="tips">点击登录账号</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 权益栏 -->
|
||||
<view class="rights">
|
||||
<image :src="otersImgs.card_background" class="rights-img"></image>
|
||||
<view class="rights-nav">
|
||||
<text class="rights-nav-text">{{userInfo?.project?.project_name||'暂无'}}</text>
|
||||
<wd-progress :percentage="userInfo?.project?.project_progress||20" color="#A3ACFE" />
|
||||
<image :src="otersImgs.qiehuan" class="rights-nav-img" />
|
||||
</view>
|
||||
<view class="rights-info">
|
||||
<view class="rights-info-item" v-for="(item,index) in userInfo?.project.project_data" :key="index">
|
||||
<navigator :url="item.url" hover-class="none">
|
||||
<view class="rights-info-item-num"> {{item.value}} </view>
|
||||
<view class="rights-info-item-text"> {{item.name}} </view>
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 海保助手&数据中心 -->
|
||||
<view class="data-center">
|
||||
<navigator hover-class="none" class="data-center-item">
|
||||
<text class="data-center-item-text">海保助手</text>
|
||||
<image :src="otersImgs.haibao_background" class="data-center-img" />
|
||||
</navigator>
|
||||
<navigator hover-class="none" class="data-center-item">
|
||||
<text class="data-center-item-text data-center-item-data">数据中心</text>
|
||||
<image :src="otersImgs.data_background" class="data-center-img" />
|
||||
</navigator>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 菜单栏 -->
|
||||
<view class="menus">
|
||||
<view v-for='(item,index) in menusList' :key="index" class="menus-item">
|
||||
<view class="menus-item-title">{{item.title}}</view>
|
||||
<blockIcon :data="item.menus" :perRow="5"></blockIcon>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { pageUrl } from '@/utils/constants'
|
||||
import { getDashboardApi } from '@/api/member'
|
||||
import { useAddressStore, useMemberStore } from '@/stores'
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onShowRefreshData } from '@/utils/common'
|
||||
import { reactive } from 'vue'
|
||||
import blockIcon from '@/pages/my/components/blockIcon.vue'
|
||||
import { postCenterConfigApi, postUserInfoApi } from '@/api/userCenter'
|
||||
const memberStore = useMemberStore()
|
||||
const pararms = {
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
}
|
||||
const dashboard = reactive({
|
||||
rights: {
|
||||
balance: '0',
|
||||
coupon_num: '0',
|
||||
favorite_num: '0',
|
||||
},
|
||||
order_bar: {
|
||||
to_pay: '',
|
||||
to_ship: '',
|
||||
to_receive: '',
|
||||
to_evaluate: '',
|
||||
refund: '',
|
||||
},
|
||||
coupon: {
|
||||
eligibility: true,
|
||||
},
|
||||
})
|
||||
// 菜单
|
||||
const menusList = ref([])
|
||||
// 其他图片配置
|
||||
const otersImgs = ref({})
|
||||
// 个人信息配置
|
||||
const userInfo = ref()
|
||||
onLoad(() => {
|
||||
getShopCenterConfig()
|
||||
})
|
||||
|
||||
// 获取按钮素材配置
|
||||
const getShopCenterConfig = () => {
|
||||
postCenterConfigApi(pararms).then((res) => {
|
||||
if (res.code == 200) {
|
||||
menusList.value = res.result.menus
|
||||
otersImgs.value = res.result.other
|
||||
}
|
||||
})
|
||||
}
|
||||
// 获取用户中心数据
|
||||
const getUserInfo = () => {
|
||||
postUserInfoApi(pararms).then((res) => {
|
||||
if (res.code == 200) {
|
||||
userInfo.value = res.result.userInfo
|
||||
}
|
||||
})
|
||||
}
|
||||
const getData = () => {
|
||||
return new Promise(() => {
|
||||
if (memberStore.profile?.token) {
|
||||
getDashboardApi().then((res) => {
|
||||
Object.assign(dashboard, res.result)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onShowRefreshData(() => {
|
||||
getData()
|
||||
getUserInfo()
|
||||
}, true)
|
||||
|
||||
const paging = ref<ZPagingInstance>()
|
||||
const onRefresh = () => {
|
||||
getData()
|
||||
setTimeout(() => {
|
||||
paging.value?.complete()
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f7f7f8;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.viewport {
|
||||
height: 100%;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 用户信息 */
|
||||
.profile {
|
||||
position: relative;
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
padding-top: 25px;
|
||||
/* #endif */
|
||||
|
||||
.overview {
|
||||
display: flex;
|
||||
height: 120rpx;
|
||||
padding: 0 36rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.gray {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
line-height: 30rpx;
|
||||
padding: 16rpx 0;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
max-width: 350rpx;
|
||||
padding-bottom: 20rpx;
|
||||
font-size: 30rpx;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.extra {
|
||||
display: flex;
|
||||
font-size: 20rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.update {
|
||||
padding: 3rpx 10rpx 1rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.8);
|
||||
margin-right: 10rpx;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
}
|
||||
|
||||
.settings {
|
||||
position: absolute;
|
||||
bottom: -20rpx;
|
||||
right: 40rpx;
|
||||
font-size: 45rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.qr-icon {
|
||||
position: absolute;
|
||||
right: 40rpx;
|
||||
top: 5%;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.rights {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: 20rpx 20rpx 120rpx;
|
||||
margin: 25rpx 20rpx 0;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 6rpx rgba(240, 240, 240, 0.6);
|
||||
position: relative;
|
||||
|
||||
&-img {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 280rpx;
|
||||
}
|
||||
|
||||
|
||||
&-nav {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 50rpx;
|
||||
|
||||
&-img {
|
||||
width: 22rpx;
|
||||
height: 16rpx;
|
||||
}
|
||||
|
||||
&-text {
|
||||
width: 200rpx;
|
||||
white-space: nowrap;
|
||||
/* 强制文字不换行 */
|
||||
overflow: hidden;
|
||||
/* 超出容器部分隐藏 */
|
||||
text-overflow: ellipsis;
|
||||
/* 溢出部分显示省略号 */
|
||||
}
|
||||
|
||||
.wd-progress {
|
||||
width: 70%;
|
||||
|
||||
.wd-progress__bar {
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.wd-progress__outer {
|
||||
background-color: #6572F8;
|
||||
height: 10rpx;
|
||||
}
|
||||
|
||||
::v-deep .wd-progress__label {
|
||||
color: #fff !important;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 0 50rpx;
|
||||
margin-top: 20rpx;
|
||||
color: #fff;
|
||||
|
||||
&-item {
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&-num {
|
||||
font-size: 28rpx;
|
||||
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1e1e1e;
|
||||
padding: 10rpx 15rpx 50rpx;
|
||||
font-weight: bold;
|
||||
|
||||
.navigator {
|
||||
font-size: 24rpx;
|
||||
color: #939393;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.section-item {
|
||||
flex-basis: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.section-item-text {
|
||||
padding-top: 10rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.contact-btn {
|
||||
display: flex;
|
||||
width: 60rpx;
|
||||
height: 68rpx;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.data-center {
|
||||
padding: 0 30rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 10rpx;
|
||||
|
||||
&-item {
|
||||
position: relative;
|
||||
|
||||
&-text {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
left: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #9A8DEB;
|
||||
}
|
||||
|
||||
&-data {
|
||||
color: #62C082;
|
||||
}
|
||||
}
|
||||
|
||||
&-img {
|
||||
width: 300rpx;
|
||||
height: 110rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.menus {
|
||||
width: 100%;
|
||||
margin-top: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 0 30rpx;
|
||||
|
||||
|
||||
&-item {
|
||||
width: 100%;
|
||||
margin-bottom: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 6rpx rgba(240, 240, 240, 0.6);
|
||||
|
||||
&-title {
|
||||
color: #333333;
|
||||
font-size: 32rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
46
src/pages/service/index.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view class="px-24 py-20 box-border">
|
||||
<rk-bg :status-bar="false"></rk-bg>
|
||||
<view class="w-full flex justify-center">
|
||||
<uv-image src="/static/logo.png" width="200rpx" height="200rpx"></uv-image>
|
||||
</view>
|
||||
<view class="w-full mt-20 py-20 bg-#fff shadow rd-16 flex flex-col items-center">
|
||||
<view class="font-500 fs-48 color-#001A0B">为出海加速</view>
|
||||
<view class="mt-20 fs-36 color-#050E19">添加海保客服</view>
|
||||
<view class="mt-20">
|
||||
<uv-qrcode ref="qrcode" size="300rpx" value="https://haibao.shop" :options="{'foregroundColor': '#45908F'}"></uv-qrcode>
|
||||
</view>
|
||||
<view class="mt-20 color-#71757A">海保世贸-一站式服务贸易平台</view>
|
||||
<view class="w-324 mt-30">
|
||||
<uv-button type="primary">长按二维码添加</uv-button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-40 font-600 text-center">添加后您还可以获得以下福利</view>
|
||||
<view class="w-full mt-40 px-20 py-24 box-border bg-#fff rd-16 shadow flex flex-wrap justify-between items-center">
|
||||
<view v-for="(item, index) in funcList" :key="`func` + index" class="w-180 mb-28 flex flex-col items-center">
|
||||
<!-- <uv-image :src="platformImg(item.img)" width="84rpx" height="84rpx"></uv-image> -->
|
||||
<image src="/static/images/service/service01.png"></image>
|
||||
<view class="mt-12 font-500 fs-24 color-#333">{{item.name}}</view>
|
||||
<view class="mt-10 fs-18 color-#999">{{item.desc}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// 功能列表
|
||||
let funcList = ref([
|
||||
{name: '8元现金到账', desc: '注册即送入账户', img: '/stat/service01.png'},
|
||||
{name: '222元优惠券', desc: '注册即送入账户', img: '/img/service02.png'},
|
||||
{name: '铂金会员7天', desc: '高峰买票', img: '/img/service03.png'},
|
||||
{name: '8元现金到账', desc: '一手福利消息', img: '/img/service04.png'},
|
||||
{name: 'VIP抢票券', desc: '高峰买票', img: '/img/service05.png'},
|
||||
{name: '一对一服务', desc: '有问题人工咨询', img: '/img/service06.png'},
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
492
src/pages/todo/index.vue
Normal file
@ -0,0 +1,492 @@
|
||||
<template>
|
||||
<view class="todo-page" :style="backgroundStyle">
|
||||
<!-- 顶部渐变背景 -->
|
||||
<view :style="topGradientStyle"></view>
|
||||
<view class="todo-page-content">
|
||||
<wd-navbar title="待办事项" :bordered="false" safeAreaInsetTop
|
||||
custom-style="background-color: transparent !important;"></wd-navbar>
|
||||
<view class="top-content mt-20 cardbox flex-row align-center justify-evenly">
|
||||
<view class="top-content-item">
|
||||
<view class="num">99+</view>
|
||||
<view class="txt1">今日待处理</view>
|
||||
</view>
|
||||
<view class="top-content-item">
|
||||
<view class="num">3</view>
|
||||
<view class="txt2">未处理</view>
|
||||
</view>
|
||||
<view class="top-content-item">
|
||||
<view class="num">5</view>
|
||||
<view class="txt3">异常</view>
|
||||
</view>
|
||||
<view class="top-content-item">
|
||||
<view class="num">1</view>
|
||||
<view class="txt4">丢件</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 切换 -->
|
||||
<view class="tab-section">
|
||||
<tab-navigation @tabChange="handleTabChange" :switchList="switchList" :activeTab="activeTab" start-color="#FFA800" end-color="rgba(4,120,244,0)"/>
|
||||
</view>
|
||||
<!-- 搜索区域 -->
|
||||
<view class="search-input flex-row">
|
||||
<view class="flex-row input-content align-center">
|
||||
<wd-picker class="picker" :columns="columns" v-model="state" @confirm="handleConfirm" >
|
||||
<view class="picker-cotent flex-row align-center">{{state}}
|
||||
<wd-icon name="caret-down-small" size="22px"></wd-icon></view>
|
||||
</wd-picker>
|
||||
<input v-model="searchValue" placeholder="请输入" />
|
||||
</view>
|
||||
<wd-button class="search-btn">查询</wd-button>
|
||||
</view>
|
||||
<!-- 列表 -->
|
||||
<view class="carditem cardbox">
|
||||
<view class="carditem-top flex-row align-center">
|
||||
<view class="time">2025-08-17 17:06:12</view>
|
||||
<view class="status1">今日待处理</view>
|
||||
</view>
|
||||
<view class="carditem-info flex-row align-center">
|
||||
<view class="title">
|
||||
二手劳斯莱斯
|
||||
</view>
|
||||
<view class="label">海保服务</view>
|
||||
</view>
|
||||
<view class="carditem-bottom flex-row justify-between align-center">
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">出口商</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">USA</view>
|
||||
<view class="val">所在国</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">进口商</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="carditem cardbox">
|
||||
<view class="carditem-top flex-row align-center">
|
||||
<view class="time">2025-08-17 17:06:12</view>
|
||||
<view class="status2">待提货</view>
|
||||
</view>
|
||||
<view class="carditem-info flex-row align-center">
|
||||
<view class="title">
|
||||
二手劳斯莱斯
|
||||
</view>
|
||||
<view class="label">海保服务</view>
|
||||
</view>
|
||||
<view class="carditem-bottom flex-row justify-between align-center">
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">出口商</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">USA</view>
|
||||
<view class="val">所在国</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">进口商</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="carditem cardbox">
|
||||
<view class="carditem-top flex-row align-center">
|
||||
<view class="time">2025-08-17 17:06:12</view>
|
||||
<view class="status3">待清关</view>
|
||||
</view>
|
||||
<view class="carditem-info flex-row align-center">
|
||||
<view class="title">
|
||||
二手劳斯莱斯
|
||||
</view>
|
||||
<view class="label">海保服务</view>
|
||||
</view>
|
||||
<view class="carditem-bottom flex-row justify-between align-center">
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">出口商</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">USA</view>
|
||||
<view class="val">所在国</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">进口商</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="carditem cardbox">
|
||||
<view class="carditem-top flex-row align-center">
|
||||
<view class="time">2025-08-17 17:06:12</view>
|
||||
<view class="status5">历史待处理</view>
|
||||
</view>
|
||||
<view class="carditem-info flex-row align-center">
|
||||
<view class="title">
|
||||
二手劳斯莱斯
|
||||
</view>
|
||||
<view class="label">海保服务</view>
|
||||
</view>
|
||||
<view class="carditem-bottom flex-row justify-between align-center">
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">出口商</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">USA</view>
|
||||
<view class="val">所在国</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">进口商</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="carditem cardbox">
|
||||
<view class="carditem-top flex-row align-center">
|
||||
<view class="time">2025-08-17 17:06:12</view>
|
||||
<view class="status6">丢件</view>
|
||||
<view class="status7">异常</view>
|
||||
<view class="status4">待处理</view>
|
||||
</view>
|
||||
<view class="carditem-info flex-row align-center">
|
||||
<view class="title">
|
||||
二手劳斯莱斯
|
||||
</view>
|
||||
<view class="label">海保服务</view>
|
||||
</view>
|
||||
<view class="carditem-bottom flex-row justify-between align-center">
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">出口商</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">USA</view>
|
||||
<view class="val">所在国</view>
|
||||
</view>
|
||||
<view class="carditem-bottom-item">
|
||||
<view class="label">Joicom Corp</view>
|
||||
<view class="val">进口商</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useTodoPage } from '@/composables/useTodoPage'
|
||||
import TabNavigation from '../home/components/tab-navigation.vue'
|
||||
const switchList = ref([{
|
||||
name: '全部',
|
||||
keys: '1',
|
||||
route: '1'
|
||||
}, {
|
||||
name: '海报服务',
|
||||
keys: '1',
|
||||
route: '1'
|
||||
}, {
|
||||
name: '商机',
|
||||
keys: '1',
|
||||
route: '1'
|
||||
}, {
|
||||
name: '关务',
|
||||
keys: '1',
|
||||
route: '1'
|
||||
}, {
|
||||
name: '商城',
|
||||
keys: '1',
|
||||
route: '1'
|
||||
}, {
|
||||
name: '竞拍',
|
||||
keys: '1',
|
||||
route: '1'
|
||||
}, {
|
||||
name: '海运',
|
||||
keys: '1',
|
||||
route: '1'
|
||||
}]);
|
||||
const columns = ref(['状态', '选项2', '选项3', '选项4', '选项5', '选项6', '选项7'])
|
||||
const state = ref('状态')
|
||||
const searchValue = ref('');
|
||||
const handleConfirm = ({ value: string }) => {
|
||||
console.log(value)
|
||||
state.value = value
|
||||
}
|
||||
const {
|
||||
backgroundStyle,
|
||||
topGradientStyle,
|
||||
isRefreshing,
|
||||
isLoading,
|
||||
hasMore,
|
||||
currentPage,
|
||||
bannerList,
|
||||
noticeBarList,
|
||||
categoryList,
|
||||
hotRecommendList,
|
||||
menuList,
|
||||
// switchList,
|
||||
systemInfo,
|
||||
noticeInfo,
|
||||
hbsmNotice,
|
||||
blackInfo,
|
||||
activeTab,
|
||||
activeTabKey,
|
||||
tabContentList,
|
||||
tabLoading,
|
||||
tabCurrentPage,
|
||||
tabHasMore,
|
||||
initPage,
|
||||
onRefresh,
|
||||
onLoadMore,
|
||||
resetState,
|
||||
switchTab,
|
||||
loadTabContent
|
||||
} = useTodoPage()
|
||||
// 处理 tab 变化
|
||||
const handleTabChange = ({ index, tab } : { index : number; tab : any }) => {
|
||||
console.log('Tab changed:', index, tab.name)
|
||||
|
||||
// 根据静态的tab标题映射到对应的key
|
||||
const tabKeyMap : { [key : string] : string } = {
|
||||
'推荐': 'goods',
|
||||
'商机': 'opportunity',
|
||||
'找买家': 'buy',
|
||||
'找卖家': 'sell',
|
||||
'保税生活': 'life'
|
||||
}
|
||||
|
||||
const tabKey = tabKeyMap[tab.name] || 'goods'
|
||||
switchTab(index, tabKey)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.todo-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
|
||||
// 底部状态栏一般建议预留60rpx-100rpx,具体根据实际底部栏高度调整
|
||||
padding-bottom: 60rpx;
|
||||
.todo-page-content{
|
||||
padding: 0 32rpx;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
width: 100vw;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.cardbox {
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0rpx 6rpx 34rpx 1rpx rgba(194, 148, 56, 0.1);
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.top-content {
|
||||
padding: 50rpx 0;
|
||||
|
||||
.top-content-item {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 0 39rpx;
|
||||
|
||||
// flex:1;
|
||||
&:not(:last-child) {
|
||||
&::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 10%;
|
||||
content: '';
|
||||
width: 1px;
|
||||
height: 80%;
|
||||
background: #EFF0F2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.num {
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: bold;
|
||||
font-size: 40rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
[class^='txt'] {
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.txt1 {
|
||||
color: #F45E04;
|
||||
}
|
||||
|
||||
.txt2 {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.txt3 {
|
||||
color: #FF0B0B;
|
||||
}
|
||||
|
||||
.txt4 {
|
||||
color: #057D51;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-section {
|
||||
margin: 40rpx 0;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
.input-content {
|
||||
background-color: white;
|
||||
border-radius: 30rpx;
|
||||
height: 60rpx;
|
||||
margin-right: 16rpx;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
|
||||
::v-deep(.picker) {
|
||||
height: 60rpx;
|
||||
.picker-cotent{
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
padding-left: 30rpx;
|
||||
}
|
||||
.wd-cell__wrapper {
|
||||
// padding: 0 !important;
|
||||
// padding-right: 20rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
height: 50rpx;
|
||||
border-left: 1px solid #f1f1f1;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.search-btn) {
|
||||
button {
|
||||
width: 130rpx !important;
|
||||
min-width: 130rpx !important;
|
||||
height: 60rpx !important;
|
||||
background: #0478F4;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.carditem {
|
||||
padding: 30rpx;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.carditem-top {
|
||||
gap:10rpx;
|
||||
.time {
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
[class^='status'] {
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 5rpx 20rpx;
|
||||
}
|
||||
|
||||
$status-map: (
|
||||
1: #F45E04,
|
||||
2: #FF0000,
|
||||
3:#3F8E0B,
|
||||
4:#3A43EF,
|
||||
5:#666666
|
||||
);
|
||||
|
||||
|
||||
@each $status,
|
||||
$color in $status-map {
|
||||
.status#{$status} {
|
||||
color: $color;
|
||||
border: 1px solid $color;
|
||||
}
|
||||
}
|
||||
|
||||
$goodstatus-map: (
|
||||
6: #0478F4,
|
||||
7: #FF0B0B
|
||||
);
|
||||
|
||||
@each $status,
|
||||
$color in $goodstatus-map {
|
||||
.status#{$status} {
|
||||
color: white;
|
||||
background: $color ;
|
||||
border: 1px solid $color;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.carditem-info {
|
||||
gap: 5rpx;
|
||||
|
||||
.title {
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #333333
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: 400;
|
||||
font-size: 20rpx;
|
||||
color: #999999;
|
||||
line-height: 30rpx;
|
||||
height: 30rpx;
|
||||
padding: 0rpx 10rpx;
|
||||
background: #E7E7E7;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.carditem-bottom {
|
||||
background: #F8F8F8;
|
||||
border-radius: 12rpx;
|
||||
padding: 25rpx;
|
||||
margin-top: 25rpx;
|
||||
|
||||
.carditem-bottom-item {
|
||||
font-family: Alibaba PuHuiTi;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
|
||||
.label {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.val {
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
164
src/pagesCompany/pages/companyPortrait/components/data-card.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<!-- 头部 -->
|
||||
<view class="content-item">
|
||||
<view class="content-item-top">
|
||||
<view class="content-item-top-title">{{data.name}}</view>
|
||||
<view class="content-item-top-tip">
|
||||
<text>查看更多</text>
|
||||
<!-- <image :src="imgUrl"></image> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-date">{{data.date}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部自定义内容区域 -->
|
||||
<view class="content-bottom">
|
||||
<view class="content-bottom-item">
|
||||
<text class="content-bottom-item-lable">供应商</text>
|
||||
<view class="content-bottom-item-value">
|
||||
<view class="content-bottom-item-value-info">
|
||||
{{data.company_sell_area}}
|
||||
</view>
|
||||
<text>{{data.company_sell}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-bottom-item">
|
||||
<text class="content-bottom-item-lable">采购商</text>
|
||||
<view class="content-bottom-item-value">
|
||||
<view class="content-bottom-item-value-info">
|
||||
{{data.company_buy_area}}
|
||||
</view>
|
||||
<text>{{data.company_buy}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-bottom-item">
|
||||
<text class="content-bottom-item-lable">起运港</text>
|
||||
<text class="content-bottom-item-value">{{data.company_sell_area}}</text>
|
||||
</view>
|
||||
<view class="content-bottom-item">
|
||||
<text class="content-bottom-item-lable">目的港</text>
|
||||
<text class="content-bottom-item-value">{{data.company_sell_area}}</text>
|
||||
</view>
|
||||
<view class="content-bottom-item content-bottom-last">
|
||||
<text class="content-bottom-item-lable">产品描述</text>
|
||||
<text class="content-bottom-item-value">{{data.remarks}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
defineProps
|
||||
} from 'vue'
|
||||
|
||||
// 接收父组件传递的标题
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-top: 20rpx;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
background: #E9F3FF;
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
&-item {
|
||||
width: 100%;
|
||||
border-bottom: 1rpx solid #C6E0FF;
|
||||
height: 120rpx;
|
||||
|
||||
&-top {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
|
||||
image {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-date {
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
margin: 10rpx 0;
|
||||
}
|
||||
|
||||
&-bottom {
|
||||
margin-top: 20rpx;
|
||||
font-weight: 500;
|
||||
font-size: 24rpx;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10rpx 0;
|
||||
|
||||
&-lable {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
width: 80%;
|
||||
|
||||
&-info {
|
||||
background: #85BCFF;
|
||||
border-radius: 30rpx;
|
||||
padding: 0 10rpx;
|
||||
color: #0052B5;
|
||||
margin-right: 10rpx;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
&-last {
|
||||
align-items: flex-start;
|
||||
height: 80rpx;
|
||||
|
||||
.content-bottom-item-value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<!-- 左侧标题区域 -->
|
||||
<view class="content-left">
|
||||
<view class="content-bar"></view>
|
||||
<text>{{ title }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 右侧自定义内容区域 -->
|
||||
<view class="content-right">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
defineProps
|
||||
} from 'vue'
|
||||
|
||||
// 接收父组件传递的标题
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true, // 标题为必填项
|
||||
default: '' // 默认值
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 20rpx;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
|
||||
&-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
|
||||
image {
|
||||
width: 15rpx;
|
||||
height: 13rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&-bar {
|
||||
width: 8rpx;
|
||||
height: 30rpx;
|
||||
margin-right: 10rpx;
|
||||
background: linear-gradient(to bottom, #1975C3, #fff);
|
||||
}
|
||||
}
|
||||
</style>
|
390
src/pagesCompany/pages/companyPortrait/index.vue
Normal file
@ -0,0 +1,390 @@
|
||||
<template>
|
||||
<view class="portrait-page" :style="backgroundStyle">
|
||||
<view :style="topGradientStyle"></view>
|
||||
<view class="portrait-page-contain">
|
||||
<!-- 搜索 -->
|
||||
<wd-search v-model="searchValue" :placeholder-left="placeholderLeft" cancel-txt="搜索" placeholder="请输入企业名称" />
|
||||
<!--企业卡片 -->
|
||||
<view class="portrait-card">
|
||||
<view class="portrait-card-title">
|
||||
<view class="portrait-card-title-circle"></view>
|
||||
{{company.company_name}}
|
||||
</view>
|
||||
<view class="portrait-card-info">
|
||||
<view class="portrait-card-info-item">
|
||||
<view class="portrait-card-info-item-label">联系人:</view>
|
||||
<view class="portrait-card-info-item-value">{{company.company_contact}}</view>
|
||||
</view>
|
||||
<view class="portrait-card-info-item">
|
||||
<view class="portrait-card-info-item-label">联系电话:</view>
|
||||
<view class="portrait-card-info-item-value">{{company.company_mobile}}</view>
|
||||
</view>
|
||||
<view class="portrait-card-info-item">
|
||||
<view class="portrait-card-info-item-label">邮箱:</view>
|
||||
<view class="portrait-card-info-item-value">{{company.company_email}}</view>
|
||||
</view>
|
||||
<view class="portrait-card-info-item">
|
||||
<view class="portrait-card-info-item-label">地址:</view>
|
||||
<view class="portrait-card-info-item-value">{{company.company_address}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- tab -->
|
||||
<wd-tabs v-model="tabChange" @change="tabIndex">
|
||||
<block v-for="(item,index) in tabList" :key="item">
|
||||
<wd-tab :title="item">
|
||||
<!-- 贸易数据 -->
|
||||
<view class="content-trade" v-if="item=='贸易数据'">
|
||||
<MarketTitle title='市场趋势分析'>
|
||||
<view class="content-right">
|
||||
<text>2025年</text>
|
||||
<image :src="imgsUrl.lower_img"></image>
|
||||
</view>
|
||||
</MarketTitle>
|
||||
<view class="content-trade-stat">
|
||||
<view v-for="(item,index) in tradeData.market_analysis_board" :key="index"
|
||||
@click="handleStatClick(index)" class="content-trade-stat-item"
|
||||
:class="{ 'content-trade-stat-item-active': activeIndex === index }">
|
||||
<text class="content-trade-stat-value">{{item.value}}</text>
|
||||
<text class="content-trade-stat-label">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 图表 -->
|
||||
<qiun-data-charts type="column" :opts="tradeOpts" :chartData="tradeCharts" class="trade-charts" />
|
||||
<MarketTitle title='近三个月'></MarketTitle>
|
||||
<wd-table :data="tradeDataList" :stripe="true" rowHeight="10" class="table">
|
||||
<wd-table-col v-for="(column, index) in tableColumns" :key="index" :prop="column.prop"
|
||||
:label="column.label" :width="index==0?100:80" align="center" ellipsis="true"></wd-table-col>
|
||||
</wd-table>
|
||||
</view>
|
||||
<!-- 出口数据 -->
|
||||
<view class="content-trade" v-if="item=='出口数据'">
|
||||
<MarketTitle title='出口数据分析'>
|
||||
<view class="content-right">
|
||||
<wd-search v-model="exportSeach" :placeholder-left="placeholderLeft" cancel-txt=" "
|
||||
placeholder="请搜索" />
|
||||
</view>
|
||||
</MarketTitle>
|
||||
<block v-for="(item,index) in exportData" :key="index">
|
||||
<DataCard :data="item"></DataCard>
|
||||
</block>
|
||||
|
||||
</view>
|
||||
</wd-tab>
|
||||
</block>
|
||||
</wd-tabs>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useHomePage } from '@/composables/useHomePage'
|
||||
import { postCompanyDataThirdDataViewApi, postDataViewExportApi } from '@/api/portrait'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import MarketTitle from './components/market-title.vue'
|
||||
import DataCard from './components/data-card.vue'
|
||||
const {
|
||||
backgroundStyle,
|
||||
topGradientStyle
|
||||
} = useHomePage()
|
||||
// 图片数据
|
||||
const imgsUrl = ref({})
|
||||
// 搜索值
|
||||
const searchValue = ref('')
|
||||
// tab选中
|
||||
const tabChange = ref()
|
||||
// tabl列表
|
||||
const tabList = ref(["贸易数据", "出口数据", "贸易伙伴", "HS编码", "出口产品"])
|
||||
// 请求参数
|
||||
const params = {
|
||||
page: 1,
|
||||
page_size: 10
|
||||
}
|
||||
// 贸易数据
|
||||
const tradeData = ref({})
|
||||
// 企业信息
|
||||
const company = ref({})
|
||||
// 记录当前统计信息选中的索引(-1表示未选中)
|
||||
const activeIndex = ref(-1);
|
||||
// 贸易数据图表
|
||||
const tradeCharts = ref({
|
||||
categories: [],
|
||||
series: []
|
||||
})
|
||||
// 市场趋势分析图表opt配置
|
||||
const tradeOpts = ref({
|
||||
color: ["#A2D2FF"],
|
||||
padding: [15, 15, 0, 5],
|
||||
dataLabel: false,//数据文案
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
extra: {
|
||||
column: {
|
||||
type: "group",
|
||||
width: 23,
|
||||
activeBgColor: "#000000",
|
||||
seriesGap: 5,
|
||||
linearOpacity: 1,
|
||||
barBorderCircle: true
|
||||
},
|
||||
|
||||
},
|
||||
yAxis: {
|
||||
data: [
|
||||
{
|
||||
min: 0,
|
||||
axisLine: false
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
// 近三月数据
|
||||
const tradeDataList = ref([])
|
||||
// 表格列配置
|
||||
const tableColumns = ref([])
|
||||
// 出口数据搜索
|
||||
const exportSeach = ref('')
|
||||
// 出口数据内容
|
||||
const exportData = ref({})
|
||||
// 搜索框设置
|
||||
const placeholderLeft = ref(true)
|
||||
onLoad(() => {
|
||||
getCompanyDataThirdDataView()
|
||||
getDataViewExport()
|
||||
})
|
||||
// tab函数
|
||||
const tabIndex = (index) => {
|
||||
if (index == 1) {
|
||||
getDataViewExport()
|
||||
}
|
||||
}
|
||||
// 企业画像
|
||||
const getCompanyDataThirdDataView = () => {
|
||||
postCompanyDataThirdDataViewApi(params).then(res => {
|
||||
if (res.code == 1) {
|
||||
tradeData.value = res.data
|
||||
// 图片数据
|
||||
imgsUrl.value = res.data.other
|
||||
// 市场趋势数据处理
|
||||
company.value = res.data.company
|
||||
tradeCharts.value.categories = res.data.market_analysis_charts.name
|
||||
let charts = res.data.market_analysis_charts.charts
|
||||
let chartsObj = {
|
||||
name: res.data.market_analysis_charts.title,
|
||||
data: res.data.market_analysis_charts.value
|
||||
}
|
||||
tradeCharts.value.series[0] = chartsObj
|
||||
// 近三月数据处理
|
||||
let originalData = res.data.market_analysis_list
|
||||
tradeDataList.value = originalData.list.map(item => {
|
||||
return {
|
||||
[originalData.name[0]]: item[0], // 最近日期
|
||||
[originalData.name[1]]: item[1], // 交易次数
|
||||
[originalData.name[2]]: item[2], // 交易笔数
|
||||
[originalData.name[3]]: item[3] // 交易重量
|
||||
};
|
||||
})
|
||||
extractTableColumns(tradeDataList.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 点击统计信息
|
||||
const handleStatClick = (index) => {
|
||||
activeIndex.value = index;
|
||||
}
|
||||
// 提取表格列信息
|
||||
const extractTableColumns = (data) => {
|
||||
if (data.length > 0) {
|
||||
// 获取第一个对象的所有键
|
||||
const keys = Object.keys(data[0]);
|
||||
// 转换为表格列配置
|
||||
tableColumns.value = keys.map(key => ({
|
||||
prop: key,
|
||||
// 这里直接用键作为label,也可以根据需要进行格式化
|
||||
label: key
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// 出口数据请求
|
||||
const getDataViewExport = () => {
|
||||
postDataViewExportApi(params).then(res => {
|
||||
if (res.code == 1) {
|
||||
exportData.value = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .portrait-page {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow-y: hidden;
|
||||
padding: 0 30rpx;
|
||||
// 底部状态栏一般建议预留60rpx-100rpx,具体根据实际底部栏高度调整
|
||||
padding-bottom: 60rpx;
|
||||
background: #FFFFFF !important;
|
||||
|
||||
&-contain {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-top: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 头部搜索框
|
||||
.wd-search {
|
||||
background-color: #E9F3FF;
|
||||
height: 80rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
::v-deep .wd-search__block {
|
||||
background-color: #E9F3FF;
|
||||
}
|
||||
|
||||
::v-deep .wd-search__cancel {
|
||||
color: #0478F4;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
// 企业卡片
|
||||
.portrait-card {
|
||||
width: 100%;
|
||||
height: 280rpx;
|
||||
margin-top: 30rpx;
|
||||
background: linear-gradient(to bottom, #1975C3, #85BCFF);
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
color: #fff;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
|
||||
&-circle {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding-bottom: 30rpx;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
margin-top: 10rpx;
|
||||
|
||||
&-label {
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
&-value {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .is-active {
|
||||
color: #0478F4;
|
||||
}
|
||||
|
||||
.content-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
|
||||
image {
|
||||
width: 15rpx;
|
||||
height: 13rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.wd-search {
|
||||
width: 400rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
::v-deep .wd-search__cover {
|
||||
background: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::v-deep .wd-search__input {
|
||||
height: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content-trade {
|
||||
&-stat {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 30rpx 0;
|
||||
|
||||
&-item {
|
||||
width: 200rpx;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
background-color: #EEEEEE;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&-item-active {
|
||||
background-color: #B9C3FF;
|
||||
color: #1159C0;
|
||||
border: 1px solid #1159C0;
|
||||
}
|
||||
|
||||
&-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trade-charts {
|
||||
height: 360rpx;
|
||||
}
|
||||
|
||||
::v-deep .is-border {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
::v-deep .wd-table__cell {
|
||||
background-color: #F8F8F8 !important;
|
||||
min-height: 80rpx !important;
|
||||
}
|
||||
|
||||
::v-deep .is-stripe {
|
||||
background-color: #CCE6FF !important;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
</style>
|
796
src/pagesCustomer/pages/customerManagement/index.vue
Normal file
@ -0,0 +1,796 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="top-bg"></view>
|
||||
<view class="top">
|
||||
<view class="top-left">
|
||||
<wd-icon name="arrow-left" size="22px"></wd-icon>
|
||||
</view>
|
||||
<view class="top-title">
|
||||
客户管理
|
||||
</view>
|
||||
</view>
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="(tab, index) in tabList"
|
||||
:key="tab.id"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === index }"
|
||||
@click="handleTabClick(index)"
|
||||
>
|
||||
{{ tab.name }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="content">
|
||||
<!-- 客户列表 -->
|
||||
<view v-if="activeTab === 0" class="list">
|
||||
<view class="search">
|
||||
<wd-search :placeholder-left="true" hide-cancel light class="search-box" />
|
||||
<view class="right">
|
||||
<view class="item">
|
||||
排序
|
||||
<image
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Polygon22x096dd60f7ca5e4e1ec14a75787200d19556225e7.jpg"
|
||||
mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="item" style="margin-left: 44rpx;">
|
||||
筛选
|
||||
<image
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Polygon22x096dd60f7ca5e4e1ec14a75787200d19556225e7.jpg"
|
||||
mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-item">
|
||||
<view class="item-top">
|
||||
<view class="item-top-left">
|
||||
<view class="avatar">
|
||||
|
||||
</view>
|
||||
<view class="name">
|
||||
空气猫有限公司
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-top-right">
|
||||
<image
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Vector2xedb375099c44a935a7add35fdb9901e086191475.png"
|
||||
class="phone" mode="widthFix"></image>
|
||||
<image
|
||||
src="https://apis.haibao.shop/storage/config/20250826/更多12x8a0b1bca93e23a53d85843c64f375d82efa4199b.png"
|
||||
class="more" mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<view class="item-content-left">
|
||||
<view class="line">
|
||||
<view class="label">
|
||||
最后跟进:
|
||||
</view>
|
||||
<view class="date">
|
||||
2025-08023 12:22
|
||||
</view>
|
||||
</view>
|
||||
<view class="line">
|
||||
<view class="label">
|
||||
客户星级:
|
||||
</view>
|
||||
<view>
|
||||
<image class="icon"
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Component302xad7d6290c98da23282c2a64713987e2456827bad.png"
|
||||
mode="widthFix"></image>
|
||||
<image class="icon"
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Component302xad7d6290c98da23282c2a64713987e2456827bad.png"
|
||||
mode="widthFix"></image>
|
||||
<image class="icon"
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Component302xad7d6290c98da23282c2a64713987e2456827bad.png"
|
||||
mode="widthFix"></image>
|
||||
<image class="icon"
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Component302xad7d6290c98da23282c2a64713987e2456827bad.png"
|
||||
mode="widthFix"></image>
|
||||
<image class="icon"
|
||||
src="https://apis.haibao.shop/storage/config/20250826/Component302xad7d6290c98da23282c2a64713987e2456827bad.png"
|
||||
mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-content-right">
|
||||
正在进行
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-bottom">
|
||||
<view class="btn-left">
|
||||
跟进
|
||||
</view>
|
||||
<view class="btn-right">
|
||||
任务
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 新建客户 -->
|
||||
<view v-else-if="activeTab === 1" class="create-customer">
|
||||
<view class="form-container">
|
||||
<!-- 基本信息标题 -->
|
||||
<view class="section-title">基本信息</view>
|
||||
|
||||
<!-- 企业名称 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label required">企业名称</view>
|
||||
<wd-input
|
||||
v-model="customerForm.companyName"
|
||||
placeholder="请输入企业名称"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 选择行业 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">选择行业</view>
|
||||
<wd-picker
|
||||
:columns="industryOptions"
|
||||
v-model="customerForm.industry"
|
||||
placeholder="请选择行业"
|
||||
class="form-picker"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 国家地区 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">国家地区</view>
|
||||
<wd-picker
|
||||
:columns="countryOptions"
|
||||
v-model="customerForm.country"
|
||||
placeholder="请选择国家"
|
||||
class="form-picker"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 企业地址 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">企业地址</view>
|
||||
<wd-picker
|
||||
:columns="addressOptions"
|
||||
v-model="customerForm.address"
|
||||
placeholder="请选择企业地址"
|
||||
class="form-picker"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 营销阶段 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">营销阶段</view>
|
||||
<wd-picker
|
||||
:columns="marketingStageOptions"
|
||||
v-model="customerForm.marketingStage"
|
||||
placeholder="请选择营销阶段"
|
||||
class="form-picker"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 标签 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">标签</view>
|
||||
<wd-picker
|
||||
:columns="tagOptions"
|
||||
v-model="customerForm.tags"
|
||||
placeholder="请选择标签"
|
||||
class="form-picker"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系人基本信息 -->
|
||||
<view v-if="activeTab === 1" class="create-customer" style="margin-top: 20rpx;">
|
||||
<view class="form-container">
|
||||
<!-- 基本信息标题 -->
|
||||
<view class="section-title">基本信息</view>
|
||||
|
||||
<!-- 联系人姓名和性别 -->
|
||||
<view class="form-item">
|
||||
<view class="name-gender-row">
|
||||
<view class="name-section">
|
||||
<view class="form-label">联系人姓名</view>
|
||||
<wd-input
|
||||
v-model="customerForm.contactName"
|
||||
placeholder="请输入联系人姓名"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
<view class="gender-section">
|
||||
<view class="gender-label">性别</view>
|
||||
<wd-picker
|
||||
:columns="genderOptions"
|
||||
v-model="customerForm.gender"
|
||||
placeholder="请选择性别"
|
||||
class="form-picker"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系电话 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">联系电话</view>
|
||||
<wd-input
|
||||
v-model="customerForm.phone"
|
||||
type="number"
|
||||
placeholder="请输入联系电话"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 邮箱 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">邮箱</view>
|
||||
<wd-input
|
||||
v-model="customerForm.email"
|
||||
placeholder="请输入邮箱"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 职位 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">职位</view>
|
||||
<wd-input
|
||||
v-model="customerForm.position"
|
||||
placeholder="请输入职位"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">备注</view>
|
||||
<wd-input
|
||||
v-model="customerForm.remarks"
|
||||
placeholder="请输入备注"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-btn-container">
|
||||
<wd-button
|
||||
type="primary"
|
||||
block
|
||||
@click="submitCustomer"
|
||||
class="submit-btn"
|
||||
>
|
||||
保存客户
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据看板 -->
|
||||
<view v-else-if="activeTab === 2" class="dashboard">
|
||||
<view class="placeholder-content">
|
||||
<text>数据看板页面内容</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 当前激活的tab索引
|
||||
const activeTab = ref(0)
|
||||
|
||||
// tab列表数据
|
||||
const tabList = ref([
|
||||
{
|
||||
id: 0,
|
||||
name: '客户列表'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: '新建客户'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '数据看板'
|
||||
}
|
||||
])
|
||||
|
||||
// 客户表单数据
|
||||
const customerForm = ref({
|
||||
companyName: '',
|
||||
industry: '',
|
||||
country: '',
|
||||
address: '',
|
||||
marketingStage: '',
|
||||
tags: '',
|
||||
contactName: '',
|
||||
gender: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
position: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 下拉选项数据
|
||||
const industryOptions = ref(['制造业', '服务业', '科技行业', '金融业', '教育行业', '医疗行业', '其他'])
|
||||
const countryOptions = ref(['中国', '美国', '英国', '德国', '日本', '韩国', '其他'])
|
||||
const addressOptions = ref(['北京市', '上海市', '广州市', '深圳市', '杭州市', '成都市', '其他'])
|
||||
const marketingStageOptions = ref(['潜在客户', '意向客户', '商机客户', '成交客户', '流失客户'])
|
||||
const tagOptions = ref(['重要客户', '优质客户', '普通客户', '新客户', '老客户'])
|
||||
const genderOptions = ref(['男', '女'])
|
||||
|
||||
// 处理tab点击事件
|
||||
const handleTabClick = (index: number) => {
|
||||
activeTab.value = index
|
||||
}
|
||||
|
||||
// 提交客户信息
|
||||
const submitCustomer = () => {
|
||||
// 表单验证
|
||||
if (!customerForm.value.companyName.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入企业名称',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!customerForm.value.contactName.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入联系人姓名',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!customerForm.value.phone.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入联系电话',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 手机号格式验证
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(customerForm.value.phone)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 邮箱格式验证(如果填写了邮箱)
|
||||
if (customerForm.value.email && customerForm.value.email.trim()) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(customerForm.value.email)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的邮箱格式',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 显示保存中
|
||||
uni.showLoading({
|
||||
title: '保存中...'
|
||||
})
|
||||
|
||||
// 模拟保存延迟
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 重置表单
|
||||
customerForm.value = {
|
||||
companyName: '',
|
||||
industry: '',
|
||||
country: '',
|
||||
address: '',
|
||||
marketingStage: '',
|
||||
tags: '',
|
||||
contactName: '',
|
||||
gender: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
position: '',
|
||||
remarks: ''
|
||||
}
|
||||
|
||||
// 切换到客户列表tab
|
||||
setTimeout(() => {
|
||||
activeTab.value = 0
|
||||
}, 1500)
|
||||
}, 1500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.active {
|
||||
color: #333333 !important;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 35rpx;
|
||||
height: 6rpx;
|
||||
background: #333333;
|
||||
border-radius: 20rpx 20rpx 20rpx 20rpx;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
overflow: hidden;
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
.top-bg {
|
||||
width: 750rpx;
|
||||
height: 513rpx;
|
||||
background: linear-gradient(134deg, #E8FFDE 0%, #BCF3FF 100%);
|
||||
border-radius: 50rpx 50rpx 50rpx 50rpx;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.top {
|
||||
height: 176rpx;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 20rpx;
|
||||
box-sizing: content-box;
|
||||
position: relative;
|
||||
|
||||
.top-left {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 33rpx;
|
||||
}
|
||||
|
||||
|
||||
.top-title {
|
||||
font-size: 32rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
padding: 0 33rpx;
|
||||
display: flex;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #9CA9AA;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 33rpx;
|
||||
margin-top: 27rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.create-customer {
|
||||
padding: 30rpx;
|
||||
box-shadow: 0rpx 1rpx 20rpx 0rpx rgba(177, 211, 255, 0.15);
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
background: #FFFFFF;
|
||||
|
||||
.form-container {
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 16rpx;
|
||||
position: relative;
|
||||
|
||||
&.required::before {
|
||||
content: "*";
|
||||
color: #ff4757;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
background: #F8F8F8 !important;
|
||||
border-radius: 12rpx;
|
||||
padding: 10rpx 31rpx;
|
||||
font-size: 24rpx;
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
border-color: #4398FF;
|
||||
}
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
background: #F8F8F8;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
color: #333333;
|
||||
min-height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name-gender-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
|
||||
.name-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.gender-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.gender-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn-container {
|
||||
margin-top: 60rpx;
|
||||
|
||||
.submit-btn {
|
||||
background: #4398FF;
|
||||
border-radius: 50rpx;
|
||||
height: 88rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深度选择器覆盖组件样式
|
||||
::v-deep .wd-input {
|
||||
background: #F8F9FA !important;
|
||||
border-radius: 12rpx !important;
|
||||
border: 1rpx solid #E9ECEF !important;
|
||||
font-size: 28rpx !important;
|
||||
}
|
||||
|
||||
// 移除特定的背景色样式
|
||||
::v-deep .wd-input.is-not-empty:not(.is-disabled)::after {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
::v-deep .wd-input::after {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
::v-deep .wd-picker {
|
||||
background: #F8F9FA !important;
|
||||
border-radius: 12rpx !important;
|
||||
border: none !important;
|
||||
font-size: 28rpx !important;
|
||||
min-height: 88rpx !important;
|
||||
}
|
||||
|
||||
// 移除picker内部元素的边框
|
||||
::v-deep .wd-picker__inner {
|
||||
border: none !important;
|
||||
background: #F8F9FA !important;
|
||||
}
|
||||
|
||||
::v-deep .wd-picker__cell {
|
||||
border: none !important;
|
||||
background: #F8F9FA !important;
|
||||
}
|
||||
|
||||
::v-deep .wd-picker__placeholder {
|
||||
color: #999999 !important;
|
||||
}
|
||||
|
||||
::v-deep .wd-button--primary {
|
||||
background: #4398FF !important;
|
||||
border-color: #4398FF !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
padding: 30rpx;
|
||||
box-shadow: 0rpx 1rpx 20rpx 0rpx rgba(177, 211, 255, 0.15);
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
background: #FFFFFF;
|
||||
min-height: 400rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.placeholder-content {
|
||||
text-align: center;
|
||||
color: #999999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.search-box {
|
||||
width: 60%;
|
||||
background-color: #fff !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 40rpx;
|
||||
border: 1rpx solid #62DF91;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.item {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
image {
|
||||
width: 23rpx;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: 30rpx;
|
||||
box-shadow: 0rpx 1rpx 20rpx 0rpx rgba(177, 211, 255, 0.15);
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
background: #FFFFFF;
|
||||
|
||||
.list-item {
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1rpx solid #E9E9E9;
|
||||
|
||||
.item-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.item-top-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
background: #D9D9D9;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.item-top-right {
|
||||
.phone {
|
||||
width: 23rpx;
|
||||
margin-right: 11rpx;
|
||||
}
|
||||
|
||||
.more {
|
||||
width: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
margin: 10rpx 0 30rpx;
|
||||
|
||||
.item-content-left {
|
||||
.line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 44rpx;
|
||||
font-size: 24rpx;
|
||||
|
||||
.label {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.date {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 24rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content-right {
|
||||
padding: 10rpx 30rpx;
|
||||
border-radius: 30rpx;
|
||||
background: #D9FFDC;
|
||||
border-radius: 30rpx 30rpx 30rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #00BE33;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
display: flex;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
border-radius: 50rpx;
|
||||
padding: 18rpx 0;
|
||||
font-size: 32rpx;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.btn-left {
|
||||
@extend .btn;
|
||||
margin-right: 13rpx;
|
||||
color: #FFFFFF;
|
||||
background: #4398FF;
|
||||
}
|
||||
|
||||
.btn-right {
|
||||
@extend .btn;
|
||||
margin-left: 13rpx;
|
||||
color: #4398FF;
|
||||
border: 2rpx solid #4398FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -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>
|
68
src/pagesCustomer/pages/customsExpress/index.vue
Normal 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>
|
258
src/pagesCustomer/pages/customsService/index.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<view class="smart_body_box" v-if="dataConfig">
|
||||
<view class="header_card_box">
|
||||
<view class="item_num_box" 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('menus')" v-if="menus.length > 4">
|
||||
更多
|
||||
<wd-icon name="arrow-down" size="12px" color="#3F8C8B" class="ml-3 "
|
||||
:class="showOption.menus ?'more_up' : 'more_down'"></wd-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="menu_icon_box" :style="{height: showOption.menus ? 'auto': '100px'}">
|
||||
<view class="menu_item_box " v-for="(item,index) in dataConfig.menus" :key="index" @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="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 " @click="jumpUrl(item.url)"
|
||||
v-for="(item,index) in dataConfig.menusLogisticsTracking" key="index">
|
||||
<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="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 " @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="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 " @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 { onLoad } from '@dcloudio/uni-app'
|
||||
import { postDataSmartApi } from '@/api/customs'
|
||||
const dataConfig = ref({})
|
||||
const showOption = reactive({
|
||||
menusHbsm: false,
|
||||
menusLogisticsTracking: false,
|
||||
menusSearchTool: false,
|
||||
menusAddedServices: false
|
||||
})
|
||||
// 海保服务
|
||||
const menus = ref([])
|
||||
const menusAddedServices = ref([])
|
||||
const menusLogisticsTracking = ref([])
|
||||
const menusSearchTool = ref([])
|
||||
const params = {
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
}
|
||||
onLoad(async () => {
|
||||
await getData()
|
||||
})
|
||||
|
||||
const getData = () => {
|
||||
postDataSmartApi(params).then(res => {
|
||||
if (res.code == 1) {
|
||||
dataConfig.value = res.data
|
||||
menus.value = res.data.menus
|
||||
menusAddedServices.value = res.data.menusAddedServices
|
||||
menusLogisticsTracking.value = res.data.menusLogisticsTracking
|
||||
menusSearchTool.value = res.data.menusSearchTool
|
||||
}
|
||||
})
|
||||
}
|
||||
const jumpUrl = (url) => {
|
||||
if (url && url != '#') {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
fail: () => {
|
||||
uni.reLaunch({
|
||||
url
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const changeMore = (data) => {
|
||||
showOption[data] = !showOption[data]
|
||||
}
|
||||
</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: 40rpx 0;
|
||||
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;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
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;
|
||||
flex-direction: column;
|
||||
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: 10rpx;
|
||||
}
|
||||
|
||||
.num_ti {
|
||||
font-size: 28px;
|
||||
color: #FFFFFF;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,214 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { changeBindApi } from '@/api/member'
|
||||
import { refreshPage, uuid } from '@/utils/common'
|
||||
import { smsTemplateCode } from '@/utils/constants'
|
||||
import { useMemberStore } from '@/stores'
|
||||
import { verificationApi } from '@/api/member'
|
||||
|
||||
const props = defineProps({
|
||||
phone: String,
|
||||
})
|
||||
|
||||
const popup = ref()
|
||||
const popupDialog = ref()
|
||||
const formRef = ref()
|
||||
|
||||
const confirmPopup = ref()
|
||||
const confirmPopupDialog = ref()
|
||||
const confrimFormRef = ref()
|
||||
|
||||
const profileData = useMemberStore().profile
|
||||
// 验证用户信息表单
|
||||
const form = reactive({
|
||||
password: '',
|
||||
sms_captcha_code: '',
|
||||
phone: props.phone,
|
||||
})
|
||||
const formRules = {
|
||||
password: {
|
||||
rules: [{ required: true, errorMessage: '请输入密码' }],
|
||||
},
|
||||
sms_captcha_code: {
|
||||
rules: [{ required: true, errorMessage: '请输入验证码' }],
|
||||
},
|
||||
}
|
||||
// 确认修改手机号表单
|
||||
const confirmForm = reactive({
|
||||
phone: '',
|
||||
sms_captcha_code: '',
|
||||
verification_token: '',
|
||||
})
|
||||
const confirmFormRules = {
|
||||
phone: {
|
||||
rules: [{ required: true, errorMessage: '请输入手机号' }],
|
||||
},
|
||||
sms_captcha_code: {
|
||||
rules: [{ required: true, errorMessage: '请输入验证码' }],
|
||||
},
|
||||
}
|
||||
const open = () => {
|
||||
popup.value?.open()
|
||||
}
|
||||
|
||||
const onConfirm = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
const res = await verificationApi({
|
||||
password: form.password,
|
||||
sms_captcha_code: form.sms_captcha_code,
|
||||
})
|
||||
|
||||
if (!res.result.verificationToken) {
|
||||
return uni.showToast({
|
||||
title: '验证码错误',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
|
||||
confirmForm.verification_token = res.result.verificationToken
|
||||
|
||||
popup.value?.close()
|
||||
confirmPopup.value.open()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const onConfirmChange = async () => {
|
||||
try {
|
||||
await confrimFormRef.value.validate()
|
||||
|
||||
const res = changeBindApi({
|
||||
verification_token: confirmForm.verification_token,
|
||||
phone: confirmForm.phone,
|
||||
sms_captcha_code: confirmForm.sms_captcha_code,
|
||||
})
|
||||
|
||||
uni.showToast({
|
||||
title: '修改成功',
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
refreshPage()
|
||||
}, 1500)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const onBeforeSend = () => {
|
||||
if (!form.password) {
|
||||
uni.showToast({
|
||||
title: '请输入密码',
|
||||
icon: 'none',
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
defineExpose({ popup, open })
|
||||
</script>
|
||||
<template>
|
||||
<view>
|
||||
<!-- 验证框 -->
|
||||
<uni-popup ref="popup" type="dialog">
|
||||
<uni-popup-dialog
|
||||
ref="popupDialog"
|
||||
mode="input"
|
||||
title="验证账户信息"
|
||||
@confirm="onConfirm"
|
||||
@close="popupDialog.popup.close()"
|
||||
:before-close="true"
|
||||
>
|
||||
<uni-forms
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
validateTrigger="blur"
|
||||
label-position="top"
|
||||
label-width="100%"
|
||||
>
|
||||
<uni-forms-item required label="用户密码" name="password">
|
||||
<uni-easyinput
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入当前账户密码"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<shop-sms-captcha
|
||||
:phone="form.phone"
|
||||
:template_code="smsTemplateCode.verifyPhone"
|
||||
:password="form.password"
|
||||
v-model:sms_captcha="form.sms_captcha_code"
|
||||
:options="{
|
||||
label: `短信验证码(接收手机号: ${profileData?.mobile})`,
|
||||
required: true,
|
||||
}"
|
||||
:before-send="onBeforeSend"
|
||||
/>
|
||||
</uni-forms>
|
||||
</uni-popup-dialog>
|
||||
</uni-popup>
|
||||
<!-- 修改框 -->
|
||||
<uni-popup ref="confirmPopup" type="dialog">
|
||||
<uni-popup-dialog
|
||||
ref="confirmPopupDialog"
|
||||
mode="input"
|
||||
title="修改手机号码"
|
||||
@confirm="onConfirmChange"
|
||||
@close="confirmPopupDialog.popup.close()"
|
||||
:before-close="true"
|
||||
>
|
||||
<uni-forms
|
||||
ref="confrimFormRef"
|
||||
:model="confirmForm"
|
||||
:rules="confirmFormRules"
|
||||
validateTrigger="blur"
|
||||
label-position="top"
|
||||
label-width="100%"
|
||||
>
|
||||
<uni-forms-item required name="phone" label="新手机号">
|
||||
<uni-easyinput
|
||||
v-model="confirmForm.phone"
|
||||
placeholder="请输入新的手机号"
|
||||
maxlength="11"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<shop-sms-captcha
|
||||
:phone="confirmForm.phone"
|
||||
:template_code="smsTemplateCode.changePhone"
|
||||
v-model:sms_captcha="confirmForm.sms_captcha_code"
|
||||
:options="{
|
||||
label: '短信验证码',
|
||||
required: true,
|
||||
}"
|
||||
/>
|
||||
</uni-forms>
|
||||
</uni-popup-dialog>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.remark {
|
||||
font-size: 24rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
color: red;
|
||||
font-size: 30rpx;
|
||||
padding-left: 20rpx;
|
||||
background: #fff;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,174 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { changePasswordApi } from '@/api/member'
|
||||
import { refreshPage } from '@/utils/common'
|
||||
|
||||
const popup = ref()
|
||||
const popupDialog = ref()
|
||||
const formRef = ref()
|
||||
const resetPasswordRef = ref()
|
||||
|
||||
const form = reactive({
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
confrim_password: '',
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
old_password: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
errorMessage: '请输入旧密码',
|
||||
},
|
||||
{
|
||||
validateFunction: (rule: any, value: string, data: any, callback: Function) => {
|
||||
if (!/^(?!.*[&<>"'\n\r]).{6,32}$/.test(value)) {
|
||||
return callback('密码要求6到32位,不能包含& < > " ')
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
new_password: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
errorMessage: '请输入新密码',
|
||||
},
|
||||
{
|
||||
validateFunction: (rule: any, value: string, data: any, callback: Function) => {
|
||||
if (!/^(?!.*[&<>"'\n\r]).{6,32}$/.test(value)) {
|
||||
return callback('密码要求6到32位,不能包含& < > " ')
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
confrim_password: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
errorMessage: '请再次输入新密码',
|
||||
},
|
||||
{
|
||||
validateFunction: (rule: any, value: string, data: any, callback: Function) => {
|
||||
if (!/^(?!.*[&<>"'\n\r]).{6,32}$/.test(value)) {
|
||||
return callback('密码要求6到32位,不能包含& < > " ')
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
popup.value?.open()
|
||||
}
|
||||
|
||||
const onConfirm = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (form.new_password !== form.confrim_password) {
|
||||
return uni.showToast({
|
||||
title: '两次输入的新密码不一致',
|
||||
icon: 'none',
|
||||
mask: true,
|
||||
})
|
||||
}
|
||||
|
||||
await changePasswordApi({
|
||||
old_password: form.old_password,
|
||||
new_password: form.new_password,
|
||||
})
|
||||
|
||||
uni.showToast({ title: '修改成功', icon: 'success' })
|
||||
|
||||
setTimeout(() => {
|
||||
refreshPage()
|
||||
}, 1500)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ popup, open })
|
||||
|
||||
// 重置密码
|
||||
const resetPassword = () => {
|
||||
resetPasswordRef.value.open()
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<view>
|
||||
<uni-popup ref="popup" type="dialog">
|
||||
<uni-popup-dialog
|
||||
ref="popupDialog"
|
||||
mode="input"
|
||||
title="修改密码"
|
||||
@confirm="onConfirm"
|
||||
@close="popupDialog.popup.close()"
|
||||
:before-close="true"
|
||||
>
|
||||
<uni-forms
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
validateTrigger="blur"
|
||||
label-position="top"
|
||||
>
|
||||
<view class="remark">
|
||||
<text>
|
||||
密码未设置或忘记密码请选择
|
||||
<text class="reset-btn" @tap="resetPassword">重置密码</text>
|
||||
</text>
|
||||
</view>
|
||||
<uni-forms-item name="old_password" label="旧密码" required>
|
||||
<uni-easyinput
|
||||
v-model="form.old_password"
|
||||
type="password"
|
||||
placeholder="请输入旧密码"
|
||||
maxlength="32"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="new_password" label="新密码" required>
|
||||
<uni-easyinput
|
||||
v-model="form.new_password"
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
maxlength="32"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="confrim_password" label="确认密码" required>
|
||||
<uni-easyinput
|
||||
v-model="form.confrim_password"
|
||||
type="password"
|
||||
placeholder="请再次输入新密码"
|
||||
maxlength="32"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
|
||||
<shop-reset-password ref="resetPasswordRef" />
|
||||
</uni-popup-dialog>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.remark {
|
||||
font-size: 24rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
color: red;
|
||||
font-size: 30rpx;
|
||||
padding-left: 20rpx;
|
||||
background: #fff;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,24 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
title?: string
|
||||
content: string
|
||||
}>()
|
||||
|
||||
const content = decodeURIComponent(props.content)
|
||||
// 设置页面标题
|
||||
if (props.title) {
|
||||
uni.setNavigationBarTitle({ title: props.title })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="rich-text">
|
||||
<mp-html :content="content" />
|
||||
<mp-html :content="richText" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { editorApi } from '@/api/other.ts'
|
||||
const props = defineProps<{
|
||||
title ?: string
|
||||
content : string
|
||||
}>()
|
||||
const content = decodeURIComponent(props.content)
|
||||
const richText = ref()
|
||||
const title = ref()
|
||||
const params = {
|
||||
key: 'expoApply'
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
editor()
|
||||
})
|
||||
// 设置页面标题
|
||||
// if (props.title) {
|
||||
// // uni.setNavigationBarTitle({ title: props.title })
|
||||
// }
|
||||
// 请求
|
||||
const editor = () => {
|
||||
editorApi(params).then(res => {
|
||||
if (res.code == 1) {
|
||||
richText.value = res.data.editor
|
||||
uni.setNavigationBarTitle({ title: res.data.title })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.rich-text {
|
||||
padding: 20rpx 10rpx;
|
||||
}
|
||||
.rich-text {
|
||||
padding: 30rpx;
|
||||
text-indent: 60rpx;
|
||||
}
|
||||
</style>
|
||||
|
@ -7,8 +7,8 @@ import { onShowRefreshData } from '@/utils/common'
|
||||
import { uploadApi } from '@/api/common'
|
||||
import { reactive } from 'vue'
|
||||
import { safeTop } from '@/utils/constants'
|
||||
import changePassword from '@/pagesMember/pages/profile/components/change-password.vue'
|
||||
import changePhone from '@/pagesMember/pages/profile/components/change-phone.vue'
|
||||
import changePassword from './components/change-password.vue'
|
||||
import changePhone from './components/change-phone.vue'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
|
@ -33,9 +33,7 @@ const onLogout = () => {
|
||||
// 跳转富文本
|
||||
const toRichTextPage = (type: string) => {
|
||||
const agreements = settngStore.data.agreement
|
||||
|
||||
const content = encodeURIComponent(agreements[type as keyof typeof agreements])
|
||||
|
||||
uni.navigateTo({
|
||||
url: `${pageUrl['rich-text']}?content=${content}`,
|
||||
})
|
@ -209,8 +209,8 @@ const formatPrice = (price: string | number) => {
|
||||
// 跳转首页
|
||||
const goToIndex = (event: any) => {
|
||||
event(false)
|
||||
uni.switchTab({
|
||||
url: `${pageUrl['index']}`,
|
||||
uni.redirectTo({
|
||||
url: `${pageUrl['shopping-mall']}`,
|
||||
})
|
||||
}
|
||||
|
||||
@ -280,7 +280,7 @@ watch(
|
||||
@tap="onChangeSelected(item)"
|
||||
></text>
|
||||
<navigator
|
||||
:url="`${pageUrl['goods-detail']}?id=${item.goods_id}`"
|
||||
:url="`${pageUrl['shopping-mall-goods-detail']}?id=${item.goods_id}`"
|
||||
hover-class="none"
|
||||
class="navigator"
|
||||
>
|
@ -173,7 +173,7 @@ onShow(() => {
|
||||
<navigator
|
||||
class="goods"
|
||||
hover-class="none"
|
||||
:url="`${pageUrl['goods-detail']}?id=${item.id}`"
|
||||
:url="`${pageUrl['shopping-mall-goods-detail']}?id=${item.id}`"
|
||||
>
|
||||
<view>
|
||||
<image class="image" :src="fullUrl(item.images[0])" />
|
@ -222,7 +222,10 @@ onShow(() => {
|
||||
<!-- 商品信息 -->
|
||||
<view class="goods" v-if="orderPreData">
|
||||
<view v-for="item in orderPreData?.goods" :key="item.sku_id" class="item">
|
||||
<navigator :url="`${pageUrl['goods-detail']}?id=${item.goods_id}`" hover-class="none">
|
||||
<navigator
|
||||
:url="`${pageUrl['shopping-mall-goods-detail']}?id=${item.goods_id}`"
|
||||
hover-class="none"
|
||||
>
|
||||
<image class="picture" :src="fullUrl(item.image)" />
|
||||
</navigator>
|
||||
<view class="meta">
|
@ -4,8 +4,8 @@ import { deleteOrderApi, getOrderDetailByIdApi, putOrderCancelByIdApi } from '@/
|
||||
import type { orderResult } from '@/types/order'
|
||||
import { ref, computed } from 'vue'
|
||||
import { fullUrl, getPayPageUrl, formatPrice, onShowRefreshData } from '@/utils/common'
|
||||
import receiveButton from '@/pagesOrder/components/receiveButton.vue'
|
||||
import cancelButton from '@/pagesOrder/components/cancelButton.vue'
|
||||
import receiveButton from '@/pagesShop/components/receiveButton.vue'
|
||||
import cancelButton from '@/pagesShop/components/cancelButton.vue'
|
||||
import { safeBottom } from '@/utils/constants'
|
||||
|
||||
//订单详情
|
||||
@ -181,7 +181,7 @@ const onCancel = () => {
|
||||
<view class="item" v-for="detail in detailData.detail" :key="detail.id">
|
||||
<navigator
|
||||
class="navigator"
|
||||
:url="`${pageUrl['goods-detail']}?id=${detail.goods_id}`"
|
||||
:url="`${pageUrl['shopping-mall-goods-detail']}?id=${detail.goods_id}`"
|
||||
hover-class="none"
|
||||
>
|
||||
<view> <image class="cover" :src="fullUrl(detail.image)"></image> </view>
|
@ -5,5 +5,5 @@ export const getShareUrl = (goodsId: number | string) => {
|
||||
const settingData = useSettingStore().data
|
||||
const profileData = useMemberStore().profile
|
||||
|
||||
return `${settingData.h5_domain}${pageUrl['goods-detail']}?id=${goodsId}&sharer=${profileData?.id}`
|
||||
return `${settingData.h5_domain}${pageUrl['shopping-mall-goods-detail']}?id=${goodsId}&sharer=${profileData?.id}`
|
||||
}
|
@ -20,10 +20,10 @@ import { useMemberStore } from '@/stores'
|
||||
import { getAddressApi } from '@/api/address'
|
||||
import type { addressItem } from '@/types/address'
|
||||
import { computeConversion } from '@/utils/common'
|
||||
import servicePanel from '@/pagesGoods/pages/goods/components/servicePanel.vue'
|
||||
import addressPanel from '@/pagesGoods/pages/goods/components//addressPanel.vue'
|
||||
import sharePanel from '@/pagesGoods/pages/goods/components/sharePanel.vue'
|
||||
import sharePoster from '@/pagesGoods/pages/goods/components/sharePoster.vue'
|
||||
import servicePanel from './components/servicePanel.vue'
|
||||
import addressPanel from './components/addressPanel.vue'
|
||||
import sharePanel from './components/sharePanel.vue'
|
||||
import sharePoster from './components/sharePoster.vue'
|
||||
import { usePopupStore } from '@/stores'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -176,7 +176,7 @@ const openAddressPopup = () => {
|
||||
|
||||
const getMoreEvaluate = () => {
|
||||
uni.navigateTo({
|
||||
url: `${pageUrl['goods-evaluate']}?goods_id=${query.id}`,
|
||||
url: `${pageUrl['shopping-mall-goods-evaluate']}?goods_id=${query.id}`,
|
||||
})
|
||||
}
|
||||
|
||||
@ -184,7 +184,7 @@ const cartInfo = ref(0)
|
||||
const menuButtons = computed<UniHelper.UniGoodsNavOption[]>(() => [
|
||||
{
|
||||
icon: 'shop',
|
||||
text: '首页',
|
||||
text: '商城',
|
||||
},
|
||||
{
|
||||
icon: 'cart',
|
||||
@ -199,13 +199,13 @@ const menuButtons = computed<UniHelper.UniGoodsNavOption[]>(() => [
|
||||
const menuClick = (event: UniHelper.UniGoodsNavOnClickEvent) => {
|
||||
switch (event.index) {
|
||||
case 0:
|
||||
uni.switchTab({
|
||||
url: `${pageUrl['index']}`,
|
||||
uni.redirectTo({
|
||||
url: `${pageUrl['shopping-mall']}`,
|
||||
})
|
||||
break
|
||||
case 1:
|
||||
uni.switchTab({
|
||||
url: `${pageUrl['cart']}`,
|
||||
uni.redirectTo({
|
||||
url: `${pageUrl['shopping-mall-cart']}`,
|
||||
})
|
||||
break
|
||||
case 2:
|
||||
@ -303,7 +303,7 @@ const onShowPoster = () => {
|
||||
onShareAppMessage(() => {
|
||||
sharePopupRef.value.close()
|
||||
|
||||
const sharePath = `${pageUrl['goods-detail']}?id=${goods.value?.id}&sharer=${
|
||||
const sharePath = `${pageUrl['shopping-mall-goods-detail']}?id=${goods.value?.id}&sharer=${
|
||||
useMemberStore().profile?.id
|
||||
}`
|
||||
console.log(sharePath)
|
@ -20,14 +20,14 @@ const storeHistroy = () => {
|
||||
|
||||
const search = async () => {
|
||||
await uni.navigateTo({
|
||||
url: `${pageUrl['goods-list']}?name=${name.value}`,
|
||||
url: `${pageUrl['shopping-mall-goods-list']}?name=${name.value}`,
|
||||
})
|
||||
storeHistroy()
|
||||
}
|
||||
|
||||
const onTapHistory = async (keyword: string) => {
|
||||
await uni.navigateTo({
|
||||
url: `${pageUrl['goods-list']}?name=${keyword}`,
|
||||
url: `${pageUrl['shopping-mall-goods-list']}?name=${keyword}`,
|
||||
})
|
||||
name.value = keyword
|
||||
storeHistroy()
|
@ -12,9 +12,9 @@ import {
|
||||
} from '@/utils/constants'
|
||||
import { fullUrl, getPayPageUrl } from '@/utils/common'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import cancelButton from '@/pagesOrder/components/cancelButton.vue'
|
||||
import cancelButton from '@/pagesShop/components/cancelButton.vue'
|
||||
import { usePopupStore } from '@/stores'
|
||||
import ReceiveButton from '@/pagesOrder/components/receiveButton.vue'
|
||||
import ReceiveButton from '@/pagesShop/components/receiveButton.vue'
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const paging = ref<ZPagingInstance>()
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import orderListItem from '@/pagesOrder/pages/list/components/order-list-item.vue'
|
||||
import orderListItem from './components/order-list-item.vue'
|
||||
import { usePopupStore } from '@/stores'
|
||||
import { pageMateStyle } from '@/utils/constants'
|
||||
|
@ -10,8 +10,8 @@ const props = defineProps<{
|
||||
|
||||
const change = (event: UniHelper.UniGridOnChangeEvent) => {
|
||||
uni.setStorageSync('activeCategoryIndex', event.detail.index)
|
||||
uni.switchTab({
|
||||
url: pageUrl['category'],
|
||||
uni.navigateTo({
|
||||
url: pageUrl['shopping-mall-category'],
|
||||
})
|
||||
}
|
||||
const groupedCategoryList = computed(() => {
|
@ -16,12 +16,12 @@ const props = withDefaults(
|
||||
const jumpTo = (item: hotRecommendItem) => {
|
||||
if (item.goods_id) {
|
||||
return uni.navigateTo({
|
||||
url: `${pageUrl['goods-detail']}?id=${item.goods_id}`,
|
||||
url: `${pageUrl['shopping-mall-goods-detail']}?id=${item.goods_id}`,
|
||||
})
|
||||
}
|
||||
if (item.classify_id) {
|
||||
return uni.navigateTo({
|
||||
url: `${pageUrl['goods-list']}?classify_id=${item.classify_id}`,
|
||||
url: `${pageUrl['shopping-mall-goods-list']}?classify_id=${item.classify_id}`,
|
||||
})
|
||||
}
|
||||
}
|
105
src/pagesShop/pages/shopMall/index.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { getBannerApi, getHotRecommendApi, getNoticeBartApi } from '@/api/home'
|
||||
import type { bannerItem, noticeBarItem } from '@/types/home'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import categoryGroup from './components/categoryGroup.vue'
|
||||
import noticeBar from './components/notice-bar.vue'
|
||||
import hotRecommend from './components/hotRecommend.vue'
|
||||
import { getTopCategoryApi } from '@/api/catogory'
|
||||
import { useCartStore, useMemberStore } from '@/stores'
|
||||
import { getCartTotalNumApi } from '@/api/cart'
|
||||
|
||||
//轮播图
|
||||
const bannerList = ref<bannerItem[]>([])
|
||||
const getBannerListData = async () => {
|
||||
const res = await getBannerApi()
|
||||
bannerList.value = res.result
|
||||
}
|
||||
//公告
|
||||
const noticeBarList = ref<noticeBarItem[]>([])
|
||||
const getNoticeBarData = async () => {
|
||||
const res = await getNoticeBartApi()
|
||||
noticeBarList.value = res.result
|
||||
}
|
||||
//分类
|
||||
const categoryList = ref()
|
||||
const getTopCategoryListData = async () => {
|
||||
const res = await getTopCategoryApi()
|
||||
categoryList.value = res.result
|
||||
}
|
||||
//热卖推荐
|
||||
const hotRecommendList = ref()
|
||||
const getHotRecommend = async () => {
|
||||
const res = await getHotRecommendApi()
|
||||
hotRecommendList.value = res.result
|
||||
}
|
||||
//商品列表
|
||||
const shopGoodsListRef = ref()
|
||||
|
||||
const getIndexData = () => {
|
||||
return Promise.all([
|
||||
getBannerListData(),
|
||||
getNoticeBarData(),
|
||||
getTopCategoryListData(),
|
||||
getHotRecommend(),
|
||||
])
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
getIndexData()
|
||||
// 首页分享
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menus: ['shareAppMessage'],
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
nextTick(() => {
|
||||
shopGoodsListRef.value.refresh()
|
||||
})
|
||||
|
||||
const cartStore = useCartStore()
|
||||
//首页初始化设置购物车角标
|
||||
if (useMemberStore().profile) {
|
||||
getCartTotalNumApi().then((res) => {
|
||||
cartStore.setCartTotalNum(res.result)
|
||||
})
|
||||
} else {
|
||||
cartStore.setCartTotalNum(0)
|
||||
}
|
||||
cartStore.setCartTabBadge()
|
||||
})
|
||||
|
||||
const handleRefresh = async (status : number) => {
|
||||
//触发刷新状态
|
||||
if (status === 2) {
|
||||
getIndexData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<shop-goods-list ref="shopGoodsListRef" @refresher-status-changed="handleRefresh" :options="{
|
||||
auto: false,
|
||||
}">
|
||||
<template #top>
|
||||
<shop-goods-search />
|
||||
</template>
|
||||
<template #middle>
|
||||
<shop-swiper :list="bannerList" v-if="bannerList" />
|
||||
<noticeBar :list="noticeBarList" />
|
||||
<category-group :list="categoryList" v-if="categoryList" />
|
||||
<hot-recommend :list="hotRecommendList" />
|
||||
<uni-section title="商品列表" type="line"></uni-section>
|
||||
</template>
|
||||
</shop-goods-list>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
</style>
|
@ -60,8 +60,8 @@ onShow(() => {
|
||||
<navigator
|
||||
hover-class="none"
|
||||
class="button navigator"
|
||||
:url="`${pageUrl['index']}`"
|
||||
open-type="switchTab"
|
||||
:url="`${pageUrl['shopping-mall']}`"
|
||||
open-type="redirect"
|
||||
>
|
||||
返回首页
|
||||
</navigator>
|
@ -135,7 +135,7 @@ onLoad(async () => {
|
||||
<text class="title">售后商品</text>
|
||||
<navigator
|
||||
class="goods"
|
||||
:url="`${pageUrl['goods-detail']}?id=${orderData.detail.goods_id}`"
|
||||
:url="`${pageUrl['shopping-mall-goods-detail']}?id=${orderData.detail.goods_id}`"
|
||||
hover-class="none"
|
||||
>
|
||||
<image class="cover" :src="fullUrl(orderData.detail.image)"></image>
|
@ -97,7 +97,7 @@ const onSubmit = async () => {
|
||||
<view class="item">
|
||||
<navigator
|
||||
class="navigator"
|
||||
:url="`${pageUrl['goods-detail']}?id=${detailData.goods_id}`"
|
||||
:url="`${pageUrl['shopping-mall-goods-detail']}?id=${detailData.goods_id}`"
|
||||
hover-class="none"
|
||||
>
|
||||
<image class="cover" :src="fullUrl(detailData.image)"></image>
|
BIN
src/static/images/search.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/static/images/userCenter/edit.png
Normal file
After Width: | Height: | Size: 852 B |
BIN
src/static/tabs/assistant.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/static/tabs/assistant_active.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |