48 Commits

Author SHA1 Message Date
Apcallover
c5955959c5 flat: ces 2025-12-03 11:01:58 +08:00
Apcallover
16b8ca84cd flat: 性能优化,animation 等\preload等 2025-12-01 20:29:19 +08:00
Apcallover
ecfacd13e3 flat: 优化2 2025-11-30 17:14:41 +08:00
Apcallover
9a38bbd298 flat: 优化 2025-11-30 16:47:06 +08:00
Apcallover
8cf55d3925 flat: 性能优化 2025-11-30 14:26:36 +08:00
Apcallover
0dec1618fa flat: 优化,还是使用原生Tabbar,empty优化 2025-11-30 14:08:16 +08:00
Apcallover
63d0cdb5ad flat: 性能优化,招聘会时间筛选性能优化、精选企业性能优化,封装缓存request、indexDb方法,主要用于不常更新的接口,以达到毫秒级性能 2025-11-29 16:31:34 +08:00
Apcallover
636818361c flat:暂存 2025-11-29 11:48:05 +08:00
Apcallover
23a2b84b4a 合并性能优化缓存备份分支 2025-11-28 19:54:18 +08:00
Apcallover
d84fd90a11 flat: 缓存 2025-11-28 19:47:42 +08:00
e5afbcedb1 Merge branch 'main' of http://124.243.245.42:3000/sdz/qingdao-employment-service 2025-11-28 18:17:23 +08:00
78661c12af 日期选择样式优化 2025-11-28 18:17:22 +08:00
Apcallover
550173c82d flat: 修改错别字 2025-11-28 17:53:26 +08:00
b53d8196b4 竞争力分析超过3个才显示, 简历完成度加入工作经历 2025-11-28 17:49:05 +08:00
983405cabe Merge branch 'main' of http://124.243.245.42:3000/sdz/qingdao-employment-service 2025-11-28 17:22:37 +08:00
b447026f99 岗位推荐固定插入改为随机插入 2025-11-28 17:22:36 +08:00
Apcallover
6eb0767a88 flatL暂存 2025-11-28 14:33:08 +08:00
Apcallover
dfd79646d6 flat: 过期状态 2025-11-27 21:45:15 +08:00
Apcallover
4563fa90af flat: 优化搜索 2025-11-27 21:26:32 +08:00
Apcallover
b6588d421f flat: 更改后面 2025-11-26 21:14:25 +08:00
Apcallover
0172f47628 flat: 体验优化,swiper优化,添加uploadfile Class方法 2025-11-26 21:11:12 +08:00
99a3fe41c5 feat 对接外部数据投递功能 2025-11-25 16:20:34 +08:00
Apcallover
fe6fe43636 flat: login点 2025-11-24 22:48:02 +08:00
Apcallover
a4233b03d7 flat: 对接经纬度2 2025-11-24 18:41:52 +08:00
Apcallover
378f71f3c7 flat: 对接经纬度 2025-11-24 18:40:59 +08:00
Apcallover
9f92fc47cb flat: 暂存 2025-11-24 14:33:23 +08:00
6af1a5def7 style 2025-11-24 09:47:03 +08:00
ca47a45d33 fix 内部数据收藏传参bug 2025-11-24 09:38:56 +08:00
xiebin
abd91e2cb7 分类渲染数据类型 : 岗位详情 公司详情 岗位收藏 公司收藏 浏览记录 预约列表 2025-11-23 18:20:28 +08:00
xiebin
06fb492cbd 修改公司详情接口地址 2025-11-22 19:23:20 +08:00
xiebin
c01abfdd64 Merge branch 'main' of http://124.243.245.42:3000/sdz/qingdao-employment-service 2025-11-22 18:53:53 +08:00
xiebin
99f02927ac 对接招聘会接口 2025-11-22 18:53:52 +08:00
Apcallover
f515d07d2a Merge branch 'copy' 2025-11-22 17:12:19 +08:00
Apcallover
42d0451869 flat:切换应用 2025-11-22 17:11:11 +08:00
xiebin
4b8056b716 fix : 雷达图渲染 2025-11-21 15:41:47 +08:00
xiebin
a3d592eb02 remove : 职位详情-竞争力分析-雷达图的技能项 2025-11-21 15:34:38 +08:00
xiebin
41196466af 隐藏电子名片没有的字段 2025-11-21 14:50:59 +08:00
Apcallover
97a5c34e70 flat:tabbar 2025-11-21 09:39:53 +08:00
6024ae44a4 feat : 每次进入简历详情页,刷新简历信息 2025-11-20 18:58:40 +08:00
ab63143792 feat : 新增上传简历功能 2025-11-20 18:49:53 +08:00
Apcallover
29fe2aff0e flat: 暂存 2025-11-20 18:14:36 +08:00
Apcallover
90591289d0 flat: 更好aes.js 2025-11-20 17:20:55 +08:00
Apcallover
e5c5902322 flat: 合并 2025-11-20 16:34:00 +08:00
Apcallover
f1b18203ae flat:合并 2025-11-20 15:56:45 +08:00
Apcallover
5497398498 flat: ai对话提交 2025-11-20 09:17:29 +08:00
Apcallover
e67c53404b flat: 暂存 2025-11-18 19:53:55 +08:00
Apcallover
60a0448aa7 flat: 暂存 2025-11-18 19:43:15 +08:00
ca7273f152 style : 样式优化 2025-11-13 10:57:01 +08:00
78 changed files with 4884 additions and 2451 deletions

66
App.vue
View File

@@ -2,30 +2,34 @@
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) => {
getUserInfo();
// useUserStore().initSeesionId(); //更新
useDictStore().getDictData();
// uni.hideTabBar();
// 登录
// let token = uni.getStorageSync('token') || ''; // 同步获取 缓存信息
// if (token) {
// useUserStore()
// .loginSetToken(token)
// .then(() => {
// $api.msg('登录成功');
// });
// } else {
// uni.redirectTo({
// url: '/pages/login/login',
// });
// }
useDictStore().getDictData();
try {
getUserInfo();
} catch {
console.log('不是爱山东平台,使用测试登陆');
useUserStore().initSeesionId(); //更新
let token = uni.getStorageSync('token') || ''; // 同步获取 缓存信息
if (token) {
useUserStore()
.loginSetToken(token)
.then(() => {
$api.msg('登录成功');
});
} else {
uni.redirectTo({
url: '/pages/login/login',
});
}
}
});
onMounted(() => {});
@@ -40,23 +44,20 @@ onHide(() => {
function getUserInfo() {
lightAppJssdk.user.getUserInfoWithEncryptedParamByAppId({
appId: 'qdsrgznrgpp', // 接入方在成功创建应用后自动生成
appId: config.appInfo.loveShandong, // 接入方在成功创建应用后自动生成
success: function (data) {
if (data == '未登录') onLoginApp();
else {
if (typeof data == 'string') data = JSON.parse(data);
const sm2_privateKey = '7e14966df4ecd4241ed082ef716d82b52113cb5899ebdc704a98844d0a32b0dc';
const sm2_privateKey = config.appInfo.sm2PrivateKey;
let sm2_encrypt_result = data.data;
let sm2_decrypt_result = sm2_Decrypt(sm2_encrypt_result, sm2_privateKey);
if (typeof sm2_decrypt_result == 'string') sm2_decrypt_result = JSON.parse(sm2_decrypt_result);
// 其次,对sm2解密后的结果进行 aes解密
// aes解密需要用到 appword , 为固定值,使用示例代码中的即可
let aes_encrypt_result = sm2_decrypt_result.data;
let aes_decrypt_result = aes_Decrypt(aes_encrypt_result, appword);
// 加密
loginCallback(aes_decrypt_result);
}
@@ -99,7 +100,7 @@ function oncloseWindow() {
function loginCallback(userInfo) {
let params = {
username: userInfo,
userInfo,
};
$api.createRequest('/app/login', params, 'post').then((resData) => {
useUserStore()
@@ -124,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 {

View File

@@ -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);
}
}

View File

@@ -464,4 +464,12 @@ html {
/* 隐藏超出的文本 */
text-overflow: ellipsis;
/* 使用省略号 */
}
.grayscale {
filter: grayscale(100%) opacity(0.6);
}
.height-100 {
height: 100%;
}

View File

@@ -1,6 +1,7 @@
import '@/lib/encryption/sm4.min.js'
import useUserStore from "../stores/useUserStore";
import {
request,
createRequestWithCache,
createRequest,
uploadFile
} from "../utils/request";
@@ -562,8 +563,6 @@ function aes_Decrypt(word, key) {
})
return decrypt.toString(CryptoJS.enc.Utf8)
}
export function sm2_Decrypt(word, key) {
return SM.decrypt(word, key);
}
@@ -572,11 +571,51 @@ export function sm2_Encrypt(word, key) {
return SM.encrypt(word, key);
}
export function sm4Decrypt(key, value, mode = "hex") {
try {
if (key.length !== 32) {
alert('密钥必须是32位16进制字符串128位');
return;
}
const decrypted = sm4.decrypt(value, key, {
mode: 'ecb',
cipherType: mode === 'hex' ? 'hex' : 'base64',
padding: 'pkcs#5'
});
return decrypted
} catch (e) {
console.log('解密失败', e)
}
}
export function sm4Encrypt(key, value, mode = "hex") {
try {
if (key.length !== 32) {
alert('密钥必须是32位16进制字符串128位');
return;
}
const encrypted = sm4.encrypt(value, key, {
mode: 'ecb',
cipherType: mode === 'hex' ? 'hex' : 'base64',
padding: 'pkcs#5'
});
return encrypted
} catch (e) {
console.log('加密失败')
}
}
export const $api = {
msg,
prePage,
sleep,
request,
createRequest,
streamRequest,
chatRequest,
@@ -586,6 +625,7 @@ export const $api = {
sendingMiniProgramMessage,
copyText,
aes_Decrypt,
createRequestWithCache
}
@@ -615,6 +655,7 @@ export default {
insertSortData,
isInWechatMiniProgramWebview,
isEmptyObject,
sm4Decrypt,
aes_Decrypt,
sm2_Decrypt,
sm2_Encrypt

View File

@@ -1,5 +1,9 @@
<template>
<view class="empty" :style="{ background: bgcolor, marginTop: mrTop + 'rpx' }">
<view
class="empty"
:class="{ 'position-center': isPosition }"
:style="{ background: bgcolor, marginTop: mrTop + 'rpx' }"
>
<view class="ty_content" :style="{ paddingTop: pdTop + 'rpx' }">
<view class="content_top">
<!-- <view class="content_top btn-shaky"> -->
@@ -30,18 +34,23 @@ export default {
pdTop: {
type: String,
required: false,
default: '80',
default: '0',
},
mrTop: {
type: String,
required: false,
default: '20',
default: '0',
},
pictrue: {
type: String,
required: false,
default: '',
},
isPosition: {
type: Boolean,
required: false,
default: false,
},
},
methods: {},
};
@@ -52,19 +61,33 @@ image {
width: 100%;
height: 100%;
}
.position-center {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.empty {
width: 100%;
min-height: 100vh;
position: relative;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
// min-height: 100vh;
// height: 400rpx;
// position: relative;
.ty_content {
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, 0);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
// position: absolute;
// left: 50%;
// top: 0;
// transform: translate(-50%, 0);
.content_top {
width: 450rpx;
height: 322rpx;

View File

@@ -0,0 +1,167 @@
<template>
<view v-for="company in listData" :key="company.id">
<view
v-if="company.dataType == 2"
:class="{ grayscale: company.isPublish }"
class="cards"
@click="nextDetail(company)"
>
<view class="card-company">
<text class="company line_1">{{ company.name }}</text>
</view>
<view class="card-bottom">
<view class="fl_box fs_14">
<view class="mar_ri10">{{ company.industry }}</view>
<view>{{ company.scale }}</view>
</view>
<view class="ris">
<text class="fs_14">
在招职位·
<text class="color_256BFA">{{ company.totalRecruitment || '-' }}</text>
</text>
</view>
</view>
<view class="card-tags">
<view class="tag" v-if="company.nature">
{{ company.nature }}
</view>
</view>
</view>
<view v-else class="cards" :class="{ grayscale: company.isPublish }" @click="nextDetail(company)">
<view class="card-company">
<text class="company line_1">{{ company.name }}</text>
</view>
<view class="card-bottom">
<view class="fl_box fs_14">
<view class="mar_ri10">{{ company.industry }}</view>
<view>{{ company.scale }}</view>
</view>
<view class="ris">
<text class="fs_14">
在招职位·
<text class="color_256BFA">{{ company.totalRecruitment || '-' }}</text>
</text>
</view>
</view>
<view class="card-tags">
<view class="tag" v-if="company.nature">
{{ company.nature }}
</view>
</view>
</view>
</view>
</template>
<script setup>
import { inject, computed, toRaw } from 'vue';
const { $api, insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const props = defineProps({
list: {
type: Array,
default: '标题',
},
longitude: {
type: Number,
default: 120.382665,
},
latitude: {
type: Number,
default: 36.066938,
},
seeDate: {
type: String,
default: '',
},
});
const listData = computed(() => {
return props.list;
});
function nextDetail(company) {
if (company.isPublish) {
return $api.msg('已过期');
}
if (company.dataType == 2) {
navTo(
`/packageA/pages/UnitDetails/UnitDetails?companyId=${company.gsID}&companyName=${company.name}&zphId=${company.zphID}&dataType=2`
);
} else {
navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${company.companyId}`);
}
}
</script>
<style lang="stylus" scoped>
.date-jobTitle{
font-weight: 400;
font-size: 28rpx;
color: #495265;
padding: 28rpx 0 0 20rpx
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.salary{
font-weight: 500;
font-size: 28rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 15rpx
margin-bottom: 10rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
.ris{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
}
</style>

View File

@@ -130,4 +130,4 @@ function nextDetail(company) {
.ris{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
}
</style>
</style>

View File

@@ -0,0 +1,142 @@
<template>
<view v-for="job in listData" :key="job.id">
<view class="cards" @click="nextDetail(job)">
<view class="card-company">
<text class="company line_1">{{ job.gsmc }}</text>
</view>
<view class="card-bottom" >
<view class="fl_box fs_14" >
<!-- <dict-tree-Label class="mar_ri10" dictType="industry" :value="job.industry"></dict-tree-Label>
<dict-Label dictType="scale" :value="job.scale"></dict-Label> -->
<view>{{job.gsxy}}</view>
</view>
<view class="ris" >
<text class="fs_14">
在招职位·
<text class="color_256BFA">{{ job.zzgwsl || '-' }}</text>
</text>
</view>
</view>
<view class="card-tags">
<view class="tag" v-if="job.nature">
<dict-Label dictType="nature" :value="job.nature"></dict-Label>
<dict-Label dictType="nature" :value="job.nature"></dict-Label>
</view>
<view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view>
<view class="tag" v-if="job.qyxz">
{{job.qyxz}}
</view>
</view>
</view>
</view>
</template>
<script setup>
import { inject, computed, toRaw } from 'vue';
const { insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const props = defineProps({
list: {
type: Array,
default: '标题',
},
longitude: {
type: Number,
default: 120.382665,
},
latitude: {
type: Number,
default: 36.066938,
},
seeDate: {
type: String,
default: '',
},
zphId: {
type: String,
default: '',
},
});
const listData = computed(() => {
return props.list;
});
function nextDetail(company) {
navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${company.gsID}&companyName=${company.gsmc}&zphId=${props.zphId}&dataType=2`);
}
</script>
<style lang="stylus" scoped>
.date-jobTitle{
font-weight: 400;
font-size: 28rpx;
color: #495265;
padding: 28rpx 0 0 20rpx
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.salary{
font-weight: 500;
font-size: 28rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 15rpx
margin-bottom: 10rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
.ris{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
}
</style>

View File

@@ -0,0 +1,210 @@
<template>
<view v-for="job in listData" :key="job.id">
<view v-if="!job.isTitle" class="cards" @click="nextDetail(job)">
<!-- 数据类型2的完整模块 -->
<view v-if="job.dataType == 2">
<view class="card-company" :class="{ grayscale: job.isPublish }">
<text class="company">{{ job.jobTitle }}</text>
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-tags">
<view class="tag">
{{ job.education == '不限' ? '学历不限' : job.education }}
</view>
<view class="tag">
{{ job.experience == '不限' ? '经验不限' : job.experience }}
</view>
<view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view>
</view>
<view class="card-bottom">
<view>{{ parseDateTime(job.createTime).date }}</view>
<view>
<!-- <convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label class="mar_le10" dictType="area" :value="job.jobLocationAreaCode"></dict-Label> -->
</view>
</view>
</view>
<!-- 数据类型1的完整模块 -->
<view v-else>
<view class="card-company" :class="{ grayscale: job.isPublish }">
<text class="company">{{ job.jobTitle }}</text>
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-tags">
<view class="tag">
{{ job.education == '不限' ? '学历不限' : job.education }}
</view>
<view class="tag">
{{ job.experience == '不限' ? '经验不限' : job.experience }}
</view>
<view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view>
</view>
<view class="card-bottom">
<view>{{ job.postingDate }}</view>
<view>
<convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label class="mar_le10" dictType="area" :value="job.jobLocationAreaCode"></dict-Label>
</view>
</view>
</view>
</view>
<view class="date-jobTitle" v-else>
{{ job.title }}
</view>
</view>
</template>
<script setup>
import { inject, computed, toRaw } from 'vue';
const { $api, insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const props = defineProps({
list: {
type: Array,
default: '标题',
},
longitude: {
type: Number,
default: 120.382665,
},
latitude: {
type: Number,
default: 36.066938,
},
seeDate: {
type: String,
default: '',
},
});
const listData = computed(() => {
if (props.seeDate && props.list.length) {
const ulist = toRaw(props.list);
const [reslist, lastDate] = insertSortData(ulist, props.seeDate);
return reslist;
}
return props.list;
});
// 解析日期时间用于数据类型2
function parseDateTime(datetimeStr) {
if (!datetimeStr) return { time: '', date: '' };
const dateObj = new Date(datetimeStr);
if (isNaN(dateObj.getTime())) return { time: '', date: '' }; // 无效时间
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const day = String(dateObj.getDate()).padStart(2, '0');
const hours = String(dateObj.getHours()).padStart(2, '0');
const minutes = String(dateObj.getMinutes()).padStart(2, '0');
return {
time: `${hours}:${minutes}`,
date: `${year}-${month}-${day}`,
};
}
function nextDetail(job) {
if (job.isPublish) {
return $api.msg('已过期');
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}&dataType=${job.dataType}`);
}
</script>
<style lang="stylus" scoped>
.date-jobTitle{
font-weight: 400;
font-size: 28rpx;
color: #495265;
padding: 28rpx 0 0 20rpx
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 30rpx;
color: #333333;
}
.salary{
font-family: DIN-Medium;
font-weight: 500;
font-size: 26rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 32rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
</style>

View File

@@ -0,0 +1,211 @@
<template>
<view v-for="job in listData" :key="job.id">
<view v-if="!job.isTitle" class="cards" @click="nextDetail(job)">
<!-- 数据类型2的完整模块 -->
<view v-if="job.dataType == 2">
<view class="card-company" :class="{ grayscale: job.isPublish }">
<text class="company">{{ job.jobTitle }}</text>
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-tags">
<view class="tag">
{{ job.education == '不限' ? '学历不限' : job.education }}
</view>
<view class="tag">
{{ job.experience == '不限' ? '经验不限' : job.experience }}
</view>
<view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view>
</view>
<view class="card-bottom">
<view>{{ parseDateTime(job.createTime).date }}</view>
<view>
<!-- <convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label class="mar_le10" dictType="area" :value="job.jobLocationAreaCode"></dict-Label> -->
</view>
</view>
</view>
<!-- 数据类型1的完整模块 -->
<view v-else>
<view class="card-company" :class="{ grayscale: job.isPublish }">
<text class="company">{{ job.jobTitle }}</text>
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-tags">
<view class="tag">
{{ job.education == '不限' ? '学历不限' : job.education }}
</view>
<view class="tag">
{{ job.experience == '不限' ? '经验不限' : job.experience }}
</view>
<view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view>
</view>
<view class="card-bottom">
<view>{{ job.postingDate }}</view>
<view>
<convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label class="mar_le10" dictType="area" :value="job.jobLocationAreaCode"></dict-Label>
</view>
</view>
</view>
</view>
<view class="date-jobTitle" v-else>
{{ job.title }}
</view>
</view>
</template>
<script setup>
import { inject, computed, toRaw } from 'vue';
const { $api, insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const props = defineProps({
list: {
type: Array,
default: '标题',
},
longitude: {
type: Number,
default: 120.382665,
},
latitude: {
type: Number,
default: 36.066938,
},
seeDate: {
type: String,
default: '',
},
});
const listData = computed(() => {
if (props.seeDate && props.list.length) {
const ulist = toRaw(props.list);
const [reslist, lastDate] = insertSortData(ulist, props.seeDate);
return reslist;
}
return props.list;
});
// 解析日期时间用于数据类型2
function parseDateTime(datetimeStr) {
if (!datetimeStr) return { time: '', date: '' };
const dateObj = new Date(datetimeStr);
if (isNaN(dateObj.getTime())) return { time: '', date: '' }; // 无效时间
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const day = String(dateObj.getDate()).padStart(2, '0');
const hours = String(dateObj.getHours()).padStart(2, '0');
const minutes = String(dateObj.getMinutes()).padStart(2, '0');
return {
time: `${hours}:${minutes}`,
date: `${year}-${month}-${day}`,
};
}
function nextDetail(job) {
if (job.isPublish) {
return $api.msg('已过期');
}
// 根据数据类型跳转到不同的详情页
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}&dataType=${job.dataType}`);
}
</script>
<style lang="stylus" scoped>
.date-jobTitle{
font-weight: 400;
font-size: 28rpx;
color: #495265;
padding: 28rpx 0 0 20rpx
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 30rpx;
color: #333333;
}
.salary{
font-family: DIN-Medium;
font-weight: 500;
font-size: 26rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 32rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
</style>

View File

@@ -0,0 +1,210 @@
<template>
<view v-for="job in listData" :key="job.id">
<view v-if="!job.isTitle" class="cards" @click="nextDetail(job)">
<!-- 数据类型2的完整模块 -->
<view v-if="job.dataType == 2">
<view class="card-company" :class="{ grayscale: job.isPublish }">
<text class="company">{{ job.jobTitle }}</text>
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-tags">
<view class="tag">
{{ job.education == '不限' ? '学历不限' : job.education }}
</view>
<view class="tag">
{{ job.experience == '不限' ? '经验不限' : job.experience }}
</view>
<view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view>
</view>
<view class="card-bottom">
<view>{{ parseDateTime(job.createTime).date }}</view>
<view>
<!-- <convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label class="mar_le10" dictType="area" :value="job.jobLocationAreaCode"></dict-Label> -->
</view>
</view>
</view>
<!-- 数据类型1的完整模块 -->
<view v-else>
<view class="card-company" :class="{ grayscale: job.isPublish }">
<text class="company">{{ job.jobTitle }}</text>
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-tags">
<view class="tag">
{{ job.education == '不限' ? '学历不限' : job.education }}
</view>
<view class="tag">
{{ job.experience == '不限' ? '经验不限' : job.experience }}
</view>
<view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view>
</view>
<view class="card-bottom">
<view>{{ job.postingDate }}</view>
<view>
<convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label class="mar_le10" dictType="area" :value="job.jobLocationAreaCode"></dict-Label>
</view>
</view>
</view>
</view>
<view class="date-jobTitle" v-else>
{{ job.title }}
</view>
</view>
</template>
<script setup>
import { inject, computed, toRaw } from 'vue';
const { $api, insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const props = defineProps({
list: {
type: Array,
default: '标题',
},
longitude: {
type: Number,
default: 120.382665,
},
latitude: {
type: Number,
default: 36.066938,
},
seeDate: {
type: String,
default: '',
},
});
const listData = computed(() => {
if (props.seeDate && props.list.length) {
const ulist = toRaw(props.list);
const [reslist, lastDate] = insertSortData(ulist, props.seeDate);
return reslist;
}
return props.list;
});
// 解析日期时间用于数据类型2
function parseDateTime(datetimeStr) {
if (!datetimeStr) return { time: '', date: '' };
const dateObj = new Date(datetimeStr);
if (isNaN(dateObj.getTime())) return { time: '', date: '' }; // 无效时间
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const day = String(dateObj.getDate()).padStart(2, '0');
const hours = String(dateObj.getHours()).padStart(2, '0');
const minutes = String(dateObj.getMinutes()).padStart(2, '0');
return {
time: `${hours}:${minutes}`,
date: `${year}-${month}-${day}`,
};
}
function nextDetail(job) {
if (job.isPublish) {
return $api.msg('已过期');
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}&dataType=${job.dataType}`);
}
</script>
<style lang="stylus" scoped>
.date-jobTitle{
font-weight: 400;
font-size: 28rpx;
color: #495265;
padding: 28rpx 0 0 20rpx
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 30rpx;
color: #333333;
}
.salary{
font-family: DIN-Medium;
font-weight: 500;
font-size: 26rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 32rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
</style>

View File

@@ -7,7 +7,7 @@
<Salary-Expectation :max-salary="job.maxSalary" :min-salary="job.minSalary"></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-companyName">{{ job.gwmc }}</view>
<view class="card-tags">
<view class="tag">
<dict-Label dictType="education" :value="job.education"></dict-Label>
@@ -77,7 +77,7 @@ function nextDetail(job) {
const recordData = recommedIndexDb.JobParameter(job);
recommedIndexDb.addRecord(recordData);
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}&dataType=1`);
}
</script>

View File

@@ -0,0 +1,167 @@
<template>
<view v-for="job in listData" :key="job.id">
<view v-if="!job.isTitle" class="cards" @click="nextDetail(job)">
<view class="card-company">
<text class="company">{{ job.gwmc }}</text>
<view class="salary">
<Salary-Expectation :max-salary="job.maxSalary" :min-salary="job.minSalary"></Salary-Expectation>
</view>
</view>
<view class="card-companyName">{{ job.gsmc }}</view>
<view class="card-tags">
<view class="tag">
{{job.xlyq == '不限' ? '学历不限' : job.xlyq}}
</view>
<view class="tag">
{{job.gwgzjy == '不限' ? '经验不限' : job.gwgzjy}}
</view>
<view class="tag">
{{ vacanciesTo(job.zprs) }}
</view>
</view>
<view class="card-bottom">
<view>{{ parseDateTime(job.createTime).date }}</view>
<view>
<!-- <convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label class="mar_le10" dictType="area" :value="job.jobLocationAreaCode"></dict-Label> -->
</view>
</view>
</view>
<view class="date-jobTitle" v-else>
{{ job.title }}
</view>
</view>
</template>
<script setup>
import { inject, computed, toRaw } from 'vue';
const { insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const props = defineProps({
list: {
type: Array,
default: '标题',
},
longitude: {
type: Number,
default: 120.382665,
},
latitude: {
type: Number,
default: 36.066938,
},
seeDate: {
type: String,
default: '',
},
});
const listData = computed(() => {
if (props.seeDate && props.list.length) {
const ulist = toRaw(props.list);
const [reslist, lastDate] = insertSortData(ulist, props.seeDate);
return reslist;
}
return props.list;
});
function nextDetail(job) {
// 记录岗位类型,用作数据分析
if (job.jobCategory) {
const recordData = recommedIndexDb.JobParameter(job);
recommedIndexDb.addRecord(recordData);
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.id)}&dataType=2`);
}
function parseDateTime(datetimeStr) {
if (!datetimeStr) return { time: '', date: '' };
const dateObj = new Date(datetimeStr);
if (isNaN(dateObj.getTime())) return { time: '', date: '' }; // 无效时间
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const day = String(dateObj.getDate()).padStart(2, '0');
const hours = String(dateObj.getHours()).padStart(2, '0');
const minutes = String(dateObj.getMinutes()).padStart(2, '0');
return {
time: `${hours}:${minutes}`,
date: `${year}-${month}-${day}`,
};
}
</script>
<style lang="stylus" scoped>
.date-jobTitle{
font-weight: 400;
font-size: 28rpx;
color: #495265;
padding: 28rpx 0 0 20rpx
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 30rpx;
color: #333333;
}
.salary{
font-family: DIN-Medium;
font-weight: 500;
font-size: 26rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 32rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
</style>

View File

@@ -86,14 +86,13 @@ const open = (newConfig = {}) => {
} = newConfig;
reset();
serchforIt(defaultId);
if (configTitle) title.value = configTitle;
if (typeof success === 'function') confirmCallback.value = success;
if (typeof cancel === 'function') cancelCallback.value = cancel;
if (typeof change === 'function') changeCallback.value = change;
if (Array.isArray(data)) listData.value = data;
serchforIt(defaultId);
rowLabel.value = configRowLabel;
rowKey.value = configRowKey;
maskClick.value = configMaskClick;
@@ -154,7 +153,19 @@ function serchforIt(defaultId) {
state.visible = true;
return;
}
$api.createRequest('/app/common/jobTitle/treeselect', {}, 'GET').then((resData) => {
if (listData.value.length) {
if (userInfo.value.jobTitleId) {
const ids = userInfo.value.jobTitleId.split(',').map((id) => Number(id));
count.value = ids.length;
setCheckedNodes(listData.value, ids);
}
state.jobTitleId = userInfo.value.jobTitleId;
state.stations = listData.value;
state.visible = true;
return;
}
const LoadCache = (resData) => {
if (userInfo.value.jobTitleId) {
const ids = userInfo.value.jobTitleId.split(',').map((id) => Number(id));
count.value = ids.length;
@@ -163,7 +174,8 @@ function serchforIt(defaultId) {
state.jobTitleId = userInfo.value.jobTitleId;
state.stations = resData.data;
state.visible = true;
});
};
$api.createRequestWithCache('/app/common/jobTitle/treeselect', {}, 'GET', false, LoadCache).then(LoadCache);
}
const reset = () => {

View File

@@ -19,7 +19,7 @@
</template>
<script setup>
import { ref, defineProps, onMounted, computed } from 'vue';
import { ref, onMounted, computed } from 'vue';
import { useReadMsg } from '@/stores/useReadMsg';
const props = defineProps({
currentpage: {

View File

@@ -1,12 +1,8 @@
export default {
baseUrl: 'https://fw.rc.qingdao.gov.cn/rgpp-api/api', // 内网
// baseUrl: 'https://qd.zhaopinzao8dian.com/api', // 测试
// baseUrl: "http://192.168.98.110:18181",
// baseUrl: "http://192.168.3.19:8080",
// sseAI+
// StreamBaseURl: 'http://39.98.44.136:8000',
StreamBaseURl: 'https://qd.zhaopinzao8dian.com/ai',
// StreamBaseURl: 'https://qd.zhaopinzao8dian.com/ai/test',
// baseUrl: 'https://fw.rc.qingdao.gov.cn/rgpp-api/api', // 内网
baseUrl: 'https://qd.zhaopinzao8dian.com/api', // 测试
// baseUrl: 'http://192.168.3.29:8081',
// baseUrl: 'http://10.213.6.207:19010/api',
// 语音转文字
// vioceBaseURl: 'ws://39.98.44.136:8080/speech-recognition',
vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/speech-recognition',
@@ -18,14 +14,14 @@ export default {
OnlyUseCachedDB: false,
// 使用模拟定位
UsingSimulatedPositioning: true,
// 私钥
pubilcKey: '',
// 公钥
privateKey: '',
// 应用信息
appInfo: {
// 应用名称
name: "青岛市就业服务",
// 爱山东应用标识
loveShandong: 'szjxrgznqzzp',
// 爱山东应用Key
sm2PrivateKey: '0d152c849f10e4469f2af8cedea62004e4f1db7be23c2f7270c1441d8050799d',
// 地区名
areaName: '青岛市',
// AI名称
@@ -75,5 +71,11 @@ export default {
title: '找工作,用 AI 更高效|青岛市智能求职平台',
desc: '融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!',
imgUrl: 'https://qd.zhaopinzao8dian.com/file/csn/qd_shareLogo.jpg',
},
sm4Config: {
key: '86C63180C1306ABC4D8F989E0A0BC9F3',
mode: 'ECB', // default
iv: 'UISwD9fW6cFh9SNS', // default is null
cipherType: 'base64' // default is base64
}
}

30
hook/page-animation.css Normal file
View File

@@ -0,0 +1,30 @@
/* #ifdef H5 */
uni-page {
opacity: 1;
will-change: opacity;
}
/* --- 进场 (Enter) --- */
uni-page.animation-enter-from {
opacity: 0;
}
uni-page.animation-enter-active {
transition: opacity 0.2s ease-out;
}
/* --- 离场 (Leave) --- */
uni-page.animation-leave-active {
transition: opacity 0.15s ease-in;
}
uni-page.animation-leave-to {
opacity: 0;
}
/* --- 稳态 --- */
uni-page.animation-show {
opacity: 1;
}
/* #endif */

66
hook/usePageAnimation.js Normal file
View File

@@ -0,0 +1,66 @@
import {
onLaunch
} from '@dcloudio/uni-app'
import {
getCurrentInstance
} from 'vue'
import './page-animation.css'
const DURATION = 130
export default function usePageAnimation() {
// #ifdef H5
const show = () => {
const page = document.querySelector('uni-page')
if (!page) return
const cl = page.classList
cl.add('animation-enter-from')
cl.remove('animation-leave-to', 'animation-leave-active')
requestAnimationFrame(() => {
requestAnimationFrame(() => {
cl.remove('animation-enter-from')
cl.add('animation-enter-active', 'animation-show')
setTimeout(() => {
cl.remove('animation-enter-active')
}, DURATION)
})
})
}
const hide = (next) => {
const page = document.querySelector('uni-page')
if (!page) {
next()
return
}
const cl = page.classList
cl.add('animation-leave-active')
requestAnimationFrame(() => {
cl.remove('animation-show')
cl.add('animation-leave-to')
setTimeout(() => {
cl.remove('animation-leave-active', 'animation-leave-to')
next()
}, DURATION - 50)
})
}
onLaunch(() => {
const instance = getCurrentInstance()
const router = instance?.proxy?.$router
if (router) {
show()
router.beforeEach((to, from, next) => hide(next))
router.afterEach(() => show())
}
})
// #endif
}

View File

@@ -17,21 +17,23 @@
})();
</script>
<title></title>
<!-- vconsole -->
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<!-- eruda -->
<!-- <script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>
eruda.init();
</script> -->
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>
// VConsole 默认会挂载到 `window.VConsole` 上
var vConsole = new window.VConsole();
vConsole.destroy();
</script>
<!-- 爱山东jssdk -->
</script> -->
<!-- 爱山东jssdk 本sdk存在性能问题 -->
<script type="text/javascript" src="https://isdapp.shandong.gov.cn/jmopen/jssdk/index.js"></script>
<script type="text/javascript" src="./static/encryption/aes.js"></script>
<script type="text/javascript" src="./static/encryption/SM.js"></script>
<!-- 只在内网有效 -->
<script type="text/javascript" src="./static/js/SM.js"></script>
</head>
<!-- <body> -->
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

7
lib/encryption/sm2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
lib/encryption/sm4.min.js vendored Normal file

File diff suppressed because one or more lines are too long

22
main.js
View File

@@ -1,8 +1,12 @@
import App from '@/App'
import * as Pinia from 'pinia'
import {
createUnistorage
} from "./uni_modules/pinia-plugin-unistorage";
import globalFunction from '@/common/globalFunction'
import '@/lib/string-similarity.min.js'
import similarityJobs from '@/utils/similarity_Job.js';
// 组件
import AppLayout from './components/AppLayout/AppLayout.vue';
import Empty from './components/empty/empty.vue';
@@ -12,8 +16,15 @@ import SelectPopup from '@/components/selectPopup/selectPopup.vue'
import SelectPopupPlugin from '@/components/selectPopup/selectPopupPlugin';
import RenderJobs from '@/components/renderJobs/renderJobs.vue';
import RenderCompanys from '@/components/renderCompanys/renderCompanys.vue';
import RenderJobsOutData from '@/components/renderJobsOutData/renderJobsOutData.vue';
import RenderCompanysOutData from '@/components/renderCompanysOutData/renderCompanysOutData.vue';
import renderDeliveryRecord from '@/components/renderDeliveryRecord/renderDeliveryRecord.vue';
import renderJobCollectionRecord from '@/components/renderJobCollectionRecord/renderJobCollectionRecord.vue';
import renderCompanyCollectionRecord from '@/components/renderCompanyCollectionRecord/renderCompanyCollectionRecord.vue';
import renderJobViewRecord from '@/components/renderJobViewRecord/renderJobViewRecord.vue';
// import Tabbar from '@/components/tabbar/midell-box.vue'
// 自动导入 directives 目录下所有指令
console.log(lightAppJssdk)
const directives = import.meta.glob('./directives/*.js', {
eager: true
});
@@ -36,6 +47,12 @@ export function createApp() {
app.component('SelectPopup', SelectPopup)
app.component('RenderJobs', RenderJobs)
app.component('RenderCompanys', RenderCompanys)
app.component('RenderJobsOutData', RenderJobsOutData) //渲染外部岗位数据列表
app.component('RenderCompanysOutData', RenderCompanysOutData) //渲染外部公司数据列表
app.component('renderDeliveryRecord', renderDeliveryRecord) //渲染岗位投递记录
app.component('renderJobCollectionRecord', renderJobCollectionRecord) //渲染岗位收藏记录
app.component('renderCompanyCollectionRecord', renderCompanyCollectionRecord) //渲染公司收藏记录
app.component('renderJobViewRecord', renderJobViewRecord) //渲染岗位浏览记录
// app.component('tabbar-custom', Tabbar)
for (const path in directives) {
@@ -52,7 +69,10 @@ export function createApp() {
app.provide('deviceInfo', globalFunction.getdeviceInfo());
app.use(SelectPopupPlugin);
app.use(Pinia.createPinia());
const store = Pinia.createPinia();
store.use(createUnistorage());
app.use(store);
return {
app,

View File

@@ -1,13 +1,13 @@
<template>
<view class="collection-content">
<renderJobs
seeDate="applyTime"
<renderDeliveryRecord
v-if="pageState.list.length"
seeDate="applyTime"
:list="pageState.list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="200"></empty>
></renderDeliveryRecord>
<empty v-else :is-position="true"></empty>
</view>
</template>
@@ -41,10 +41,6 @@ onReachBottom(() => {
getJobList();
});
function navToPost(jobId) {
navTo(`/packageA/pages/post/post?jobId=${btoa(jobId)}`);
}
function getJobList(type = 'add') {
if (type === 'refresh') {
pageState.page = 1;
@@ -57,7 +53,7 @@ function getJobList(type = 'add') {
current: pageState.page,
pageSize: pageState.pageSize,
};
$api.createRequest('/app/user/apply/job', params).then((resData) => {
const LoadCache = (resData) => {
const { rows, total } = resData;
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 1);
@@ -70,8 +66,9 @@ function getJobList(type = 'add') {
// pageState.list = resData.rows;
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
console.log(pageState.list);
});
};
$api.createRequestWithCache('/app/user/apply/job', params, 'GET', false, {}, LoadCache).then(LoadCache);
}
</script>
@@ -79,7 +76,8 @@ function getJobList(type = 'add') {
.collection-content{
padding: 1rpx 28rpx 20rpx 28rpx;
background: #F4F4F4;
height: 100%
height: 100%;
min-height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
position: relative;
}
</style>

View File

@@ -21,7 +21,7 @@
<image src="@/static/icon/companyIcon.png" mode=""></image>
</view>
<view class="companyinfo-right">
<view class="row1">{{ companyInfo?.name }}</view>
<view class="row1">{{ dataType === 2 ? companyInfo?.gsmc : companyInfo?.name }}</view>
<view class="row2">
<dict-tree-Label
v-if="companyInfo?.industry"
@@ -30,14 +30,13 @@
></dict-tree-Label>
<span v-if="companyInfo?.industry">&nbsp;</span>
<dict-Label dictType="scale" :value="companyInfo?.scale"></dict-Label>
<span v-if="dataType === 2">{{ companyInfo.gsxy }}</span>
</view>
</view>
</view>
<view class="conetent-info" :class="{ expanded: isExpanded }">
<view class="info-title">公司介绍</view>
<view class="info-desirption">{{ companyInfo.description }}</view>
<!-- <view class="info-title title2">公司地址</view>
<view class="locationCompany"></view> -->
<view class="info-desirption">{{ dataType === 2 ? companyInfo.qyxz : companyInfo.description }}</view>
</view>
<view class="expand" @click="expand">
<text>{{ isExpanded ? '收起' : '展开' }}</text>
@@ -50,13 +49,20 @@
<scroll-view scroll-y class="Detailscroll-view" @scrolltolower="getJobsList('add')">
<view class="views">
<view class="Detail-title"><text class="title">在招职位</text></view>
<!-- 根据 dataType 使用不同的职位列表组件 -->
<renderJobsOutData
v-if="dataType === 2 && pageState.list.length"
:list="pageState.list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobsOutData>
<renderJobs
v-if="pageState.list.length"
v-else-if="dataType !== 2 && pageState.list.length"
:list="pageState.list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="200"></empty>
<empty v-else :is-position="true"></empty>
</view>
</scroll-view>
</view>
@@ -75,6 +81,7 @@ const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction');
const isExpanded = ref(false);
const pageState = reactive({
current: 0,
page: 0,
list: [],
total: 0,
@@ -82,35 +89,113 @@ const pageState = reactive({
pageSize: 10,
});
const companyInfo = ref({});
const pageOptions = ref({});
const dataType = ref(1); // 1: 原数据, 2: 第三方数据
onLoad((options) => {
console.log(options);
getCompanyInfo(options.companyId || options.bussinessId);
// console.log(options);
dataType.value = options.dataType ? parseInt(options.dataType) : 1;
pageOptions.value = options;
if (dataType.value === 2) {
// 第三方数据
getCompanyInfo(options.companyId, options.zphId);
getJobsList('refresh');
} else {
// 原数据
getCompanyInfo(options.companyId || options.bussinessId);
}
});
function companyCollection() {
const companyId = companyInfo.value.companyId;
if (companyInfo.value.isCollection) {
$api.createRequest(`/app/company/collection/${companyId}`, {}, 'DELETE').then((resData) => {
getCompanyInfo(companyId);
$api.msg('取消收藏成功');
if (dataType.value === 2) {
// 第三方数据收藏逻辑
const id = companyInfo.value.id;
const companyId = companyInfo.value.gsID;
const zphId = companyInfo.value.zphID;
if (companyInfo.value.isCollection) {
$api.createRequest(`/app/company/collection/${id}/2`, {}, 'DELETE').then((resData) => {
getCompanyInfo(companyId, zphId);
$api.msg('取消收藏成功');
});
} else {
$api.createRequest(`/app/company/collection/${id}/2`, {}, 'POST').then((resData) => {
getCompanyInfo(companyId, zphId);
$api.msg('收藏成功');
});
}
} else {
// 原数据收藏逻辑
const companyId = companyInfo.value.companyId;
if (companyInfo.value.isCollection) {
$api.createRequest(`/app/company/collection/${companyId}/1`, {}, 'DELETE').then((resData) => {
getCompanyInfo(companyId);
$api.msg('取消收藏成功');
});
} else {
$api.createRequest(`/app/company/collection/${companyId}/1`, {}, 'POST').then((resData) => {
getCompanyInfo(companyId);
$api.msg('收藏成功');
});
}
}
}
function getCompanyInfo(...args) {
if (dataType.value === 2) {
// 第三方数据接口
const [companyId, zphId] = args;
$api.createRequest(`/app/internal/companyThirdPart/${companyId}/${zphId}`, {}, 'GET', true).then((resData) => {
companyInfo.value = resData.data;
});
} else {
$api.createRequest(`/app/company/collection/${companyId}`, {}, 'POST').then((resData) => {
getCompanyInfo(companyId);
$api.msg('收藏成功');
// 原数据接口
const [companyId] = args;
$api.createRequest(`/app/company/${companyId}`, {}, 'GET', true).then((resData) => {
companyInfo.value = resData.data;
getJobsList();
});
}
}
function getCompanyInfo(id) {
$api.createRequest(`/app/company/${id}`).then((resData) => {
companyInfo.value = resData.data;
getJobsList();
function getJobsList(type = 'add') {
if (dataType.value === 2) {
// 第三方数据职位列表
getThirdPartyJobsList(type);
} else {
// 原数据职位列表
getOriginalJobsList(type);
}
}
function getThirdPartyJobsList(type = 'add') {
const { companyId, companyName, zphId } = pageOptions.value;
if (type === 'refresh') {
pageState.current = 1;
pageState.maxPage = 1;
}
if (type === 'add' && pageState.current < pageState.maxPage) {
pageState.current += 1;
}
let params = {
current: pageState.current,
pageSize: pageState.pageSize,
};
$api.createRequest(
`/app/internal/jobThirdPart?gsID=${companyId}&gsmc=${companyName}&zphID=${zphId}`,
params,
'GET',
true
).then((resData) => {
const { rows, total } = resData;
handleJobsListResponse(type, rows, total, 'current');
});
}
function getJobsList(type = 'add') {
function getOriginalJobsList(type = 'add') {
if (type === 'refresh') {
pageState.page = 1;
pageState.maxPage = 1;
@@ -118,25 +203,31 @@ function getJobsList(type = 'add') {
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
let params = {
current: pageState.page,
pageSize: pageState.pageSize,
};
$api.createRequest(`/app/company/job/${companyInfo.value.companyId}`, params).then((resData) => {
$api.createRequest(`/app/company/job/${companyInfo.value.companyId}`, params, 'GET', true).then((resData) => {
const { rows, total } = resData;
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 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);
handleJobsListResponse(type, rows, total, 'page');
});
}
function handleJobsListResponse(type, rows, total, pageKey) {
if (type === 'add') {
const str = pageState.pageSize * (pageState[pageKey] - 1);
const end = pageState.list.length;
const reslist = rows;
pageState.list.splice(str, end, ...reslist);
} else {
pageState.list = rows;
}
pageState.total = total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
}
function expand() {
isExpanded.value = !isExpanded.value;
}
@@ -174,7 +265,6 @@ image {
margin-right: 24rpx
}
.companyinfo-right{
.row1{
font-weight: 500;
font-size: 32rpx;
@@ -210,7 +300,7 @@ image {
}
}
.expanded {
max-height: 1000rpx; // 足够显示完整内容
max-height: 1000rpx;
}
.expand{
display: flex
@@ -237,6 +327,8 @@ image {
background: #F4F4F4;
.views{
padding: 28rpx
min-height: calc(100% - 56rpx)
position: relative
.Detail-title{
font-weight: 600;
font-size: 32rpx;
@@ -285,7 +377,7 @@ image {
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{

View File

@@ -12,11 +12,33 @@
<view class="lf-text">选择想找的工作我的将在首页为你推荐</view>
</view>
<view class="title-ri">
<!-- <text style="color: #256bfa">2</text>
<!-- <text style="color: #256bfa">2</text>
<text>/2</text> -->
</view>
</view>
<view class="content-list">
<view class="list-search">
<uni-icons type="search" color="#333333" size="24"></uni-icons>
<input
class="search-input"
v-model="inputVal"
placeholder="请输入岗位名称"
@input="handelChangeInpute"
@blur="handleBlur"
@focus="handleFocus"
/>
<view class="search-container" v-show="filterList.length">
<view
class="list-item"
v-for="(item, index) in filterList"
@click="handelClickItem(item)"
:key="item.id"
>
<view class="item-txt line_1">{{ item.lable }}</view>
</view>
</view>
</view>
<view class="list-row" v-for="(item, index) in userInfo.jobTitle" :key="index">
<text>{{ item }}</text>
<image
@@ -31,18 +53,85 @@
</view>
<SelectJobs ref="selectJobsModel"></SelectJobs>
</AppLayout>
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog
mode="base"
title="确定添加该期望岗位吗"
type="info"
:duration="2000"
:before-close="true"
@confirm="confirm"
@close="close"
></uni-popup-dialog>
</uni-popup>
</template>
<script setup>
import { inject, ref, reactive } from 'vue';
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
import { onLoad, onUnload } from '@dcloudio/uni-app';
const { $api, navBack } = inject('globalFunction');
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { getUserResume } = useUserStore();
const { userInfo } = storeToRefs(useUserStore());
const popup = ref(null);
const selectJobsModel = ref(null);
const treeDataList = ref([]);
const dataSource = ref([]);
const filterList = ref([]);
const dataItem = ref(null);
const inputVal = ref('');
onLoad(() => {
getTree();
});
function close() {
popup.value.close();
}
function handleBlur() {
setTimeout(() => {
filterList.value = [];
}, 100);
}
function handleFocus() {
const val = inputVal.value.toLowerCase();
if (val && dataSource.value) {
filterList.value = dataSource.value.filter((item) => item.lable.toLowerCase().search(val) !== -1);
} else {
filterList.value = [];
}
}
function confirm() {
const { id } = dataItem.value;
let ids = userInfo.value.jobTitleId + `,${id}`;
const result = dedupeAndCheck(ids);
if (result.hasDuplicate) {
popup.value.close();
$api.msg('期望岗位已重复');
return;
}
complete({ jobTitleId: result.deduplicated });
inputVal.value = '';
popup.value.close();
}
function dedupeAndCheck(str) {
const items = str.split(',').map((s) => s.trim());
const uniqueItems = [...new Set(items)];
const hasDuplicate = uniqueItems.length !== items.length;
return {
deduplicated: uniqueItems.join(','),
hasDuplicate,
};
}
function deleteItem(item, index) {
const ids = userInfo.value.jobTitleId
.split(',')
@@ -55,12 +144,27 @@ function changeJobs() {
selectJobsModel.value?.open({
title: '添加岗位',
maskClick: true,
data: treeDataList.value,
success: (ids, labels) => {
complete({ jobTitleId: ids });
},
});
}
function handelChangeInpute(e) {
const val = e.detail.value.toLowerCase();
if (val && dataSource.value) {
filterList.value = dataSource.value.filter((item) => item.lable.toLowerCase().search(val) !== -1);
} else {
filterList.value = [];
}
}
function handelClickItem(item) {
dataItem.value = item;
popup.value.open();
}
function complete(values) {
if (!values.jobTitleId.length) {
return $api.msg('至少添加一份期望岗位');
@@ -70,9 +174,82 @@ function complete(values) {
getUserResume();
});
}
function getTree() {
const LoadCache = (resData) => {
if (resData.code === 200) {
dataSource.value = flattenTree(resData.data);
treeDataList.value = resData.data;
}
};
$api.createRequestWithCache('/app/common/jobTitle/treeselect', {}, 'GET', false, LoadCache).then(LoadCache);
}
function flattenTree(treeData, parentPath = '') {
let result = [];
treeData.forEach((node) => {
const currentName = node.lable || node.label;
const fullPath = parentPath ? `${parentPath}-${currentName}` : currentName;
const children = node.children || node.chidren;
if (children && children.length > 0) {
result = result.concat(flattenTree(children, fullPath));
} else {
result.push({
id: node.id,
lable: fullPath,
currentName,
});
}
});
return result;
}
</script>
<style lang="stylus" scoped>
.list-search{
height: 76rpx;
background: #FFFFFF;
border-radius: 8rpx;
border: 1px solid #ECECEC;
margin-bottom: 24rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
position: relative;
.search-input{
flex: 1;
padding: 0 20rpx;
font-size: 28rpx;
color: #6C7282;
border: none;
outline: none;
background: transparent;
}
.search-container{
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #FFFFFF;
border: 2rpx solid #ECECEC;
border-top: 0;
z-index: 1;
max-height: 30vh;
overflow: hidden;
.list-item{
height: 80rpx
padding: 0rpx 24rpx;
display: flex;
align-items: center;
justify-items: flex-start;
border-top: 2rpx dashed #e3e3e3;
}
.list-item:hover{
background: #e5e5e5
}
}
}
.btn {
display: flex;
justify-content: space-between;

View File

@@ -24,16 +24,14 @@
</view>
<scroll-view scroll-y class="main-scroll" @scrolltolower="getJobList('add')">
<view class="one-cards">
<view class="mian">
<renderJobs
:list="pageState.list"
v-if="pageState.list.length"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="200"></empty>
<!-- <loadmore ref="loadmoreRef"></loadmore> -->
</view>
<renderJobViewRecord
:list="pageState.list"
v-if="pageState.list.length"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobViewRecord>
<empty v-else></empty>
<!-- <loadmore ref="loadmoreRef"></loadmore> -->
</view>
</scroll-view>
</view>
@@ -88,10 +86,6 @@ function toSelectDate() {
});
}
function navToPost(jobId) {
navTo(`/packageA/pages/post/post?jobId=${btoa(jobId)}`);
}
function searchCollection(e) {
const value = e.detail.value;
pageState.search.jobTitle = value;
@@ -170,9 +164,12 @@ image {
.collection-content
height: 100%
display: flex
flex-direction: column
flex-direction: column;
background: #f4f4f4
.collection-search
padding: 10rpx 20rpx;
background: #FFFFFF;
.search-content
position: relative
@@ -219,6 +216,6 @@ image {
.one-cards{
padding: 0 20rpx 20rpx 20rpx;
background: #f4f4f4
height: 100%
}
</style>
</style>

View File

@@ -50,10 +50,11 @@ function delCollectionCard(item) {
}
function getPremiumList() {
$api.createRequest('/app/company/card').then((resData) => {
const LoadCache = (resData) => {
const { rows, total } = resData;
list.value = rows;
});
};
$api.createRequestWithCache('/app/company/card', {}, 'GET', false, {}, LoadCache).then(LoadCache);
}
function seeDetail(item) {

View File

@@ -27,7 +27,7 @@
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderCompanys>
<empty v-else pdTop="200"></empty>
<empty v-else is-position></empty>
</view>
</AppLayout>
</template>
@@ -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
}
</style>

View File

@@ -11,30 +11,40 @@
<view class="button-click" :class="{ active: type === 1 }" @click="changeType(1)">公司企业</view>
</view>
<view class="coll-main">
<swiper class="swiper" :current="type" @change="changeSwiperType">
<swiper-item class="list">
<swiper class="swiper" :disable-touch="disableTouch" :current="type" @change="changeSwiperType">
<swiper-item
class="list"
@touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove"
@touchend="disableTouch = false"
>
<scroll-view scroll-y class="main-scroll" @scrolltolower="handleScrollToLower">
<view class="mian">
<renderJobs
:list="pageState.list"
<renderJobCollectionRecord
v-if="pageState.list.length"
:list="pageState.list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="200"></empty>
></renderJobCollectionRecord>
<empty v-else></empty>
</view>
</scroll-view>
</swiper-item>
<swiper-item class="list">
<swiper-item
class="list"
@touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove"
@touchend="disableTouch = false"
>
<scroll-view scroll-y class="main-scroll" @scrolltolower="handleScrollToLowerCompany">
<view class="mian">
<renderCompanys
<renderCompanyCollectionRecord
:list="pageCompanyState.list"
v-if="pageCompanyState.list.length"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderCompanys>
<empty v-else pdTop="200"></empty>
></renderCompanyCollectionRecord>
<empty v-else></empty>
</view>
</scroll-view>
</swiper-item>
@@ -69,14 +79,79 @@ const pageCompanyState = reactive({
pageSize: 10,
});
const disableTouch = ref(false);
const startPointX = ref(0);
const totalPage = 2;
const THRESHOLD = 5;
onShow(() => {
getJobList();
getCompanyList();
});
function handleTouchStart(e) {
// 确保有触摸点
if (e.touches.length > 0) {
startPointX.value = e.touches[0].clientX;
disableTouch.value = false;
}
}
function handleTouchMove(e) {
if (e.touches.length === 0) return;
const currentX = e.touches[0].clientX;
const diffX = currentX - startPointX.value;
if (type.value === 0) {
if (diffX > THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
if (type.value === totalPage - 1) {
if (diffX < -THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
disableTouch.value = false;
}
function changeSwiperType(e) {
const newIndex = e.detail.current;
const lastIndex = type.value;
const isSwipingRight = newIndex < lastIndex;
const isSwipingLeft = newIndex > lastIndex;
if (lastIndex === 0 && isSwipingRight) {
disableTouch.value = true;
type.value = 0;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
if (lastIndex === totalPage - 1 && isSwipingLeft) {
disableTouch.value = true;
type.value = lastIndex;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
const current = e.detail.current;
type.value = current;
disableTouch.value = false;
}
function changeType(e) {
@@ -103,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);
@@ -116,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') {
@@ -131,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);
@@ -144,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);
}
</script>
@@ -187,6 +265,7 @@ function getCompanyList(type = 'add') {
.swiper{
height: 100%
.mian{
height: 100%
padding: 0 28rpx 28rpx 28rpx
}
}

View File

@@ -11,15 +11,15 @@
<image src="@/static/icon/companyIcon.png" mode=""></image>
</view>
<view class="companyinfo-right">
<view class="row1 line_2">{{ fairInfo?.name }}</view>
<view class="row1 line_2" @tap="$api.copyText(fairInfo.zphmc)">{{ fairInfo?.zphmc }}</view>
<view class="row2">
<text>{{ fairInfo.location }}</text>
<convert-distance
<text @tap="$api.copyText(fairInfo.jbf)">{{ fairInfo.jbf }}</text>
<!-- <convert-distance
:alat="fairInfo.latitude"
:along="fairInfo.longitude"
:blat="latitudeVal"
:blong="longitudeVal"
></convert-distance>
></convert-distance> -->
</view>
</view>
</view>
@@ -27,36 +27,38 @@
<image class="location-img" src="/static/icon/mapLine.png"></image>
<view class="location-info">
<view class="info">
<text class="info-title">{{ fairInfo.address }}</text>
<text class="info-text">位置</text>
<text class="info-title line_1" @tap="$api.copyText(fairInfo.zphdz)">
{{ fairInfo.zphdz }}
</text>
<!-- <text class="info-text">位置</text> -->
</view>
</view>
</view>
<view class="conetent-info" :class="{ expanded: isExpanded }">
<view class="info-title">内容描述</view>
<view class="info-desirption">{{ fairInfo.description }}</view>
<view class="info-desirption">{{ fairInfo.zphjj }}</view>
<!-- <view class="info-title title2">公司地址</view>
<view class="locationCompany"></view> -->
<view class="company-times">
<view class="info-title">内容描述</view>
<view class="card-times">
<view class="time-left">
<view class="left-date">{{ parseDateTime(fairInfo.startTime).time }}</view>
<view class="left-dateDay">{{ parseDateTime(fairInfo.startTime).date }}</view>
<view class="left-date">{{ parseDateTime(fairInfo.zphjbsj).time }}</view>
<view class="left-dateDay">{{ parseDateTime(fairInfo.zphjbsj).date }}</view>
</view>
<view class="line"></view>
<view class="time-center">
<view class="center-date">
{{ getTimeStatus(fairInfo.startTime, fairInfo.endTime).statusText }}
{{ getTimeStatus(fairInfo.zphjbsj, fairInfo.zphjzsj).statusText }}
</view>
<view class="center-dateDay">
{{ getHoursBetween(fairInfo.startTime, fairInfo.endTime) }}小时
{{ getHoursBetween(fairInfo.zphjbsj, fairInfo.zphjzsj) }}小时
</view>
</view>
<view class="line"></view>
<view class="time-right">
<view class="left-date">{{ parseDateTime(fairInfo.endTime).time }}</view>
<view class="left-dateDay">{{ parseDateTime(fairInfo.endTime).date }}</view>
<view class="left-date">{{ parseDateTime(fairInfo.zphjzsj).time }}</view>
<view class="left-dateDay">{{ parseDateTime(fairInfo.zphjzsj).date }}</view>
</view>
</view>
</view>
@@ -69,18 +71,19 @@
src="@/static/icon/downs.png"
></image>
</view>
<scroll-view scroll-y class="Detailscroll-view">
<scroll-view scroll-y class="Detailscroll-view" @scrolltolower="getCompanyList('add')">
<view class="views">
<view class="Detail-title">
<text class="title">参会单位{{ companyList.length }}</text>
<text class="title">参会单位{{ pageState.total }}</text>
</view>
<renderCompanys
v-if="companyList.length"
:list="companyList"
<renderCompanysOutData
v-if="pageState.list.length"
:zphId="zphId"
:list="pageState.list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderCompanys>
<empty v-else pdTop="200"></empty>
></renderCompanysOutData>
<empty v-else is-position></empty>
</view>
</scroll-view>
</view>
@@ -110,28 +113,74 @@ const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const isExpanded = ref(false);
const fairInfo = ref({});
const companyList = ref([]);
const pageState = reactive({
current: 0,
list: [],
total: 0,
maxPage: 1,
pageSize: 10,
});
const hasnext = ref(true);
const zphId = ref('');
const pageOptions = ref({});
onLoad((options) => {
getCompanyInfo(options.jobFairId);
zphId.value = options.jobFairId;
pageOptions.value = options;
getJobFairInfo(options.jobFairId, options.jobFairName);
getCompanyList('refresh');
});
function getCompanyInfo(id) {
$api.createRequest(`/app/fair/${id}`).then((resData) => {
function getJobFairInfo(id, name) {
$api.createRequest(`/app/internal/jobFairThirdPart/${id}`, {}, 'GET', true).then((resData) => {
fairInfo.value = resData.data;
companyList.value = resData.data.companyList;
hasAppointment();
});
}
function getCompanyList(type = 'add') {
const { jobFairId, jobFairName } = pageOptions.value;
if (type === 'refresh') {
pageState.current = 1;
pageState.maxPage = 1;
}
if (type === 'add' && pageState.current < pageState.maxPage) {
pageState.current += 1;
}
let params = {
current: pageState.current,
pageSize: 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 = () => {
const isTimePassed = (timeStr) => {
if (!timeStr) return false;
const targetTime = new Date(timeStr.replace(/-/g, '/')).getTime(); // 兼容格式
const now = Date.now();
return now < targetTime;
};
hasnext.value = isTimePassed(fairInfo.value.startTime);
hasnext.value = isTimePassed(fairInfo.value.zphjbsj);
};
function openMap(lat, lng, name = '位置') {
@@ -149,16 +198,16 @@ function expand() {
// 取消/收藏岗位
function applyExhibitors() {
const fairId = fairInfo.value.jobFairId;
const fairId = fairInfo.value.zphID;
if (fairInfo.value.isCollection) {
// $api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => {
// getCompanyInfo(fairId);
// $api.msg('取消预约成功');
// });
$api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => {
getJobFairInfo(fairId);
$api.msg('取消预约成功');
});
$api.msg('已预约成功');
} else {
$api.createRequest(`/app/fair/collection/${fairId}`, {}, 'POST').then((resData) => {
getCompanyInfo(fairId);
getJobFairInfo(fairId);
$api.msg('预约成功');
});
}
@@ -387,6 +436,8 @@ image {
background: #F4F4F4;
.views{
padding: 28rpx
min-height: calc(100% - 56rpx);
position: relative
.Detail-title{
font-weight: 600;
font-size: 32rpx;

View File

@@ -1,109 +1,213 @@
<template>
<AppLayout title="我的简历" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
<template #headerleft>
<view class="btn">
<image src="@/static/icon/back-white.png" @click="navBack"></image>
</view>
</template>
<view class="mys-container">
<!-- 个人信息 -->
<view class="card-top" style="margin-top: 12rpx; padding: 0; background: none">
<view class="mys-tops btn-feel">
<view class="tops-left">
<view class="name">
<text>{{ userInfo.name || "编辑用户名" }}</text>
<view class="edit-icon mar_le10">
<image class="button-click" src="@/static/icon/edit1.png" @click="navTo('/packageA/pages/personalInfo/personalInfo')"></image>
</view>
<AppLayout title="我的简历" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
<template #headerleft>
<view class="btn">
<image src="@/static/icon/back-white.png" @click="navBack"></image>
</view>
<view class="subName">
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
<text class="mar_ri10">{{ userInfo.age }}</text>
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
<dict-Label class="mar_ri10" dictType="affiliation" :value="userInfo.politicalAffiliation"></dict-Label>
</view>
<view class="subName">{{ userInfo.phone }}</view>
</view>
<view class="tops-right">
<view class="right-imghead">
<image v-if="userInfo.avatar" :src="userInfo.avatar"></image>
<image v-else-if="userInfo.sex == '0'" src="@/static/icon/boy.png"></image>
<image v-else src="@/static/icon/girl.png"></image>
</view>
<view class="right-sex">
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy1.png"></image>
<image v-else src="@/static/icon/girl1.png"></image>
</view>
</view>
</template>
<view
v-if="userInfo.resumeOcrStatus && showNotice"
class="notice-line"
:class="userInfo.resumeOcrStatus?.includes('成功') ? 'green' : 'blue'"
>
<image
v-if="userInfo.resumeOcrStatus?.includes('成功')"
class="icon"
src="@/static/icon/notice-green.png"
/>
<image v-else class="icon" src="@/static/icon/notice-blue.png" />
<view class="text">{{ userInfo.resumeOcrStatus }}</view>
<image
@click="closeNotice"
v-if="userInfo.resumeOcrStatus?.includes('成功')"
class="close"
src="@/static/icon/close-green.png"
/>
<image @click="closeNotice" v-else class="close" src="@/static/icon/close-blue.png" />
</view>
<!-- 求职期望 -->
<view class="mys-line">
<view class="line"></view>
<view class="mys-container">
<!-- 个人信息 -->
<view class="card-top" style="margin-top: 12rpx; padding: 0; background: none">
<view class="mys-tops">
<view class="tops-left">
<view class="name">
<text>{{ userInfo.name || '编辑用户名' }}</text>
<view class="edit-icon mar_le10">
<image
class="button-click"
src="@/static/icon/edit1.png"
@click="navTo('/packageA/pages/personalInfo/personalInfo')"
></image>
</view>
</view>
<view class="subName">
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
<text class="mar_ri10">{{ userInfo.age }}</text>
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
<dict-Label
class="mar_ri10"
dictType="affiliation"
:value="userInfo.politicalAffiliation"
></dict-Label>
</view>
<view class="subName">{{ userInfo.phone }}</view>
</view>
<view class="tops-right">
<view class="right-imghead">
<image v-if="userInfo.avatar" :src="userInfo.avatar"></image>
<image v-else-if="userInfo.sex == '0'" src="@/static/icon/boy.png"></image>
<image v-else src="@/static/icon/girl.png"></image>
</view>
<view class="right-sex">
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy1.png"></image>
<image v-else src="@/static/icon/girl1.png"></image>
</view>
</view>
</view>
<!-- 求职期望 -->
<view class="mys-line">
<view class="line"></view>
</view>
<view class="mys-info">
<view class="mys-h4">
<view>求职期望</view>
<image
class="icon"
src="@/static/icon/edit1.png"
@click="navTo('/packageA/pages/jobExpect/jobExpect')"
></image>
</view>
<view class="mys-text">
<text>期望薪资</text>
<text>{{ userInfo.salaryMin / 1000 }}k-{{ userInfo.salaryMax / 1000 }}k</text>
</view>
<view class="mys-text">
<text>期望工作地</text>
<text>青岛市-</text>
<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label>
</view>
<view class="mys-list">
<view class="cards button-click" v-for="(title, index) in userInfo.jobTitle" :key="index">
{{ title }}
</view>
</view>
</view>
</view>
<view class="card-top" style="margin-top: 24rpx">
<view class="mys-info" style="padding: 0">
<view class="mys-h4">
<text>工作经历</text>
<view class="mys-edit-icon btn-tada" @click="navTo('/packageA/pages/workExp/workExp')">
<image class="icon button-click btn-feel" src="@/static/icon/plus.png"></image>
<view class="txt">添加</view>
</view>
</view>
<view class="exp-item button-click" v-for="item in userInfo.workExp" :key="item.id">
<view class="fl_box fl_justbet mar_top15">
<view class="fs_16">{{ item.company }}</view>
<image
class="icon btn-feel"
src="@/static/icon/edit1.png"
@click="navTo(`/packageA/pages/workExp/workExp?id=${item.id}`)"
></image>
</view>
<view class="mys-text fl_box fl_justbet">
<text class="color_333333 fs_14">{{ item.position }}</text>
<text class="datetext">{{ item.startTime }}--{{ item.endTime || '至今' }}</text>
</view>
<view class="mys-text">
<text>{{ item.duty }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="mys-info btn-feel">
<view class="mys-h4">
<view>求职期望</view>
<image class="icon" src="@/static/icon/edit1.png" @click="navTo('/packageA/pages/jobExpect/jobExpect')"></image>
</view>
<view class="mys-text">
<text>期望薪资</text>
<text>{{ userInfo.salaryMin / 1000 }}k-{{ userInfo.salaryMax / 1000 }}k</text>
</view>
<view class="mys-text">
<text>期望工作地</text>
<text>青岛市-</text>
<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label>
</view>
<view class="mys-list">
<view class="cards button-click" v-for="(title, index) in userInfo.jobTitle" :key="index">
{{ title }}
<template #footer>
<view class="footer-container">
<view class="footer-button btn-feel" @click="chooseResume">上传简历</view>
</view>
</view>
</view>
</view>
<view class="card-top" style="margin-top: 24rpx">
<view class="mys-info" style="padding: 0">
<view class="mys-h4 btn-feel">
<text>工作经历</text>
<view class="mys-edit-icon btn-feel" @click="navTo('/packageA/pages/workExp/workExp')">
<image class="icon button-click btn-feel" src="@/static/icon/plus.png"></image>
<view class="txt">添加</view>
</view>
</view>
<view class="exp-item btn-feel" v-for="item in userInfo.workExp" :key="item.id">
<view class="fl_box fl_justbet mar_top15">
<view class="fs_16">{{ item.company }}</view>
<image class="icon btn-feel" src="@/static/icon/edit1.png" @click="navTo(`/packageA/pages/workExp/workExp?id=${item.id}`)"></image>
</view>
<view class="mys-text fl_box fl_justbet">
<text class="color_333333 fs_14">{{ item.position }}</text>
<text class="datetext">{{ item.startTime }}--{{ item.endTime || "至今" }}</text>
</view>
<view class="mys-text">
<text>{{ item.duty }}</text>
</view>
</view>
</view>
</view>
</view>
<template #footer>
<view class="footer-container">
<view class="footer-button btn-feel">上传简历</view>
</view>
</template>
</AppLayout>
</template>
</AppLayout>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted, computed } from "vue";
const { $api, navTo, navBack } = inject("globalFunction");
import { onLoad, onShow } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia";
import useUserStore from "@/stores/useUserStore";
import useDictStore from "@/stores/useDictStore";
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
const { $api, navTo, navBack } = inject('globalFunction');
import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
const { userInfo } = storeToRefs(useUserStore());
const { getUserResume } = useUserStore();
const { getDictData, oneDictData } = useDictStore();
import config from '@/config.js';
const showNotice = ref(true);
onLoad(() => {
getUserResume();
});
function closeNotice() {
showNotice.value = false;
}
function chooseResume() {
uni.chooseImage({
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
count: 1,
success: ({ tempFilePaths, tempFiles }) => {
uploadResume(tempFilePaths[0], true)
.then((res) => {
res = JSON.parse(res);
getUserResume();
$api.msg('上传成功');
})
.catch((err) => {
$api.msg('上传失败');
});
},
fail: (error) => {},
});
}
function uploadResume(tempFilePath, loading) {
if (loading) {
uni.showLoading({
title: '请稍后',
mask: true,
});
}
let Authorization = '';
if (useUserStore().token) {
Authorization = `${useUserStore().token}`;
}
const header = {};
header['Authorization'] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.uploadFile({
url: config.baseUrl + '/app/oss/uploadToObs',
filePath: tempFilePath,
name: 'file',
header,
success: (uploadFileRes) => {
if (uploadFileRes.statusCode === 200) {
resolve(uploadFileRes.data);
} else {
reject();
}
},
fail: (err) => {
reject(err);
},
complete: () => {
if (loading) {
uni.hideLoading();
}
},
});
});
}
</script>
<style lang="stylus" scoped>
@@ -133,6 +237,36 @@ const { getDictData, oneDictData } = useDictStore();
text-align: center;
}
}
.notice-line
width 100%;
height:60rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding:0 30rpx
margin-bottom: 20rpx
.icon
width:35rpx;
height:35rpx;
.text
flex: 1;
overflow hidden
padding:0 25rpx
.close
width:25rpx;
height:25rpx;
.notice-line.blue{
background: #E8F1FF
color: #1677ff
}
.notice-line.green{
background: #D4FFF1
color: #38bb8f
}
image{
width: 100%;
height: 100%

View File

@@ -24,12 +24,11 @@
<scroll-view class="scroll-view" scroll-y @scrolltolower="scrollBottom">
<view class="list">
<renderJobs
:list="pageState.list"
v-if="pageState.list.length"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="200"></empty>
<empty v-else></empty>
</view>
</scroll-view>
</view>
@@ -140,6 +139,7 @@ function getList(type = 'add', loading = true) {
height: 100%
.list{
padding: 0 28rpx 28rpx 28rpx
height: calc(100% - 28rpx)
}
}
}

View File

@@ -28,9 +28,12 @@ watch(
() => props.value,
(newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
const { skill, experience, education, salary, age, location } = newVal.radarChart;
const labels = ['学历', '年龄', '工作地', '技能', '工作经验', '期望薪资'];
const data = [education, age, location, skill, experience, salary].map((item) => item * 0.05);
// const { skill, experience, education, salary, age, location } = newVal.radarChart;
const { experience, education, salary, age, location } = newVal.radarChart;
// const labels = ['学历', '年龄', '工作地', '技能', '工作经验', '期望薪资'];
const labels = ['学历', '年龄', '工作地', '工作经验', '期望薪资'];
// const data = [education, age, location, skill, experience, salary].map((item) => item * 0.05);
const data = [education, age, location, experience, salary].map((item) => item * 0.05);
rawRadarChart(labels, data);
}
},
@@ -43,10 +46,8 @@ function rawRadarChart(labels, data) {
const height = 80;
const centerX = 150;
const centerY = 125;
// const data = [2, 3.5, 5, 3.5, 5, 3.5]; // 示例数据
// const labels = ['火烧', '泡水', '事故', '外观', '部件', '火烧'];
const colors = ['#F5F5F5', '#F5F5F5', '#F5F5F5', '#F5F5F5', '#F5F5F5'];
const maxScore = 5; // 数据最大值
const maxScore = 5;
const angleStep = (2 * Math.PI) / labels.length;
@@ -62,9 +63,8 @@ function rawRadarChart(labels, data) {
ctx.fill();
//多边形圈
// 多边形圈
for (let i = 5; i > 0; i--) {
ctx.setStrokeStyle(colors[i - 1]); // 设置边框颜色
ctx.setStrokeStyle(colors[i - 1]);
ctx.beginPath();
labels.forEach((label, index) => {
const x = centerX + (width / 5) * i * Math.cos(angleStep * index - Math.PI / 2);
@@ -76,19 +76,20 @@ function rawRadarChart(labels, data) {
}
});
ctx.closePath();
ctx.stroke(); // 只描边,不填充
ctx.stroke();
}
// //竖线
//竖线
labels.forEach((label, index) => {
ctx.setStrokeStyle('#F5F5F5');
ctx.setFillStyle('#F5F5F5');
ctx.beginPath();
const x1 = centerX + width * 0.6 * Math.sin(angleStep * index);
const y1 = centerY + height * 0.6 * Math.cos(angleStep * index);
const x = centerX + width * Math.sin(angleStep * index);
const y = centerY + height * Math.cos(angleStep * index);
// 修改坐标计算,使用与多边形圈相同的角度计算方式
const x1 = centerX + width * 0.6 * Math.cos(angleStep * index - Math.PI / 2);
const y1 = centerY + height * 0.6 * Math.sin(angleStep * index - Math.PI / 2);
const x = centerX + width * Math.cos(angleStep * index - Math.PI / 2);
const y = centerY + height * Math.sin(angleStep * index - Math.PI / 2);
ctx.moveTo(x1, y1);
ctx.lineTo(x, y);
@@ -102,11 +103,11 @@ function rawRadarChart(labels, data) {
ctx.setFillStyle('rgba(37,107,250, 0.24)');
ctx.setLineWidth(2);
ctx.beginPath();
const pointList = []; // 记录每个点的位置,等会画小圆点
const pointList = [];
data.forEach((score, index) => {
const x = centerX + width * (score / maxScore) * Math.cos(angleStep * index - Math.PI / 2);
const y = centerY + height * (score / maxScore) * Math.sin(angleStep * index - Math.PI / 2);
pointList.push({ x, y }); // 保存位置
pointList.push({ x, y });
if (index === 0) {
ctx.moveTo(x, y);
} else {
@@ -118,15 +119,16 @@ function rawRadarChart(labels, data) {
ctx.stroke();
// 绘制每个小圆点
ctx.setFillStyle('#256BFA'); // 小圆点颜色(你可以改)
ctx.setFillStyle('#256BFA');
pointList.forEach((point) => {
ctx.beginPath();
ctx.arc(point.x, point.y, 4, 0, 2 * Math.PI); // 半径 4可以自己调大小
ctx.arc(point.x, point.y, 4, 0, 2 * Math.PI);
ctx.fill();
});
// 绘制标签
// 绘制标签
ctx.setTextAlign('center');
ctx.setTextBaseline('middle');
labels.forEach((label, index) => {
const x = centerX + (width + 30) * Math.cos(angleStep * index - Math.PI / 2);
@@ -137,30 +139,9 @@ function rawRadarChart(labels, data) {
ctx.setFontSize(12);
ctx.font = 'bold 12px sans-serif';
ctx.fillText(label, x, y);
// ctx.setFillStyle('#A2A4A2');
// ctx.font = '12px sans-serif';
// ctx.setFontSize(12);
// ctx.fillText(data[index], x, y + 16);
});
ctx.draw();
//转图片
// uni.canvasToTempFilePath({
// x: 0,
// y: 0,
// width: 320,
// height: 320,
// destWidth: 840,
// destHeight: 840,
// canvasId: 'radarCanvas',
// success: (res) => {
// // 在H5平台下tempFilePath 为 base64
// src = res.tempFilePath;
// },
// });
}
</script>

View File

@@ -9,8 +9,8 @@
width: isFullScreen ? '100%' : videoWidth + 'rpx',
height: isFullScreen ? '100vh' : videoHeight + 'rpx',
}"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove"
@touchend="handleTouchEnd"
@touchmove.stop.prevent
>
@@ -187,4 +187,4 @@ defineExpose({ open });
background: rgba(255, 255, 255, 0.2);
border-radius: 4rpx;
}
</style>
</style>

View File

@@ -6,39 +6,59 @@
</view>
</template>
<template #headerright>
<!-- <view class="btnshare">
<image src="@/static/icon/share.png" @click="shareJob"></image>
</view> -->
<view class="btn mar_ri10">
<image src="@/static/icon/collect3.png" v-if="!jobInfo.isCollection" @click="jobCollection"></image>
<image src="@/static/icon/collect2.png" v-else @click="jobCollection"></image>
</view>
</template>
<!-- 根据 dataType 显示不同内容 -->
<view class="content" v-show="!isEmptyObject(jobInfo)">
<!-- 顶部信息区域 -->
<view class="content-top btn-feel">
<view class="top-salary">
<view class="top-salary" v-if="jobInfo.maxSalary">
<Salary-Expectation
:max-salary="jobInfo.maxSalary"
:min-salary="jobInfo.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<view class="top-name">{{ jobInfo.jobTitle }}</view>
<view class="top-salary" v-else>
<Salary-Expectation
:max-salary="jobInfo.maxSalary"
:min-salary="jobInfo.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<view class="top-name">{{ dataType === 2 ? jobInfo.gwmc : jobInfo.jobTitle }}</view>
<view class="top-info">
<view class="info-img"><image src="/static/icon/post12.png"></image></view>
<view class="info-text">
<!-- 第三方数据展示 -->
<view class="info-text" v-if="dataType === 2">
{{ jobInfo.xlyq == '不限' ? '学历不限' : jobInfo.xlyq }}
</view>
<!-- 原数据展示 -->
<view class="info-text" v-else>
<dict-Label dictType="experience" :value="jobInfo.experience"></dict-Label>
</view>
<view class="info-img mar_le20"><image src="/static/icon/post13.png"></image></view>
<view class="info-text">
<!-- 第三方数据展示 -->
<view class="info-text" v-if="dataType === 2">
{{ jobInfo.gwgzjy == '不限' ? '经验不限' : jobInfo.gwgzjy }}
</view>
<!-- 原数据展示 -->
<view class="info-text" v-else>
<dict-Label dictType="education" :value="jobInfo.education"></dict-Label>
</view>
</view>
<view class="position-source">
<text>来源&nbsp;</text>
{{ jobInfo.dataSource }}
{{ dataType === 2 ? '青岛人才网' : jobInfo.dataSource }}
</view>
</view>
<!-- AI讲解区域 -->
<view class="ai-explain" v-if="jobInfo.isExplain">
<view class="exbg">
<view class="explain-left btn-shaky">
@@ -48,38 +68,42 @@
<view class="explain-right button-click" @click="seeExplain">点击查看</view>
</view>
</view>
<!-- 职位描述区域 -->
<view class="content-card">
<view class="card-title">
<text class="title">职位描述</text>
</view>
<view class="description" :style="{ whiteSpace: 'pre-wrap' }">
{{ jobInfo.description }}
{{ dataType === 2 ? jobInfo.gwms : jobInfo.description }}
</view>
</view>
<!-- 公司信息区域 -->
<view class="content-card">
<view class="card-title">
<text class="title">公司信息</text>
<text
class="btntext button-click"
@click="navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${jobInfo.company.companyId}`)"
>
单位详情
</text>
<text class="btntext button-click" @click="handleCompanyDetail">单位详情</text>
</view>
<view class="company-info">
<view class="companyinfo-left">
<image src="@/static/icon/companyIcon.png" mode=""></image>
</view>
<view class="companyinfo-right">
<view class="row1">{{ jobInfo.company?.name }}</view>
<view class="row1">{{ dataType === 2 ? jobInfo.gsmc : jobInfo.company?.name }}</view>
<view class="row2">
<dict-tree-Label
v-if="jobInfo.company?.industry"
v-if="dataType !== 2 && jobInfo.company?.industry"
dictType="industry"
:value="jobInfo.company?.industry"
></dict-tree-Label>
<span v-if="jobInfo.company?.industry">&nbsp;</span>
<dict-Label dictType="scale" :value="jobInfo.company?.scale"></dict-Label>
<span v-if="dataType !== 2 && jobInfo.company?.industry">&nbsp;</span>
<dict-Label
v-if="dataType !== 2"
dictType="scale"
:value="jobInfo.company?.scale"
></dict-Label>
<span v-if="dataType === 2">{{ jobInfo.qyxz }}</span>
</view>
<view class="row2">
<text>在招</text>
@@ -97,14 +121,16 @@
></map>
</view>
</view>
<view class="content-card">
<!-- 竞争力分析区域 -->
<view class="content-card" v-if="dataType !== 2 && raderData?.totalApplicants > 2">
<view class="card-title">
<text class="title">竞争力分析</text>
</view>
<view class="description">
三个月内共15位求职者申请你的简历匹配度为{{ raderData.matchScore }}排名位于第{{
raderData.rank
}}超过{{ raderData.percentile }}%的竞争者处在优秀位置
三个月内共{{ raderData.totalApplicants }}位求职者申请你的简历匹配度为{{
raderData.matchScore
}}排名位于第{{ raderData.rank }}超过{{ raderData.percentile }}%的竞争者处在优秀位置
</view>
<RadarMap :value="raderData"></RadarMap>
@@ -125,11 +151,25 @@
</view>
</view>
</view>
<view style="height: 24px"></view>
</view>
<template #footer>
<view class="footer">
<view class="btn-wq button-click" @click="jobApply">立即前往</view>
<view
v-if="dataType == 2"
class="btn-wq button-click"
:class="{ 'btn-des': jobInfo.isApply }"
@click="jobApply"
>
<span v-if="jobInfo.isApply">已投递</span>
<span v-if="!jobInfo.isApply">立即投递</span>
</view>
<view v-else class="btn-wq button-click" @click="jobApply">
<span v-if="jobInfo.isApply">立即前往</span>
<span v-if="!jobInfo.isApply">立即投递</span>
</view>
</view>
</template>
<VideoPlayer ref="videoPalyerRef" />
@@ -143,8 +183,10 @@ import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue';
import RadarMap from './component/radarMap.vue';
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import config from '@/config.js';
const matchingDegree = ref(['一般', '良好', '优秀', '极好']);
const currentStep = ref(1);
const companyCount = ref(0);
@@ -155,9 +197,11 @@ const jobIdRef = ref();
const raderData = ref({});
const videoPalyerRef = ref(null);
const explainUrlRef = ref('');
const dataType = ref(1); // 1: 原数据, 2: 第三方数据
onLoad((option) => {
if (option.jobId) {
dataType.value = option.dataType ? parseInt(option.dataType) : 1;
initLoad(option);
}
});
@@ -165,6 +209,7 @@ onLoad((option) => {
onShow(() => {
const option = parseQueryParams(); // 兼容微信内置浏览器
if (option.jobId) {
dataType.value = option.dataType ? parseInt(option.dataType) : 1;
initLoad(option);
}
});
@@ -180,91 +225,166 @@ function initLoad(option) {
function seeExplain() {
if (jobInfo.value.explainUrl) {
videoPalyerRef.value?.open(jobInfo.value.explainUrl);
// console.log(jobInfo.value.explainUrl);
// explainUrlRef.value = jobInfo.value.explainUrl;
}
}
function getDetail(jobId) {
return new Promise((reslove, reject) => {
$api.createRequest(`/app/job/${jobId}`).then((resData) => {
if (dataType.value === 2) {
// 第三方数据接口
return new Promise((reslove, reject) => {
$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);
}
});
});
} else {
// 原数据接口
$api.createRequest(`/app/job/${jobId}`, {}, 'GET', true).then((resData) => {
const { latitude, longitude, companyName, companyId } = resData.data;
jobInfo.value = resData.data;
reslove(resData.data);
getCompanyIsAJobs(companyId);
getCompetivetuveness(jobId);
if (latitude && longitude) {
mapCovers.value = [
{
latitude: latitude,
longitude: longitude,
iconPath: point,
label: {
content: companyName,
textAlign: 'center',
padding: 3,
fontSize: 12,
bgColor: '#FFFFFF',
anchorX: getTextWidth(companyName), // X 轴调整,负数向左
borderRadius: 5,
},
width: 34,
},
];
initMapCovers(latitude, longitude, companyName);
}
});
});
}
}
function getCompanyIsAJobs(companyId) {
$api.createRequest(`/app/company/count/${companyId}`).then((resData) => {
companyCount.value = resData.data;
});
function initMapCovers(latitude, longitude, companyName) {
mapCovers.value = [
{
latitude: latitude,
longitude: longitude,
iconPath: point,
label: {
content: companyName,
textAlign: 'center',
padding: 3,
fontSize: 12,
bgColor: '#FFFFFF',
anchorX: getTextWidth(companyName),
borderRadius: 5,
},
width: 34,
},
];
}
function getCompanyIsAJobs(...args) {
if (dataType.value === 2) {
// 第三方数据获取公司职位数量
const [gsID, gsmc, zphID] = args;
$api.createRequest(`/app/internal/jobThirdPart?gsID=${gsID}&gsmc=${gsmc}&zphID=${zphID}`).then((resData) => {
companyCount.value = resData.total;
});
} else {
// 原数据获取公司职位数量
const [companyId] = args;
$api.createRequest(`/app/company/count/${companyId}`).then((resData) => {
companyCount.value = resData.data;
});
}
}
function getTextWidth(text, size = 12) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = `${12}px Arial`;
return -(context.measureText(text).width / 2) - 20; // 计算文字中心点
return -(context.measureText(text).width / 2) - 20;
}
function getCompetivetuveness(jobId) {
$api.createRequest(`/app/job/competitiveness/${jobId}`, {}, 'GET').then((resData) => {
raderData.value = resData.data;
currentStep.value = resData.data.matchScore * 0.04;
});
if (dataType.value !== 2) {
$api.createRequest(`/app/job/competitiveness/${jobId}`, {}, 'GET').then((resData) => {
raderData.value = resData.data;
currentStep.value = resData.data.matchScore * 0.04;
});
}
}
// 申请岗位
function jobApply() {
const jobId = jobInfo.value.jobId;
if (jobInfo.value.isApply) {
const jobUrl = jobInfo.value.jobUrl;
return window.open(jobUrl);
if (dataType.value === 2) {
// 第三方数据申请逻辑
const params = {
jobid: jobInfo.value.id,
jobname: jobInfo.value.gwmc,
};
if (jobInfo.value.isApply) {
$api.msg('已经投递过该岗位了~');
return;
} else {
$api.createRequest(`/app/internal/sendResume`, params, 'POST').then((resData) => {
$api.msg('投递成功');
getDetail(jobIdRef.value);
});
}
} else {
$api.createRequest(`/app/job/apply/${jobId}`, {}, 'GET').then((resData) => {
getDetail(jobId);
$api.msg('申请成功');
// 原数据申请逻辑
const jobId = jobInfo.value.jobId;
if (jobInfo.value.isApply) {
const jobUrl = jobInfo.value.jobUrl;
return window.open(jobUrl);
});
} else {
$api.createRequest(`/app/job/apply/${jobId}`, {}, 'GET').then((resData) => {
getDetail(jobId);
$api.msg('申请成功');
const jobUrl = jobInfo.value.jobUrl;
return window.open(jobUrl);
});
}
}
}
// 取消/收藏岗位
function jobCollection() {
const jobId = jobInfo.value.jobId;
if (jobInfo.value.isCollection) {
$api.createRequest(`/app/job/collection/${jobId}`, {}, 'DELETE').then((resData) => {
getDetail(jobId);
$api.msg('取消收藏成功');
});
if (dataType.value === 2) {
// 第三方数据收藏逻辑
const id = jobInfo.value.id;
if (jobInfo.value.isCollection) {
$api.createRequest(`/app/job/collection/${id}/2`, {}, 'DELETE').then((resData) => {
getDetail(jobIdRef.value);
$api.msg('取消收藏成功');
});
} else {
$api.createRequest(`/app/job/collection/${id}/2`, {}, 'POST').then((resData) => {
getDetail(jobIdRef.value);
$api.msg('收藏成功');
});
}
} else {
$api.createRequest(`/app/job/collection/${jobId}`, {}, 'POST').then((resData) => {
getDetail(jobId);
$api.msg('收藏成功');
});
// 原数据收藏逻辑
const jobId = jobInfo.value.jobId;
if (jobInfo.value.isCollection) {
$api.createRequest(`/app/job/collection/${jobId}/1`, {}, 'DELETE').then((resData) => {
getDetail(jobId);
$api.msg('取消收藏成功');
});
} else {
$api.createRequest(`/app/job/collection/${jobId}/1`, {}, 'POST').then((resData) => {
getDetail(jobId);
$api.msg('收藏成功');
});
}
}
}
// 处理公司详情跳转
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`
);
} else {
navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${jobInfo.value.company.companyId}`);
}
}
@@ -285,6 +405,7 @@ function getClass(index) {
</script>
<style lang="stylus" scoped>
/* 样式保持不变与现在的post页面相同 */
.btnback{
width: 64rpx;
height: 64rpx;
@@ -308,15 +429,14 @@ image {
.progress-container {
display: flex;
align-items: center;
gap: 8rpx; /* 间距 */
gap: 8rpx;
margin-top: 24rpx
}
.progress-text{
margin-top: 8rpx
display: flex;
align-items: center;
gap: 8rpx; /* 间距 */
gap: 8rpx;
justify-content: space-around
width: 100%
font-weight: 400;
@@ -335,12 +455,10 @@ image {
transition: background-color 0.3s;
}
/* 完整激活格子 */
.progress-item.active {
background: linear-gradient(to right, #256bfa, #8c68ff);
}
/* 当前进度进行中的格子 */
for i in 0..100
.progress-item.half{i}::before
content ''
@@ -352,36 +470,12 @@ for i in 0..100
background linear-gradient(to right, #256bfa (i)%, #eaeaea (i)%)
border-radius 24rpx
.card-footer{
.footer-title{
font-weight: 600;
font-size: 28rpx;
color: #000000;
}
.footer-content{
.content-line{
display: grid
grid-template-columns: repeat(4, 1fr)
background: linear-gradient( to left, #9E74FD 0%, #256BFA 100%);
border-radius: 10rpx
.line-pargrah{
height: 20rpx;
position: relative
}
.line-pargrah::after{
position: absolute;
content: '';
right: 10
top: 0
width: 6rpx
height: 20rpx
background: #FFFFFF
}
}
}
}
// ai
.ai-explain{
@@ -527,7 +621,6 @@ for i in 0..100
margin-right: 24rpx
}
.companyinfo-right{
.row1{
font-weight: 500;
font-size: 32rpx;
@@ -564,5 +657,9 @@ for i in 0..100
text-align: center;
line-height: 90rpx
}
.btn-des{
background: #6697FB;
box-shadow: 0rpx -4rpx 24rpx 0rpx rgba(11,44,112,0.12);
}
}
</style>

View File

@@ -12,32 +12,41 @@
</view>
</view>
<view class="main">
<scroll-view scroll-y>
<view v-if="pageState.list.length">
<scroll-view class="height-100" scroll-y>
<view>
<view class="card" v-for="(item, index) in pageState.list" :key="index">
<view @click="navTo('/packageA/pages/exhibitors/exhibitors?jobFairId=' + item.jobFairId)">
<view
@click="
navTo(
'/packageA/pages/exhibitors/exhibitors?jobFairId=' +
item.zphID +
'&jobFairName=' +
item.zphmc
)
"
>
<view class="card-row">
<Countdown startTime="item.startTime" :endTime="item.endTime" />
<Countdown :startTime="item.zphjbsj" :endTime="item.zphjzsj" />
</view>
<view class="card-Title">{{ item.name }}</view>
<view class="card-Title">{{ item.zphmc }}</view>
<view class="card-row">
<view class="rowleft">{{ item.location }}</view>
<view class="rowright">
<convert-distance
<view class="rowleft">{{ item.zphdz }}</view>
<view class="rowright" style="white-space: nowrap">
<!-- <convert-distance
:alat="item.latitude"
:along="item.longitude"
:blat="latitudeVal"
:blong="longitudeVal"
></convert-distance>
></convert-distance> -->
</view>
</view>
</view>
<view class="footer" v-if="isTimePassed(item.startTime)">
<view class="footer" v-if="isTimePassed(item.zphjbsj)">
<view class="card_cancel" @click="updateCancel(item)">取消预约</view>
</view>
</view>
</view>
<empty v-else pdTop="200"></empty>
<empty v-if="!pageState.list.length"></empty>
</scroll-view>
</view>
</view>
@@ -72,6 +81,7 @@ const ranOptions = ref([
]);
function isTimePassed(timeStr) {
if (!timeStr) return false;
const targetTime = new Date(timeStr.replace(/-/g, '/')).getTime(); // 兼容格式
const now = Date.now();
return now < targetTime;
@@ -88,10 +98,19 @@ function chnageRanOption(item) {
}
function updateCancel(item) {
const fairId = item.jobFairId;
$api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => {
getList('refresh');
$api.msg('取消预约成功');
const fairId = item.zphID;
uni.showModal({
title: '提示',
content: '确定要取消预约吗?',
showCancel: true,
success: ({ confirm, cancel }) => {
if (confirm) {
$api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => {
getList('refresh');
$api.msg('取消预约成功');
});
}
},
});
}
@@ -108,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);
@@ -121,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);
}
</script>
@@ -166,6 +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;

View File

@@ -26,6 +26,7 @@
<view
class="item button-click"
:class="{
optional: item.isThisMonth && hasZphInData(item),
noOptional: !item.isThisMonth,
active: current.date === item.date && item.isThisMonth,
}"
@@ -59,6 +60,8 @@ const pages = reactive({
month: 0,
});
const hasZphDateArray = ref([]);
onLoad((options) => {
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({

View File

@@ -32,22 +32,23 @@
</view>
</view>
<view class="info-bottom">
<view>到岗2025-11-02</view>
<!-- <view>到岗2025-11-02</view> -->
<view></view>
<view>地点青岛市-<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label></view>
</view>
<view class="des-card" style="margin-top: 24rpx">
<view class="fl_box fl_justbet">
<view>求职意向岗位</view>
<view>{{ userInfo.jobIntention || "-" }}</view>
<view style="white-space:nowrap">求职意向岗位</view>
<view class="line_1" style="padding-left:40rpx" >{{ userInfo.jobIntention || userInfo.jobTitle?.join(',') || '-' }}</view>
</view>
<view class="fl_box fl_justbet">
<view>毕业学校</view>
<view>{{ userInfo.graduationSchool || "-" }}</view>
</view>
<view class="fl_box fl_justbet">
<!-- <view class="fl_box fl_justbet">
<view>当前状态</view>
<view>在职 看工作机会</view>
</view>
</view> -->
</view>
</view>

View File

@@ -1,288 +1,284 @@
{
"pages": [
//pages数组中第一项表示应用启动页参考https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "青岛智慧就业平台",
// #ifdef H5
"navigationStyle": "custom"
// #endif
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/msglog/msglog",
"style": {
"navigationBarTitleText": "消息",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/careerfair/careerfair",
"style": {
"navigationBarTitleText": "招聘会",
"navigationStyle": "custom"
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "AI+就业服务程序",
"navigationStyle": "custom"
}
},
{
"path": "pages/nearby/nearby",
"style": {
"navigationBarTitleText": "附近",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "pages/chat/chat",
"style": {
"navigationBarTitleText": "AI+",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": false,
// #ifdef H5
"navigationStyle": "custom"
//#endif
}
},
{
"path": "pages/search/search",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
}
],
"subpackages": [
{
"root": "packageA",
"pages": [
"pages": [
//pages数组中第一项表示应用启动页参考https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/choiceness/choiceness",
"style": {
"navigationBarTitleText": "精选",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "青岛智慧就业平台",
// #ifdef H5
"navigationStyle": "custom"
// #endif
}
},
{
"path": "pages/post/post",
"style": {
"navigationBarTitleText": "职位详情",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/UnitDetails/UnitDetails",
"style": {
"navigationBarTitleText": "单位详情",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
"path": "pages/msglog/msglog",
"style": {
"navigationBarTitleText": "消息",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/exhibitors/exhibitors",
"style": {
"navigationBarTitleText": "参展单位",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
"path": "pages/careerfair/careerfair",
"style": {
"navigationBarTitleText": "招聘会",
"navigationStyle": "custom"
}
},
{
"path": "pages/myResume/myResume",
"style": {
"navigationBarTitleText": "我的简历",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "AI+就业服务程序",
"navigationStyle": "custom"
}
},
{
"path": "pages/vCard/vCard",
"style": {
"navigationBarTitleText": "点子名片",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
"path": "pages/nearby/nearby",
"style": {
"navigationBarTitleText": "附近",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "pages/Intendedposition/Intendedposition",
"style": {
"navigationBarTitleText": "投递记录",
"navigationBarBackgroundColor": "#FFFFFF"
}
"path": "pages/chat/chat",
"style": {
"navigationBarTitleText": "AI+",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": false,
// #ifdef H5
"navigationStyle": "custom"
//#endif
}
},
{
"path": "pages/collection/collection",
"style": {
"navigationBarTitleText": "我的收藏",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/browseJob/browseJob",
"style": {
"navigationBarTitleText": "我的浏览",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/addPosition/addPosition",
"style": {
"navigationBarTitleText": "添加岗位",
"navigationStyle": "custom"
}
},
{
"path": "pages/selectDate/selectDate",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "pages/personalInfo/personalInfo",
"style": {
"navigationBarTitleText": "个人信息",
"navigationStyle": "custom"
}
},
{
"path": "pages/jobExpect/jobExpect",
"style": {
"navigationBarTitleText": "求职期望",
"navigationStyle": "custom"
}
},
{
"path": "pages/workExp/workExp",
"style": {
"navigationBarTitleText": "工作经历",
"navigationStyle": "custom"
}
},
{
"path": "pages/reservation/reservation",
"style": {
"navigationBarTitleText": "我的预约",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/choicenessList/choicenessList",
"style": {
"navigationBarTitleText": "精选企业",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/newJobPosition/newJobPosition",
"style": {
"navigationBarTitleText": "新职位推荐",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/systemNotification/systemNotification",
"style": {
"navigationBarTitleText": "系统通知",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/tiktok/tiktok",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/moreJobs/moreJobs",
"style": {
"navigationBarTitleText": "更多岗位",
"navigationBarBackgroundColor": "#FFFFFF"
}
"path": "pages/search/search",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
}
]
}
],
"tabBar": {
"custom": true,
"display": "none",
"color": "#5E5F60",
"selectedColor": "#256BFA",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"midButton": {
"width": "50px",
"height": "50px",
"backgroundImage": "static/tabbar/logo2copy.png"
],
"subpackages": [{
"root": "packageA",
"pages": [{
"path": "pages/choiceness/choiceness",
"style": {
"navigationBarTitleText": "精选",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "pages/post/post",
"style": {
"navigationBarTitleText": "职位详情",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "pages/UnitDetails/UnitDetails",
"style": {
"navigationBarTitleText": "单位详情",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "pages/exhibitors/exhibitors",
"style": {
"navigationBarTitleText": "参展单位",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "pages/myResume/myResume",
"style": {
"navigationBarTitleText": "我的简历",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/vCard/vCard",
"style": {
"navigationBarTitleText": "电子名片",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/Intendedposition/Intendedposition",
"style": {
"navigationBarTitleText": "投递记录",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/collection/collection",
"style": {
"navigationBarTitleText": "我的收藏",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/browseJob/browseJob",
"style": {
"navigationBarTitleText": "我的浏览",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/addPosition/addPosition",
"style": {
"navigationBarTitleText": "添加岗位",
"navigationStyle": "custom"
}
},
{
"path": "pages/selectDate/selectDate",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "pages/personalInfo/personalInfo",
"style": {
"navigationBarTitleText": "个人信息",
"navigationStyle": "custom"
}
},
{
"path": "pages/jobExpect/jobExpect",
"style": {
"navigationBarTitleText": "求职期望",
"navigationStyle": "custom"
}
},
{
"path": "pages/workExp/workExp",
"style": {
"navigationBarTitleText": "工作经历",
"navigationStyle": "custom"
}
},
{
"path": "pages/reservation/reservation",
"style": {
"navigationBarTitleText": "我的预约",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/choicenessList/choicenessList",
"style": {
"navigationBarTitleText": "精选企业",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/newJobPosition/newJobPosition",
"style": {
"navigationBarTitleText": "新职位推荐",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/systemNotification/systemNotification",
"style": {
"navigationBarTitleText": "系统通知",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/tiktok/tiktok",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
},
{
"path": "pages/moreJobs/moreJobs",
"style": {
"navigationBarTitleText": "更多岗位",
"navigationBarBackgroundColor": "#FFFFFF"
}
}
]
}],
"tabBar": {
// "custom": true,
// "display": "none",
"color": "#5E5F60",
"selectedColor": "#256BFA",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"midButton": {
"width": "50px",
"height": "50px",
"backgroundImage": "static/tabbar/logo2copy.png"
},
"list": [{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/calendar.png",
"selectedIconPath": "static/tabbar/calendared.png",
"text": "职位"
},
{
"pagePath": "pages/careerfair/careerfair",
"iconPath": "static/tabbar/post.png",
"selectedIconPath": "static/tabbar/posted.png",
"text": "招聘会"
},
{
"pagePath": "pages/chat/chat",
"iconPath": "static/tabbar/logo3.png",
"selectedIconPath": "static/tabbar/logo3.png"
},
{
"pagePath": "pages/msglog/msglog",
"iconPath": "static/tabbar/chat4.png",
"selectedIconPath": "static/tabbar/chat4ed.png",
"text": "消息"
},
{
"pagePath": "pages/mine/mine",
"iconPath": "static/tabbar/mine.png",
"selectedIconPath": "static/tabbar/mined.png",
"text": "我的"
}
]
},
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/calendar.png",
"selectedIconPath": "static/tabbar/calendared.png",
"text": "职位"
},
{
"pagePath": "pages/careerfair/careerfair",
"iconPath": "static/tabbar/post.png",
"selectedIconPath": "static/tabbar/posted.png",
"text": "招聘会"
},
{
"pagePath": "pages/chat/chat",
"iconPath": "static/tabbar/logo3.png",
"selectedIconPath": "static/tabbar/logo3.png"
},
{
"pagePath": "pages/msglog/msglog",
"iconPath": "static/tabbar/chat4.png",
"selectedIconPath": "static/tabbar/chat4ed.png",
"text": "消息"
},
{
"pagePath": "pages/mine/mine",
"iconPath": "static/tabbar/mine.png",
"selectedIconPath": "static/tabbar/mined.png",
"text": "我的"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
// "enablePullDownRefresh": false,
// "navigationStyle": "custom",
"rpxCalcBaseDeviceWidth": 375,
"rpxCalcMaxDeviceWidth": 750,
"rpxCalcIncludeWidth": 750
},
"uniIdRouter": {}
}
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
// "enablePullDownRefresh": false,
// "navigationStyle": "custom",
"rpxCalcBaseDeviceWidth": 375,
"rpxCalcMaxDeviceWidth": 750,
"rpxCalcIncludeWidth": 750
},
"uniIdRouter": {}
}

View File

@@ -0,0 +1,544 @@
<template>
<view class="app-custom-root">
<view class="app-container">
<!-- 顶部头部区域 -->
<view class="container-header">
<view class="header-top">
<view class="header-btnLf button-click" @click="seemsg(0)" :class="{ active: state.current === 0 }">
现场招聘
</view>
<view class="header-btnLf button-click" @click="seemsg(1)" :class="{ active: state.current === 1 }">
VR虚拟招聘会
</view>
</view>
<view class="header-input btn-feel">
<uni-icons class="iconsearch" color="#666666" type="search" size="18"></uni-icons>
<input class="input" placeholder="招聘会" placeholder-class="inputplace" />
</view>
<view class="header-date">
<view class="data-week">
<view
class="weel-days button-click"
:class="{ active: currentDay.fullDate === item.fullDate }"
v-for="(item, index) in weekList"
:key="index"
@click="selectDate(item)"
>
<view class="label">{{ item.day }}</view>
<view class="day">{{ item.date }}</view>
</view>
</view>
<view class="data-all">
<image class="allimg button-click" @click="toSelectDate" src="/static/icon/date1.png"></image>
</view>
</view>
</view>
<!-- 主体内容区域 -->
<view class="container-main">
<scroll-view scroll-y class="main-scroll" @scrolltolower="handleScrollToLower">
<view class="cards">
<view
class="card press-button"
v-for="(item, index) in fairList"
:key="index"
@click="navTo('/packageA/pages/exhibitors/exhibitors?jobFairId=' + item.jobFairId)"
>
<view class="card-title">{{ item.zphmc }}</view>
<view class="card-row">
<text class="">{{ item.zphdz }}</text>
<text class="">
<convert-distance
:alat="item.latitude"
:along="item.longitude"
:blat="latitudeVal"
:blong="longitudeVal"
></convert-distance>
</text>
</view>
<view class="card-times">
<view class="time-left">
<view class="left-date">{{ parseDateTime(item.zphjbsj).time }}</view>
<view class="left-dateDay">{{ parseDateTime(item.zphjbsj).date }}</view>
</view>
<view class="line"></view>
<view class="time-center">
<view class="center-date">
{{ getTimeStatus(item.zphjbsj, item.zphjzsj).statusText }}
</view>
<view class="center-dateDay">
{{ getHoursBetween(item.zphjbsj, item.zphjzsj) }}小时
</view>
</view>
<view class="line"></view>
<view class="time-right">
<view class="left-date">{{ parseDateTime(item.zphjzsj).time }}</view>
<view class="left-dateDay">{{ parseDateTime(item.zphjzsj).date }}</view>
</view>
</view>
<view class="recommend-card-line"></view>
<view class="card-footer">内容简介{{ item.zphjj }}</view>
</view>
</view>
<empty v-if="!fairList.length" pdTop="200"></empty>
</scroll-view>
</view>
<Tabbar :currentpage="1"></Tabbar>
</view>
</view>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
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 } = inject('globalFunction');
const weekList = ref([]);
const fairList = ref([]);
const currentDay = ref({});
const state = reactive({
current: 0,
all: [{}],
});
const pageState = reactive({
page: 0,
total: 0,
maxPage: 2,
pageSize: 10,
search: {},
});
onLoad(() => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const currentDate = `${year}-${month}-${day}`;
const result = getNextDates({
startDate: currentDate,
});
weekList.value = result;
currentDay.value.fullDate = result[0].fullDate;
getFair('refresh');
});
function toSelectDate() {
navTo('/packageA/pages/selectDate/selectDate', {
query: {
date: currentDay.value.fullDate,
},
onBack: (res) => {
console.log(res);
const result = getNextDates({
startDate: res.date,
});
const formattedDate = res.date.slice(5); // MM-DD
const dateFull = {
date: res.date.slice(5),
day: '周' + res.week,
fullDate: res.date,
};
currentDay.value = dateFull;
weekList.value = result;
getFair('refresh');
},
});
}
// 查看消息类型
function changeSwiperMsgType(e) {
const currented = e.detail.current;
state.current = currented;
}
function seemsg(index) {
if (index === 1) {
return $api.msg('功能确定中');
}
state.current = index;
}
const handleScrollToLower = () => {
return;
getFair();
console.log('触底');
};
function getFair(type = 'add') {
if (type === 'refresh') {
pageState.page = 1;
pageState.maxPage = 1;
}
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
let params = {
...pageState.search,
// current: pageState.page,
// pageSize: pageState.pageSize,
};
// if (currentDay.value?.fullDate) {
// params.queryDate = currentDay.value.fullDate;
// }
if (currentDay.value?.fullDate) {
params.zphjbsj = currentDay.value.fullDate.replace(/-/g, '');
}
$api.createRequest('/app/internal/jobFairThirdPart', params).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;
} else {
fairList.value = rows;
}
// pageState.list = resData.rows;
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
});
}
function parseDateTime(datetimeStr) {
if (!datetimeStr) return { time: '', date: '' };
const dateObj = new Date(datetimeStr);
if (isNaN(dateObj.getTime())) return { time: '', date: '' }; // 无效时间
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const day = String(dateObj.getDate()).padStart(2, '0');
const hours = String(dateObj.getHours()).padStart(2, '0');
const minutes = String(dateObj.getMinutes()).padStart(2, '0');
return {
time: `${hours}:${minutes}`,
date: `${year}${month}${day}`,
};
}
function getTimeStatus(startTimeStr, endTimeStr) {
const now = new Date();
const startTime = new Date(startTimeStr);
const endTime = new Date(endTimeStr);
// 判断状态0 开始中1 过期2 待开始
let status = 0;
let statusText = '开始中';
if (now < startTime) {
status = 2; // 待开始
statusText = '待开始';
} else if (now > endTime) {
status = 1; // 已过期
statusText = '已过期';
} else {
status = 0; // 进行中
statusText = '进行中';
}
return {
status, // 0: 进行中1: 已过期2: 待开始
statusText,
};
}
function getHoursBetween(startTimeStr, endTimeStr) {
const start = new Date(startTimeStr);
const end = new Date(endTimeStr);
const diffMs = end - start;
const diffHours = diffMs / (1000 * 60 * 60);
return +diffHours.toFixed(2); // 保留 2 位小数
}
const selectDate = (item) => {
if (currentDay.value?.fullDate === item.fullDate) {
currentDay.value = {};
getFair('refresh');
return;
}
currentDay.value = item;
getFair('refresh');
};
function getNextDates({ startDate = '', count = 6 }) {
const baseDate = startDate ? new Date(startDate) : new Date(); // 指定起点或今天
const dates = [];
const dayNames = ['日', '一', '二', '三', '四', '五', '六'];
for (let i = 0; i < count; i++) {
const date = new Date(baseDate);
date.setDate(baseDate.getDate() + i);
const fullDate = date.toISOString().slice(0, 10); // YYYY-MM-DD
const formattedDate = fullDate.slice(5); // MM-DD
const dayOfWeek = dayNames[date.getDay()];
dates.push({
date: formattedDate,
fullDate,
day: '周' + dayOfWeek,
});
}
// 可选设置默认选中项
// currentDay.value = dates[0];
return dates;
}
</script>
<style lang="stylus" scoped>
.app-custom-root {
position: fixed;
z-index: 10;
width: 100vw;
height: calc(100% - var(--window-bottom));
overflow: hidden;
}
.app-container {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
.container-header {
background: url('@/static/icon/background2.png') 0 0 no-repeat;
background-size: 100% 400rpx;
.header-top{
display: flex;
line-height: calc(88rpx - 14rpx);
padding: 16rpx 44rpx 14rpx 44rpx;
.header-btnLf {
display: flex;
width: fit-content;
white-space: nowrap
justify-content: flex-start;
align-items: center;
width: calc(60rpx * 3);
font-weight: 500;
font-size: 40rpx;
color: #696969;
margin-right: 44rpx;
position: relative;
.btns-wd{
position: absolute
top: 2rpx;
right: 2rpx
width: 16rpx;
height: 16rpx;
background: #F73636;
border-radius: 50%;
border: 4rpx solid #EEEEFF;
}
}
.active {
font-weight: 600;
font-size: 40rpx;
color: #000000;
}
}
.header-input{
padding: 0 24rpx
width: calc(100% - 48rpx);
position: relative
.iconsearch{
position: absolute
left: 50rpx;
top: 50%
transform: translate(0, -50%)
}
.input{
padding: 0 30rpx 0 80rpx
height: 80rpx;
background: #FFFFFF;
border-radius: 75rpx 75rpx 75rpx 75rpx;
font-size: 28rpx;
}
.inputplace{
font-weight: 400;
font-size: 28rpx;
color: #B5B5B5;
}
}
.header-date{
padding: 28rpx
display: flex
justify-content: space-between
align-items: center
.data-week{
flex: 1
display: flex
justify-content: space-between
flex-wrap: nowrap
overflow: hidden
.weel-days{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
display: flex
justify-content: center
flex-direction: column
text-align: center
font-weight: 400;
font-size: 24rpx;
color: #333333;
width: 96rpx;
height: 88rpx;
.label{}
.day{
font-weight: 500;
}
}
.active{
background: rgba(37,107,250,0.1);
border-radius: 12rpx 12rpx 12rpx 12rpx;
color: #256BFA;
}
}
.data-all{
width: 66rpx;
height: 66rpx;
margin-left: 18rpx
.allimg{
width: 100%;
height: 100%
}
}
}
}
}
.container-main {
flex: 1;
overflow: hidden;
background-color: #f4f4f4;
}
.main-scroll {
width: 100%
height: 100%;
}
.cards{
padding: 28rpx 28rpx 28rpx 28rpx;
.card{
margin-top: 28rpx
padding: 32rpx;
background: #FFFFFF
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
.card-title{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.card-row{
display: flex
justify-content: space-between
font-weight: 400;
font-size: 28rpx;
color: #495265;
margin-top: 4rpx
}
.card-times{
display: flex;
justify-content: space-between
align-items: center
margin-top: 24rpx
.time-left,
.time-right{
text-align: center
.left-date{
font-weight: 500;
font-size: 48rpx;
color: #333333;
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
}
.left-dateDay{
font-weight: 400;
font-size: 24rpx;
color: #333333;
margin-top: 12rpx
}
}
.line{
width: 40rpx;
height: 0rpx;
border: 2rpx solid #D4D4D4;
margin-top: 64rpx
}
.time-center{
text-align: center;
display: flex
flex-direction: column
justify-content: center
align-items: center
.center-date{
font-weight: 400;
font-size: 28rpx;
color: #FF881A;
padding-top: 10rpx
}
.center-dateDay{
font-weight: 400;
font-size: 24rpx;
color: #333333;
margin-top: 6rpx
line-height: 48rpx;
width: 104rpx;
height: 48rpx;
background: #F9F9F9;
border-radius: 8rpx 8rpx 8rpx 8rpx;
}
}
}
.recommend-card-line{
width: calc(100%);
height: 0rpx;
border-radius: 0rpx 0rpx 0rpx 0rpx;
border: 2rpx dashed rgba(0,0,0,0.14);
margin-top: 32rpx
position: relative
}
.recommend-card-line::before{
position: absolute
content: ''
left: 0
top: 0
transform: translate(-50% - 110rpx, -50%)
width: 28rpx;
height: 28rpx;
background: #F4F4F4;
border-radius: 50%;
}
.recommend-card-line::after{
position: absolute
content: ''
right: 0
top: 0
transform: translate(50% + 100rpx, -50%)
width: 28rpx;
height: 28rpx;
background: #F4F4F4;
border-radius: 50%;
}
.card-footer{
margin-top: 32rpx
min-height: 50rpx;
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
}
.card:first-child{
margin-top: 0
}
}
</style>

View File

@@ -13,7 +13,14 @@
</view>
<view class="header-input btn-feel">
<uni-icons class="iconsearch" color="#666666" type="search" size="18"></uni-icons>
<input class="input" placeholder="招聘会" placeholder-class="inputplace" />
<input
v-model="pageState.zphmc"
confirm-type="search"
@confirm="getFair"
class="input"
placeholder="招聘会"
placeholder-class="inputplace"
/>
</view>
<view class="header-date">
<view class="data-week">
@@ -37,53 +44,60 @@
<!-- 主体内容区域 -->
<view class="container-main">
<scroll-view scroll-y class="main-scroll" @scrolltolower="handleScrollToLower">
<view class="cards" v-if="fairList.length">
<view class="cards">
<view
class="card press-button"
v-for="(item, index) in fairList"
:key="index"
@click="navTo('/packageA/pages/exhibitors/exhibitors?jobFairId=' + item.jobFairId)"
@click="
navTo(
'/packageA/pages/exhibitors/exhibitors?jobFairId=' +
item.zphID +
'&jobFairName=' +
item.zphmc
)
"
>
<view class="card-title">{{ item.name }}</view>
<view class="card-title">{{ item.zphmc }}</view>
<view class="card-row">
<text class="">{{ item.location }}</text>
<text class="">{{ item.jbf }}</text>
<text class="">
<convert-distance
<!-- <convert-distance
:alat="item.latitude"
:along="item.longitude"
:blat="latitudeVal"
:blong="longitudeVal"
></convert-distance>
></convert-distance> -->
</text>
</view>
<view class="card-times">
<view class="time-left">
<view class="left-date">{{ parseDateTime(item.startTime).time }}</view>
<view class="left-dateDay">{{ parseDateTime(item.startTime).date }}</view>
<view class="left-date">{{ parseDateTime(item.zphjbsj).time }}</view>
<view class="left-dateDay">{{ parseDateTime(item.zphjbsj).date }}</view>
</view>
<view class="line"></view>
<view class="time-center">
<view class="center-date">
{{ getTimeStatus(item.startTime, item.endTime).statusText }}
{{ getTimeStatus(item.zphjbsj, item.zphjzsj).statusText }}
</view>
<view class="center-dateDay">
{{ getHoursBetween(item.startTime, item.endTime) }}小时
{{ getHoursBetween(item.zphjbsj, item.zphjzsj) }}小时
</view>
</view>
<view class="line"></view>
<view class="time-right">
<view class="left-date">{{ parseDateTime(item.endTime).time }}</view>
<view class="left-dateDay">{{ parseDateTime(item.endTime).date }}</view>
<view class="left-date">{{ parseDateTime(item.zphjzsj).time }}</view>
<view class="left-dateDay">{{ parseDateTime(item.zphjzsj).date }}</view>
</view>
</view>
<view class="recommend-card-line"></view>
<view class="card-footer">内容简介{{ item.description }}</view>
<view class="card-footer">内容简介{{ item.zphjj }}</view>
</view>
</view>
<empty v-else pdTop="200"></empty>
<empty v-if="!fairList.length"></empty>
</scroll-view>
</view>
<Tabbar :currentpage="1"></Tabbar>
<!-- <Tabbar :currentpage="1"></Tabbar> -->
</view>
</view>
</template>
@@ -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 } = 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,
search: {},
zphmc: '',
});
onLoad(() => {
@@ -122,6 +136,7 @@ onLoad(() => {
startDate: currentDate,
});
weekList.value = result;
currentDay.value.fullDate = result[0].fullDate;
getFair('refresh');
});
@@ -129,6 +144,7 @@ function toSelectDate() {
navTo('/packageA/pages/selectDate/selectDate', {
query: {
date: currentDay.value.fullDate,
entrance: 'careerfair',
},
onBack: (res) => {
console.log(res);
@@ -161,6 +177,7 @@ function seemsg(index) {
}
const handleScrollToLower = () => {
return;
getFair();
console.log('触底');
};
@@ -174,21 +191,24 @@ function getFair(type = 'add') {
pageState.page += 1;
}
let params = {
...pageState.search,
current: pageState.page,
pageSize: pageState.pageSize,
zphmc: pageState.zphmc,
// current: pageState.page,
// pageSize: pageState.pageSize,
};
// if (currentDay.value?.fullDate) {
// params.queryDate = currentDay.value.fullDate;
// }
if (currentDay.value?.fullDate) {
params.queryDate = currentDay.value.fullDate;
params.zphjbsj = currentDay.value.fullDate.replace(/-/g, '');
}
$api.createRequest('/app/fair', params).then((resData) => {
$api.createRequest('/app/internal/jobFairThirdPart', params, 'GET', true).then((resData) => {
const { rows, total } = resData;
console.log(rows);
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 1);
const end = fairList.value.length;
const reslist = rows;
fairList.value.splice(str, end, ...reslist);
// const str = pageState.pageSize * (pageState.page - 1);
// const end = fairList.value.length;
// const reslist = rows;
// fairList.value.splice(str, end, ...reslist);
fairList.value = rows;
} else {
fairList.value = rows;
}
@@ -253,8 +273,8 @@ function getHoursBetween(startTimeStr, endTimeStr) {
const selectDate = (item) => {
if (currentDay.value?.fullDate === item.fullDate) {
currentDay.value = {};
getFair('refresh');
// currentDay.value = {};
// getFair('refresh');
return;
}
currentDay.value = item;

View File

@@ -63,9 +63,9 @@
<ai-paging ref="paging"></ai-paging>
</view>
<!-- 自定义tabbar -->
<view class="chatmain-footer" v-show="!isDrawerOpen">
<!-- <view class="chatmain-footer" v-show="!isDrawerOpen">
<Tabbar :currentpage="2"></Tabbar>
</view>
</view> -->
</view>
</view>
</template>
@@ -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;

View File

@@ -148,7 +148,7 @@
<view
class="input_vio"
@touchstart.prevent="handleTouchStart"
@touchmove="handleTouchMove"
@touchmove.passive="handleTouchMove"
@touchend="handleTouchEnd"
@touchcancel="handleTouchCancel"
:catchtouchstart="true"
@@ -250,8 +250,6 @@ import {
ref,
inject,
nextTick,
defineProps,
defineEmits,
onMounted,
onUnmounted,
toRaw,
@@ -270,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']);
@@ -448,7 +446,7 @@ const scrollToBottom = throttle(function () {
}, 500);
function getGuess() {
$api.chatRequest('/guest', { sessionId: chatSessionID.value }, 'POST').then((res) => {
$api.chatRequest('/app/chat/guest', { sessionId: chatSessionID.value }, 'POST').then((res) => {
guessList.value = res.data;
showGuess.value = true;
nextTick(() => {
@@ -634,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);
}
}

View File

@@ -37,7 +37,7 @@
</template>
<script setup>
import { ref, inject, defineEmits } from 'vue';
import { ref, inject } from 'vue';
const emit = defineEmits(['onSend']);
const { $api } = inject('globalFunction');
const popup = ref(null);

View File

@@ -66,7 +66,7 @@
</view>
<view class="table-list">
<scroll-view :scroll-y="true" class="falls-scroll" @scroll="handleScroll" @scrolltolower="scrollBottom">
<view class="falls" v-if="list.length">
<view class="falls">
<custom-waterfalls-flow
:column="columnCount"
:columnSpace="columnSpace"
@@ -142,7 +142,7 @@
</custom-waterfalls-flow>
<loadmore ref="loadmoreRef"></loadmore>
</view>
<empty v-else pdTop="200"></empty>
<empty v-if="!list.length"></empty>
</scroll-view>
</view>
<!-- 筛选 -->
@@ -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);

View File

@@ -3,8 +3,21 @@
<view class="app-container">
<!-- 主体内容区域 -->
<view class="container-main">
<swiper class="swiper" :current="state.current" @change="changeSwiperType">
<swiper-item class="swiper-item" v-for="(_, index) in 2" :key="index">
<swiper
class="swiper"
:disable-touch="disableTouch"
:current="state.current"
@change="changeSwiperType"
>
<!-- 绑定首页和尾页 -->
<swiper-item
@touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove"
@touchend="disableTouch = false"
class="swiper-item"
v-for="(_, index) in 2"
:key="index"
>
<!-- #ifndef MP-WEIXIN -->
<component
:is="components[index]"
@@ -28,7 +41,7 @@
</swiper>
</view>
<Tabbar v-show="showTabbar" :currentpage="0"></Tabbar>
<!-- <Tabbar v-show="showTabbar" :currentpage="0"></Tabbar> -->
<!-- maskFristEntry -->
<view class="maskFristEntry" v-if="maskFristEntry">
@@ -36,7 +49,7 @@
<text class="text1">左滑查看视频</text>
<text class="text2">快去体验吧</text>
<view class="goExperience" @click="goExperience">去体验</view>
<view class="maskFristEntry-Close" @click="closeFristEntry">1</view>
<view class="maskFristEntry-Close" @click="closeFristEntry"></view>
</view>
</view>
</view>
@@ -58,11 +71,25 @@ const { unreadCount } = storeToRefs(useReadMsg());
const showTabbar = ref(true);
const maskFristEntry = ref(false);
const disableTouch = ref(false);
const startPointX = ref(0);
const totalPage = 2;
const THRESHOLD = 5;
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(() => {
@@ -85,15 +112,69 @@ const handelComponentsRef = (el, index) => {
}
};
function handleTouchStart(e) {
startPointX.value = e.touches[0].clientX;
disableTouch.value = false;
}
function handleTouchMove(e) {
const currentX = e.touches[0].clientX;
const diffX = currentX - startPointX.value;
if (state.current === 0) {
if (diffX > THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
if (state.current === totalPage - 1) {
if (diffX < -THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
disableTouch.value = false;
}
function changeShowTabbar(val) {
showTabbar.value = val;
}
//1 查看消息类型
function changeSwiperType(e) {
const newIndex = e.detail.current;
const lastIndex = state.current;
const isSwipingRight = newIndex < lastIndex;
const isSwipingLeft = newIndex > lastIndex;
if (lastIndex === 0 && isSwipingRight) {
disableTouch.value = true;
state.current = 0;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
if (lastIndex === totalPage - 1 && isSwipingLeft) {
disableTouch.value = true;
state.current = lastIndex;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
const index = e.detail.current;
state.current = index;
handleTabChange(index);
disableTouch.value = false;
}
function changeType(index) {
state.current = index;
@@ -115,10 +196,12 @@ function changeSwiperMsgType(e) {
function closeFristEntry() {
uni.setStorageSync('fristEntry', false);
maskFristEntry.value = false;
uni.showTabBar();
}
function goExperience() {
closeFristEntry();
uni.showTabBar();
state.current = 1;
}
</script>

View File

@@ -109,6 +109,9 @@
</template>
</tabcontrolVue>
<SelectJobs ref="selectJobsModel"></SelectJobs>
<view class="backdoor" @click="loginbackdoor">
<uni-icons type="gift-filled" size="30"></uni-icons>
</view>
</AppLayout>
</template>
@@ -149,7 +152,6 @@ const fromValue = reactive({
});
onLoad((parmas) => {
console.log(parmas);
getTreeselect();
});
@@ -239,8 +241,26 @@ 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() {
$api.createRequest('/app/mock/login', {}, 'post').then((resData) => {
$api.msg('模拟帐号密码测试登录成功');
loginSetToken(resData.token).then((resume) => {
if (resume.data.jobTitleId) {
// 设置推荐列表,每次退出登录都需要更新
useUserStore().initSeesionId();
uni.reLaunch({
url: '/pages/index/index',
});
} else {
nextStep();
}
});
});
}
@@ -290,6 +310,11 @@ function complete() {
</script>
<style lang="stylus" scoped>
.backdoor{
position: fixed;
left: 0;
bottom: 200rpx;
}
.input-nx
position: relative
border-bottom: 2rpx solid #EBEBEB
@@ -402,6 +427,7 @@ function complete() {
font-size: 28rpx;
color: #6A6A6A;
.input-con
pointer-events: none;
font-weight: 400;
font-size: 32rpx;
color: #333333;

View File

@@ -45,13 +45,11 @@
<text v-if="userInfo.jobTitle.length - 1 !== index">|</text>
</text>
</view>
<view class="top-btn button-click" >
电子名片
</view>
<view class="top-btn button-click">电子名片</view>
</view>
<view class="card-main">
<view class="main-title">服务专区</view>
<view class="main-row btn-feel">
<view class="main-row btn-feel" @click="selectFile">
<view class="row-left">
<image class="left-img" src="@/static/icon/server1.png"></image>
<text class="left-text">实名认证</text>
@@ -97,9 +95,9 @@
></uni-popup-dialog>
</uni-popup>
</view>
<template #footer>
<!-- <template #footer>
<Tabbar :currentpage="4"></Tabbar>
</template>
</template> -->
</AppLayout>
</template>
@@ -108,16 +106,17 @@ import { reactive, inject, watch, ref, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import Tabbar from '@/components/tabbar/midell-box.vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
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() {
@@ -130,12 +129,14 @@ function confirm() {
const isAbove90 = (percent) => parseFloat(percent) < 90;
function getUserstatistics() {
$api.createRequest('/app/user/statistics').then((resData) => {
console.log(resData);
counts.value = resData.data;
});
function selectFile() {
// FileUploader.showMenuAndUpload({
// success: function (res) {
// alert('上传成功: ' + JSON.stringify(res));
// },
// });
}
function chooseFileUploadTest(pam) {}
</script>
<style lang="stylus" scoped>

View File

@@ -15,8 +15,20 @@
<!-- 主体内容区域 -->
<view class="container-main">
<swiper class="swiper" :current="state.current" @change="changeSwiperType">
<swiper-item class="swiper-item" v-for="(_, index) in 2" :key="index">
<swiper
class="swiper"
:disable-touch="disableTouch"
:current="state.current"
@change="changeSwiperType"
>
<swiper-item
class="swiper-item"
@touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove"
@touchend="disableTouch = false"
v-for="(_, index) in 2"
:key="index"
>
<!-- #ifndef MP-WEIXIN -->
<component :is="components[index]" :ref="(el) => handelComponentsRef(el, index)" />
<!-- #endif -->
@@ -28,7 +40,7 @@
</swiper>
</view>
<Tabbar :currentpage="3"></Tabbar>
<!-- <Tabbar :currentpage="3"></Tabbar> -->
</view>
</view>
</template>
@@ -46,6 +58,11 @@ import { storeToRefs } from 'pinia';
import { useReadMsg } from '@/stores/useReadMsg';
const { unreadCount } = storeToRefs(useReadMsg());
const disableTouch = ref(false);
const startPointX = ref(0);
const totalPage = 2;
const THRESHOLD = 5;
onShow(() => {
// 获取消息列表
useReadMsg().fetchMessages();
@@ -59,6 +76,40 @@ onMounted(() => {
handleTabChange(state.current);
});
function handleTouchStart(e) {
// 确保有触摸点
if (e.touches.length > 0) {
startPointX.value = e.touches[0].clientX;
disableTouch.value = false;
}
}
function handleTouchMove(e) {
if (e.touches.length === 0) return;
const currentX = e.touches[0].clientX;
const diffX = currentX - startPointX.value;
if (state.current === 0) {
if (diffX > THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
if (state.current === totalPage - 1) {
if (diffX < -THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
disableTouch.value = false;
}
const handelComponentsRef = (el, index) => {
if (el) {
swiperRefs[index].value = el;
@@ -66,9 +117,35 @@ const handelComponentsRef = (el, index) => {
};
// 查看消息类型
function changeSwiperType(e) {
const newIndex = e.detail.current;
const lastIndex = state.current;
const isSwipingRight = newIndex < lastIndex;
const isSwipingLeft = newIndex > lastIndex;
if (lastIndex === 0 && isSwipingRight) {
disableTouch.value = true;
state.current = 0;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
if (lastIndex === totalPage - 1 && isSwipingLeft) {
disableTouch.value = true;
state.current = lastIndex;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
const index = e.detail.current;
state.current = index;
handleTabChange(index);
disableTouch.value = false;
}
function changeType(index) {
state.current = index;

View File

@@ -35,6 +35,7 @@
<view class="info-text line_2">{{ item.subTitle || '消息' }}</view>
</view>
</view>
<empty v-if="!msgList.length"></empty>
</view>
</scroll-view>
</template>
@@ -83,6 +84,7 @@ defineExpose({ loadData });
}
.scrollmain{
padding: 28rpx
height: calc(100% - 56rpx)
}
.read{

View File

@@ -33,6 +33,7 @@
<view class="info-text line_2">{{ item.subTitle || '消息' }}</view>
</view>
</view>
<empty v-if="!unreadMsgList.length"></empty>
</view>
</scroll-view>
</template>
@@ -69,6 +70,7 @@ defineExpose({ loadData });
}
.scrollmain{
padding: 28rpx
height: calc(100% - 56rpx)
}
.read{

View File

@@ -69,14 +69,9 @@
</view>
</view>
<view class="one-cards">
<renderJobs
v-if="list.length"
:list="list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="60"></empty>
<loadmore ref="loadmoreRef"></loadmore>
<renderJobs :list="list" :longitude="longitudeVal" :latitude="latitudeVal"></renderJobs>
<empty v-if="!list.length"></empty>
<loadmore v-show="list.length > pageState.pageSize" ref="loadmoreRef"></loadmore>
</view>
</view>
<!-- 筛选 -->
@@ -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)
</style>
</style>

View File

@@ -74,14 +74,9 @@
</view>
</view>
<view class="one-cards">
<renderJobs
v-if="list.length"
:list="list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="60"></empty>
<loadmore ref="loadmoreRef"></loadmore>
<renderJobs :list="list" :longitude="longitudeVal" :latitude="latitudeVal"></renderJobs>
<empty v-if="!list.length"></empty>
<loadmore v-show="list.length > pageState.pageSize" ref="loadmoreRef"></loadmore>
</view>
</view>
<!-- 筛选 -->
@@ -220,7 +215,7 @@ function handleControl(e) {
}
onMounted(() => {
$api.msg('使用模拟定位');
// $api.msg('使用模拟定位');
getInit();
});
@@ -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;

View File

@@ -95,14 +95,9 @@
</view>
</view>
<view class="one-cards">
<renderJobs
v-if="list.length"
:list="list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="60"></empty>
<loadmore ref="loadmoreRef"></loadmore>
<renderJobs :list="list" :longitude="longitudeVal" :latitude="latitudeVal"></renderJobs>
<empty v-if="!list.length"></empty>
<loadmore v-show="list.length > pageState.pageSize" ref="loadmoreRef"></loadmore>
</view>
</view>
<!-- 筛选 -->
@@ -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;

View File

@@ -65,14 +65,9 @@
</view>
</view>
<view class="one-cards">
<renderJobs
v-if="list.length"
:list="list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="60"></empty>
<loadmore ref="loadmoreRef"></loadmore>
<renderJobs :list="list" :longitude="longitudeVal" :latitude="latitudeVal"></renderJobs>
<empty v-if="!list.length"></empty>
<loadmore v-show="list.length > pageState.pageSize" ref="loadmoreRef"></loadmore>
</view>
</view>
<!-- 筛选 -->
@@ -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;

View File

@@ -13,8 +13,20 @@
<view class="head-item" :class="{ actived: state.current === 3 }" @click="changeType(3)">商圈附近</view>
</view>
<view class="nearby-content">
<swiper class="swiper" :current="state.current" @change="changeSwiperType">
<swiper-item class="swiper-item" v-for="(_, index) in 4" :key="index">
<swiper
class="swiper"
:disable-touch="disableTouch"
:current="state.current"
@change="changeSwiperType"
>
<swiper-item
@touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove"
@touchend="disableTouch = false"
class="swiper-item"
v-for="(_, index) in 4"
:key="index"
>
<!-- #ifndef MP-WEIXIN -->
<component :is="components[index]" :ref="(el) => handelComponentsRef(el, index)" />
<!-- #endif -->
@@ -49,6 +61,11 @@ const showFilter1 = ref(false);
const showFilter2 = ref(false);
const showFilter3 = ref(false);
const disableTouch = ref(false);
const startPointX = ref(0);
const totalPage = 4;
const THRESHOLD = 5;
const state = reactive({
current: 0,
all: [{}],
@@ -63,11 +80,72 @@ const handelComponentsRef = (el, index) => {
swiperRefs[index].value = el;
}
};
function handleTouchStart(e) {
// 确保有触摸点
if (e.touches.length > 0) {
startPointX.value = e.touches[0].clientX;
disableTouch.value = false;
}
}
function handleTouchMove(e) {
if (e.touches.length === 0) return;
const currentX = e.touches[0].clientX;
const diffX = currentX - startPointX.value;
if (state.current === 0) {
if (diffX > THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
if (state.current === totalPage - 1) {
if (diffX < -THRESHOLD) {
disableTouch.value = true;
} else {
disableTouch.value = false;
}
return;
}
disableTouch.value = false;
}
// 查看消息类型
function changeSwiperType(e) {
const newIndex = e.detail.current;
const lastIndex = state.current;
const isSwipingRight = newIndex < lastIndex;
const isSwipingLeft = newIndex > lastIndex;
if (lastIndex === 0 && isSwipingRight) {
disableTouch.value = true;
state.current = 0;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
if (lastIndex === totalPage - 1 && isSwipingLeft) {
disableTouch.value = true;
state.current = lastIndex;
setTimeout(() => {
disableTouch.value = false;
}, 50);
return;
}
const index = e.detail.current;
state.current = index;
handleTabChange(index);
disableTouch.value = false;
}
function changeType(index) {
state.current = index;

File diff suppressed because it is too large Load Diff

BIN
static/icon/close-blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
static/icon/close-green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
static/icon/notice-blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -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

View File

@@ -16,46 +16,34 @@ const useLocationStore = defineStore("location", () => {
function getLocation() {
return new Promise((resole, reject) => {
uni.getLocation({
type: 'wgs84',
altitude: true,
isHighAccuracy: true,
enableHighAccuracy: true, // 关键参数:启用传感器辅助
timeout: 10000,
success: function(res) {
const resd = {
longitude: 120.382665,
latitude: 36.066938
try {
lightAppJssdk.map.getLocation({
success: function(data) {
longitudeVal.value = Number(data.longitude)
latitudeVal.value = Number(data.latitude)
resole(data)
},
fail: function(data) {
longitudeVal.value = 120.382665
latitudeVal.value = 36.066938
resole({
longitude: 120.382665,
latitude: 36.066938
})
msg('用户位置获取失败')
console.log('失败', data)
}
if (config.UsingSimulatedPositioning) { // 使用模拟定位
longitudeVal.value = resd.longitude
latitudeVal.value = resd.latitude
msg('用户位置获取成功')
resole(resd)
} else {
longitudeVal.value = res.longitude
latitudeVal.value = res.latitude
msg('用户位置获取成功')
resole(res)
}
},
fail: function(err) {
// longitudeVal.value = ''
// latitudeVal.value = ''
// reject(err)
const resd = {
longitude: 120.382665,
latitude: 36.066938
}
longitudeVal.value = resd.longitude
latitudeVal.value = resd.latitude
msg('用户位置获取失败,使用模拟定位')
resole(resd)
},
complete: function(e) {
console.warn('getUserLocation' + JSON.stringify(e))
}
})
})
} catch (e) {
longitudeVal.value = 120.382665
latitudeVal.value = 36.066938
resole({
longitude: 120.382665,
latitude: 36.066938
})
msg('测试环境,使用模拟定位')
console.log('失败', data)
}
})
}

View File

@@ -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, // 开启持久化
})

View File

@@ -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 {

View File

@@ -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;

View File

@@ -6,7 +6,7 @@ import {
ref,
toRaw
} from 'vue'
import baseDB from './BaseDBStore';
import baseDB from '@/utils/db.js';
import {
msg,
CloneDeep,
@@ -180,7 +180,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
resolve();
}
$api.streamRequest('/chat', params, onDataReceived, onError, onComplete);
$api.streamRequest('/app/chat/chat', params, onDataReceived, onError, onComplete);
} catch (err) {
console.log(err);
reject(err);
@@ -230,7 +230,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
// 云端数据
function getHistory() {
$api.chatRequest('/getHistory').then((res) => {
$api.chatRequest('/app/chat/getHistory').then((res) => {
if (!res.data.list.length) return
let tabel = parseHistory(res.data.list)
if (tabel && tabel.length) {
@@ -250,7 +250,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
const params = {
sessionId: chatSessionID.value
}
$api.chatRequest('/detail', params, 'GET', loading).then((res) => {
$api.chatRequest('/app/chat/detail', params, 'GET', loading).then((res) => {
let list = parseHistoryDetail(res.data.list, chatSessionID.value)
if (list.length) {
baseDB.db.add(massageName.value, list).then((ids) => {

View File

@@ -0,0 +1,31 @@
## 0.1.22024-07-17
chore: 移除冗余的 typescript 依赖
## 0.1.12024-07-17
fix: 修复 createUnistorage 导出
## 0.1.02024-07-10
fix!: 更新 pinia 类型
## 0.0.212024-07-10
chore!: 继承 pinia-plugin-persistedstate
## 0.0.192024-01-18
fix: 重新构建,不需要默认参数
## 0.0.162023-05-06
fix: 修复全局 key 移除
## 0.0.142023-04-29
fix: 修复全局 global key 选项
## 0.0.122023-04-07
- fix: 修复类型错误
## 0.0.112023-03-22
- chore: ts 支持
## 0.0.72022-04-29
- 更新 README

View File

@@ -0,0 +1,112 @@
import * as pinia from 'pinia';
import { StateTree, PiniaPluginContext, PiniaPlugin } from 'pinia';
type Prettify<T> = {
[K in keyof T]: T[K];
};
type StorageLike = Pick<Storage, 'getItem' | 'setItem'>;
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<string>;
/**
* 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<Pick<PersistedStateOptions, 'storage' | 'serializer' | 'afterRestore' | 'beforeRestore' | 'debug'> & {
/**
* 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<S extends StateTree, Store> {
/**
* 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;

View File

@@ -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 };

View File

@@ -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"
}
}
}
}
}

View File

@@ -0,0 +1,226 @@
<div align="center">
<img width="200px" height="200px" src="https://gitee.com/dishait/pinia-plugin-unistorage/raw/main/static/favicon.png" />
<h1>pinia-plugin-unistorage</h1>
<p>uniapp 下 pinia 的本地数据缓存插件</p>
</div>
<br />
<br />
<div align="center">
<img width="100%" height="100%" src="https://gitee.com/dishait/pinia-plugin-unistorage/raw/main/static/pinia-plugin-unistorage.gif" />
</div>
<br />
<br />
## 引用
该插件是
[pinia-plugin-persistedstate](https://github.com/prazdevs/pinia-plugin-persistedstate)
`uniapp` 版本,如果你需要在纯 `vue` 或者 `nuxt` 项目中使用 `pinia`
的本地数据缓存,请使用
[pinia-plugin-persistedstate](https://github.com/prazdevs/pinia-plugin-persistedstate)。
<br />
<br />
## 动机
为了实现多端的更简单的全局本地数据缓存
<br />
<br />
## 组织 🦔
欢迎关注 **帝莎编程**
- [官网](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)
<br />
<br />
## 使用
### 安装
#### 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 返回
};
}
```
<br />
#### 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 的数据读写都将持久化
},
);
```
<br />
### 选项
#### 钩子
```js
import { defineStore } from "pinia";
export const useStore = defineStore("main", {
state() {
return {
someState: "hello pinia",
};
},
unistorage: {
// 初始化恢复前触发
beforeRestore(ctx) {},
// 初始化恢复后触发
afterRestore(ctx) {},
},
});
```
<br />
#### 序列化
大多数情况下你并不需要了解该选项
```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);
},
},
},
});
```
<br />
#### 其他
```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 会被缓存
},
});
```
<br />
<br />
## License
Made with [markthree](https://github.com/markthree)
Published under [MIT License](./LICENSE).

View File

@@ -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);
};
}

150
utils/FileUploader.js Normal file
View File

@@ -0,0 +1,150 @@
/**
* LightApp 文件上传工具类封装
* * 使用方法:
* 1. 列表选择上传:
* FileUploader.showMenuAndUpload({ baseUrl: 'http://...' }).then(res => ...);
* * 2. 直接调用某种类型上传:
* FileUploader.directUpload({
* baseUrl: 'http://...',
* chooseType: 'chooseImageUpload'
* }).then(res => ...);
*/
import config from "@/config.js"
export default {
/**
* 默认配置
*/
defaults: {
baseUrl: config.baseUrl, // 必填API的基础路径
uploadPath: '/app/oss/upload', // 上传接口路径
fileKey: 'file',
maxSize: 10,
maxSelectNum: 1,
minTime: 3,
maxTime: 15,
transmissionType: 0, // 0-图片地址, 1-base64
},
/**
* 映射菜单索引到上传类型
*/
typeMapping: {
0: 'chooseImageUpload', // 相册
1: 'takingPicturesUpload', // 相机
2: 'takingVideoUpload', // 视频
3: 'chooseFileUpload' // 文件
},
/**
* 核心方法:调用 SDK 的 chooseFileUpload
* @param {Object} options - 配置项
* @returns {Promise}
*/
_executeUpload: function(options) {
return new Promise((resolve, reject) => {
// 合并配置
const config = {
...this.defaults,
...options
};
// 构造 SDK 需要的参数结构
const pam = {
url: config.baseUrl + config.uploadPath,
fileKey: config.fileKey,
params: config.params || {},
header: config.header || {},
chooseType: config.chooseType || 'chooseFileUpload',
transmissionType: config.transmissionType,
maxSize: config.maxSize,
maxSelectNum: config.maxSelectNum,
minTime: config.minTime,
maxTime: config.maxTime
};
// 针对特定类型的特殊处理 (参考原代码逻辑)
if (config.chooseType === 'takingPicturesUpload') {
// 原代码注释中提到: // pam1.fileKey = 'picfile';
// 如果需要特殊 fileKey 可以在这里处理
}
console.log('开始调用SDK上传, 参数:', pam);
if (typeof lightAppJssdk === 'undefined') {
const msg = 'lightAppJssdk 未定义,请在 爱山东 环境中运行';
alert(msg);
return reject(msg);
}
lightAppJssdk.uploadFile.chooseFileUpload({
arg0: pam,
success: function(data) {
// 支持传入 success 回调,也支持 Promise resolve
if (options.success) options.success(data);
console.log(data)
resolve(data);
},
fail: function(err) {
// 支持传入 fail 回调,也支持 Promise reject
if (options.fail) options.fail(err);
// 默认弹窗提示错误,可以通过 silent: true 关闭
if (!options.silent) alert(typeof err === 'object' ? JSON.stringify(
err) : err);
reject(err);
}
});
});
},
/**
* 场景1: 弹出菜单选择,然后上传 (封装了原 selectFile)
* @param {Object} options - 配置项 (需包含 baseUrl)
*/
showMenuAndUpload: function(options = {}) {
return new Promise((resolve, reject) => {
if (typeof lightAppJssdk === 'undefined') {
alert('lightAppJssdk 未定义');
return reject('lightAppJssdk undefined');
}
const menuList = options.menuList || ['相册', '相机', '视频', '文件'];
lightAppJssdk.notification.showMediaAlert({
arg0: menuList,
success: (data) => {
// data.index 对应 menuList 的索引
const selectedType = this.typeMapping[data.index];
if (!selectedType) {
const err = '未知的选择类型';
if (options.fail) options.fail(err);
return reject(err);
}
// 选中后,调用核心上传方法
this._executeUpload({
...options,
chooseType: selectedType
}).then(resolve).catch(reject);
},
fail: (err) => {
if (options.fail) options.fail(err);
if (!options.silent) alert(err);
reject(err);
}
});
});
},
/**
* 场景2: 直接上传 (不弹窗,直接调起特定类型的上传)
* @param {Object} options - 需包含 baseUrl 和 chooseType
*/
directUpload: function(options = {}) {
if (!options.chooseType) {
// 如果没传类型,默认当做普通文件上传
options.chooseType = 'chooseFileUpload';
}
return this._executeUpload(options);
}
};

91
utils/db.js Normal file
View File

@@ -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

View File

@@ -1,76 +1,70 @@
import config from "@/config.js"
import {
sm2_Decrypt,
sm2_Encrypt
sm2_Encrypt,
sm4Decrypt,
sm4Encrypt
} 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"],
["get", "/app/user/resume"],
["post", "/app/user/resume"],
["post", "/app/user/experience/edit"],
["post", "/app/user/experience/delete"],
["get", "/app/user/experience/getSingle/{value}"],
["get", "/app/user/experience/list"]
]
export function request({
url,
method = 'GET',
data = {},
load = false,
header = {}
} = {}) {
/**
* 带缓存的请求方法
*/
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)}`;
return new Promise((resolve, reject) => {
if (load) {
uni.showLoading({
title: '请稍候',
mask: true
});
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().userInfo.token}${useUserStore().token}`
}
uni.request({
url: config.baseUrl + url,
method,
data: data,
header: {
'Authorization': Authorization || '',
},
success: resData => {
// 响应拦截
if (resData.statusCode === 200) {
const {
code,
msg
} = resData.data
if (code === 200) {
resolve(resData.data)
return
}
uni.showToast({
title: msg,
icon: 'none'
})
}
if (resData.data?.code === 401 || resData.data?.code === 402) {
useUserStore().logOut()
uni.showToast({
title: '登录过期,请重新登录',
icon: 'none'
})
return
}
const err = new Error('请求出现异常,请联系工作人员')
err.error = resData
reject(err)
},
fail: err => reject(err),
complete() {
if (load) {
uni.hideLoading();
}
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
@@ -87,22 +81,54 @@ 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;
if (token) {
// 确保 Authorization 不会被覆盖,且进行编码
header["Authorization"] = encodeURIComponent(token);
}
const header = headers || {};
header["Authorization"] = encodeURIComponent(Authorization);
// ------------------------------------------------------------------
// 检查当前请求是否需要加密
const isEncrypt = needToEncrypt.some(item => {
const matchMethod = item[0].toLowerCase() === method.toLowerCase();
const matchUrl = item[1].includes('{') ?
url.startsWith(item[1].split('/{')[0]) // 检查动态路径的前缀
:
item[1] === url; // 检查静态路径
return matchMethod && matchUrl;
});
let requestData = data;
if (isEncrypt) {
const jsonData = JSON.stringify(data);
const encryptedBody = sm4Encrypt(config.sm4Config.key, jsonData);
requestData = {
encrypted: true,
encryptedData: encryptedBody,
timestamp: Date.now()
};
}
// ------------------------------------------------------------------
return new Promise((resolve, reject) => {
uni.request({
url: config.baseUrl + url,
method: method,
data: data,
data: requestData,
header,
success: resData => {
// 响应拦截
if (resData.statusCode === 200) {
if (resData.data.encrypted) {
const decryptedData = sm4Decrypt(config.sm4Config.key, resData.data
.encryptedData)
resData.data = JSON.parse(decryptedData)
}
const {
code,
msg
@@ -111,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()

View File

@@ -17,7 +17,7 @@ export default function StreamRequest(url, data = {}, onDataReceived, onError, o
};
return new Promise(async (resolve, reject) => {
try {
const response = await fetch(config.StreamBaseURl + url, {
const response = await fetch(config.baseUrl + url, {
method: "POST",
headers,
body: JSON.stringify(data)
@@ -46,6 +46,7 @@ export default function StreamRequest(url, data = {}, onDataReceived, onError, o
let lines = buffer.split("\n");
buffer = lines.pop(); // 可能是不完整的 JSON 片段,留待下次解析
for (let line of lines) {
line = line.slice(5).trim()
if (line.startsWith("data: ")) {
const jsonData = line.slice(6).trim();
if (jsonData === "[DONE]") {
@@ -104,7 +105,7 @@ export function chatRequest(url, data = {}, method = 'GET', loading = false, hea
header["Authorization"] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.request({
url: config.StreamBaseURl + url,
url: config.baseUrl + url,
method: method,
data: data,
header,