diff --git a/src/api/home.ts b/src/api/home.ts index de9fcdf..821acea 100644 --- a/src/api/home.ts +++ b/src/api/home.ts @@ -6,7 +6,7 @@ import { request } from '@/utils/request' * 获取轮播图 * @returns */ -export const getBannerApi = (type: number = 1) => { +export const getBannerApi = (type : number = 1) => { return request({ method: 'GET', url: '/shop/advert/banner', @@ -21,7 +21,7 @@ export const getBannerApi = (type: number = 1) => { * @param data * @returns */ -export const getGoodsListApi = (data?: PageParams) => { +export const getGoodsListApi = (data ?: PageParams) => { return request>({ method: 'GET', url: '/shop/goods', @@ -47,3 +47,11 @@ export const getHotRecommendApi = () => { url: '/shop/advert/hotRecommend', }) } +// 移动端-首页接口 +export const configIndex = (data : { page : number, page_size : number }) => { + return request({ + url: '/api/index/config', + method: 'POST', + data, + }) +} diff --git a/src/api/portrait.ts b/src/api/portrait.ts new file mode 100644 index 0000000..559acdf --- /dev/null +++ b/src/api/portrait.ts @@ -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({ + method: 'POST', + url: '/company/CompanyDataThird/dataView', + }) +} +// 出口数据 +export const postDataViewExportApi = (data : PageParams) => { + return request({ + method: 'POST', + url: '/company/CompanyDataThird/dataViewExport', + }) +} +// 进口数据 +export const postDataViewImportApi = (data : PageParams) => { + return request({ + method: 'POST', + url: '/company/CompanyDataThird/dataViewImport', + }) +} +// 贸易伙伴 +export const postDataViewPartnersApi = (data : PageParams) => { + return request({ + method: 'POST', + url: '/company/CompanyDataThird/dataViewPartners', + }) +} +// 海关编码统计 +export const postDataViewHscodeApi = (data : PageParams) => { + return request({ + method: 'POST', + url: '/company/CompanyDataThird/dataViewHscode', + }) +} +// 海贸区域 +export const postDataViewAreaApi = (data : PageParams) => { + return request({ + method: 'POST', + url: '/company/CompanyDataThird/dataViewArea', + }) +} diff --git a/src/composables/common/useBackground.ts b/src/composables/common/useBackground.ts new file mode 100644 index 0000000..694e3d9 --- /dev/null +++ b/src/composables/common/useBackground.ts @@ -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 + } +} diff --git a/src/composables/common/useDataFetch.ts b/src/composables/common/useDataFetch.ts new file mode 100644 index 0000000..f6fa858 --- /dev/null +++ b/src/composables/common/useDataFetch.ts @@ -0,0 +1,124 @@ +import { ref, computed, type Ref } from 'vue' + +export interface FetchOptions { + // 是否立即执行 + immediate?: boolean + // 是否显示加载状态 + showLoading?: boolean + // 加载提示文字 + loadingText?: string + // 是否显示错误提示 + showError?: boolean + // 错误提示文字 + errorText?: string + // 重试次数 + retryCount?: number + // 重试延迟 + retryDelay?: number +} + +export interface FetchResult { + data: Ref + loading: Ref + error: Ref + execute: (...args: any[]) => Promise + refresh: () => Promise + reset: () => void +} + +export function useDataFetch( + fetchFn: (...args: any[]) => Promise, + options: FetchOptions = {}, +): FetchResult { + const { + immediate = false, + showLoading = true, + loadingText = '加载中...', + showError = true, + errorText = '加载失败', + retryCount = 0, + retryDelay = 1000, + } = options + + const data = ref(null) as Ref + const loading = ref(false) + const error = ref(null) + const retryTimes = ref(0) + + // 是否正在重试 + const isRetrying = computed(() => retryTimes.value > 0) + + // 执行数据获取 + const execute = async (...args: any[]): Promise => { + 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 => { + 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, + } +} diff --git a/src/composables/common/useRefreshLoad.ts b/src/composables/common/useRefreshLoad.ts new file mode 100644 index 0000000..5e219ad --- /dev/null +++ b/src/composables/common/useRefreshLoad.ts @@ -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) => { + 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) => { + 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, + } +} diff --git a/src/composables/common/useSearch.ts b/src/composables/common/useSearch.ts new file mode 100644 index 0000000..2fd3804 --- /dev/null +++ b/src/composables/common/useSearch.ts @@ -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([]) + const searchTimer = ref | 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, + } +} diff --git a/src/composables/useHomePage.ts b/src/composables/useHomePage.ts new file mode 100644 index 0000000..a4f2f09 --- /dev/null +++ b/src/composables/useHomePage.ts @@ -0,0 +1,150 @@ +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([ + { + 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([ + // { + // id: 1, + // title: '智慧', + // content: '公告公告公告公告公告', + // }, + // ]) + const noticeBarList = ref([ + '公告公告公告公告公告1111111', + '智慧关务系统升级通知', + '保税物流服务优化公告', + '海关政策更新提醒', + '新功能上线通知', + ]) + const categoryList = ref([]) + const hotRecommendList = ref([]) + + // ==================== 数据获取 ==================== + 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 + console.log(homeData.value) + } + })]) + // 在这里赋值或者获取接口返回的数据 + } 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 + } +} diff --git a/src/pages.json b/src/pages.json index ebb3782..044b1ff 100644 --- a/src/pages.json +++ b/src/pages.json @@ -359,6 +359,18 @@ // #endif } }] + }, + { + "root": "pagesEnterprise", + "pages": [{ + "path": "pages/portrait/index", + "style": { + "navigationBarTitleText": "企业画像", + // #ifdef WEB + "navigationStyle": "custom" + // #endif + } + }] } ], "preloadRule": { @@ -376,7 +388,7 @@ }, "pages/my/userCenter": { "network": "all", - "packages": ["pagesMember", "pagesOrder"] + "packages": ["pagesMember", "pagesOrder","pagesEnterprise"] } } } diff --git a/src/pagesEnterprise/pages/portrait/components/data-card.vue b/src/pagesEnterprise/pages/portrait/components/data-card.vue new file mode 100644 index 0000000..c0197b8 --- /dev/null +++ b/src/pagesEnterprise/pages/portrait/components/data-card.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/pagesEnterprise/pages/portrait/components/market-title.vue b/src/pagesEnterprise/pages/portrait/components/market-title.vue new file mode 100644 index 0000000..482fdd8 --- /dev/null +++ b/src/pagesEnterprise/pages/portrait/components/market-title.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/src/pagesEnterprise/pages/portrait/index.vue b/src/pagesEnterprise/pages/portrait/index.vue new file mode 100644 index 0000000..7cd3285 --- /dev/null +++ b/src/pagesEnterprise/pages/portrait/index.vue @@ -0,0 +1,390 @@ + + + + + diff --git a/src/types/home.d.ts b/src/types/home.d.ts index 0541fc1..e0f2987 100644 --- a/src/types/home.d.ts +++ b/src/types/home.d.ts @@ -24,6 +24,15 @@ export type bannerItem = { jump: boolean //跳转商品ID goods_id: string + linkurl:string + target:string +} + +export type menuItem = { + background: string + img:string + name:string + url: string } export type categoryPanelItem = {