diff --git a/App.vue b/App.vue index 9a66f58..eb78bd7 100644 --- a/App.vue +++ b/App.vue @@ -2,10 +2,11 @@ import { reactive, inject, onMounted } from 'vue'; import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'; import useUserStore from './stores/useUserStore'; +import usePageAnimation from './hook/usePageAnimation'; import useDictStore from './stores/useDictStore'; const { $api, navTo, appendScriptTagElement, aes_Decrypt, sm2_Decrypt } = inject('globalFunction'); import config from '@/config.js'; - +usePageAnimation(); const appword = 'aKd20dbGdFvmuwrt'; // 固定值 onLaunch((options) => { @@ -51,7 +52,6 @@ function getUserInfo() { const sm2_privateKey = config.appInfo.sm2PrivateKey; let sm2_encrypt_result = data.data; let sm2_decrypt_result = sm2_Decrypt(sm2_encrypt_result, sm2_privateKey); - console.log(sm2_decrypt_result); if (typeof sm2_decrypt_result == 'string') sm2_decrypt_result = JSON.parse(sm2_decrypt_result); // 其次,对sm2解密后的结果进行 aes解密 @@ -100,7 +100,7 @@ function oncloseWindow() { function loginCallback(userInfo) { let params = { - username: userInfo, + userInfo, }; $api.createRequest('/app/login', params, 'post').then((resData) => { useUserStore() @@ -125,11 +125,22 @@ function loginCallback(userInfo) { /*每个页面公共css */ @import '@/common/animation.css'; @import '@/common/common.css'; -/* 修改pages tabbar样式 H5有效 */ + +/* 修改pages tabbar样式 H5才有效 */ .uni-tabbar .uni-tabbar__item:nth-child(4) .uni-tabbar__bd .uni-tabbar__icon { - height: 110rpx !important; - width: 122rpx !important; - margin-top: 6rpx; + width: 108rpx !important; + height: 98rpx !important; + margin-top: 0rpx; + transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); + transform-origin: center center; + /* transition: transform 0.15s ease-in-out; */ + /* transform-origin: center center; */ +} + +.uni-tabbar .uni-tabbar__item:nth-child(4) .uni-tabbar__bd .uni-tabbar__icon:active { + transform: scale(0.8); + transition: transform 0.1s ease-out; + /* animation: jelly 0.5s; */ } .uni-tabbar-border { diff --git a/common/animation.css b/common/animation.css index d6e411f..3125565 100644 --- a/common/animation.css +++ b/common/animation.css @@ -190,4 +190,38 @@ .btn-rubberBand:active { -webkit-animation-name: tada; animation-name: tada +} + +@keyframes jelly { + 0% { + transform: scale(1); + } + + 30% { + transform: scale(1.25, 0.75); + } + + /* 压扁 */ + 40% { + transform: scale(0.75, 1.25); + } + + /* 拉长 */ + 50% { + transform: scale(1.15, 0.85); + } + + /* 稍微压扁 */ + 65% { + transform: scale(0.95, 1.05); + } + + /* 稍微拉长 */ + 75% { + transform: scale(1.05, 0.95); + } + + 100% { + transform: scale(1); + } } \ No newline at end of file diff --git a/common/common.css b/common/common.css index 7e4329d..da7cc25 100644 --- a/common/common.css +++ b/common/common.css @@ -464,4 +464,12 @@ html { /* 隐藏超出的文本 */ text-overflow: ellipsis; /* 使用省略号 */ +} + +.grayscale { + filter: grayscale(100%) opacity(0.6); +} + +.height-100 { + height: 100%; } \ No newline at end of file diff --git a/common/globalFunction.js b/common/globalFunction.js index a4bf4bd..34ec784 100644 --- a/common/globalFunction.js +++ b/common/globalFunction.js @@ -1,6 +1,7 @@ import '@/lib/encryption/sm4.min.js' import useUserStore from "../stores/useUserStore"; import { + createRequestWithCache, createRequest, uploadFile } from "../utils/request"; @@ -624,6 +625,7 @@ export const $api = { sendingMiniProgramMessage, copyText, aes_Decrypt, + createRequestWithCache } diff --git a/components/empty/empty.vue b/components/empty/empty.vue index 03f072e..ecd0aec 100644 --- a/components/empty/empty.vue +++ b/components/empty/empty.vue @@ -1,5 +1,9 @@ @@ -151,6 +151,8 @@ image { } .main-list{ background-color: #F4F4F4; - padding: 1rpx 28rpx 28rpx 28rpx + padding: 1rpx 28rpx 28rpx 28rpx; + min-height: calc(100% - 29rpx); + position: relative } diff --git a/packageA/pages/collection/collection.vue b/packageA/pages/collection/collection.vue index d181d7b..5f1bc81 100644 --- a/packageA/pages/collection/collection.vue +++ b/packageA/pages/collection/collection.vue @@ -26,7 +26,7 @@ :longitude="longitudeVal" :latitude="latitudeVal" > - + @@ -44,7 +44,7 @@ :longitude="longitudeVal" :latitude="latitudeVal" > - + @@ -178,7 +178,8 @@ function getJobList(type = 'add') { current: pageState.page, pageSize: pageState.pageSize, }; - $api.createRequest('/app/user/collection/job', params).then((resData) => { + const LoadCache = (resData) => { + console.log(resData); const { rows, total } = resData; if (type === 'add') { const str = pageState.pageSize * (pageState.page - 1); @@ -191,7 +192,8 @@ function getJobList(type = 'add') { // pageState.list = resData.rows; pageState.total = resData.total; pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize); - }); + }; + $api.createRequestWithCache('/app/user/collection/job', params, 'GET', false, {}, LoadCache).then(LoadCache); } function getCompanyList(type = 'add') { @@ -206,7 +208,7 @@ function getCompanyList(type = 'add') { current: pageCompanyState.page, pageSize: pageCompanyState.pageSize, }; - $api.createRequest('/app/user/collection/company', params).then((resData) => { + const LoadCache = (resData) => { const { rows, total } = resData; if (type === 'add') { const str = pageCompanyState.pageSize * (pageCompanyState.page - 1); @@ -219,7 +221,8 @@ function getCompanyList(type = 'add') { // pageCompanyState.list = resData.rows; pageCompanyState.total = resData.total; pageCompanyState.maxPage = Math.ceil(pageCompanyState.total / pageCompanyState.pageSize); - }); + }; + $api.createRequestWithCache('/app/user/collection/company', params, 'GET', false, {}, LoadCache).then(LoadCache); } @@ -262,6 +265,7 @@ function getCompanyList(type = 'add') { .swiper{ height: 100% .mian{ + height: 100% padding: 0 28rpx 28rpx 28rpx } } diff --git a/packageA/pages/exhibitors/exhibitors.vue b/packageA/pages/exhibitors/exhibitors.vue index a4b7827..0a8659a 100644 --- a/packageA/pages/exhibitors/exhibitors.vue +++ b/packageA/pages/exhibitors/exhibitors.vue @@ -83,7 +83,7 @@ :longitude="longitudeVal" :latitude="latitudeVal" > - + @@ -134,7 +134,7 @@ onLoad((options) => { }); function getJobFairInfo(id, name) { - $api.createRequest(`/app/internal/jobFairThirdPart/${id}`).then((resData) => { + $api.createRequest(`/app/internal/jobFairThirdPart/${id}`, {}, 'GET', true).then((resData) => { fairInfo.value = resData.data; hasAppointment(); }); @@ -152,21 +152,24 @@ function getCompanyList(type = 'add') { current: pageState.current, pageSize: pageState.pageSize, }; - $api.createRequest(`/app/internal/companyThirdPart/?zphID=${jobFairId}&zphmc=${jobFairName}`, params).then( - (resData) => { - const { rows, total } = resData; - if (type === 'add') { - const str = pageState.pageSize * (pageState.current - 1); - const end = pageState.list.length; - const reslist = rows; - pageState.list.splice(str, end, ...reslist); - } else { - pageState.list = rows; - } - pageState.total = resData.total; - pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize); + $api.createRequest( + `/app/internal/companyThirdPart/?zphID=${jobFairId}&zphmc=${jobFairName}`, + params, + 'GET', + true + ).then((resData) => { + const { rows, total } = resData; + if (type === 'add') { + const str = pageState.pageSize * (pageState.current - 1); + const end = pageState.list.length; + const reslist = rows; + pageState.list.splice(str, end, ...reslist); + } else { + pageState.list = rows; } - ); + pageState.total = resData.total; + pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize); + }); } const hasAppointment = () => { @@ -433,6 +436,8 @@ image { background: #F4F4F4; .views{ padding: 28rpx + min-height: calc(100% - 56rpx); + position: relative .Detail-title{ font-weight: 600; font-size: 32rpx; diff --git a/packageA/pages/newJobPosition/newJobPosition.vue b/packageA/pages/newJobPosition/newJobPosition.vue index 4536ce0..c6c0b2c 100644 --- a/packageA/pages/newJobPosition/newJobPosition.vue +++ b/packageA/pages/newJobPosition/newJobPosition.vue @@ -24,12 +24,11 @@ - + @@ -140,6 +139,7 @@ function getList(type = 'add', loading = true) { height: 100% .list{ padding: 0 28rpx 28rpx 28rpx + height: calc(100% - 28rpx) } } } diff --git a/packageA/pages/post/post.vue b/packageA/pages/post/post.vue index fe84ac0..449ea0d 100644 --- a/packageA/pages/post/post.vue +++ b/packageA/pages/post/post.vue @@ -11,7 +11,7 @@ - + @@ -23,7 +23,7 @@ :is-month="true" > - + - {{jobInfo.xlyq == '不限' ? '学历不限' : jobInfo.xlyq}} + {{ jobInfo.xlyq == '不限' ? '学历不限' : jobInfo.xlyq }} - + - {{jobInfo.gwgzjy == '不限' ? '经验不限' : jobInfo.gwgzjy}} + {{ jobInfo.gwgzjy == '不限' ? '经验不限' : jobInfo.gwgzjy }} @@ -83,12 +83,7 @@ 公司信息 - - 单位详情 - + 单位详情 @@ -103,12 +98,12 @@ :value="jobInfo.company?.industry" >   - - {{jobInfo.qyxz}} + {{ jobInfo.qyxz }} 在招 @@ -128,7 +123,7 @@ - + 竞争力分析 @@ -156,19 +151,24 @@ - + @@ -232,12 +232,12 @@ function getDetail(jobId) { if (dataType.value === 2) { // 第三方数据接口 return new Promise((reslove, reject) => { - $api.createRequest(`/app/internal/jobThirdPart/${jobId}`).then((resData) => { + $api.createRequest(`/app/internal/jobThirdPart/${jobId}`, {}, 'GET', true).then((resData) => { const { gsID, gsmc, zphID } = resData.data; jobInfo.value = resData.data; reslove(resData.data); getCompanyIsAJobs(gsID, gsmc, zphID); - + if (resData.data.latitude && resData.data.longitude) { initMapCovers(resData.data.latitude, resData.data.longitude, resData.data.gsmc); } @@ -245,12 +245,12 @@ function getDetail(jobId) { }); } else { // 原数据接口 - $api.createRequest(`/app/job/${jobId}`).then((resData) => { + $api.createRequest(`/app/job/${jobId}`, {}, 'GET', true).then((resData) => { const { latitude, longitude, companyName, companyId } = resData.data; jobInfo.value = resData.data; getCompanyIsAJobs(companyId); getCompetivetuveness(jobId); - + if (latitude && longitude) { initMapCovers(latitude, longitude, companyName); } @@ -315,12 +315,12 @@ function jobApply() { if (dataType.value === 2) { // 第三方数据申请逻辑 const params = { - jobid:jobInfo.value.id, - jobname:jobInfo.value.gwmc - } + jobid: jobInfo.value.id, + jobname: jobInfo.value.gwmc, + }; if (jobInfo.value.isApply) { $api.msg('已经投递过该岗位了~'); - return ; + return; } else { $api.createRequest(`/app/internal/sendResume`, params, 'POST').then((resData) => { $api.msg('投递成功'); @@ -380,7 +380,9 @@ function jobCollection() { // 处理公司详情跳转 function handleCompanyDetail() { if (dataType.value === 2) { - navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${jobInfo.value.gsID}&companyName=${jobInfo.value.gsmc}&zphId=${jobInfo.value.zphID}&dataType=2`); + navTo( + `/packageA/pages/UnitDetails/UnitDetails?companyId=${jobInfo.value.gsID}&companyName=${jobInfo.value.gsmc}&zphId=${jobInfo.value.zphID}&dataType=2` + ); } else { navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${jobInfo.value.company.companyId}`); } @@ -660,4 +662,4 @@ for i in 0..100 box-shadow: 0rpx -4rpx 24rpx 0rpx rgba(11,44,112,0.12); } } - \ No newline at end of file + diff --git a/packageA/pages/reservation/reservation.vue b/packageA/pages/reservation/reservation.vue index 89bf7c7..d8302fd 100644 --- a/packageA/pages/reservation/reservation.vue +++ b/packageA/pages/reservation/reservation.vue @@ -12,10 +12,19 @@ - - + + - + @@ -37,7 +46,7 @@ - + @@ -72,7 +81,7 @@ const ranOptions = ref([ ]); function isTimePassed(timeStr) { - if(!timeStr) return false + if (!timeStr) return false; const targetTime = new Date(timeStr.replace(/-/g, '/')).getTime(); // 兼容格式 const now = Date.now(); return now < targetTime; @@ -95,16 +104,14 @@ function updateCancel(item) { content: '确定要取消预约吗?', showCancel: true, success: ({ confirm, cancel }) => { - if(confirm){ - $api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => { + if (confirm) { + $api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => { getList('refresh'); $api.msg('取消预约成功'); }); } - } - }) - - + }, + }); } function getList(type = 'add', loading = true) { @@ -120,7 +127,7 @@ function getList(type = 'add', loading = true) { pageSize: pageState.pageSize, type: ranItem.value.value, }; - $api.createRequest('/app/user/collection/fair', params).then((resData) => { + const LoadCache = (resData) => { const { rows, total } = resData; if (type === 'add') { const str = pageState.pageSize * (pageState.page - 1); @@ -133,7 +140,8 @@ function getList(type = 'add', loading = true) { // pageState.list = resData.rows; pageState.total = resData.total; pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize); - }); + }; + $api.createRequestWithCache('/app/user/collection/fair', params, 'GET', false, {}, LoadCache).then(LoadCache); } @@ -178,7 +186,7 @@ function getList(type = 'add', loading = true) { display: flex align-items: center } - + } .card-Title{ font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif; diff --git a/packageA/pages/selectDate/selectDate.vue b/packageA/pages/selectDate/selectDate.vue index 03cf809..5812aa9 100644 --- a/packageA/pages/selectDate/selectDate.vue +++ b/packageA/pages/selectDate/selectDate.vue @@ -26,6 +26,7 @@ { if (options.date) { current.value = { @@ -77,8 +80,32 @@ onLoad((options) => { addMonth(); }); } + if (options.entrance === 'careerfair') { + updateDateArray(); + } }); +function hasZphInData(item) { + if (!item || typeof item.date !== 'string') { + return false; + } + + const dateArray = Array.isArray(hasZphDateArray.value) ? hasZphDateArray.value : []; + + return dateArray.some((date) => { + return typeof date === 'string' && date === item.date; + }); +} + +async function updateDateArray() { + const LoadCache = (resData) => { + if (resData.code === 200) { + hasZphDateArray.value = resData.data; + } + }; + $api.createRequestWithCache('/app/internal/getDateList', {}, 'GET', false, {}, LoadCache).then(LoadCache); +} + function backParams() { if (isValidDateString(current.value.date)) { navBack({ diff --git a/pages.json b/pages.json index 37782d1..94bbace 100644 --- a/pages.json +++ b/pages.json @@ -117,7 +117,7 @@ { "path": "pages/vCard/vCard", "style": { - "navigationBarTitleText": "点子名片", + "navigationBarTitleText": "电子名片", "navigationBarBackgroundColor": "#FFFFFF", "navigationStyle": "custom" } @@ -227,8 +227,8 @@ ] }], "tabBar": { - "custom": true, - "display": "none", + // "custom": true, + // "display": "none", "color": "#5E5F60", "selectedColor": "#256BFA", "borderStyle": "black", diff --git a/pages/careerfair/careerfair copy.vue b/pages/careerfair/careerfair copy.vue index 0abc265..69f3b55 100644 --- a/pages/careerfair/careerfair copy.vue +++ b/pages/careerfair/careerfair copy.vue @@ -37,7 +37,7 @@ - + 内容简介:{{ item.zphjj }} - + @@ -122,7 +122,7 @@ onLoad(() => { startDate: currentDate, }); weekList.value = result; - currentDay.value.fullDate = result[0].fullDate + currentDay.value.fullDate = result[0].fullDate; getFair('refresh'); }); @@ -162,12 +162,11 @@ function seemsg(index) { } const handleScrollToLower = () => { - return + return; getFair(); console.log('触底'); }; - function getFair(type = 'add') { if (type === 'refresh') { pageState.page = 1; @@ -194,7 +193,7 @@ function getFair(type = 'add') { // const end = fairList.value.length; // const reslist = rows; // fairList.value.splice(str, end, ...reslist); - fairList.value = rows + fairList.value = rows; } else { fairList.value = rows; } diff --git a/pages/careerfair/careerfair.vue b/pages/careerfair/careerfair.vue index 69b88ae..59ce01f 100644 --- a/pages/careerfair/careerfair.vue +++ b/pages/careerfair/careerfair.vue @@ -13,7 +13,14 @@ - + @@ -37,12 +44,19 @@ - + {{ item.zphmc }} @@ -80,10 +94,10 @@ 内容简介:{{ item.zphjj }} - + - + @@ -95,7 +109,7 @@ import Tabbar from '@/components/tabbar/midell-box.vue'; import useLocationStore from '@/stores/useLocationStore'; import { storeToRefs } from 'pinia'; const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); -const { $api, navTo, cloneDeep,debounce } = inject('globalFunction'); +const { $api, navTo, cloneDeep, debounce } = inject('globalFunction'); const weekList = ref([]); const fairList = ref([]); const currentDay = ref({}); @@ -108,7 +122,7 @@ const pageState = reactive({ total: 0, maxPage: 2, pageSize: 10, - zphmc:'' + zphmc: '', }); onLoad(() => { @@ -122,7 +136,7 @@ onLoad(() => { startDate: currentDate, }); weekList.value = result; - currentDay.value.fullDate = result[0].fullDate + currentDay.value.fullDate = result[0].fullDate; getFair('refresh'); }); @@ -130,6 +144,7 @@ function toSelectDate() { navTo('/packageA/pages/selectDate/selectDate', { query: { date: currentDay.value.fullDate, + entrance: 'careerfair', }, onBack: (res) => { console.log(res); @@ -162,12 +177,11 @@ function seemsg(index) { } const handleScrollToLower = () => { - return + return; getFair(); console.log('触底'); }; - function getFair(type = 'add') { if (type === 'refresh') { pageState.page = 1; @@ -177,7 +191,7 @@ function getFair(type = 'add') { pageState.page += 1; } let params = { - zphmc:pageState.zphmc, + zphmc: pageState.zphmc, // current: pageState.page, // pageSize: pageState.pageSize, }; @@ -187,14 +201,14 @@ function getFair(type = 'add') { if (currentDay.value?.fullDate) { params.zphjbsj = currentDay.value.fullDate.replace(/-/g, ''); } - $api.createRequest('/app/internal/jobFairThirdPart', params).then((resData) => { + $api.createRequest('/app/internal/jobFairThirdPart', params, 'GET', true).then((resData) => { const { rows, total } = resData; if (type === 'add') { // const str = pageState.pageSize * (pageState.page - 1); // const end = fairList.value.length; // const reslist = rows; // fairList.value.splice(str, end, ...reslist); - fairList.value = rows + fairList.value = rows; } else { fairList.value = rows; } diff --git a/pages/chat/chat.vue b/pages/chat/chat.vue index 591a30c..35f8b4b 100644 --- a/pages/chat/chat.vue +++ b/pages/chat/chat.vue @@ -63,9 +63,9 @@ - + @@ -213,27 +213,48 @@ footer-height = 98rpx background: #FFFFFF; display: flex flex-direction: column - .drawer-user - border-top: 1rpx solid rgba(0,0,0,.1); - padding: 20rpx 28rpx - display: flex + .drawer-user { + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + padding: 24rpx 32rpx; + padding-bottom: calc(24rpx + constant(safe-area-inset-bottom)); + padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); + border-top: 1rpx solid rgba(0, 0, 0, 0.06); + background-color: #ffffff; + color: #333333; font-weight: 500; - align-items: center - position: relative - margin-bottom: calc( 32rpx + var(--window-bottom)); /*兼容 IOS<11.2*/ - margin-bottom: calc( 32rpx +var(--window-bottom)); /*兼容 IOS>11.2*/ - color: #000000 - .drawer-user-img - width: 57.2rpx; - height: 57.2rpx - margin-right: 20rpx - .drawer-user-setting - width: 48rpx - height: 48rpx - position: absolute - top: 50% - right: 28rpx - transform: translate(0,-50%) + font-size: 28rpx; + + &:active { + background-color: #f9f9f9; + } + + .drawer-user-img { + width: 60rpx; + height: 60rpx; + border-radius: 50%; + margin-right: 24rpx; + background-color: #eee; + flex-shrink: 0; + } + + .user-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 30rpx; + } + + .drawer-user-setting { + width: 48rpx; + height: 48rpx; + margin-left: auto; + opacity: 0.8; + } + } .drawer-title height: header-height; line-height: header-height; diff --git a/pages/chat/components/ai-paging.vue b/pages/chat/components/ai-paging.vue index 0dc6d79..3db1b13 100644 --- a/pages/chat/components/ai-paging.vue +++ b/pages/chat/components/ai-paging.vue @@ -268,10 +268,10 @@ import WaveDisplay from './WaveDisplay.vue'; import FileIcon from './fileIcon.vue'; import FileText from './fileText.vue'; // 系统功能hook和阿里云hook -// import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js'; -import { useAudioRecorder } from '@/hook/useSystemSpeechReader.js'; -// import { useTTSPlayer } from '@/hook/useTTSPlayer.js'; -import { useTTSPlayer } from '@/hook/useSystemPlayer.js'; +import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js'; +// import { useAudioRecorder } from '@/hook/useSystemSpeechReader.js'; +import { useTTSPlayer } from '@/hook/useTTSPlayer.js'; +// import { useTTSPlayer } from '@/hook/useSystemPlayer.js'; // 全局 const { $api, navTo, throttle } = inject('globalFunction'); const emit = defineEmits(['onConfirm']); @@ -632,7 +632,7 @@ function readMarkdown(value, index) { if (isPaused.value) { resume(); } else { - console.log(value, speechIndex.value, index, isPaused.value) + // console.log(value, speechIndex.value, index, isPaused.value) speak(value); } } diff --git a/pages/index/components/index-one.vue b/pages/index/components/index-one.vue index 45a519a..c6191d6 100644 --- a/pages/index/components/index-one.vue +++ b/pages/index/components/index-one.vue @@ -66,7 +66,7 @@ - + - + @@ -298,6 +298,7 @@ function nextDetail(job) { function openFilter() { showFilter.value = true; emits('onShowTabbar', false); + uni.hideTabBar(); selectFilterModel.value?.open({ title: '筛选', maskClick: true, @@ -310,10 +311,12 @@ function openFilter() { } showFilter.value = false; getJobList('refresh'); + uni.showTabBar(); }, cancel: () => { showFilter.value = false; emits('onShowTabbar', true); + uni.showTabBar(); }, }); } @@ -359,7 +362,11 @@ function getJobRecommend(type = 'add') { ...pageState.search, ...conditionSearch.value, }; - let comd = { recommend: true, jobCategory: '', tip: '确认你的兴趣,为您推荐更多合适的岗位' }; + let comd = { + recommend: true, + jobCategory: '', + tip: '确认你的兴趣,为您推荐更多合适的岗位', + }; $api.createRequest('/app/job/recommend', params).then((resData) => { const { data, total } = resData; pageState.total = 0; @@ -380,7 +387,16 @@ function getJobRecommend(type = 'add') { if (question) { comd.jobCategory = question; - data.unshift(comd); + // 生成随机插入位置,排除前两个和最后两个位置 + let insertIndex; + if (data.length <= 4) { + // 如果数据长度小于等于4,直接插入到中间位置 + insertIndex = Math.floor(data.length / 2); + } else { + // 生成2到data.length-2之间的随机位置 + insertIndex = Math.floor(Math.random() * (data.length - 4)) + 2; + } + data.splice(insertIndex, 0, comd); } } const reslist = dataToImg(data); diff --git a/pages/index/index.vue b/pages/index/index.vue index e569d57..4437b6c 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -41,7 +41,7 @@ - + @@ -49,7 +49,7 @@ 左滑查看视频 快去体验吧~ 去体验 - 1 + @@ -82,7 +82,16 @@ onLoad(() => { // 判断浏览器是否有 fristEntry 第一次进入 let fristEntry = uni.getStorageSync('fristEntry') === false ? false : true; // 默认未读 maskFristEntry.value = fristEntry; - // maskFristEntry.value = true; + if (fristEntry) { + uni.hideTabBar(); + } + // 预加载较重页面 + setTimeout(() => { + uni.preloadPage({ url: '/packageA/pages/post/post' }); + uni.preloadPage({ url: '/pages/nearby/nearby' }); + uni.preloadPage({ url: '/pages/chat/chat' }); + uni.preloadPage({ url: '/packageA/pages/choiceness/choiceness' }); + }, 3000); }); onShow(() => { @@ -189,10 +198,12 @@ function changeSwiperMsgType(e) { function closeFristEntry() { uni.setStorageSync('fristEntry', false); maskFristEntry.value = false; + uni.showTabBar(); } function goExperience() { closeFristEntry(); + uni.showTabBar(); state.current = 1; } diff --git a/pages/login/login.vue b/pages/login/login.vue index 096b691..578c658 100644 --- a/pages/login/login.vue +++ b/pages/login/login.vue @@ -241,9 +241,10 @@ function nextStep() { // 获取职位 function getTreeselect() { - $api.createRequest('/app/common/jobTitle/treeselect', {}, 'GET').then((resData) => { + const LoadCache = (resData) => { state.station = resData.data; - }); + }; + $api.createRequestWithCache('/app/common/jobTitle/treeselect', {}, 'GET', false, LoadCache).then(LoadCache); } function loginbackdoor() { diff --git a/pages/mine/mine.vue b/pages/mine/mine.vue index 71f5645..186dea1 100644 --- a/pages/mine/mine.vue +++ b/pages/mine/mine.vue @@ -95,9 +95,9 @@ > - @@ -110,13 +110,13 @@ import FileUploader from '@/utils/FileUploader.js'; const { $api, navTo } = inject('globalFunction'); import useUserStore from '@/stores/useUserStore'; const popup = ref(null); -const { userInfo, Completion } = storeToRefs(useUserStore()); -const counts = ref({}); +const { userInfo, Completion, counts } = storeToRefs(useUserStore()); + function logOut() { popup.value.open(); } onShow(() => { - getUserstatistics(); + useUserStore().getUserstatistics(); }); function close() { @@ -129,12 +129,6 @@ function confirm() { const isAbove90 = (percent) => parseFloat(percent) < 90; -function getUserstatistics() { - $api.createRequest('/app/user/statistics').then((resData) => { - counts.value = resData.data; - }); -} - function selectFile() { // FileUploader.showMenuAndUpload({ // success: function (res) { diff --git a/pages/msglog/msglog.vue b/pages/msglog/msglog.vue index 4d73acc..e2a6b55 100644 --- a/pages/msglog/msglog.vue +++ b/pages/msglog/msglog.vue @@ -40,7 +40,7 @@ - + diff --git a/pages/msglog/read.vue b/pages/msglog/read.vue index 58d67a9..b187216 100644 --- a/pages/msglog/read.vue +++ b/pages/msglog/read.vue @@ -35,6 +35,7 @@ {{ item.subTitle || '消息' }} + @@ -83,6 +84,7 @@ defineExpose({ loadData }); } .scrollmain{ padding: 28rpx + height: calc(100% - 56rpx) } .read{ diff --git a/pages/msglog/unread.vue b/pages/msglog/unread.vue index 9181049..b675823 100644 --- a/pages/msglog/unread.vue +++ b/pages/msglog/unread.vue @@ -33,6 +33,7 @@ {{ item.subTitle || '消息' }} + @@ -69,6 +70,7 @@ defineExpose({ loadData }); } .scrollmain{ padding: 28rpx + height: calc(100% - 56rpx) } .read{ diff --git a/pages/nearby/components/four.vue b/pages/nearby/components/four.vue index 0cba282..b11030d 100644 --- a/pages/nearby/components/four.vue +++ b/pages/nearby/components/four.vue @@ -69,14 +69,9 @@ - - - + + + @@ -340,15 +335,18 @@ defineExpose({ loadData, handleFilterConfirm }); color: #4778EC !important .nearby-scroll overflow: hidden; + height: 100%; + background: #f4f4f4; .two-head - margin: 22rpx; + padding: 22rpx; display: flex; flex-direction: column flex-wrap: no-wrap // grid-template-columns: repeat(4, 1fr); // grid-column-gap: 10rpx; // grid-row-gap: 24rpx; - border-radius: 17rpx 17rpx 17rpx 17rpx; + background: #FFFFFF + // border-radius: 17rpx 17rpx 17rpx 17rpx; .head-all{ display: flex; justify-content: space-between; @@ -380,15 +378,21 @@ defineExpose({ loadData, handleFilterConfirm }); border-radius: 12rpx 12rpx 12rpx 12rpx; .nearby-list border-top: 2rpx solid #EBEBEB; - height: 100% + min-height: calc(100% - 140rpx) + background: #f4f4f4 + display: flex; + flex-direction: column; .one-cards{ + height: 100% display: flex; flex-direction: column; padding: 0 20rpx 20rpx 20rpx; background: #f4f4f4 + flex: 1 } .nav-filter padding: 16rpx 28rpx 0 28rpx + background: #ffffff .filter-top display: flex justify-content: space-between; @@ -447,4 +451,4 @@ defineExpose({ loadData, handleFilterConfirm }); height: 26rpx; .active transform: rotate(180deg) - \ No newline at end of file + diff --git a/pages/nearby/components/one.vue b/pages/nearby/components/one.vue index a92cb9e..c3fc9d3 100644 --- a/pages/nearby/components/one.vue +++ b/pages/nearby/components/one.vue @@ -74,14 +74,9 @@ - - - + + + @@ -364,20 +359,28 @@ defineExpose({ loadData, handleFilterConfirm }); } .nearby-scroll overflow: hidden; + height: 100%; + background: #f4f4f4; .nearby-map height: 767rpx; background: #e8e8e8; overflow: hidden .nearby-list + min-height: calc(100% - 384rpx) + background: #f4f4f4 + display: flex; + flex-direction: column; .one-cards{ display: flex; flex-direction: column; padding: 0 20rpx 20rpx 20rpx; background: #f4f4f4 height: 100% + flex: 1 } .nav-filter padding: 16rpx 28rpx 0 28rpx + background: #ffffff .filter-top display: flex justify-content: space-between; diff --git a/pages/nearby/components/three.vue b/pages/nearby/components/three.vue index 973ca51..828ff20 100644 --- a/pages/nearby/components/three.vue +++ b/pages/nearby/components/three.vue @@ -95,14 +95,9 @@ - - - + + + @@ -359,9 +354,12 @@ defineExpose({ loadData, handleFilterConfirm }); color: #4778EC !important; .nearby-scroll overflow: hidden; + background: #f4f4f4; + height: 100% .three-head - margin: 24rpx 0 0 0; + // margin: 24rpx 0 0 0; padding: 26rpx 0 0 0; + background: #FFFFFF; border-radius: 17rpx 17rpx 17rpx 17rpx; .one-picker height: 100% @@ -482,14 +480,21 @@ defineExpose({ loadData, handleFilterConfirm }); z-index: 1; .nearby-list border-top: 2rpx solid #EBEBEB; + min-height: calc(100% - 222rpx) + background: #f4f4f4 + display: flex; + flex-direction: column; .one-cards{ + height: 100% display: flex; flex-direction: column; padding: 0 20rpx 20rpx 20rpx; background: #f4f4f4 + flex: 1 } .nav-filter padding: 16rpx 28rpx 0 28rpx + background: #ffffff .filter-top display: flex justify-content: space-between; diff --git a/pages/nearby/components/two.vue b/pages/nearby/components/two.vue index 2ffc419..85d4b61 100644 --- a/pages/nearby/components/two.vue +++ b/pages/nearby/components/two.vue @@ -65,14 +65,9 @@ - - - + + + @@ -255,10 +250,13 @@ defineExpose({ loadData, handleFilterConfirm }); color: #4778EC !important .nearby-scroll overflow: hidden; + height: 100%; + background: #f4f4f4; .two-head - margin: 22rpx; + padding: 22rpx; display: flex; flex-wrap: wrap + background: #FFFFFF; // grid-template-columns: repeat(4, 1fr); // grid-column-gap: 10rpx; // grid-row-gap: 24rpx; @@ -284,14 +282,21 @@ defineExpose({ loadData, handleFilterConfirm }); border-radius: 12rpx 12rpx 12rpx 12rpx; .nearby-list border-top: 2rpx solid #EBEBEB; + min-height: calc(100% - 252rpx) + background: #f4f4f4 + display: flex; + flex-direction: column; .one-cards{ display: flex; flex-direction: column; padding: 0 20rpx 20rpx 20rpx; background: #f4f4f4 + height: 100% + flex: 1 } .nav-filter padding: 16rpx 28rpx 0 28rpx + background: #ffffff .filter-top display: flex justify-content: space-between; diff --git a/stores/BaseDBStore.js b/stores/BaseDBStore.js deleted file mode 100644 index ad61257..0000000 --- a/stores/BaseDBStore.js +++ /dev/null @@ -1,73 +0,0 @@ -// BaseStore.js - 基础Store类 -import IndexedDBHelper from '@/common/IndexedDBHelper.js' -// import UniStorageHelper from '../common/UniStorageHelper' -import useChatGroupDBStore from './userChatGroupStore' -import config from '@/config' - - -class BaseStore { - db = null - isDBReady = false - dbName = 'BrowsingHistory' - constructor() { - this.checkAndInitDB() - } - checkAndInitDB() { - // 获取本地数据库版本 - if (config.OnlyUseCachedDB) { - return this.initDB() - } - const localVersion = uni.getStorageSync('indexedDBVersion') || 1 - console.log('DBVersion: ', localVersion, config.DBversion) - if (localVersion === config.DBversion) { - this.initDB() - } else { - console.log('清空本地数据库') - this.clearDB().then(() => { - uni.setStorageSync('indexedDBVersion', config.DBversion); - this.initDB(); - }); - } - } - initDB() { - // // #ifdef H5 - this.db = new IndexedDBHelper(this.dbName, config.DBversion); - // // #endif - // // #ifndef H5 - // this.db = new UniStorageHelper(this.dbName, config.DBversion); - // // #endif - this.db.openDB([{ - name: 'record', - keyPath: "id", - autoIncrement: true, - }, { - name: 'messageGroup', - keyPath: "id", - autoIncrement: true, - }, { - name: 'messages', - keyPath: "id", - autoIncrement: true, - indexes: [{ - name: 'parentGroupId', - key: 'parentGroupId', - unique: false - }] - }]).then(async () => { - useChatGroupDBStore().init() - this.isDBReady = true - }); - } - async clearDB() { - return new Promise((resolve, rejetc) => { - new IndexedDBHelper().deleteDB(this.dbName).then(() => { - resolve() - }) - }) - } - -} - -const baseDB = new BaseStore() - -export default baseDB \ No newline at end of file diff --git a/stores/useReadMsg.js b/stores/useReadMsg.js index 5e78e60..f99f62a 100644 --- a/stores/useReadMsg.js +++ b/stores/useReadMsg.js @@ -12,27 +12,25 @@ import { $api, } from '../common/globalFunction'; -// 控制消息 +// 常量定义:消息在 TabBar 的索引位置 +const TABBAR_INDEX = 3; + export const useReadMsg = defineStore('readMsg', () => { const msgList = ref([]) + // 用于自定义 Tabbar 组件的渲染 const badges = ref([{ - count: 0 - }, - { - count: 0 - }, - { - count: 0 - }, - { - count: 0 - }, - { - count: 0 - }, - ]) + count: 0 + }, { + count: 0 + }, { + count: 0 + }, { + count: 0 + }, { + count: 0 + }]) - // 计算总未读数量,基于 notReadCount 字段 + // 计算总未读数量 const unreadCount = computed(() => msgList.value.reduce((sum, msg) => sum + (msg.notReadCount || 0), 0) ) @@ -42,40 +40,49 @@ export const useReadMsg = defineStore('readMsg', () => { msgList.value.filter(msg => msg.notReadCount > 0) ) - - // 设置 TabBar 角标 - function updateTabBarBadge() { + function updateBadgeEffect() { const count = unreadCount.value - const index = 3 - const countVal = count > 99 ? '99+' : String(count) - if (count === 0) { - uni.removeTabBarBadge({ - index - }) // 替换为你消息页面的 TabBar index - badges.value[index] = { - count: 0 + // 处理显示文本:超过99显示99+ + const countStr = count > 99 ? '99+' : String(count) + + // 1. 更新内部状态 (用于自定义 UI) + if (badges.value[TABBAR_INDEX]) { + badges.value[TABBAR_INDEX].count = count === 0 ? 0 : countStr + } + + // 2. 更新系统原生 TabBar + // 加 try-catch 防止在非 Tabbar 页面或加栽未完成时报错 + try { + if (count > 0) { + uni.setTabBarBadge({ + index: TABBAR_INDEX, + text: countStr + }) + } else { + uni.removeTabBarBadge({ + index: TABBAR_INDEX + }) } - } else { - badges.value[index] = { - count: countVal - } - uni.setTabBarBadge({ - index, - text: countVal - }) + } catch (e) { + console.warn('TabBar Badge 更新失败(可能当前非TabBar页面):', e) } } + watch(unreadCount, () => { + updateBadgeEffect() + console.log('value', unreadCount.value) + }, { + immediate: true + }) + // 拉取消息列表 async function fetchMessages() { try { - $api.createRequest('/app/notice/info', { + const res = await $api.createRequest('/app/notice/info', { isRead: 1 - }, "GET").then((res) => { - msgList.value = res.data || [] - updateTabBarBadge() - }) + }, "GET") + msgList.value = res.data || [] } catch (err) { console.error('获取消息失败:', err) } @@ -83,17 +90,23 @@ export const useReadMsg = defineStore('readMsg', () => { // 设置为已读 async function markAsRead(item, index) { - const msg = msgList.value[index] - if (!msg || msg.isRead === 1) return + const targetMsg = msgList.value[index] + if (!targetMsg) return + + // 如果已经是已读,直接返回,避免无效请求 + // 假设服务端逻辑是:isRead=1 表示已读 (注意检查你的字段定义) + // 你的原代码判断是 if (msg.isRead === 1) return,如果是这样,下面请求成功应该设为 1 + // 但通常未读是0,已读是1。这里维持你原有的逻辑,假设服务端把 notReadCount 清零 try { let params = { - id: msg.noticeId + id: targetMsg.noticeId } - $api.createRequest('/app/notice/read?id=' + msg.noticeId, params, "POST").then((res) => { - msgList.value[index].isRead = 1 - updateTabBarBadge() - }) + await $api.createRequest('/app/notice/read?id=' + targetMsg.noticeId, params, "POST") + + // 更新本地数据 + msgList.value[index].notReadCount = 0 + msgList.value[index].isRead = 1 // 标记已读状态 } catch (err) { console.error('设置消息已读失败:', err) } @@ -106,6 +119,8 @@ export const useReadMsg = defineStore('readMsg', () => { unreadCount, fetchMessages, markAsRead, - updateTabBarBadge + updateTabBarBadge: updateBadgeEffect } +}, { + unistorage: true, // 开启持久化 }) \ No newline at end of file diff --git a/stores/useRecommedIndexedDBStore.js b/stores/useRecommedIndexedDBStore.js index 3f74d19..3669828 100644 --- a/stores/useRecommedIndexedDBStore.js +++ b/stores/useRecommedIndexedDBStore.js @@ -9,7 +9,7 @@ import jobAnalyzer from '@/utils/jobAnalyzer'; import { msg } from '@/common/globalFunction.js' -import baseDB from './BaseDBStore'; +import baseDB from '@/utils/db.js'; import config from '../config'; class JobRecommendation { diff --git a/stores/useUserStore.js b/stores/useUserStore.js index 646a226..cd1e655 100644 --- a/stores/useUserStore.js +++ b/stores/useUserStore.js @@ -14,6 +14,10 @@ import { import { useReadMsg } from '@/stores/useReadMsg'; +import { + msg, + $api, +} from '../common/globalFunction'; // 简历完成度计算 function getResumeCompletionPercentage(resume) { @@ -31,6 +35,7 @@ function getResumeCompletionPercentage(resume) { 'status', 'jobTitleId', 'jobTitle', + 'workExp' ]; const totalFields = requiredFields.length; @@ -51,7 +56,8 @@ const useUserStore = defineStore("user", () => { const token = ref('') const resume = ref({}) const Completion = ref('0%') - const seesionId = ref(uni.getStorageSync('seesionId') || '') + const seesionId = ref('') + const counts = ref({}) const login = (value) => { hasLogin.value = true; @@ -127,6 +133,13 @@ const useUserStore = defineStore("user", () => { seesionId.value = seesionIdVal } + function getUserstatistics() { + $api.createRequest('/app/user/statistics').then((resData) => { + counts.value = resData.data; + }); + } + + // 导入 return { hasLogin, @@ -139,8 +152,12 @@ const useUserStore = defineStore("user", () => { getUserResume, initSeesionId, seesionId, - Completion + Completion, + getUserstatistics, + counts } +}, { + unistorage: true, }) export default useUserStore; \ No newline at end of file diff --git a/stores/userChatGroupStore.js b/stores/userChatGroupStore.js index af70f96..54478d3 100644 --- a/stores/userChatGroupStore.js +++ b/stores/userChatGroupStore.js @@ -6,7 +6,7 @@ import { ref, toRaw } from 'vue' -import baseDB from './BaseDBStore'; +import baseDB from '@/utils/db.js'; import { msg, CloneDeep, diff --git a/uni_modules/pinia-plugin-unistorage/changelog.md b/uni_modules/pinia-plugin-unistorage/changelog.md new file mode 100644 index 0000000..053621c --- /dev/null +++ b/uni_modules/pinia-plugin-unistorage/changelog.md @@ -0,0 +1,31 @@ +## 0.1.2(2024-07-17) +chore: 移除冗余的 typescript 依赖 +## 0.1.1(2024-07-17) +fix: 修复 createUnistorage 导出 +## 0.1.0(2024-07-10) +fix!: 更新 pinia 类型 +## 0.0.21(2024-07-10) +chore!: 继承 pinia-plugin-persistedstate +## 0.0.19(2024-01-18) + +fix: 重新构建,不需要默认参数 + +## 0.0.16(2023-05-06) + +fix: 修复全局 key 移除 + +## 0.0.14(2023-04-29) + +fix: 修复全局 global key 选项 + +## 0.0.12(2023-04-07) + +- fix: 修复类型错误 + +## 0.0.11(2023-03-22) + +- chore: ts 支持 + +## 0.0.7(2022-04-29) + +- 更新 README diff --git a/uni_modules/pinia-plugin-unistorage/index.d.ts b/uni_modules/pinia-plugin-unistorage/index.d.ts new file mode 100644 index 0000000..b6d3c73 --- /dev/null +++ b/uni_modules/pinia-plugin-unistorage/index.d.ts @@ -0,0 +1,112 @@ +import * as pinia from 'pinia'; +import { StateTree, PiniaPluginContext, PiniaPlugin } from 'pinia'; + +type Prettify = { + [K in keyof T]: T[K]; +}; +type StorageLike = Pick; +interface Serializer { + /** + * Serializes state into string before storing + * @default JSON.stringify + */ + serialize: (value: StateTree) => string; + /** + * Deserializes string into state before hydrating + * @default JSON.parse + */ + deserialize: (value: string) => StateTree; +} +interface PersistedStateOptions { + /** + * Storage key to use. + * @default $store.id + */ + key?: string | ((id: string) => string); + /** + * Where to store persisted state. + * @default localStorage + */ + storage?: StorageLike; + /** + * Dot-notation paths to partially save state. Saves everything if undefined. + * @default undefined + */ + paths?: Array; + /** + * Customer serializer to serialize/deserialize state. + */ + serializer?: Serializer; + /** + * Hook called before state is hydrated from storage. + * @default null + */ + beforeRestore?: (context: PiniaPluginContext) => void; + /** + * Hook called after state is hydrated from storage. + * @default undefined + */ + afterRestore?: (context: PiniaPluginContext) => void; + /** + * Logs errors in console when enabled. + * @default false + */ + debug?: boolean; +} +type PersistedStateFactoryOptions = Prettify & { + /** + * Global key generator, allows pre/postfixing store keys. + * @default storeKey => storeKey + */ + key?: (storeKey: string) => string; + /** + * Automatically persists all stores, opt-out individually. + * @default false + */ + auto?: boolean; +}>; +declare module 'pinia' { + interface DefineStoreOptionsBase { + /** + * Persists store in storage. + * @see https://prazdevs.github.io/pinia-plugin-persistedstate + */ + persist?: boolean | PersistedStateOptions | PersistedStateOptions[]; + unistorage?: boolean | PersistedStateOptions | PersistedStateOptions[]; + } + interface PiniaCustomProperties { + /** + * Rehydrates store from persisted state + * Warning: this is for advances usecases, make sure you know what you're doing. + * @see https://prazdevs.github.io/pinia-plugin-persistedstate/guide/advanced.html#forcing-the-rehydration + */ + $hydrate: (opts?: { + runHooks?: boolean; + }) => void; + /** + * Persists store into configured storage + * Warning: this is for advances usecases, make sure you know what you're doing. + * @see https://prazdevs.github.io/pinia-plugin-persistedstate/guide/advanced.html#forcing-the-persistence + */ + $persist: () => void; + } +} + +/** + * Creates a pinia persistence plugin + * @param factoryOptions global persistence options + * @returns pinia plugin + */ +declare function createPersistedState(factoryOptions?: PersistedStateFactoryOptions): PiniaPlugin; + +declare const _default: pinia.PiniaPlugin; + +export { PersistedStateFactoryOptions, PersistedStateOptions, Serializer, StorageLike, createPersistedState, _default as default, createUnistorage }; + +/** + * Creates a pinia persistence plugin with uniapp + * @param factoryOptions global persistence options + * @returns pinia plugin + */ +declare function createUnistorage(factoryOptions?: PersistedStateFactoryOptions): PiniaPlugin; + \ No newline at end of file diff --git a/uni_modules/pinia-plugin-unistorage/index.js b/uni_modules/pinia-plugin-unistorage/index.js new file mode 100644 index 0000000..02c4e79 --- /dev/null +++ b/uni_modules/pinia-plugin-unistorage/index.js @@ -0,0 +1,162 @@ +// src/normalize.ts +function isObject(v) { + return typeof v === "object" && v !== null; +} +function normalizeOptions(options, factoryOptions) { + options = isObject(options) ? options : /* @__PURE__ */ Object.create(null); + return new Proxy(options, { + get(target, key, receiver) { + if (key === "key") + return Reflect.get(target, key, receiver); + return Reflect.get(target, key, receiver) || Reflect.get(factoryOptions, key, receiver); + } + }); +} + +// src/pick.ts +function get(state, path) { + return path.reduce((obj, p) => { + return obj == null ? void 0 : obj[p]; + }, state); +} +function set(state, path, val) { + return path.slice(0, -1).reduce((obj, p) => { + if (/^(__proto__)$/.test(p)) + return {}; + else + return obj[p] = obj[p] || {}; + }, state)[path[path.length - 1]] = val, state; +} +function pick(baseState, paths) { + return paths.reduce((substate, path) => { + const pathArray = path.split("."); + return set(substate, pathArray, get(baseState, pathArray)); + }, {}); +} + +// src/plugin.ts +function parsePersistence(factoryOptions, store) { + return (o) => { + var _a; + try { + const { + storage = localStorage, + beforeRestore = void 0, + afterRestore = void 0, + serializer = { + serialize: JSON.stringify, + deserialize: JSON.parse + }, + key = store.$id, + paths = null, + debug = false + } = o; + return { + storage, + beforeRestore, + afterRestore, + serializer, + key: ((_a = factoryOptions.key) != null ? _a : (k) => k)(typeof key == "string" ? key : key(store.$id)), + paths, + debug + }; + } catch (e) { + if (o.debug) + console.error("[pinia-plugin-persistedstate]", e); + return null; + } + }; +} +function hydrateStore(store, { storage, serializer, key, debug }) { + try { + const fromStorage = storage == null ? void 0 : storage.getItem(key); + if (fromStorage) + store.$patch(serializer == null ? void 0 : serializer.deserialize(fromStorage)); + } catch (e) { + if (debug) + console.error("[pinia-plugin-persistedstate]", e); + } +} +function persistState(state, { storage, serializer, key, paths, debug }) { + try { + const toStore = Array.isArray(paths) ? pick(state, paths) : state; + storage.setItem(key, serializer.serialize(toStore)); + } catch (e) { + if (debug) + console.error("[pinia-plugin-persistedstate]", e); + } +} +function createPersistedState(factoryOptions = {}) { + return (context) => { + const { auto = false } = factoryOptions; + const { + options: { persist = auto }, + store, + pinia + } = context; + if (!persist) + return; + if (!(store.$id in pinia.state.value)) { + const original_store = pinia._s.get(store.$id.replace("__hot:", "")); + if (original_store) + Promise.resolve().then(() => original_store.$persist()); + return; + } + const persistences = (Array.isArray(persist) ? persist.map((p) => normalizeOptions(p, factoryOptions)) : [normalizeOptions(persist, factoryOptions)]).map(parsePersistence(factoryOptions, store)).filter(Boolean); + store.$persist = () => { + persistences.forEach((persistence) => { + persistState(store.$state, persistence); + }); + }; + store.$hydrate = ({ runHooks = true } = {}) => { + persistences.forEach((persistence) => { + const { beforeRestore, afterRestore } = persistence; + if (runHooks) + beforeRestore == null ? void 0 : beforeRestore(context); + hydrateStore(store, persistence); + if (runHooks) + afterRestore == null ? void 0 : afterRestore(context); + }); + }; + persistences.forEach((persistence) => { + const { beforeRestore, afterRestore } = persistence; + beforeRestore == null ? void 0 : beforeRestore(context); + hydrateStore(store, persistence); + afterRestore == null ? void 0 : afterRestore(context); + store.$subscribe( + (_mutation, state) => { + persistState(state, persistence); + }, + { + detached: true + } + ); + }); + }; +} + +function createUnistorage(globalOptions = {}) { + const persistedState = createPersistedState({ + storage: { + getItem(key) { + return uni.getStorageSync(key); + }, + setItem(key, value) { + uni.setStorageSync(key, value); + } + }, + serializer: { + deserialize: JSON.parse, + serialize: JSON.stringify + }, + ...globalOptions + }); + return (ctx) => { + if (ctx.options.unistorage) { + ctx.options.persist = ctx.options.unistorage; + } + return persistedState(ctx); + }; +} + +export { createPersistedState, createUnistorage }; diff --git a/uni_modules/pinia-plugin-unistorage/package.json b/uni_modules/pinia-plugin-unistorage/package.json new file mode 100644 index 0000000..fd82db4 --- /dev/null +++ b/uni_modules/pinia-plugin-unistorage/package.json @@ -0,0 +1,94 @@ +{ + "id": "pinia-plugin-unistorage", + "displayName": "pinia-plugin-unistorage", + "version": "0.1.2", + "description": "uniapp 下 pinia 的本地数据缓存插件", + "keywords": [ + "pinia", + "uniapp", + "storage", + "pinia-plugin", + "persistence" +], + "type": "module", + "main": "./index.js", + "types": "./index.d.ts", + "exports": { + ".": { + "import": "./index.js", + "types": "./index.d.ts" + } + }, + "engines": { + "HBuilderX": "^3.4.7" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/pinia-plugin-unistorage", + "type": "sdk-js" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y", + "alipay": "n" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "钉钉": "y", + "快手": "y", + "飞书": "y", + "京东": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + } + } + } + } +} diff --git a/uni_modules/pinia-plugin-unistorage/readme.md b/uni_modules/pinia-plugin-unistorage/readme.md new file mode 100644 index 0000000..7724e13 --- /dev/null +++ b/uni_modules/pinia-plugin-unistorage/readme.md @@ -0,0 +1,226 @@ +
+ +

pinia-plugin-unistorage

+

uniapp 下 pinia 的本地数据缓存插件

+
+ +
+
+ +
+ +
+ +
+
+ +## 引用 + +该插件是 +[pinia-plugin-persistedstate](https://github.com/prazdevs/pinia-plugin-persistedstate) +的 `uniapp` 版本,如果你需要在纯 `vue` 或者 `nuxt` 项目中使用 `pinia` +的本地数据缓存,请使用 +[pinia-plugin-persistedstate](https://github.com/prazdevs/pinia-plugin-persistedstate)。 + +
+
+ +## 动机 + +为了实现多端的更简单的全局本地数据缓存 + +
+
+ +## 组织 🦔 + +欢迎关注 **帝莎编程** + +- [官网](http://dishaxy.dishait.cn/) +- [Gitee](https://gitee.com/dishait) +- [Github](https://github.com/dishait) +- [网易云课堂](https://study.163.com/provider/480000001892585/index.htm?share=2&shareId=480000001892585) + +
+
+ +## 使用 + +### 安装 + +#### 1. `cli` 创建的 `uniapp` 项目 + +```shell +npm i pinia-plugin-unistorage -D +``` + +```js +// main.js +import { createSSRApp } from "vue"; +import * as Pinia from "pinia"; +import { createUnistorage } from "pinia-plugin-unistorage"; + +export function createApp() { + const app = createSSRApp(App); + + const store = Pinia.createPinia(); + + // 关键代码 👇 + store.use(createUnistorage()); + + app.use(store); + + return { + app, + Pinia, // 此处必须将 Pinia 返回 + }; +} +``` + +
+ +#### 2. `hbuilderx` 创建的 `uniapp` 项目 + +直接插件市场安装后引入注册 + +```js +// main.js +import { createSSRApp } from "vue"; +import * as Pinia from "pinia"; +import { createUnistorage } from "./uni_modules/pinia-plugin-unistorage"; + +export function createApp() { + const app = createSSRApp(App); + + const store = Pinia.createPinia(); + + // 关键代码 👇 + store.use(createUnistorage()); + + app.use(store); + + return { + app, + Pinia, // 此处必须将 Pinia 返回 + }; +} +``` + +### 基础 + +```js +import { defineStore } from "pinia"; + +export const useStore = defineStore("main", { + state() { + return { + someState: "hello pinia", + }; + }, + unistorage: true, // 开启后对 state 的数据读写都将持久化 +}); +``` + +或者 `setup` 语法也是支持的 + +```js +import { defineStore } from "pinia"; + +export const useStore = defineStore( + "main", + () => { + const someState = ref("hello pinia"); + return { someState }; + }, + { + unistorage: true, // 开启后对 state 的数据读写都将持久化 + }, +); +``` + +
+ +### 选项 + +#### 钩子 + +```js +import { defineStore } from "pinia"; + +export const useStore = defineStore("main", { + state() { + return { + someState: "hello pinia", + }; + }, + unistorage: { + // 初始化恢复前触发 + beforeRestore(ctx) {}, + // 初始化恢复后触发 + afterRestore(ctx) {}, + }, +}); +``` + +
+ +#### 序列化 + +大多数情况下你并不需要了解该选项 + +```js +import { defineStore } from "pinia"; + +export const useStore = defineStore("main", { + state() { + return { + someState: "hello pinia", + }; + }, + unistorage: { + serializer: { + // 序列化,默认为 JSON.stringify + serialize(v) { + return JSON.stringify(v); + }, + // 反序列化,默认为 JSON.parse + deserialize(v) { + return JSON.parse(v); + }, + }, + }, +}); +``` + +
+ +#### 其他 + +```js +import { defineStore } from "pinia"; + +export const useStore = defineStore("main", { + state() { + return { + foo: "foo", + nested: { + data: "nested pinia", + }, + someState: "hello pinia", + }; + }, + unistorage: { + key: "foo", // 缓存的键,默认为该 store 的 id,这里是 main, + paths: ["foo", "nested.data"], // 需要缓存的路径,这里设置 foo 和 nested 下的 data 会被缓存 + }, +}); +``` + +
+
+ +## License + +Made with [markthree](https://github.com/markthree) + +Published under [MIT License](./LICENSE). diff --git a/uni_modules/pinia-plugin-unistorage/src/index.ts b/uni_modules/pinia-plugin-unistorage/src/index.ts new file mode 100644 index 0000000..bc6bcb9 --- /dev/null +++ b/uni_modules/pinia-plugin-unistorage/src/index.ts @@ -0,0 +1,35 @@ +import { + createPersistedState, + type PersistedStateFactoryOptions, +} from "pinia-plugin-persistedstate"; + +export * from "pinia-plugin-persistedstate"; + +export function createUnistorage( + globalOptions: PersistedStateFactoryOptions = {}, +) { + const persistedState = createPersistedState({ + storage: { + getItem(key) { + // @ts-ignore + return uni.getStorageSync(key); + }, + setItem(key, value) { + // @ts-ignore + uni.setStorageSync(key, value); + }, + }, + serializer: { + deserialize: JSON.parse, + serialize: JSON.stringify, + }, + ...globalOptions, + }); + // @ts-ignore + return (ctx) => { + if (ctx.options.unistorage) { + ctx.options.persist = ctx.options.unistorage; + } + return persistedState(ctx); + }; +} diff --git a/utils/db.js b/utils/db.js new file mode 100644 index 0000000..d901575 --- /dev/null +++ b/utils/db.js @@ -0,0 +1,91 @@ +// BaseDBStore.js +import IndexedDBHelper from '@/common/IndexedDBHelper.js' +// import UniStorageHelper from '../common/UniStorageHelper' +import useChatGroupDBStore from '@/stores/userChatGroupStore' +import config from '@/config' + +class BaseStore { + db = null + isDBReady = false + dbName = 'BrowsingHistory' // 'AppMainDB' + initPromise = null + + constructor() { + this.initPromise = this.checkAndInitDB() + } + + async getDB() { + if (!this.initPromise) { + this.initPromise = this.checkAndInitDB(); + } + await this.initPromise; // 等待初始化完成 + return this.db; + } + + async checkAndInitDB() { + if (config.OnlyUseCachedDB) { + return this.initDB() + } + const localVersion = uni.getStorageSync('indexedDBVersion') || 1 + console.log('DBVersion: ', localVersion, config.DBversion) + if (localVersion === config.DBversion) { + return this.initDB() // 🟢 记得加 return + } else { + console.log('清空本地数据库') + await this.clearDB() // 🟢 建议用 await + uni.setStorageSync('indexedDBVersion', config.DBversion); + return this.initDB(); // 🟢 记得加 return + } + } + + initDB() { + // // #ifdef H5 + this.db = new IndexedDBHelper(this.dbName, config.DBversion); + // // #endif + + return this.db.openDB([{ + name: 'record', + keyPath: "id", + autoIncrement: true, + }, + { + name: 'messageGroup', + keyPath: "id", + autoIncrement: true, + }, + { + name: 'messages', + keyPath: "id", + autoIncrement: true, + indexes: [{ + name: 'parentGroupId', + key: 'parentGroupId', + unique: false + }] + }, + { + name: 'api_cache', + keyPath: "cacheKey", // 使用 URL+参数 作为主键 + indexes: [] + } + ]).then(async () => { + // 这里原来的逻辑保留 + if (useChatGroupDBStore) { + useChatGroupDBStore().init() + } + this.isDBReady = true + return this.db; + }); + } + + async clearDB() { + return new Promise((resolve, rejetc) => { + new IndexedDBHelper().deleteDB(this.dbName).then(() => { + resolve() + }) + }) + } +} + +const baseDB = new BaseStore() +export default baseDB \ No newline at end of file diff --git a/utils/request.js b/utils/request.js index 9ee9c0d..fe39634 100644 --- a/utils/request.js +++ b/utils/request.js @@ -1,14 +1,15 @@ import config from "@/config.js" import { sm2_Decrypt, - sm2_Encrypt -} from '@/common/globalFunction'; -import useUserStore from '@/stores/useUserStore'; -import { + sm2_Encrypt, sm4Decrypt, sm4Encrypt -} from '../common/globalFunction'; +} from '@/common/globalFunction'; +import IndexedDBHelper from '@/common/IndexedDBHelper'; +import useUserStore from '@/stores/useUserStore'; +import baseDB from '@/utils/db.js'; +const CACHE_STORE_NAME = 'api_cache'; const needToEncrypt = [ ["post", "/app/login"], @@ -20,6 +21,51 @@ const needToEncrypt = [ ["get", "/app/user/experience/list"] ] +/** + * 带缓存的请求方法 + */ +export async function createRequestWithCache(url, data = {}, method = 'GET', loading = false, headers = {}, + onCacheLoad = null) { + // 是分页接口的话, 只缓存第一页的数据 + if (data.current && data.current > 1) { + return createRequest(url, data, method, loading, headers); + } + const cacheKey = `${method.toUpperCase()}:${url}:${JSON.stringify(data)}`; + + baseDB.getDB().then(async (dbHelper) => { + try { + const cachedRecord = await dbHelper.get(CACHE_STORE_NAME, cacheKey); + + if (cachedRecord && cachedRecord.response && typeof onCacheLoad === 'function') { + onCacheLoad(cachedRecord.response); + } + } catch (e) { + console.error('读取缓存失败', e); + } + }); + + // 3. 发起网络请求 + try { + const networkResponse = await createRequest(url, data, method, loading, headers); + baseDB.getDB().then(async (dbHelper) => { + try { + await dbHelper.update(CACHE_STORE_NAME, { + cacheKey: cacheKey, + response: networkResponse, + timestamp: Date.now() + }); + console.log('💾 [BaseDB] 缓存更新:', url); + } catch (e) { + console.error('更新缓存失败', e); + } + }); + + return networkResponse; + } catch (error) { + throw error; + } +} + /** * @param url String,请求的地址,默认:none * @param data Object,请求的参数,默认:{} @@ -35,13 +81,16 @@ export function createRequest(url, data = {}, method = 'GET', loading = false, h mask: true }) } - let Authorization = '' - if (useUserStore().token) { - Authorization = `${useUserStore().token}` - } + let header = { + ...headers + }; + const userStore = useUserStore(); + const token = userStore.token; - const header = headers || {}; - header["Authorization"] = encodeURIComponent(Authorization); + if (token) { + // 确保 Authorization 不会被覆盖,且进行编码 + header["Authorization"] = encodeURIComponent(token); + } // ------------------------------------------------------------------ // 检查当前请求是否需要加密 @@ -88,10 +137,12 @@ export function createRequest(url, data = {}, method = 'GET', loading = false, h resolve(resData.data) return } - uni.showToast({ - title: msg, - icon: 'none' - }) + if (msg) { + uni.showToast({ + title: msg, + icon: 'none' + }) + } } if (resData.data?.code === 401 || resData.data?.code === 402) { useUserStore().logOut()