26 Commits

Author SHA1 Message Date
Apcallover
7479176eff flat:暂存 2025-12-18 11:16:47 +08:00
Apcallover
3fe0a1b9e1 flat: 兼容写法 2025-12-18 11:16:20 +08:00
284d696b25 fix 2025-12-18 10:47:39 +08:00
85a24d3346 fix : 统一文件类型 2025-12-18 10:39:37 +08:00
854bc9c197 fix : 图片类型判断, 一体机modal样式 2025-12-18 10:31:54 +08:00
Apcallover
606b6104bb flat: bug修复2 2025-12-17 21:12:26 +08:00
Apcallover
a10fd29440 flat: bug修复 2025-12-17 19:48:45 +08:00
Apcallover
ab3201b015 flat:合并、扫码上传文件等 2025-12-17 18:31:17 +08:00
Apcallover
8420d265e1 flat: 注入全局弹窗 2025-12-17 18:27:59 +08:00
a30a302e90 新增一体机附件上传 2025-12-17 18:10:06 +08:00
Apcallover
3d826aec86 flat: 一体机兼容,添加全局事件扫码回调等 2025-12-17 15:16:48 +08:00
8b9f7890af style 扫码扫脸登录 2025-12-17 15:08:58 +08:00
32e147e601 隐藏一体机下部分模块 修改扫码登录页 2025-12-17 13:42:05 +08:00
8ccdcbf93b style : 一体机下隐藏部分模块 2025-12-17 11:32:13 +08:00
01d817f99c delete : 去除无用文件 2025-12-17 10:25:27 +08:00
137542a92f Merge branch 'main-ALL-IN-ONE' of http://124.243.245.42:3000/sdz/qingdao-employment-service into main-ALL-IN-ONE 2025-12-17 10:15:15 +08:00
cd9c3acfc0 首页-人工只能专区,招聘会-VR,我的-AI面试, 一体机模式下提示暂未开放 2025-12-17 10:15:13 +08:00
Apcallover
80213b18e9 flat: 微调 2025-12-16 20:24:03 +08:00
4a03b4d4cb feat : 新增一体机支付宝扫码登录页 2025-12-16 16:51:56 +08:00
Apcallover
77dfab84f1 flat: 暂存,新增一体机sdk,和一体机环境 2025-12-16 15:36:42 +08:00
33f1d0a8ab 修改宽屏判断阈值 2025-12-16 10:34:40 +08:00
92d87347d0 Merge branch 'main-ALL-IN-ONE' of http://124.243.245.42:3000/sdz/qingdao-employment-service into main-ALL-IN-ONE 2025-12-16 09:31:56 +08:00
add2cfeac4 静态资源压缩 2025-12-16 09:31:54 +08:00
Apcallover
8e316cf2a1 flat: 2025-12-16 09:30:45 +08:00
Apcallover
d6bf2648de Merge branch 'main-ALL-IN-ONE' of http://124.243.245.42:3000/sdz/qingdao-employment-service into main-ALL-IN-ONE 2025-12-16 09:25:54 +08:00
Apcallover
d9f0356491 flat:修改 2025-12-16 09:22:57 +08:00
69 changed files with 5681 additions and 2228 deletions

129
App.vue
View File

@@ -1,36 +1,55 @@
<script setup>
import { reactive, inject, onMounted } from 'vue';
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
import { IncreaseRevie } from '@/common/all-in-one-listen.js';
import useUserStore from './stores/useUserStore';
import usePageAnimation from './hook/usePageAnimation';
import useDictStore from './stores/useDictStore';
import { GlobalInactivityManager } from '@/utils/GlobalInactivityManager';
const { $api, navTo, appendScriptTagElement, aes_Decrypt, sm2_Decrypt, safeReLaunch } = inject('globalFunction');
import config from '@/config.js';
import baseDB from '@/utils/db.js';
import { $confirm } from '@/utils/modal.js';
usePageAnimation();
const appword = 'aKd20dbGdFvmuwrt'; // 固定值
let uQRListen = null;
let inactivityManager = null;
let inactivityModalTimer = null;
onLaunch((options) => {
useDictStore().getDictData();
try {
if (lightAppJssdk.user) {
console.warn('爱山东环境');
getUserInfo();
useUserStore().changMiniProgramAppStatus(false);
} catch {
console.log('不是爱山东平台,使用测试登陆');
useUserStore().changMachineEnv(false);
return;
}
if (isY9MachineType()) {
console.warn('求职一体机环境');
baseDB.resetAndReinit(); // 清空indexdb
useUserStore().logOutApp();
useUserStore().changMiniProgramAppStatus(true);
useUserStore().initSeesionId(); //更新
let token = uni.getStorageSync('token') || ''; // 同步获取 缓存信息
if (token) {
useUserStore()
.loginSetToken(token)
.then(() => {
$api.msg('登录成功');
});
} else {
safeReLaunch('/pages/login/login');
// uni.redirectTo({
// url: '/pages/login/login',
// });
}
useUserStore().changMachineEnv(true);
uQRListen = new IncreaseRevie();
inactivityManager = new GlobalInactivityManager(handleInactivity, 60 * 1000);
inactivityManager.start();
return;
}
// 正式上线去除此方法
console.warn('浏览器环境');
useUserStore().changMiniProgramAppStatus(true);
useUserStore().changMachineEnv(false);
useUserStore().initSeesionId(); //更新
let token = uni.getStorageSync('token') || '';
if (token) {
useUserStore()
.loginSetToken(token)
.then(() => {
$api.msg('登录成功');
});
} else {
safeReLaunch('/pages/login/login');
}
});
@@ -44,6 +63,68 @@ onHide(() => {
console.log('App Hide');
});
function handleInactivity() {
console.log('【全局】60秒无操作执行安全逻辑');
if (inactivityModalTimer) {
clearTimeout(inactivityModalTimer);
inactivityModalTimer = null;
}
if (useUserStore().hasLogin) {
// 1. 正常弹出确认框
$confirm({
title: '会话即将过期',
content: '长时间无操作,是否继续使用?',
success: (res) => {
if (inactivityModalTimer) {
clearTimeout(inactivityModalTimer);
inactivityModalTimer = null;
}
if (res.confirm) {
inactivityManager?.resume();
} else {
performLogout();
}
},
fail: () => {
if (inactivityModalTimer) {
clearTimeout(inactivityModalTimer);
inactivityModalTimer = null;
}
performLogout();
},
});
// 2. 启动 10 秒倒计时
inactivityModalTimer = setTimeout(() => {
inactivityModalTimer = null;
console.log('【自动登出】10秒无响应强制清理状态');
// 【关键改进】:通知全局组件强制关闭弹窗,防止用户点击陈旧弹窗
uni.$emit('hide-global-popup');
performLogout();
}, 10000);
} else {
inactivityManager?.resume();
}
}
function performLogout() {
uni.clearStorageSync();
baseDB.resetAndReinit();
useUserStore().logOutApp();
inactivityManager?.resume(); // 恢复监听
}
// 一体机环境判断
function isY9MachineType() {
const ua = navigator.userAgent;
const isY9Machine = /Y9-ZYYH/i.test(ua); // 匹配机器型号
return isY9Machine;
}
// 爱山东环境登录
function getUserInfo() {
lightAppJssdk.user.getUserInfoWithEncryptedParamByAppId({
appId: config.appInfo.loveShandong, // 接入方在成功创建应用后自动生成
@@ -110,15 +191,9 @@ function loginCallback(userInfo) {
.then((resume) => {
if (resume.data.jobTitleId) {
useUserStore().initSeesionId();
// uni.reLaunch({
// url: '/pages/index/index',
// });
safeReLaunch('/pages/index/index');
} else {
safeReLaunch('/pages/login/login');
// uni.redirectTo({
// url: '/pages/login/login',
// });
}
});
});
@@ -130,7 +205,6 @@ function loginCallback(userInfo) {
@import '@/common/animation.css';
@import '@/common/common.css';
/* 修改pages tabbar样式 H5才有效 */
.uni-tabbar .uni-tabbar__item:nth-child(4) .uni-tabbar__bd .uni-tabbar__icon {
width: 108rpx !important;
@@ -163,7 +237,6 @@ uni-modal,
z-index: 998;
}
@font-face {
font-family: DingTalk JinBuTi;
src: url('/static/font/DingTalk JinBuTi_min.woff2') format('woff2');
@@ -172,19 +245,19 @@ uni-modal,
@font-face {
font-family: PingFangSC-Regular;
src: url('https://qd.zhaopinzao8dian.com/file/csn/PingFangSC-Regular.woff2') format('woff2');
src: url('/static/font/PingFangSC-Regular.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: PingFangSC-Medium;
src: url('https://qd.zhaopinzao8dian.com/file/csn/PingFangSC-Medium.woff2') format('woff2');
src: url('/static/font/PingFangSC-Medium.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: DIN-Medium;
src: url('https://qd.zhaopinzao8dian.com/file/csn/DIN-Medium.woff2') format('woff2');
src: url('/static/font/DIN-Medium.woff2') format('woff2');
font-display: swap;
}

View File

@@ -0,0 +1,65 @@
import {
$api
} from "./globalFunction";
import baseDB from '@/utils/db.js';
import useUserStore from '@/stores/useUserStore';
export class IncreaseRevie {
constructor(arg) {
this.myEventCallback = this.myCallback.bind(this);
this._debounceTimer = null;
this.init();
}
init() {
this.close();
setTimeout(() => {
if (window.hh?.on) {
window.hh.on('initQRListener', this.myEventCallback);
}
}, 1000);
}
close() {
if (window.hh?.off) {
window.hh.off('initQRListener', this.myEventCallback);
}
if (this._debounceTimer) {
clearTimeout(this._debounceTimer);
this._debounceTimer = null;
}
}
myCallback(res) {
if (this._debounceTimer) {
clearTimeout(this._debounceTimer);
}
this._debounceTimer = setTimeout(() => {
this.handleDebouncedCallback(res);
this._debounceTimer = null;
}, 300);
}
async handleDebouncedCallback(res) {
if (res.data) {
const code = res.data.qrQode
console.log('二维码code', code);
// 把code给到后端后端拿code兑换用户信息给前端返回token进行登录
// 一体机用户需要清空indexDB
// useUserStore()
// .loginSetToken(resData.token)
// .then((resume) => {
// if (resume.data.jobTitleId) {
// useUserStore().initSeesionId();
// safeReLaunch('/pages/index/index');
// } else {
// safeReLaunch('/pages/login/login');
// }
// });
// baseDB.resetAndReinit(); // 清空indexdb
} else {
$api.msg('识别失败')
}
}
}

View File

@@ -30,3 +30,15 @@ uni-toast .uni-toast__content {
font-size: 30rpx !important;
}
uni-modal .uni-modal{
max-width: 450rpx !important;
}
uni-modal .uni-modal__bd{
font-size: 34rpx !important;
min-height: 100rpx !important;
}
uni-modal .uni-modal__ft{
font-size: 36rpx !important;
line-height: 80rpx !important;
}

View File

@@ -27,7 +27,7 @@
</view>
<!-- 主体内容区域 -->
<view class="container-main">
<view class="container-main" :style="{ backgroundColor: mainbackGroundColor }">
<scroll-view v-if="useScrollView" scroll-y class="main-scroll" @scrolltolower="handleScrollToLower">
<slot></slot>
</scroll-view>
@@ -70,6 +70,10 @@ defineProps({
type: String,
default: '#ffffff',
},
mainbackGroundColor: {
type: String,
default: '',
},
useScrollView: {
type: Boolean,
default: true,

View File

@@ -0,0 +1,115 @@
<template>
<uni-popup ref="popup" type="dialog" mask-background-color="rgba(0,0,0,0.5)" @change="onPopupChange">
<view class="global-confirm-wrapper">
<uni-popup-dialog
mode="base"
:type="state.type"
:title="state.title"
:content="state.content"
:before-close="true"
@confirm="onConfirm"
@close="onClose"
></uni-popup-dialog>
</view>
</uni-popup>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
const popup = ref(null);
const state = reactive({
title: '',
content: '',
type: 'info',
resolve: null,
});
onMounted(() => {
uni.$on('show-global-popup', (options) => {
state.title = options.title || '提示';
state.content = options.content || '';
state.type = options.type || 'info';
state.resolve = options.resolve;
popup.value.open();
});
});
onUnmounted(() => {
uni.$off('show-global-popup');
});
const onConfirm = () => {
if (state.resolve) state.resolve(true);
popup.value.close();
};
const onClose = () => {
if (state.resolve) state.resolve(false);
popup.value.close();
};
const onPopupChange = (e) => {
if (!e.show && state.resolve) {
state.resolve(false);
state.resolve = null;
}
};
</script>
<style lang="scss" scoped>
.global-confirm-wrapper {
/* 整个弹窗容器宽度自适应 */
width: 600rpx;
/* 深度覆盖 uni-popup-dialog 内置样式 */
:deep(.uni-popup-dialog) {
width: 600rpx !important;
background-color: #fff;
border-radius: 24rpx;
/* 标题部分 */
.uni-dialog-title {
padding: 40rpx 0 20rpx;
.uni-dialog-title-text {
font-size: 36rpx;
font-weight: bold;
}
}
/* 内容部分 */
.uni-dialog-content {
padding: 20rpx 40rpx 40rpx;
.uni-dialog-content-text {
font-size: 30rpx;
color: #666;
line-height: 1.5;
}
}
/* 底部按钮组 */
.uni-dialog-button-group {
height: 100rpx;
border-top: 1rpx solid #f0f0f0;
.uni-dialog-button {
height: 100%;
/* 每个按钮的样式 */
.uni-dialog-button-text {
font-size: 32rpx;
}
/* 确定按钮颜色(通常是蓝色或主题色) */
.uni-button-color {
color: #007aff;
}
}
/* 按钮中间的分割线 */
.uni-border-left {
border-left: 1rpx solid #f0f0f0;
}
}
}
}
</style>

View File

@@ -4,11 +4,12 @@ export default {
// baseUrl: 'http://192.168.3.29:8081',
// baseUrl: 'http://10.213.6.207:19010/api',
// 语音转文字
// vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/system/asr/connect', // 自定义
vioceBaseURl: 'wss://fw.rc.qingdao.gov.cn/rgpp-api/api/system/asr/connect', // 内网
vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/app/asr/connect', // 自定义
// vioceBaseURl: 'wss://fw.rc.qingdao.gov.cn/rgpp-api/api/app/asr/connect', // 内网
// 语音合成
speechSynthesis: 'wss://qd.zhaopinzao8dian.com/api/speech-synthesis',
// speechSynthesis: 'wss://qd.zhaopinzao8dian.com/api/speech-synthesis',
speechSynthesis2: 'wss://resource.zhuoson.com/synthesis/', //直接替换即可
// speechSynthesis2: 'http://39.98.44.136:19527', //直接替换即可
// indexedDB
DBversion: 3,
// 只使用本地缓寸的数据

View File

@@ -8,11 +8,13 @@
export class PiperTTS {
constructor(config = {}) {
this.baseUrl = config.baseUrl || 'http://localhost:5001';
this.wsUrl = config.wsUrl || '/ws/synthesize'
this.audioCtx = config.audioCtx || new(window.AudioContext || window.webkitAudioContext)();
this.onStatus = config.onStatus || ((msg, type) => console.log(`[Piper] ${msg}`));
this.onStart = config.onStart || (() => {});
this.onEnd = config.onEnd || (() => {});
// 内部状态
this.ws = null;
this.nextTime = 0; // 下一段音频的预定播放时间
@@ -56,7 +58,7 @@ export class PiperTTS {
this.onStatus('正在建立连接...', 'processing');
try {
const wsUrl = this.baseUrl.replace(/^http/, 'ws') + '/ws/synthesize';
const wsUrl = this.baseUrl.replace(/^http/, 'ws') + this.wsUrl;
this.ws = new WebSocket(wsUrl);
this.ws.binaryType = 'arraybuffer';

View File

@@ -22,17 +22,19 @@
<script>
eruda.init();
</script> -->
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>
// VConsole 默认会挂载到 `window.VConsole` 上
var vConsole = new window.VConsole();
</script> -->
</script>
<!-- 爱山东jssdk 本sdk存在性能问题 -->
<script type="text/javascript" src="./static/js/jsbridge.js"></script>
<!-- <script type="text/javascript" src="./static/js/jweixin-1.4.0.js"></script> -->
<script type="text/javascript" src="https://isdapp.shandong.gov.cn/jmopen/jssdk/index.js"></script>
<!-- 只在内网有效 -->
<script type="text/javascript" src="./static/js/SM.js"></script>
<script type="text/javascript" src="./static/js/pixi.min.js"></script>
</head>
<body>
<div id="app"><!--app-html--></div>

View File

@@ -8,6 +8,7 @@ import '@/lib/string-similarity.min.js'
import similarityJobs from '@/utils/similarity_Job.js';
import useScreenStore from './stores/useScreenStore'
// 组件
import AppLayout from './components/AppLayout/AppLayout.vue';
import Empty from './components/empty/empty.vue';
@@ -24,6 +25,7 @@ import renderJobCollectionRecord from '@/components/renderJobCollectionRecord/re
import renderCompanyCollectionRecord from '@/components/renderCompanyCollectionRecord/renderCompanyCollectionRecord.vue';
import renderJobViewRecord from '@/components/renderJobViewRecord/renderJobViewRecord.vue';
import MyIcons from '@/components/My-icons/my-icons.vue';
import GlobalPopup from '@/components/GlobalPopup/GlobalPopup.vue'
// import Tabbar from '@/components/tabbar/midell-box.vue'
// 自动导入 directives 目录下所有指令
console.log(lightAppJssdk)
@@ -54,7 +56,8 @@ export function createApp() {
app.component('renderJobCollectionRecord', renderJobCollectionRecord) //渲染岗位收藏记录
app.component('renderCompanyCollectionRecord', renderCompanyCollectionRecord) //渲染公司收藏记录
app.component('renderJobViewRecord', renderJobViewRecord) //渲染岗位浏览记录
app.component('MyIcons', MyIcons)
app.component('MyIcons', MyIcons)
app.component('global-popup', GlobalPopup)
// app.component('tabbar-custom', Tabbar)
for (const path in directives) {
@@ -68,6 +71,7 @@ export function createApp() {
...globalFunction,
similarityJobs
});
app.provide('deviceInfo', globalFunction.getdeviceInfo());
app.use(SelectPopupPlugin);

970
package-lock.json generated Normal file
View File

@@ -0,0 +1,970 @@
{
"name": "qingdao-employment-service",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"unplugin-vue-components": "^0.26.0"
}
},
"node_modules/@antfu/utils": {
"version": "0.7.10",
"resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz",
"integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.3.0",
"resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
"node_modules/@vue/compiler-core": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
"integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.25",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz",
"integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-core": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz",
"integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.25",
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-ssr": "3.5.25",
"@vue/shared": "3.5.25",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz",
"integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.25.tgz",
"integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.25.tgz",
"integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz",
"integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.25",
"@vue/runtime-core": "3.5.25",
"@vue/shared": "3.5.25",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.25.tgz",
"integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.25",
"@vue/shared": "3.5.25"
},
"peerDependencies": {
"vue": "3.5.25"
}
},
"node_modules/@vue/shared": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/anymatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/local-pkg": {
"version": "0.4.3",
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz",
"integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC",
"peer": true
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"queue-microtask": "^1.2.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/unplugin": {
"version": "1.16.1",
"resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz",
"integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.14.0",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/unplugin-vue-components": {
"version": "0.26.0",
"resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz",
"integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@antfu/utils": "^0.7.6",
"@rollup/pluginutils": "^5.0.4",
"chokidar": "^3.5.3",
"debug": "^4.3.4",
"fast-glob": "^3.3.1",
"local-pkg": "^0.4.3",
"magic-string": "^0.30.3",
"minimatch": "^9.0.3",
"resolve": "^1.22.4",
"unplugin": "^1.4.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@babel/parser": "^7.15.8",
"@nuxt/kit": "^3.2.2",
"vue": "2 || 3"
},
"peerDependenciesMeta": {
"@babel/parser": {
"optional": true
},
"@nuxt/kit": {
"optional": true
}
}
},
"node_modules/vue": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz",
"integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-sfc": "3.5.25",
"@vue/runtime-dom": "3.5.25",
"@vue/server-renderer": "3.5.25",
"@vue/shared": "3.5.25"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"dev": true,
"license": "MIT"
}
}
}

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"devDependencies": {
"unplugin-vue-components": "^0.26.0"
}
}

View File

@@ -1,5 +1,11 @@
<template>
<AppLayout title="投递记录" :use-scroll-view="true" :show-bg-image="false" @onScrollBottom="getJobList()">
<AppLayout
title="投递记录"
:use-scroll-view="true"
mainback-ground-color="#f4f4f4"
:show-bg-image="false"
@onScrollBottom="getJobList()"
>
<template #headerleft v-if="isMiniProgram">
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>
@@ -12,7 +18,7 @@
:list="pageState.list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderDeliveryRecord>
></renderDeliveryRecord>
<empty v-else :is-position="true"></empty>
</view>
</AppLayout>
@@ -45,8 +51,6 @@ onLoad(() => {
getJobList();
});
function getJobList(type = 'add') {
if (type === 'refresh') {
pageState.page = 1;

View File

@@ -65,7 +65,6 @@ function removeNullProperties(obj) {
}
return obj;
}
</script>
<style lang="stylus" scoped>

View File

@@ -168,7 +168,7 @@
</view>
<view v-else class="btn-wq button-click" @click="jobApply">
<span v-if="jobInfo.isApply">立即前往</span>
<span v-if="!jobInfo.isApply">立即投递</span>
<span v-if="!jobInfo.isApply">立即前往</span>
</view>
</view>
</template>
@@ -185,7 +185,7 @@ import dictLabel from '@/components/dict-Label/dict-Label.vue';
import RadarMap from './component/radarMap.vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
const { isMiniProgram, hasLogin } = storeToRefs(useUserStore());
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import config from '@/config.js';
@@ -252,7 +252,10 @@ function getDetail(jobId) {
const { latitude, longitude, companyName, companyId } = resData.data;
jobInfo.value = resData.data;
getCompanyIsAJobs(companyId);
getCompetivetuveness(jobId);
if (hasLogin.value) {
getCompetivetuveness(jobId);
}
if (latitude && longitude) {
initMapCovers(latitude, longitude, companyName);
@@ -285,12 +288,12 @@ function getCompanyIsAJobs(...args) {
if (dataType.value === 2) {
// 第三方数据获取公司职位数量
const [gsID, gsmc, zphID] = args;
const params={
const params = {
gsID,
gsmc,
zphID
}
$api.createRequest(`/app/internal/jobThirdPart`,params).then((resData) => {
zphID,
};
$api.createRequest(`/app/internal/jobThirdPart`, params).then((resData) => {
companyCount.value = resData.total;
});
} else {

View File

@@ -281,7 +281,7 @@
// "enablePullDownRefresh": false,
// "navigationStyle": "custom",
"rpxCalcBaseDeviceWidth": 375,
"rpxCalcMaxDeviceWidth": 1200,
"rpxCalcMaxDeviceWidth": 960,
"rpxCalcIncludeWidth": 750
},
"uniIdRouter": {}

View File

@@ -1,544 +0,0 @@
<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">
<my-icons class="iconsearch" color="#666666" type="search" size="32"></my-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

@@ -7,7 +7,7 @@
<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 }">
<view v-if="!isMachineEnv" class="header-btnLf button-click" @click="seemsg(1)" :class="{ active: state.current === 1 }">
VR虚拟招聘会
</view>
</view>
@@ -113,6 +113,8 @@ import useLocationStore from '@/stores/useLocationStore';
import { storeToRefs } from 'pinia';
import config from '@/config';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
import useUserStore from '@/stores/useUserStore';
const { isMachineEnv } = storeToRefs(useUserStore());
const { $api, navTo, cloneDeep, debounce } = inject('globalFunction');
import useScreenStore from '@/stores/useScreenStore'
@@ -195,7 +197,6 @@ function changeSwiperMsgType(e) {
function seemsg(index) {
if (index === 1) {
handleItemClick();
// $api.msg('功能确定中');
return;
}
state.current = index;

View File

@@ -71,7 +71,7 @@
</template>
<script setup>
import { ref, inject, nextTick, computed,watch } from 'vue';
import { ref, inject, nextTick, computed, watch } from 'vue';
const { $api, navTo, insertSortData } = inject('globalFunction');
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import Tabbar from '@/components/tabbar/midell-box.vue';
@@ -88,19 +88,18 @@ import config from '@/config';
const searchText = ref('');
const paging = ref(null);
import useScreenStore from '@/stores/useScreenStore';
const screenStore = useScreenStore();
import useScreenStore from '@/stores/useScreenStore'
const screenStore = useScreenStore()
const showTabbar = ref(false)
const showTabbar = ref(false);
watch(
() => screenStore.isWideScreen,
(newVal) => {
showTabbar.value = newVal
showTabbar.value = newVal;
},
{ immediate: true }
)
);
// 实时过滤
const filteredList = computed(() => {
@@ -125,7 +124,7 @@ onHide(() => {
paging.value?.handleTouchCancel();
if (isDrawerOpen.value) {
isDrawerOpen.value = false;
if(!screenStore.isWideScreen)uni.showTabBar();
if (!screenStore.isWideScreen) uni.showTabBar();
}
});
@@ -134,7 +133,7 @@ const toggleDrawer = () => {
if (isDrawerOpen.value) {
uni.hideTabBar();
} else {
if(!screenStore.isWideScreen)uni.showTabBar();
if (!screenStore.isWideScreen) uni.showTabBar();
}
};

View File

@@ -241,6 +241,7 @@
</view>
</view>
<PopupFeeBack ref="feeback" @onClose="colseFeeBack" @onSend="confirmFeeBack"></PopupFeeBack>
<UploadQrcode ref="qrcodeRef" @onSend="handleFileSend" :sessionId="chatSessionID"></UploadQrcode>
<MsgTips ref="feeBackTips" content="已收到反馈,感谢您的关注" title="反馈成功" :icon="successIcon"></MsgTips>
</view>
</template>
@@ -263,10 +264,13 @@ import useChatGroupDBStore from '@/stores/userChatGroupStore';
import MdRender from '@/components/md-render/md-render.vue';
import CollapseTransition from '@/components/CollapseTransition/CollapseTransition.vue';
import PopupFeeBack from './popupbadFeeback.vue';
import UploadQrcode from './uploadQrcode.vue';
import AudioWave from './AudioWave.vue';
import WaveDisplay from './WaveDisplay.vue';
import FileIcon from './fileIcon.vue';
import FileText from './fileText.vue';
import useScreenStore from '@/stores/useScreenStore'
const screenStore = useScreenStore();
// 系统功能hook和阿里云hook
import { useAudioRecorder } from '@/hook/useRealtimeRecorder2.js';
// import { useAudioRecorder } from '@/hook/useSystemSpeechReader.js';
@@ -277,6 +281,8 @@ const { $api, navTo, throttle } = inject('globalFunction');
const emit = defineEmits(['onConfirm']);
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
import successIcon from '@/static/icon/success.png';
import useUserStore from '@/stores/useUserStore';
const { isMachineEnv } = storeToRefs(useUserStore());
// hook
// 语音识别
const {
@@ -314,6 +320,7 @@ const feeBackTips = ref(null);
const state = reactive({
uploadFileTips: '请根据以上附件,帮我推荐岗位。',
});
const qrcodeRef = ref(null);
const statusText = computed(() => {
switch (status.value) {
@@ -446,7 +453,7 @@ const scrollToBottom = throttle(function () {
}, 500);
function getGuess() {
$api.chatRequest('/app/chat/guest', { sessionId: chatSessionID.value }, 'POST').then((res) => {
$api.createRequest('/app/chat/guest', { sessionId: chatSessionID.value }, 'POST').then((res) => {
guessList.value = res.data;
showGuess.value = true;
nextTick(() => {
@@ -456,7 +463,15 @@ function getGuess() {
}
function isImage(type) {
return new RegExp('image').test(type);
if (!type || typeof type !== 'string') return false;
const imageTypes = [
'jpg', 'jpeg', 'png', 'gif', 'bmp',
'webp', 'svg', 'tiff', 'tif', 'ico',
'apng', 'avif', 'heic', 'heif', 'jfif'
];
const lowerType = type.toLowerCase();
return imageTypes.some(item=>lowerType.includes(item))
}
function isFile(type) {
@@ -496,6 +511,7 @@ function uploadCamera(type = 'camera') {
// 继续上传
$api.uploadFile(tempFilePaths[0], true).then((resData) => {
resData = JSON.parse(resData);
console.log(file.type,'++')
if (isImage(file.type)) {
filesList.value.push({
url: resData.msg,
@@ -536,6 +552,31 @@ function getUploadFile(type = 'camera') {
});
}
const handleFileSend = (rows)=>{
filesList.value = []
try {
rows.map(item=>{
if(isImage(item.fileSuffix)){
filesList.value.push({
url: item.fileUrl,
type: item.fileSuffix,
name: item.fileName,
});
}else{
filesList.value.push({
url: item.fileUrl,
type: item.fileSuffix,
name: item.fileName,
});
}
})
textInput.value = state.uploadFileTips;
} catch (error) {
console.log(error)
}
}
const tipsPermisson = () => {
uni.showToast({
title: '需要授权麦克风权限才能使用语音功能',
@@ -597,6 +638,10 @@ function changeVoice() {
}
function changeShowFile() {
if(isMachineEnv.value){
qrcodeRef.value?.open()
return
}
showfile.value = !showfile.value;
}
@@ -619,14 +664,14 @@ function confirmFeeBack(value) {
useChatGroupDBStore()
.badFeedback(feebackData.value, value)
.then(() => {
uni.showTabBar()
if (!screenStore.isWideScreen) uni.showTabBar()
feeback.value?.close();
feeBackTips.value?.open();
});
}
function colseFeeBack() {
uni.showTabBar()
if (!screenStore.isWideScreen) uni.showTabBar()
}
function readMarkdown(value, index) {
@@ -898,6 +943,7 @@ image-margin-top = 40rpx
position: relative
background: #F5F5F5;
border-radius: 60rpx 60rpx 60rpx 60rpx;
font-size: 28rpx;
}
.input_vio
flex: 1;
@@ -1009,6 +1055,7 @@ image-margin-top = 40rpx
left: 0
padding: 10rpx 0 10rpx 30rpx
box-shadow: 0rpx -4rpx 10rpx 0rpx rgba(11,44,112,0.06);
z-index:1
.uploadfiles-scroll
height: 100%
.uploadfiles-list

View File

@@ -1,9 +1,11 @@
<template>
<image v-if="type === 'application/pdf'" :src="pdfIcon" class="file-icon" />
<image v-if="type === 'application/pdf' || type === 'pdf'" :src="pdfIcon" class="file-icon" />
<image
v-else-if="
type === 'application/msword' ||
type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
type === 'doc' ||
type === 'docx'
"
:src="docIcon"
class="file-icon"
@@ -11,18 +13,22 @@
<image
v-else-if="
type === 'application/vnd.ms-powerpoint' ||
type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation'||
type === 'ppt' ||
type === 'pptx'
"
:src="pptIcon"
class="file-icon"
/>
<image v-else-if="type === 'text/markdown'" :src="mdIcon" class="file-icon" />
<image v-else-if="type === 'text/plain'" :src="txtIcon" class="file-icon" />
<image v-else-if="type === 'text/html'" :src="htmlIcon" class="file-icon" />
<image v-else-if="type === 'text/markdown' || type === 'md'" :src="mdIcon" class="file-icon" />
<image v-else-if="type === 'text/plain' || type=== 'txt'" :src="txtIcon" class="file-icon" />
<image v-else-if="type === 'text/html' || type === 'html'" :src="htmlIcon" class="file-icon" />
<image
v-else-if="
type === 'application/vnd.ms-excel' ||
type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'||
type === 'xls' ||
type === 'xlsx'
"
:src="excelIcon"
class="file-icon"

View File

@@ -23,6 +23,16 @@ const fileAbbreviation = computed(() => {
'text/markdown': 'MD',
'text/plain': 'TXT',
'text/html': 'HTML',
'pdf':'PDF',
'doc':'DOC',
'docx':'DOCX',
'ppt':'PPT',
'pptx':'PPTX',
'xls': 'XLS',
'xlsx': 'XLSX',
'md':'MD',
'txt':'TXT',
'html':'HTML'
};
return typeMap[props.type] || 'OTHER';
});

View File

@@ -0,0 +1,303 @@
<template>
<uni-popup ref="popup" type="center" borderRadius="12px 12px 0 0" @change="changePopup">
<view class="popup-inner">
<view class="title">请扫码上传附件</view>
<view class="img-box">
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<uni-load-more status="loading" :icon-size="24" :content-text="loadingText" />
</view>
<canvas canvas-id="qrcode" id="qrcode" />
</view>
<view class="tips" v-if="!loading">
已上传
<span class="num">{{ fileCount }}</span>
个文件
</view>
<view class="tips" v-if="!loading">请使用手机扫描二维码上传文件</view>
<view class="close-btn" @click="close"></view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, inject, onUnmounted, watch, computed } from 'vue';
import uQRCode from '@/static/js/qrcode';
import config from '@/config';
import { onShow, onHide } from '@dcloudio/uni-app';
import { UUID } from '../../../lib/uuid-min';
const props = defineProps({
sessionId: {
type: [Number, String],
default: '',
},
});
const emit = defineEmits(['onSend', 'onClose']);
const { $api } = inject('globalFunction');
const popup = ref(null);
const loading = ref(false);
const pollingTimer = ref(null);
const isPolling = ref(false);
const isVisible = ref(false);
const uuid = ref(null);
const fileCount = ref(0);
// 计算加载文本
const loadingText = computed(() => ({
contentdown: '二维码生成中',
contentrefresh: '二维码生成中',
}));
onUnmounted(() => {
stopPolling();
clearResources();
});
function open() {
uuid.value = UUID.generate();
resetState();
isVisible.value = true;
popup.value.open();
initQrCode();
}
function close() {
isVisible.value = false;
stopPolling();
popup.value.close();
resetState();
}
function changePopup(e) {
if (e.show) {
} else {
stopPolling();
emit('onClose');
}
}
// 重置所有状态
function resetState() {
fileCount.value = 0
loading.value = false;
stopPolling();
}
async function initQrCode() {
try {
loading.value = true;
// 清除之前的二维码
clearCanvas();
await makeQrcode();
// 二维码生成成功后开始轮询
if (isVisible.value) {
startPolling();
}
} catch (error) {
console.error('生成二维码失败:', error);
uni.showToast({
title: '生成二维码失败,请重试',
icon: 'none',
});
close();
} finally {
loading.value = false;
}
}
function makeQrcode() {
const protocol = window.location.protocol;
const host = window.location.host;
const isLocal = host.includes('localhost') || host.includes('127.0.0.1');
// const pathPrefix = isLocal ? '' : '/rgpp-api/all-in-one';
let pathPrefix = '';
if (host.includes('localhost') || host.includes('127.0.0.1')) {
pathPrefix = '';
} else if (host.includes('qd.zhaopinzao8dian.com')) {
// 外网测试环境
pathPrefix = '/app';
} else if (host.includes('fw.rc.qingdao.gov.cn')) {
// 青岛政务网环境
pathPrefix = '/rgpp-api/all-in-one';
} else {
pathPrefix = '';
}
const htmlPath = `${protocol}//${host}${pathPrefix}/static/upload.html?sessionId=${uuid.value}&uploadApi=${config.baseUrl}/app/kiosk/upload`;
// const htmlPath = `${window.location.host}/static/upload.html?sessionId=${uuid.value}&uploadApi=${
// config.baseUrl + '/app/kiosk/upload'
// }`;
// const htmlPath = `${window.location.host}/static/upload.html?sessionId=${props.sessionId}&uploadApi=${
// config.baseUrl + '/app/kiosk/upload'
// }`;
console.log(htmlPath);
return new Promise((resolve, reject) => {
setTimeout(() => {
uQRCode.make({
canvasId: 'qrcode',
text: htmlPath,
size: uni.upx2px(300),
margin: 0,
backgroundColor: '#ffffff',
foregroundColor: '#1677ff',
fileType: 'png',
correctLevel: uQRCode.defaults.correctLevel,
});
resolve();
}, 100);
});
}
// 清除画布
function clearCanvas() {
const ctx = uni.createCanvasContext('qrcode');
ctx.clearRect(0, 0, uni.upx2px(300), uni.upx2px(300));
ctx.draw();
}
function startPolling() {
if (isPolling.value) return;
isPolling.value = true;
console.log('开始轮询');
// 轮询检查上传状态
const poll = async () => {
if (!isPolling.value || !isVisible.value) return;
const { data } = await $api.createRequest('/app/kiosk/list', { sessionId: uuid.value });
// const { data } = await $api.createRequest('/app/kiosk/list',{sessionId:props.sessionId});
if (data && data.length) {
// 上传完成,触发事件
fileCount.value = data.length;
emit('onSend', data);
}
if (isPolling.value && isVisible.value) {
pollingTimer.value = setTimeout(poll, 2000); // 每2秒轮询一次
}
};
poll();
}
function stopPolling() {
isPolling.value = false;
if (pollingTimer.value) {
clearTimeout(pollingTimer.value);
pollingTimer.value = null;
}
console.log('停止轮询');
}
function clearResources() {
stopPolling();
clearCanvas();
}
defineExpose({ open, close });
</script>
<style lang="scss" scoped>
.popup-inner {
padding: 40rpx 30rpx;
padding-bottom: 50rpx;
width: 440rpx;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
border-radius: 20rpx;
background: #fafafa;
}
.title {
font-size: 32rpx;
color: #333;
margin-bottom: 30rpx;
font-weight: bold;
text-align: center;
}
.img-box {
margin: 0 auto 30rpx;
width: 300rpx;
height: 300rpx;
background-color: #ffffff;
border-radius: 10rpx;
overflow: hidden;
position: relative;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
padding: 20rpx;
canvas {
width: 100%;
height: 100%;
transition: opacity 0.3s ease;
}
}
.loading-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
}
.tips {
font-size: 24rpx;
color: #666;
text-align: center;
line-height: 1.6;
padding: 0 20rpx;
.num {
color: #4191fe;
font-size: 28rpx;
}
}
.close-btn {
position: absolute;
right: 20rpx;
top: 20rpx;
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s;
&:active {
background-color: rgba(0, 0, 0, 0.1);
}
&::before {
position: absolute;
left: 50%;
top: 50%;
content: '';
width: 4rpx;
height: 28rpx;
border-radius: 2rpx;
background: #5a5a68;
transform: translate(50%, -50%) rotate(-45deg);
}
&::after {
position: absolute;
left: 50%;
top: 50%;
content: '';
width: 4rpx;
height: 28rpx;
border-radius: 2rpx;
background: #5a5a68;
transform: translate(50%, -50%) rotate(45deg);
}
}
</style>

View File

@@ -1,14 +1,14 @@
<template>
<view class="out">
<view v-if="loading" class="loading">
<view class="semicircle-loader"></view>
<view class="out">
<view v-if="loading" class="loading">
<view class="semicircle-loader"></view>
</view>
<view v-else class="container" id="pixi-box" ref="pixiContainerRef"></view>
</view>
<view v-else class="container" id="pixi-box" ref="pixiContainerRef"></view>
</view>
</template>
<script setup>
import { onMounted, onUnmounted, ref, nextTick ,watch} from "vue";
import { onMounted, onUnmounted, ref, nextTick, watch } from 'vue';
const props = defineProps({
tags: {
@@ -20,7 +20,7 @@ const props = defineProps({
default: false,
},
});
const emit = defineEmits(["tag-click"]);
const emit = defineEmits(['tag-click']);
// DOM Ref
const pixiContainerRef = ref(null);
@@ -31,359 +31,365 @@ let tagsContainer = null;
let activeTagInstances = [];
// 配置数据
const tagsConfig =ref(
[
{ bgColor: 0x0069fe, fontColor: 0xffffff, size: 16, opacity: 1.0, angle: 0, radius: 0 },
{
bgColor: 0x87e2ec,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: -Math.PI / 2,
radius: 68,
tailRotation: Math.PI / 2,
},
{
bgColor: 0xffebeb,
tailColor: 0xffe1e1,
fontColor: 0xff6969,
size: 11.5,
opacity: 1,
angle: -Math.PI / 4.2,
radius: 125,
tailRotation: (3 * Math.PI) / 4.5,
},
{
bgColor: 0x21ea85,
fontColor: 0xffffff,
size: 15,
opacity: 1,
angle: -Math.PI / 10,
radius: 130,
tailRotation: (3 * Math.PI) / 4.2,
},
{
bgColor: 0xebf3ff,
tailColor: 0xb9d3ff,
fontColor: 0x1d71ef,
size: 12,
opacity: 1,
angle: Math.PI / 18,
radius: 135,
tailRotation: (3 * Math.PI) / 4.3,
},
{
bgColor: 0xffd4b6,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: Math.PI / 4.3,
radius: 100,
tailRotation: -(3 * Math.PI) / 4.5,
},
const tagsConfig = ref([
{ bgColor: 0x0069fe, fontColor: 0xffffff, size: 16, opacity: 1.0, angle: 0, radius: 0 },
{
bgColor: 0x87e2ec,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: -Math.PI / 2,
radius: 68,
tailRotation: Math.PI / 2,
},
{
bgColor: 0xffebeb,
tailColor: 0xffe1e1,
fontColor: 0xff6969,
size: 11.5,
opacity: 1,
angle: -Math.PI / 4.2,
radius: 125,
tailRotation: (3 * Math.PI) / 4.5,
},
{
bgColor: 0x21ea85,
fontColor: 0xffffff,
size: 15,
opacity: 1,
angle: -Math.PI / 10,
radius: 130,
tailRotation: (3 * Math.PI) / 4.2,
},
{
bgColor: 0xebf3ff,
tailColor: 0xb9d3ff,
fontColor: 0x1d71ef,
size: 12,
opacity: 1,
angle: Math.PI / 18,
radius: 135,
tailRotation: (3 * Math.PI) / 4.3,
},
{
bgColor: 0xffd4b6,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: Math.PI / 4.3,
radius: 100,
tailRotation: -(3 * Math.PI) / 4.5,
},
{
bgColor: 0xff9400,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: (2 * Math.PI) / 3,
radius: 92,
tailRotation: -Math.PI / 2.4,
},
{
bgColor: 0xebf3ff,
tailColor: 0xb9d3ff,
fontColor: 0x1d71ef,
size: 10.5,
opacity: 1,
angle: (5.4 * Math.PI) / 6,
radius: 110,
tailRotation: (3 * Math.PI) / 1.79,
},
{
bgColor: 0xff6969,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: (6.3 * Math.PI) / 5.8,
radius: 120,
tailRotation: Math.PI / 2.9,
},
{
bgColor: 0xfce9c9,
fontColor: 0xfbc55f,
size: 13,
opacity: 1,
angle: (7.2 * Math.PI) / 5.9,
radius: 120,
tailRotation: Math.PI / 3,
},
]
)
{
bgColor: 0xff9400,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: (2 * Math.PI) / 3,
radius: 92,
tailRotation: -Math.PI / 2.4,
},
{
bgColor: 0xebf3ff,
tailColor: 0xb9d3ff,
fontColor: 0x1d71ef,
size: 10.5,
opacity: 1,
angle: (5.4 * Math.PI) / 6,
radius: 110,
tailRotation: (3 * Math.PI) / 1.79,
},
{
bgColor: 0xff6969,
fontColor: 0xffffff,
size: 14,
opacity: 1,
angle: (6.3 * Math.PI) / 5.8,
radius: 120,
tailRotation: Math.PI / 2.9,
},
{
bgColor: 0xfce9c9,
fontColor: 0xfbc55f,
size: 13,
opacity: 1,
angle: (7.2 * Math.PI) / 5.9,
radius: 120,
tailRotation: Math.PI / 3,
},
]);
watch(
()=>props.tags,
(val)=>{
if(val&&val.length){
tagsConfig.value.map((tag,index)=>{
console.log(val[index])
tag.name = val[index]?.job_name
})
setTimeout(() => {
initPixi()
}, 100);
}
},
{deep:true}
)
() => props.tags,
(val) => {
if (val && val.length) {
tagsConfig.value.map((tag, index) => {
// console.log(val[index])
tag.name = val[index]?.job_name;
});
setTimeout(() => {
initPixi();
}, 100);
}
},
{ deep: true }
);
onMounted(async () => {
window.addEventListener("resize", handleResize);
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
if (app) {
app.destroy(true, { children: true, texture: true, baseTexture: true });
app = null;
}
window.removeEventListener('resize', handleResize);
if (app) {
app.destroy(true, { children: true, texture: true, baseTexture: true });
app = null;
}
});
const getContainerDOM = () => {
const refVal = pixiContainerRef.value;
if (!refVal) return document.getElementById("pixi-box");
if (refVal.$el) return refVal.$el;
return refVal;
const refVal = pixiContainerRef.value;
if (!refVal) return document.getElementById('pixi-box');
if (refVal.$el) return refVal.$el;
return refVal;
};
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const initPixi = () => {
const container = getContainerDOM();
if (!container) return;
const container = getContainerDOM();
if (!container) return;
const width = container.clientWidth || 300;
const height = container.clientHeight || 300;
const width = container.clientWidth || 300;
const height = container.clientHeight || 300;
if (app) return;
if (app) return;
app = new PIXI.Application({
width: width,
height: height,
backgroundAlpha: 0,
backgroundColor: 0xf5f7fa,
antialias: true,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
});
app.view.style.touchAction = "auto";
app = new PIXI.Application({
width: width,
height: height,
backgroundAlpha: 0,
backgroundColor: 0xf5f7fa,
antialias: true,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
});
app.view.style.touchAction = 'auto';
container.appendChild(app.view);
container.appendChild(app.view);
tagsContainer = new PIXI.Container();
app.stage.addChild(tagsContainer);
tagsContainer = new PIXI.Container();
app.stage.addChild(tagsContainer);
renderScene(width, height);
renderScene(width, height);
};
const renderScene = (sw, sh) => {
let ratio = window.innerWidth / 400;
if (ratio < 1) ratio = 1;
tagsContainer.removeChildren();
activeTagInstances = [];
let ratio = window.innerWidth / 400;
if (ratio < 1) ratio = 1;
tagsContainer.removeChildren();
activeTagInstances = [];
tagsConfig.value.forEach((data, index) => {
const scaledRadius = data.radius * ratio;
tagsConfig.value.forEach((data, index) => {
const scaledRadius = data.radius * ratio;
let x = sw / 2 + scaledRadius * Math.cos(data.angle);
let y = sh / 2 + scaledRadius * Math.sin(data.angle);
let x = sw / 2 + scaledRadius * Math.cos(data.angle);
let y = sh / 2 + scaledRadius * Math.sin(data.angle);
const tag = createTag(data, index, ratio);
const tag = createTag(data, index, ratio);
tagsContainer.addChild(tag);
tagsContainer.addChild(tag);
const safeW = tag.width / 2 + 10;
const safeH = tag.height / 2 + 10;
const safeW = tag.width / 2 + 10;
const safeH = tag.height / 2 + 10;
// 强制修正 x 和 y使其不超出屏幕
x = clamp(x, safeW, sw - safeW);
y = clamp(y, safeH, sh - safeH);
// 强制修正 x 和 y使其不超出屏幕
x = clamp(x, safeW, sw - safeW);
y = clamp(y, safeH, sh - safeH);
tag.x = x;
tag.y = y;
tag.x = x;
tag.y = y;
// 4. 保存元数据
tag.userData = {
originalX: x,
originalY: y,
angle: data.angle,
radius: scaledRadius,
floatOffset: Math.random() * Math.PI * 2,
floatSpeed: 0.01 + Math.random() * 0.02,
floatRange: 2 + Math.random() * 2,
safeH: safeH,
};
// 4. 保存元数据
tag.userData = {
originalX: x,
originalY: y,
angle: data.angle,
radius: scaledRadius,
floatOffset: Math.random() * Math.PI * 2,
floatSpeed: 0.01 + Math.random() * 0.02,
floatRange: 2 + Math.random() * 2,
safeH: safeH,
};
if (data.radius > 0) {
const tail = createCometTail(data.tailColor || data.bgColor, data.tailRotation, tag.width, tag.height, ratio);
tag.addChildAt(tail, 0);
tag.updateTail = () => tail.updateAnim();
}
if (data.radius > 0) {
const tail = createCometTail(
data.tailColor || data.bgColor,
data.tailRotation,
tag.width,
tag.height,
ratio
);
tag.addChildAt(tail, 0);
tag.updateTail = () => tail.updateAnim();
}
activeTagInstances.push(tag);
});
// 动画循环
app.ticker.add(() => {
const screenH = app.screen.height;
activeTagInstances.forEach((tag) => {
const meta = tag.userData;
if (meta) {
// 计算新的浮动位置
meta.floatOffset += meta.floatSpeed;
let nextY = meta.originalY + Math.sin(meta.floatOffset) * meta.floatRange;
// 再次进行边界检查
if (nextY < meta.safeH) nextY = meta.safeH;
if (nextY > screenH - meta.safeH) nextY = screenH - meta.safeH;
tag.y = nextY;
if (tag.updateTail) tag.updateTail();
}
activeTagInstances.push(tag);
});
// 动画循环
app.ticker.add(() => {
const screenH = app.screen.height;
activeTagInstances.forEach((tag) => {
const meta = tag.userData;
if (meta) {
// 计算新的浮动位置
meta.floatOffset += meta.floatSpeed;
let nextY = meta.originalY + Math.sin(meta.floatOffset) * meta.floatRange;
// 再次进行边界检查
if (nextY < meta.safeH) nextY = meta.safeH;
if (nextY > screenH - meta.safeH) nextY = screenH - meta.safeH;
tag.y = nextY;
if (tag.updateTail) tag.updateTail();
}
});
});
});
};
const createTag = (tagData, index, ratio) => {
if (ratio > 1) ratio = ratio * 0.9;
const tagGroup = new PIXI.Container();
tagGroup.eventMode = "static";
tagGroup.cursor = "pointer";
if (ratio > 1) ratio = ratio * 0.9;
const tagGroup = new PIXI.Container();
tagGroup.eventMode = 'static';
tagGroup.cursor = 'pointer';
tagGroup.on("pointertap", () => emit("tag-click", tagData));
tagGroup.on('pointertap', () => emit('tag-click', tagData));
const text = new PIXI.Text(tagData.name, {
fontFamily: ["PingFang SC", "Microsoft YaHei", "Arial"],
fontSize: tagData.size * ratio,
fill: tagData.fontColor,
padding: 4 * ratio,
resolution: 2,
});
text.anchor.set(0.5);
const text = new PIXI.Text(tagData.name, {
fontFamily: ['PingFang SC', 'Microsoft YaHei', 'Arial'],
fontSize: tagData.size * ratio,
fill: tagData.fontColor,
padding: 4 * ratio,
resolution: 2,
});
text.anchor.set(0.5);
const paddingH = 26 * ratio;
const paddingV = 10 * ratio;
let bgWidth = text.width + paddingH;
let bgHeight = text.height + paddingV;
const paddingH = 26 * ratio;
const paddingV = 10 * ratio;
let bgWidth = text.width + paddingH;
let bgHeight = text.height + paddingV;
if (index === 0) bgWidth = Math.max(bgWidth, tagData.size * 4.5);
if (index === 0) bgWidth = Math.max(bgWidth, tagData.size * 4.5);
const bg = new PIXI.Graphics();
bg.beginFill(tagData.bgColor, tagData.opacity ?? 1);
bg.drawRoundedRect(-bgWidth / 2, -bgHeight / 2, bgWidth, bgHeight, bgHeight / 2);
bg.endFill();
const bg = new PIXI.Graphics();
bg.beginFill(tagData.bgColor, tagData.opacity ?? 1);
bg.drawRoundedRect(-bgWidth / 2, -bgHeight / 2, bgWidth, bgHeight, bgHeight / 2);
bg.endFill();
tagGroup.addChild(bg);
tagGroup.addChild(text);
tagGroup.addChild(bg);
tagGroup.addChild(text);
return tagGroup;
return tagGroup;
};
const createCometTail = (bgColor, tailRotation, parentWidth,parentHeight, ratio) => {
if (ratio > 1) ratio = ratio ;
const tailGroup = new PIXI.Container();
const graphics = new PIXI.Graphics();
tailGroup.addChild(graphics);
const createCometTail = (bgColor, tailRotation, parentWidth, parentHeight, ratio) => {
if (ratio > 1) ratio = ratio;
const tailGroup = new PIXI.Container();
const graphics = new PIXI.Graphics();
tailGroup.addChild(graphics);
const baseLength = 45 * ratio;
const startWidth = parentHeight + 20 * ratio;
const endWidth = parentHeight * 0.4;
const baseLength = 45 * ratio;
const startWidth = parentHeight + 20 * ratio;
const endWidth = parentHeight * 0.4;
let breathPhase = Math.random() * Math.PI * 2;
const breathSpeed = 0.04;
let breathPhase = Math.random() * Math.PI * 2;
const breathSpeed = 0.04;
tailGroup.updateAnim = () => {
breathPhase += breathSpeed;
const breathScale = 0.85 + 0.15 * Math.sin(breathPhase);
graphics.clear();
const currentLength = baseLength * breathScale;
tailGroup.updateAnim = () => {
breathPhase += breathSpeed;
const breathScale = 0.85 + 0.15 * Math.sin(breathPhase);
graphics.clear();
const currentLength = baseLength * breathScale;
const cos = Math.cos(tailRotation);
const sin = Math.sin(tailRotation);
const perpX = -sin;
const perpY = cos;
const cos = Math.cos(tailRotation);
const sin = Math.sin(tailRotation);
const perpX = -sin;
const perpY = cos;
const p1 = { x: perpX * (startWidth / 2), y: perpY * (startWidth / 2) };
const p2 = { x: -perpX * (startWidth / 2), y: -perpY * (startWidth / 2) };
const endCX = cos * currentLength;
const endCY = sin * currentLength;
const p3 = { x: endCX - perpX * (endWidth / 2), y: endCY - perpY * (endWidth / 2) };
const p4 = { x: endCX + perpX * (endWidth / 2), y: endCY + perpY * (endWidth / 2) };
const p1 = { x: perpX * (startWidth / 2), y: perpY * (startWidth / 2) };
const p2 = { x: -perpX * (startWidth / 2), y: -perpY * (startWidth / 2) };
const endCX = cos * currentLength;
const endCY = sin * currentLength;
const p3 = { x: endCX - perpX * (endWidth / 2), y: endCY - perpY * (endWidth / 2) };
const p4 = { x: endCX + perpX * (endWidth / 2), y: endCY + perpY * (endWidth / 2) };
const segments = 8;
for (let i = 0; i < segments; i++) {
const t1 = i / segments;
const t2 = (i + 1) / segments;
const alpha = 0.4 * (1 - t1);
const sp1 = { x: p1.x + (p4.x - p1.x) * t1, y: p1.y + (p4.y - p1.y) * t1 };
const sp2 = { x: p2.x + (p3.x - p2.x) * t1, y: p2.y + (p3.y - p2.y) * t1 };
const ep1 = { x: p1.x + (p4.x - p1.x) * t2, y: p1.y + (p4.y - p1.y) * t2 };
const ep2 = { x: p2.x + (p3.x - p2.x) * t2, y: p2.y + (p3.y - p2.y) * t2 };
graphics.beginFill(bgColor, alpha);
graphics.moveTo(sp1.x, sp1.y);
graphics.lineTo(sp2.x, sp2.y);
graphics.lineTo(ep2.x, ep2.y);
graphics.lineTo(ep1.x, ep1.y);
graphics.endFill();
}
};
tailGroup.updateAnim();
return tailGroup;
const segments = 8;
for (let i = 0; i < segments; i++) {
const t1 = i / segments;
const t2 = (i + 1) / segments;
const alpha = 0.4 * (1 - t1);
const sp1 = { x: p1.x + (p4.x - p1.x) * t1, y: p1.y + (p4.y - p1.y) * t1 };
const sp2 = { x: p2.x + (p3.x - p2.x) * t1, y: p2.y + (p3.y - p2.y) * t1 };
const ep1 = { x: p1.x + (p4.x - p1.x) * t2, y: p1.y + (p4.y - p1.y) * t2 };
const ep2 = { x: p2.x + (p3.x - p2.x) * t2, y: p2.y + (p3.y - p2.y) * t2 };
graphics.beginFill(bgColor, alpha);
graphics.moveTo(sp1.x, sp1.y);
graphics.lineTo(sp2.x, sp2.y);
graphics.lineTo(ep2.x, ep2.y);
graphics.lineTo(ep1.x, ep1.y);
graphics.endFill();
}
};
tailGroup.updateAnim();
return tailGroup;
};
const handleResize = () => {
const container = getContainerDOM();
if (!app || !container) return;
app.destroy(true, { children: true, texture: true, baseTexture: true });
app = null;
initPixi()
const container = getContainerDOM();
if (!app || !container) return;
app.destroy(true, { children: true, texture: true, baseTexture: true });
app = null;
initPixi();
};
</script>
<style scoped>
.out{
width: 100%;
height: 100%;
.out {
width: 100%;
height: 100%;
}
.loading{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.loading {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.semicircle-loader {
.semicircle-loader {
width: 150rpx;
height: 150rpx;
border: 12rpx solid #f3f3f3;
border-top: 12rpx solid #3498db;
border-radius: 50%;
animation: spin 1.2s linear infinite;
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
color: #b9d3ff;
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
color: #b9d3ff;
}
</style>

View File

@@ -1,911 +0,0 @@
<template>
<view class="app-container">
<view class="nav-hidden hidden-animation" :class="{ 'hidden-height': isScrollingDown }">
<view class="container-search">
<view class="search-input button-click" @click="navTo('/pages/search/search')">
<my-icons class="iconsearch" color="#666666" type="search" size="32"></my-icons>
<text class="inpute">职位名称薪资要求等</text>
</view>
<!-- <view class="chart button-click">职业图谱</view> -->
</view>
<view class="cards">
<view class="card press-button" @click="navTo('/pages/nearby/nearby')">
<view class="card-title">附近工作</view>
<view class="card-text">好岗职等你来</view>
</view>
<view class="card press-button" @click="navTo('/packageA/pages/choiceness/choiceness')">
<view class="card-title">精选企业</view>
<view class="card-text">优选职得信赖</view>
</view>
</view>
</view>
<view class="nav-filter">
<view class="filter-top" @touchmove.stop.prevent>
<scroll-view :scroll-x="true" :show-scrollbar="false" class="tab-scroll">
<view class="jobs-left">
<view
class="job button-click"
:class="{ active: state.tabIndex === 'all' }"
@click="choosePosition('all')"
>
全部
</view>
<view
class="job button-click"
:class="{ active: state.tabIndex === index }"
v-for="(item, index) in userInfo.jobTitle"
:key="index"
@click="choosePosition(index)"
>
{{ item }}
</view>
</view>
</scroll-view>
<view class="jobs-add button-click" @click="navTo('/packageA/pages/addPosition/addPosition')">
<my-icons class="iconsearch" color="#666D7F" type="plusempty" size="32"></my-icons>
<text>添加</text>
</view>
</view>
<view class="filter-bottom">
<view class="btm-left">
<view
class="button-click filterbtm"
:class="{ active: pageState.search.order === item.value }"
v-for="item in rangeOptions"
@click="handelHostestSearch(item)"
:key="item.value"
>
{{ item.text }}
</view>
</view>
<view class="btm-right button-click" @click="openFilter">
筛选
<image class="right-sx" :class="{ active: showFilter }" src="@/static/icon/shaixun.png"></image>
</view>
</view>
</view>
<view class="table-list">
<scroll-view :scroll-y="true" class="falls-scroll" @scroll="handleScroll" @scrolltolower="scrollBottom">
<view class="falls">
<custom-waterfalls-flow
:column="columnCount"
:columnSpace="columnSpace"
ref="waterfallsFlowRef"
:value="list"
>
<template v-slot:default="job">
<view class="item btn-feel" v-if="!job.recommend">
<view class="falls-card" @click="nextDetail(job)">
<view class="falls-card-pay">
<view class="pay-text">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<image v-if="job.isHot" class="flame" src="/static/icon/flame.png"></image>
</view>
<view class="falls-card-title">{{ job.jobTitle }}</view>
<view class="fl_box fl_warp">
<view class="falls-card-education mar_ri10" v-if="job.education">
<dict-Label dictType="education" :value="job.education"></dict-Label>
</view>
<view class="falls-card-experience" v-if="job.experience">
<dict-Label dictType="experience" :value="job.experience"></dict-Label>
</view>
</view>
<view class="falls-card-company">
青岛
<dict-Label dictType="area" :value="job.jobLocationAreaCode"></dict-Label>
</view>
<view class="falls-card-pepleNumber">
<view>
<image class="point2" src="/static/icon/pintDate.png"></image>
<view class="fl_1">
{{ job.postingDate || '发布日期' }}
</view>
</view>
<view>
<image class="point3" src="/static/icon/pointpeople.png"></image>
<view class="fl_1">
{{ vacanciesTo(job.vacancies) }}
</view>
</view>
</view>
<view class="falls-card-company2">
<image class="point3" src="/static/icon/point3.png"></image>
<view class="fl_1">
{{ job.companyName }}
</view>
</view>
<!-- <view class="falls-card-matchingrate">
<view class=""><matchingDegree :job="job"></matchingDegree></view>
<my-icons type="star" size="60"></my-icons>
</view> -->
</view>
</view>
<view class="item" :class="{ isBut: job.isBut }" v-else>
<view class="recommend-card">
<view class="card-content">
<view class="recommend-card-title">在找{{ job.jobCategory }}工作吗</view>
<view class="recommend-card-tip">{{ job.tip }}</view>
<view class="recommend-card-line"></view>
<view class="recommend-card-controll">
<view class="controll-no" @click="clearfindJob(job)">不是</view>
<view class="controll-yes" @click="findJob(job)">是的</view>
</view>
</view>
</view>
</view>
</template>
</custom-waterfalls-flow>
<loadmore ref="loadmoreRef"></loadmore>
</view>
<empty v-if="!list.length"></empty>
</scroll-view>
</view>
<!-- 筛选 -->
<select-filter ref="selectFilterModel"></select-filter>
<view class="maskFristEntry" v-if="maskFristEntry">
<view class="entry-content">
<text class="text1">左滑查看视频</text>
<text class="text2">左滑查看视频</text>
<view class="goExperience">去体验</view>
<view class="maskFristEntry-Close" @click="closeFristEntry">1</view>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted, watchEffect, nextTick } from 'vue';
import img from '@/static/icon/filter.png';
import dictLabel from '@/components/dict-Label/dict-Label.vue';
const { $api, navTo, vacanciesTo, formatTotal } = inject('globalFunction');
import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore());
import useDictStore from '@/stores/useDictStore';
const { getTransformChildren, oneDictData } = useDictStore();
import useLocationStore from '@/stores/useLocationStore';
import selectFilter from '@/components/selectFilter/selectFilter.vue';
import { useRecommedIndexedDBStore, jobRecommender } from '@/stores/useRecommedIndexedDBStore.js';
import { useScrollDirection } from '@/hook/useScrollDirection';
import { useColumnCount } from '@/hook/useColumnCount';
const { isScrollingDown, handleScroll } = useScrollDirection();
const recommedIndexDb = useRecommedIndexedDBStore();
const emits = defineEmits(['onShowTabbar']);
const waterfallsFlowRef = ref(null);
const loadmoreRef = ref(null);
const conditionSearch = ref({});
const waterfallcolumn = ref(2);
const maskFristEntry = ref(true);
const state = reactive({
tabIndex: 'all',
});
const list = ref([]);
const pageState = reactive({
page: 0,
total: 0,
maxPage: 2,
pageSize: 10,
search: {
order: 0,
},
});
const inputText = ref('');
const showFilter = ref(false);
const selectFilterModel = ref(null);
const showModel = ref(false);
const rangeOptions = ref([
{ value: 0, text: '推荐' },
{ value: 1, text: '最热' },
{ value: 2, text: '最新发布' },
]);
const isLoaded = ref(false);
const { columnCount, columnSpace } = useColumnCount(() => {
pageState.pageSize = 10 * (columnCount.value - 1);
getJobRecommend('refresh');
nextTick(() => {
waterfallsFlowRef.value?.refresh?.();
useLocationStore().getLocation();
});
});
async function loadData() {
try {
if (isLoaded.value) return;
isLoaded.value = true;
} catch (err) {
isLoaded.value = false; // 重置状态允许重试
throw err;
}
}
function scrollBottom() {
loadmoreRef.value.change('loading');
if (state.tabIndex === 'all') {
getJobRecommend();
} else {
getJobList();
}
}
function findJob(job) {
if (job.isBut) {
$api.msg('已确认');
} else {
list.value = list.value.map((item) => {
if (item.recommend && item.jobCategory === job.jobCategory) {
return {
...item,
isBut: true,
};
}
return item;
});
const jobstr = job.jobCategory;
const jobsObj = {
地区: 'area',
岗位: 'jobTitle',
经验: 'experience',
};
const [name, value] = jobstr.split(':');
const nameAttr = jobsObj[name];
if (name === '岗位') {
conditionSearch.value[nameAttr] = value;
} else {
const valueAttr = oneDictData(nameAttr).filter((item) => item.label === value);
if (valueAttr.length) {
const val = valueAttr[0].value;
conditionSearch.value[nameAttr] = val;
}
}
}
}
function clearfindJob(job) {
if (job.isBut) {
$api.msg('已确认');
} else {
list.value = list.value.map((item) => {
if (item.recommend && item.jobCategory === job.jobCategory) {
return {
...item,
isBut: true,
};
}
return item;
});
recommedIndexDb.deleteRecords(job);
}
}
function nextDetail(job) {
// 记录岗位类型,用作数据分析
if (job.jobCategory) {
const recordData = recommedIndexDb.JobParameter(job);
recommedIndexDb.addRecord(recordData);
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
}
function openFilter() {
showFilter.value = true;
emits('onShowTabbar', false);
uni.hideTabBar();
selectFilterModel.value?.open({
title: '筛选',
maskClick: true,
success: (values) => {
pageState.search = {
...pageState.search,
};
for (const [key, value] of Object.entries(values)) {
pageState.search[key] = value.join(',');
}
showFilter.value = false;
getJobList('refresh');
uni.showTabBar();
},
cancel: () => {
showFilter.value = false;
emits('onShowTabbar', true);
uni.showTabBar();
},
});
}
function handleFilterConfirm(e) {
console.log(e);
}
function choosePosition(index) {
state.tabIndex = index;
list.value = [];
if (index === 'all') {
pageState.search = {
order: pageState.search.order,
};
inputText.value = '';
getJobRecommend('refresh');
} else {
// const id = useUserStore().userInfo.jobTitleId.split(',')[index];
pageState.search.jobTitle = userInfo.value.jobTitle[index];
inputText.value = '';
getJobList('refresh');
}
}
function handelHostestSearch(val) {
pageState.search.order = val.value;
if (state.tabIndex === 'all') {
getJobRecommend('refresh');
} else {
getJobList('refresh');
}
}
function getJobRecommend(type = 'add') {
if (type === 'refresh') {
list.value = [];
if (waterfallsFlowRef.value) waterfallsFlowRef.value.refresh();
}
let params = {
pageSize: pageState.pageSize,
sessionId: useUserStore().seesionId,
...pageState.search,
...conditionSearch.value,
};
let comd = {
recommend: true,
jobCategory: '',
tip: '确认你的兴趣,为您推荐更多合适的岗位',
};
$api.createRequest('/app/job/recommend', params).then((resData) => {
const { data, total } = resData;
pageState.total = 0;
if (type === 'add') {
// 记录系统
recommedIndexDb.getRecord().then((res) => {
if (res.length) {
// 数据分析系统
const resultData = recommedIndexDb.analyzer(res);
const { sort, result } = resultData;
// 岗位询问系统
const conditionCounts = Object.fromEntries(
sort.filter((item) => item[1] > 1) // 过滤掉次数为 1 的项
);
jobRecommender.updateConditions(conditionCounts);
const question = jobRecommender.getNextQuestion();
if (question) {
comd.jobCategory = question;
// 生成随机插入位置,排除前两个和最后两个位置
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);
list.value.push(...reslist);
});
} else {
list.value = dataToImg(data);
}
// 切换状态
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
if (data.length < pageState.pageSize) {
loadmoreRef.value.change('noMore');
} else {
loadmoreRef.value.change('more');
}
}
// 当没有岗位刷新sessionId重新啦
if (!data.length) {
useUserStore().initSeesionId();
}
});
}
function getJobList(type = 'add') {
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
if (type === 'refresh') {
list.value = [];
pageState.page = 1;
pageState.maxPage = 2;
// waterfallsFlowRef.value.refresh();
if (waterfallsFlowRef.value) waterfallsFlowRef.value.refresh();
}
let params = {
current: pageState.page,
pageSize: pageState.pageSize,
...pageState.search,
// ...conditionSearch.value,
};
$api.createRequest('/app/job/list', params).then((resData) => {
const { rows, total } = resData;
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 1);
const end = list.value.length;
const reslist = dataToImg(rows);
list.value.splice(str, end, ...reslist);
} else {
list.value = dataToImg(rows);
}
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
// 切换状态
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
if (rows.length < pageState.pageSize) {
loadmoreRef.value?.change('noMore');
} else {
loadmoreRef.value?.change('more');
}
}
});
}
function dataToImg(data) {
return data.map((item) => ({
...item,
image: img,
hide: true,
}));
}
defineExpose({ loadData });
</script>
<style lang="stylus" scoped>
// .maskFristEntry
// position: fixed;
// // right: 20rpx;
// // bottom: calc(50% - 200rpx);
// height: 100vh
// width: 100vw
// background: rgba(0,0,0,0.3)
// .entry-content
// display: flex;
// align-items: center
// position: absolute
// left: 50%
// top: 40%
// transform: translate(-50%, -50%)
// flex-direction: column
// background: url('@/static/imgs/fristEntry.png') 0 0 no-repeat;
// background-size: 100% 100%;
// width: 480rpx
// height: 584rpx
// // padding-left: 80rpx
// .text1
// margin-top: 370rpx
// font-size: 36rpx
// background: linear-gradient(273.34deg, #356CFA 3.58%, #A47FFD 85.84%);
// -webkit-background-clip: text;
// -webkit-text-fill-color: transparent;
// background-clip: text; /* 有些浏览器兼容用 */
// text-fill-color: transparent;
// padding-left: 28rpx
// .text2
// padding-left: 28rpx
// margin-top: 8rpx
// font-size: 20rpx;
// color: #666666;
// text-align: center;
// .indicateArrow
// height: 76rpx
// width: 68rpx
// .indicatefristEntry
// width: 244rpx
// height: 244rpx
// .goExperience
// margin-left: 28rpx
// margin-top: 28rpx
// width: 160rpx;
// height: 60rpx;
// background: linear-gradient( 180deg, #9974FD 0%, #286BFA 100%);
// border-radius: 12rpx 12rpx 12rpx 12rpx;
// font-size: 28rpx;
// color: #FFFFFF;
// text-align: center;
// line-height: 60rpx
// .maskFristEntry-Close
// position: absolute;
// left: calc(50% - 10rpx);
// bottom: -130rpx
// width: 42rpx
// height: 42rpx
// background: linear-gradient(273.34deg, #356CFA 3.58%, #A47FFD 85.84%);
// border-radius: 50%;
// .maskFristEntry-Close::before
// position: absolute;
// left: calc( 50% - 2rpx)
// top: calc( 50% - 10rpx)
// transform: rotate(45deg);
// content: ''
// background: #FFFFFF
// width: 4rpx
// height: 20rpx
// .maskFristEntry-Close::after
// position: absolute;
// left: calc( 50% - 2rpx)
// top: calc( 50% - 10rpx)
// transform: rotate(-45deg);
// content: ''
// background: #FFFFFF
// width: 4rpx
// height: 20rpx
.app-container
width: 100%;
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
background: url('@/static/icon/background2.png') 0 0 no-repeat;
background-size: 100% 728rpx;
background-color: #FFFFFF;
display: flex;
flex-direction: column
.hidden-animation
max-height: 1000px;
transition: all 0.3s ease;
overflow: hidden;
.hidden-height
max-height: 0;
padding-top: 0;
padding-bottom: 0;
.container-search
padding: 16rpx 24rpx
display: flex
justify-content: space-between
.search-input
display: flex
align-items: center;
width: 100%
height: 80rpx;
line-height: 80rpx
// margin-right: 24rpx
background: #FFFFFF;
border-radius: 75rpx 75rpx 75rpx 75rpx;
.iconsearch
padding-left: 36rpx
.inpute
margin-left: 20rpx
font-weight: 400;
font-size: 28rpx;
color: #B5B5B5;
width: 100%
.chart
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: 170rpx;
background: radial-gradient( 0% 56% at 87% 61%, rgba(255,255,255,0.82) 0%, rgba(255,255,255,0.47) 100%);
box-shadow: 0rpx 8rpx 40rpx 0rpx rgba(210,210,210,0.14);
border-radius: 80rpx 80rpx 80rpx 80rpx;
border: 2rpx solid #FFFFFF;
text-align: center
font-weight: 500;
font-size: 28rpx;
height: 36rpx;
color: #000000;
padding: 20rpx 30rpx
.cards
padding: 10rpx 28rpx
display: grid
grid-gap: 38rpx;
grid-template-columns: 1fr 1fr;
.card
height: calc(158rpx - 40rpx);
padding: 22rpx 26rpx
box-shadow: 0rpx 8rpx 40rpx 0rpx rgba(210,210,210,0.14);
border-radius: 16rpx 16rpx 16rpx 16rpx;
border: 2rpx solid #FFFFFF;
.card-title
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 600;
font-size: 32rpx;
color: #000000;
.card-text
font-weight: 400;
font-size: 24rpx;
color: #9E9E9E;
margin-top: 4rpx
.card:first-child
background: radial-gradient( 0% 56% at 87% 61%, rgba(255,255,255,0.82) 0%, rgba(255,255,255,0.47) 100%),
url('@/static/icon/fujin.png');
background-size: 100%, 100%
.card:last-child
background: radial-gradient( 0% 56% at 87% 61%, rgba(255,255,255,0.82) 0%, rgba(255,255,255,0.47) 100%),
url('@/static/icon/jinxuan.png');
background-size: 100%, 100%
background-size: cover;
background-position: center;
.nav-filter
padding: 16rpx 28rpx 0 28rpx
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
.filter-top
display: flex
justify-content: space-between;
.tab-scroll
flex: 1;
overflow: hidden;
margin-right: 20rpx
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
-webkit-mask-image: linear-gradient(to right, black 60%, transparent);
mask-image: linear-gradient(to right, black 60%, transparent);
.jobs-left
display: flex
flex-wrap: nowrap
.job
font-weight: 400;
font-size: 36rpx;
color: #666D7F;
margin-right: 32rpx;
white-space: nowrap
.active
font-weight: 500;
font-size: 36rpx;
color: #000000;
.jobs-add
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
display: flex
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 32rpx;
color: #666D7F;
line-height: 38rpx;
.filter-bottom
display: flex
justify-content: space-between
padding: 24rpx 0
.btm-left
display: flex
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
.filterbtm
font-weight: 400;
font-size: 32rpx;
color: #666D7F;
margin-right: 24rpx
padding: 0rpx 16rpx
.active
font-weight: 500;
font-size: 32rpx;
color: #256BFA;
.btm-right
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 400;
font-size: 32rpx;
color: #6C7282;
.right-sx
width: 26rpx;
height: 26rpx;
.active
transform: rotate(180deg)
.table-list
background: #F4F4F4
flex: 1
overflow: hidden
.falls-scroll
width: 100%
height: 100%
.falls
padding: 28rpx 28rpx;
.item
position: relative;
// background: linear-gradient( 180deg, rgba(19, 197, 124, 0.4) 0%, rgba(255, 255, 255, 0) 30%), rgba(255, 255, 255, 0);
.falls-card
padding: 30rpx;
.falls-card-title
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
color: #606060;
text-align: left;
word-break:break-all
font-weight: 500;
font-size: 30rpx;
color: #333333;
margin-top: 10rpx
.falls-card-pay
// height: 50rpx;
word-break:break-all
color: #002979;
text-align: left;
display: flex;
align-items: end;
position: relative
.pay-text
font-family: DIN-Medium;
color: #4C6EFB;
padding-right: 10rpx
font-weight: 500;
font-size: 28rpx;
color: #4C6EFB;
line-height: 45rpx;
text-align: left;
.flame
position: absolute
bottom: 0
right: -10rpx
transform: translate(0, -30%)
width: 24rpx
height: 31rpx
.falls-card-education,.falls-card-experience
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: 20rpx;
white-space: nowrap
.falls-card-company,.falls-card-pepleNumber
margin-top: 20rpx;
font-size: 24rpx;
color: #999999;
line-height: 25rpx;
text-align: left;
.falls-card-pepleNumber
display: flex;
justify-content: space-between;
flex-wrap: wrap
margin-top: 10rpx;
font-weight: 400;
font-size: 24rpx;
color: #999999;
line-height: 46rpx
view
display:flex
align-items: center
white-space: nowrap;
.point2
margin: 0rpx 6rpx 0 2rpx
height: 22rpx
width: 22rpx
.point3
margin: 0rpx 4rpx 0 0
height: 28rpx
width: 28rpx
.falls-card-matchingrate
margin-top: 10rpx;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 21rpx;
color: #4778EC;
text-align: left;
.falls-card-company2
margin-top: 4rpx;
font-size: 24rpx;
color: #999999;
text-align: left;
display: flex
.point3
margin: 4rpx 4rpx 0 0
height: 26rpx
width: 26rpx
// 推荐卡片
.recommend-card::before
position: absolute
left: 0
top: 0
content: ''
height: 60rpx
width: 100%
height: 8rpx;
background: linear-gradient( to left, #9E74FD 0%, #256BFA 100%);
box-shadow: 0rpx 8rpx 40rpx 0rpx rgba(0,54,170,0.15);
.recommend-card::after
content ''
position absolute
z-index 0
left 50%
top 40%
transform: translate(-50%, -50%)
width 250rpx
height 250rpx
background url('@/static/icon/backAI.png') no-repeat center center
opacity 0.6
background-size contain
pointer-events none
filter: blur(3rpx)
.recommend-card
padding 36rpx 24rpx
background: linear-gradient( 360deg, #DFE9FF 0%, #FFFFFF 52%, #FFFFFF 100%);
border-radius: 20rpx 20rpx 20rpx 20rpx;
position relative
box-shadow 0rpx 4rpx 8rpx 0rpx rgba(72, 89, 123, 0.3)
.card-content
position: relative;
z-index: 2;
.recommend-card-title
font-weight: 500;
font-size: 28rpx;
color: #333333;
.recommend-card-tip
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
margin-top: 28rpx
.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: 50rpx
position: relative
// .recommend-card-line::before
// position: absolute
// content: ''
// left: 0
// top: 0
// transform: translate(-50% - 90rpx, -50%)
// width: 28rpx;
// height: 28rpx;
// background: #F4F4F4;
// border-radius: 50%;
// .recommend-card-line::after
// position: absolute
// content: ''
// right: 0
// top: 0
// transform: translate(50% + 90rpx, -50%)
// width: 28rpx;
// height: 28rpx;
// background: #F4F4F4;
// border-radius: 50%;
.recommend-card-controll
display: flex
align-items: center
justify-content: space-between
margin-top: 40rpx
padding: 0 6rpx;
.controll-yes
width: 124rpx;
height: 60rpx;
background: rgba(37,107,250,0.1);
border-radius: 12rpx 12rpx 12rpx 12rpx;
text-align: center;
line-height: 60rpx
color: #256BFA
.controll-no
width: 124rpx;
height: 56rpx;
line-height: 56rpx
border-radius: 12rpx 12rpx 12rpx 12rpx;
border: 2rpx solid #DEDEDE;
font-weight: 400;
font-size: 28rpx;
color: #333333;
text-align: center;
.controll-yes:active, .controll-no:active
width: 120rpx;
height: 66rpx;
line-height: 66rpx
background: #e8e8e8
border: 2rpx solid #e8e8e8
.isBut{
filter: grayscale(100%);
}
</style>

View File

@@ -20,7 +20,7 @@
<image class="bg-robot button-click" mode="widthFix" src="@/static/icon/index-robot.png"></image>
</view>
</view>
<view class="ai-card-out">
<view v-if="!isMachineEnv" class="ai-card-out" >
<view class="ai-card">
<image class="ai-card-bg" src="@/static/icon/ai-card-bg.png" />
<view class="ai-card-inner">
@@ -56,7 +56,7 @@
</view>
</view>
</view>
<view class="match-card-out">
<view v-if="hasLogin" :class="{'match-move-top':isMachineEnv}" class="match-card-out">
<view class="match-card">
<image class="match-card-bg" src="@/static/icon/match-card-bg.png" />
<view class="title">简历匹配职位</view>
@@ -65,8 +65,7 @@
</view>
</view>
</view>
<view class="cards">
<view :class="{'cards-move-top':isMachineEnv && !hasLogin}" class="cards">
<view class="card card1 press-button" @click="navTo('/pages/nearby/nearby')">
<view class="card-title">附近工作</view>
<view class="card-text">好岗职等你来</view>
@@ -259,7 +258,7 @@ const { $api, navTo, vacanciesTo, formatTotal, throttle } = inject('globalFuncti
import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore());
const { userInfo, hasLogin ,isMachineEnv} = storeToRefs(useUserStore());
import useDictStore from '@/stores/useDictStore';
const { getTransformChildren, oneDictData } = useDictStore();
import useLocationStore from '@/stores/useLocationStore';
@@ -272,6 +271,7 @@ const recommedIndexDb = useRecommedIndexedDBStore();
import config from '@/config';
import AIMatch from './AIMatch.vue';
const { proxy } = getCurrentInstance();
const maskFirstEntry = ref(true);
@@ -280,8 +280,8 @@ const isSticky = ref(false);
const showScrollBottom = ref(false);
const scrollTop = ref(0);
const matchTags = ref([])
const matchLoading = ref(false)
const matchTags = ref([]);
const matchLoading = ref(false);
const emits = defineEmits(['onShowTabbar']);
@@ -314,6 +314,8 @@ const rangeOptions = ref([
]);
const isLoaded = ref(false);
const colors = ['#0069FE', '#FF9400', '#FF6969', '#21EA85', '#87E2EC'];
const occupations = [
'律师',
'工程师',
@@ -357,26 +359,40 @@ const occupations = [
'导游',
];
const colors = ['#0069FE', '#FF9400', '#FF6969', '#21EA85', '#87E2EC'];
onMounted(() => {
let firstEntry = uni.getStorageSync('firstEntry') === false ? false : true; // 默认未读
maskFirstEntry.value = firstEntry;
getMatchTags()
getMatchTags();
console.log(isMachineEnv.value,'+++++++++')
});
async function getMatchTags(){
try{
matchLoading.value=true
const {data} = await $api.createRequest('/app/user/getJobAdviceByResume')
matchTags.value = data
matchLoading.value=false
}catch(err){
console.err('简历匹配职位获取失败')
}finally{
async function getMatchTags() {
if (hasLogin.value) {
try {
matchLoading.value = true;
const { data } = await $api.createRequest('/app/user/getJobAdviceByResume');
matchTags.value = data;
matchLoading.value = false;
} catch (err) {
console.error('简历匹配职位获取失败');
}
} else {
const vals = getRandomStrings(occupations).map((text) => ({
job_name: text,
score: 0,
}));
matchTags.value = vals;
}
}
function getRandomStrings(arr, count = 10) {
const size = Math.min(arr.length, count);
const shuffled = [...arr];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled.slice(0, size);
}
const checkStickyStatus = (e) => {
@@ -405,7 +421,7 @@ function closeVideoTip() {
const handleTagClick = (tagInfo) => {
console.log('点击的标签信息:', tagInfo);
navTo(`/pages/search/search?keyWord=${encodeURIComponent(tagInfo.name)}`)
navTo(`/pages/search/search?keyWord=${encodeURIComponent(tagInfo.name)}`);
};
const handleItemClick = (item) => {
@@ -875,6 +891,12 @@ defineExpose({ loadData });
height: 36rpx;
color: #000000;
padding: 20rpx 30rpx
.match-move-top
margin-top:-20rpx !important;
.cards-move-top
margin-top:-60rpx !important;
position:relative;
z-index:1
.ai-card-out
width:100%;
box-sizing:border-box

View File

@@ -18,25 +18,11 @@
v-for="(_, index) in 2"
:key="index"
>
<!-- #ifndef MP-WEIXIN -->
<component
:is="components[index]"
@onShowTabbar="changeShowTabbar"
:ref="(el) => handelComponentsRef(el, index)"
/>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<IndexOne
v-show="currentIndex === 0"
@onShowTabbar="changeShowTabbar"
:ref="(el) => handelComponentsRef(el, index)"
/>
<IndexTwo
v-show="currentIndex === 1"
@onShowTabbar="changeShowTabbar"
:ref="(el) => handelComponentsRef(el, index)"
/>
<!-- #endif -->
</swiper-item>
</swiper>
</view>
@@ -61,17 +47,14 @@ import { reactive, inject, watch, ref, onMounted } from 'vue';
import Tabbar from '@/components/tabbar/midell-box.vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import IndexRefactor from './components/index-refactor.vue';
import IndexOne from './components/index-one.vue';
import IndexTwo from './components/index-two.vue';
import useScreenStore from '@/stores/useScreenStore'
const screenStore = useScreenStore()
const {isWideScreen} = screenStore
import useScreenStore from '@/stores/useScreenStore';
const screenStore = useScreenStore();
const { isWideScreen } = screenStore;
const loadedMap = reactive([false, false]);
const swiperRefs = [ref(null), ref(null)];
// const components = [IndexOne, IndexTwo];
const components = [IndexRefactor, IndexTwo];
import { storeToRefs } from 'pinia';
import { useReadMsg } from '@/stores/useReadMsg';
@@ -87,21 +70,20 @@ const THRESHOLD = 5;
watch(
() => screenStore.isWideScreen,
(newVal) => {
showTabbar.value = newVal
showTabbar.value = newVal;
},
{ immediate: true }
)
);
onLoad(() => {
// 判断浏览器是否有 fristEntry 第一次进入
let fristEntry = uni.getStorageSync('fristEntry') === false ? false : true; // 默认未读
maskFristEntry.value = fristEntry
maskFristEntry.value = fristEntry;
if (fristEntry) {
if(!isWideScreen)uni.hideTabBar();
else showTabbar.value = false
}else{
if(isWideScreen)showTabbar.value = true
if (!isWideScreen) uni.hideTabBar();
else showTabbar.value = false;
} else {
if (isWideScreen) showTabbar.value = true;
}
// 预加载较重页面
setTimeout(() => {
@@ -163,11 +145,11 @@ function handleTouchMove(e) {
}
function changeShowTabbar(val) {
if(isWideScreen){
showTabbar.value=val
}else{
if(val)uni.showTabBar()
else uni.hideTabBar()
if (isWideScreen) {
showTabbar.value = val;
} else {
if (val) uni.showTabBar();
else uni.hideTabBar();
}
}
@@ -223,14 +205,14 @@ function changeSwiperMsgType(e) {
function closeFristEntry() {
uni.setStorageSync('fristEntry', false);
maskFristEntry.value = false;
if(!isWideScreen)uni.showTabBar();
else showTabbar.value=true
if (!isWideScreen) uni.showTabBar();
else showTabbar.value = true;
}
function goExperience() {
closeFristEntry();
if(!isWideScreen)uni.showTabBar();
else showTabbar.value = true
if (!isWideScreen) uni.showTabBar();
else showTabbar.value = true;
state.current = 1;
}
</script>
@@ -393,4 +375,4 @@ function goExperience() {
background: #FFFFFF
width: 4rpx
height: 20rpx
</style>
</style>

View File

@@ -1,6 +1,78 @@
<template>
<AppLayout title="就业服务程序">
<tabcontrolVue :current="tabCurrent">
<view v-if="isMachineEnv" class="alipay-login-container">
<!-- 切换 -->
<view class="login-method-switch">
<view
class="method-item"
:class="{ active: loginMethod === 'face' }"
@click="switchLoginMethod('face')"
>
扫脸登录
</view>
<view
class="method-item"
:class="{ active: loginMethod === 'qrcode' }"
@click="switchLoginMethod('qrcode')"
>
扫码登录
</view>
</view>
<view class="login-scan-area">
<view class="login-title">{{ loginMethod === 'qrcode' ? '扫码登录' : '扫脸登录' }}</view>
<view class="qrcode-tips">
<text class="tips-text">
{{ loginMethod === 'qrcode' ? '请将二维码对准机器下方' : '请将面部对准摄像头区域' }}
</text>
</view>
</view>
<!-- 扫码登录 -->
<view v-if="loginMethod === 'qrcode'" class="qrcode-container">
<view class="qrcode-wrapper">
<view class="qrcode-border">
<view class="qrcode-content">
<view class="qrcode-pattern">
<image class="qrcode-img" src="@/static/icon/qrcode.png" />
<view class="qrcode-corner top-left"></view>
<view class="qrcode-corner top-right"></view>
<view class="qrcode-corner bottom-left"></view>
<view class="qrcode-corner bottom-right"></view>
<view class="scan-line" :style="{ top: scanLineTop + 'rpx' }"></view>
</view>
</view>
</view>
</view>
</view>
<!-- 扫脸登录 -->
<view v-else class="face-container">
<view class="face-wrapper">
<view class="face-border">
<view class="face-content">
<view class="face-pattern">
<image class="face-img" src="@/static/icon/face-icon.png" />
<view class="face-scan-arc arc-1"></view>
<view class="face-scan-arc arc-2"></view>
<view class="face-scan-arc arc-3"></view>
<view class="scan-line" :style="{ top: scanLineTop + 'rpx' }"></view>
</view>
</view>
</view>
</view>
</view>
<view class="countdown-container">
<view class="countdown-wrapper">
<text class="countdown-number">{{ countdown }}</text>
<text class="countdown-text">秒后自动返回</text>
</view>
<view class="cancel-btn" @click="cancelLogin">取消登录</view>
</view>
</view>
<!-- 正常登录-->
<tabcontrolVue v-else :current="tabCurrent">
<template v-slot:tab0>
<view class="login-content">
<image class="logo" src="@/static/logo.png"></image>
@@ -110,21 +182,23 @@
</tabcontrolVue>
<SelectJobs ref="selectJobsModel"></SelectJobs>
<!-- 后门 -->
<view class="backdoor" @click="loginbackdoor">
<view class="backdoor" v-if="!isMachineEnv" @click="loginbackdoor">
<my-icons type="gift-filled" size="60"></my-icons>
</view>
</AppLayout>
</template>
<script setup>
import { storeToRefs } from 'pinia';
import tabcontrolVue from './components/tabcontrol.vue';
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { reactive, inject, watch, ref, onMounted, onUnmounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
const { $api, navTo } = inject('globalFunction');
const { loginSetToken, getUserResume } = useUserStore();
const { isMachineEnv } = storeToRefs(useUserStore());
const { getDictSelectOption, oneDictData } = useDictStore();
const openSelectPopup = inject('openSelectPopup');
// status
@@ -152,12 +226,85 @@ const fromValue = reactive({
experience: '1',
});
// 扫码扫脸登录
const scanLineTop = ref(0);
let scanInterval = null;
const countdown = ref(60);
let countdownTimer = null;
const loginMethod = ref('face'); // 'qrcode' / 'face'
onLoad((parmas) => {
getTreeselect();
$api.msg('请完善微简历');
if (!isMachineEnv.value) $api.msg('请完善微简历');
});
onMounted(() => {});
onMounted(() => {
if (isMachineEnv) {
startCountdown();
startScanAnimation();
}
});
onUnmounted(() => {
stopScanAnimation();
stopCountdown();
});
const startCountdown = () => {
stopCountdown();
countdown.value = 60;
countdownTimer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
returnToHome();
}
}, 1000);
};
const stopCountdown = () => {
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
};
const resetCountdown = () => {
stopCountdown();
startCountdown();
};
// 返回首页
const returnToHome = () => {
stopCountdown();
stopScanAnimation();
useUserStore().logOutApp();
};
// 取消登录
const cancelLogin = () => {
returnToHome();
};
// 切换登录方式
const switchLoginMethod = (method) => {
if (loginMethod.value !== method) {
loginMethod.value = method;
resetCountdown();
}
};
// 开始动画
const startScanAnimation = () => {
clearInterval(scanInterval);
scanInterval = setInterval(() => {
scanLineTop.value = (scanLineTop.value + 2) % 300;
}, 30);
};
// 停止动画
const stopScanAnimation = () => {
clearInterval(scanInterval);
};
function changeSex(sex) {
fromValue.sex = sex;
@@ -250,6 +397,11 @@ function getTreeselect() {
}
function loginbackdoor() {
if (isMachineEnv.value) {
useUserStore().logOutApp();
$api.msg('返回首页');
return;
}
$api.createRequest('/app/mock/login', {}, 'post').then((resData) => {
$api.msg('模拟帐号密码测试登录成功');
loginSetToken(resData.token).then((resume) => {
@@ -311,6 +463,316 @@ function complete() {
}
</script>
<style lang="scss" scoped>
.alipay-login-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
box-sizing: border-box;
}
/* 登录方式切换 */
.login-method-switch {
display: flex;
width: 80%;
margin-bottom: 40rpx;
background: #f5f5f5;
border-radius: 50rpx;
padding: 6rpx;
}
.method-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
font-size: 28rpx;
color: #666;
border-radius: 50rpx;
transition: all 0.3s;
&.active {
background: #1677ff;
color: #fff;
font-weight: 500;
}
}
/* 扫码登录区域样式 */
.login-scan-area {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 40rpx;
}
.login-title {
font-size: 36rpx;
font-weight: 600;
color: #1677ff;
margin-bottom: 20rpx;
text-align: center;
}
.qrcode-tips {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 40rpx;
}
.tips-text {
font-size: 28rpx;
color: #333;
}
/* 二维码容器 */
.qrcode-container {
width: 100%;
display: flex;
justify-content: center;
}
.qrcode-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.qrcode-border {
width: 400rpx;
height: 400rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
box-shadow: 0 8rpx 30rpx rgba(22, 119, 255, 0.1);
position: relative;
}
.qrcode-content {
width: 100%;
height: 100%;
background-color: #f8f8f8;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.qrcode-pattern {
width: 300rpx;
height: 300rpx;
background-color: #fff;
position: relative;
padding: 30rpx;
}
.qrcode-img {
width: 100%;
height: 100%;
}
.qrcode-corner {
position: absolute;
width: 60rpx;
height: 60rpx;
border: 6rpx solid #1677ff;
&.top-left {
top: 0;
left: 0;
border-right: none;
border-bottom: none;
border-radius: 10rpx 0 0 0;
}
&.top-right {
top: 0;
right: 0;
border-left: none;
border-bottom: none;
border-radius: 0 10rpx 0 0;
}
&.bottom-left {
bottom: 0;
left: 0;
border-right: none;
border-top: none;
border-radius: 0 0 0 10rpx;
}
&.bottom-right {
bottom: 0;
right: 0;
border-left: none;
border-top: none;
border-radius: 0 0 10rpx 0;
}
}
.scan-line {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 6rpx;
background: linear-gradient(90deg, transparent, #217bf9, transparent);
box-shadow: 0 -6rpx 4rpx #ffffff;
z-index: 10;
}
/* 扫脸登录样式 */
.face-container {
width: 100%;
display: flex;
justify-content: center;
}
.face-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.face-border {
width: 400rpx;
height: 400rpx;
background-color: #fff;
border-radius: 50%;
padding: 20rpx;
box-shadow: 0 8rpx 30rpx rgba(22, 119, 255, 0.1);
position: relative;
}
.face-content {
width: 100%;
height: 100%;
background-color: #f8f8f8;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.face-pattern {
width: 300rpx;
height: 300rpx;
position: relative;
}
.face-img {
width: 100%;
height: 100%;
}
.face-scan-arc {
position: absolute;
border: 4rpx solid transparent;
border-top-color: #1677ff;
border-radius: 50%;
width: 300rpx;
height: 300rpx;
top: 0rpx;
left: 0rpx;
&.arc-1 {
animation: faceScanRotate1 3s linear infinite;
}
&.arc-2 {
transform: rotate(120deg);
animation: faceScanRotate2 3s linear infinite;
}
&.arc-3 {
transform: rotate(240deg);
animation: faceScanRotate3 3s linear infinite;
}
}
@keyframes faceScanRotate1 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes faceScanRotate2 {
0% {
transform: rotate(120deg);
}
100% {
transform: rotate(480deg);
}
}
@keyframes faceScanRotate3 {
0% {
transform: rotate(240deg);
}
100% {
transform: rotate(600deg);
}
}
/* 底部操作区域 */
.bottom-action-area {
width: 100%;
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 30rpx;
}
.countdown-wrapper {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20rpx;
}
.countdown-number {
font-size: 40rpx;
font-weight: 600;
color: #1677ff;
min-width: 60rpx;
text-align: center;
}
.countdown-text {
font-size: 28rpx;
color: #666;
}
.cancel-btn {
margin-top: 20rpx;
width: 300rpx;
height: 80rpx;
background: transparent;
border: 2rpx solid #1677ff;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #1677ff;
font-weight: 500;
transition: all 0.3s;
&:active {
background: rgba(22, 119, 255, 0.1);
}
}
</style>
<style lang="stylus" scoped>
.backdoor{
position: fixed;

View File

@@ -56,7 +56,7 @@
</view>
<view class="row-right">已认证</view>
</view>
<view class="main-row btn-feel" @click="handleItemClick('素质测评')">
<view v-if="!isMachineEnv" class="main-row btn-feel" @click="handleItemClick('素质测评')">
<view class="row-left">
<image class="left-img" src="@/static/icon/server2.png"></image>
<text class="left-text">素质测评</text>
@@ -65,7 +65,7 @@
<my-icons color="#909090" type="right" size="28"></my-icons>
</view>
</view>
<view class="main-row btn-feel" @click="handleItemClick('模拟面试')">
<view v-if="!isMachineEnv" class="main-row btn-feel" @click="handleItemClick('模拟面试')">
<view class="row-left">
<image class="left-img" src="@/static/icon/server3.png"></image>
<text class="left-text">AI面试</text>
@@ -82,7 +82,7 @@
<view class="row-right">已开启</view>
</view>
</view>
<!-- <view class="card-back button-click" @click="logOut">退出登录</view> -->
<view class="card-back button-click" v-if="isMachineEnv" @click="logOut">退出登录</view>
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog
mode="base"
@@ -112,22 +112,20 @@ const { $api, navTo } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore';
const popup = ref(null);
const { userInfo, Completion, counts } = storeToRefs(useUserStore());
const { userInfo, Completion, counts, isMachineEnv } = storeToRefs(useUserStore());
import useScreenStore from '@/stores/useScreenStore'
const screenStore = useScreenStore()
import useScreenStore from '@/stores/useScreenStore';
const screenStore = useScreenStore();
const showTabbar = ref(false)
const showTabbar = ref(false);
watch(
() => screenStore.isWideScreen,
(newVal) => {
showTabbar.value = newVal
showTabbar.value = newVal;
},
{ immediate: true }
)
);
function logOut() {
popup.value.open();
@@ -357,4 +355,4 @@ const handleItemClick = (item) => {
border-radius: 2rpx
background: #A2A2A2;
transform: rotate(45deg)
</style>
</style>

View File

@@ -2,7 +2,7 @@
<scroll-view :scroll-y="true" class="nearby-scroll" @scrolltolower="scrollBottom">
<view class="nearby-map" @touchmove.stop.prevent>
<map
style="width: 100%; height:410rpx "
style="width: 100%; height: 410rpx"
:latitude="latitudeVal"
:longitude="longitudeVal"
:markers="mapCovers"
@@ -16,7 +16,7 @@
<image class="view-sx" :class="{ active: rangeShow }" src="@/static/icon/shaixun.png"></image>
</view>
<transition name="fade-slide">
<view class="select-list" v-if="rangeShow">
<view class="select-list" v-show="rangeShow">
<view class="list-item button-click" v-for="(item, index) in range" @click="changeRadius(item)">
{{ item }}km
</view>
@@ -147,6 +147,7 @@ function changeRangeShow() {
}
function changeRadius(item) {
console.log(item);
pageState.search.radius = item;
rangeShow.value = false;
progressChange(item);
@@ -244,7 +245,7 @@ function getInit() {
}
function progressChange(value) {
const range = 1 + value;
const range = value < 1 ? 1 : value;
pageState.search.radius = range;
mapCircles.value = [
{

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
static/icon/face-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
static/icon/qrcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

69
static/js/jsbridge.js Normal file
View File

@@ -0,0 +1,69 @@
(() => {
if (!/android/.test(navigator.userAgent.toLowerCase())) {
console.error("当前环境不支持WebView桥接通信, 请检查是否为安卓环境");
return
}
let bridge;
function setupWebViewJavascriptBridge(callback) {
return new Promise((reslove, reject) => {
if (window.WebViewJavascriptBridge) {
bridge = window.WebViewJavascriptBridge;
callback && callback()
reslove()
} else {
document.addEventListener(
"WebViewJavascriptBridgeReady",
function () {
bridge = window.WebViewJavascriptBridge;
callback && callback()
reslove()
},
false
);
}
})
}
let funcs = {}
setupWebViewJavascriptBridge(() => {
bridge.init()
bridge.registerHandler("ampeHHCommunication", (data) => {
console.log('[zyh][registerHandler]',data);
data = JSON.parse(data || '{}');
let eventName = data.eventName || '{}';
let eventData = JSON.parse(data.eventData || '{}');
if (funcs[eventName]) {
for (let callback of funcs[eventName]) {
callback({type: eventName, data: eventData})
}
}
});
})
window.hh = {
async call(funcName, params, callback) {
if (!bridge) {
await setupWebViewJavascriptBridge()
}
bridge.callHandler(funcName, params, callback);
},
async on(funcName, callback) {
if (funcs[funcName]) {
funcs[funcName].add(callback)
} else {
funcs[funcName] = new Set([callback])
}
},
off(funcName, callback) {
if (!funcs[funcName]) {
return false
}
if (!callback) {
return delete funcs[funcName]
}
let res = funcs[funcName].delete(callback)
if(funcs[funcName].size <= 0) {
delete funcs[funcName]
}
return res
}
};
})();

1360
static/js/qrcode.js Normal file

File diff suppressed because it is too large Load Diff

1034
static/upload.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,26 +14,48 @@ const useLocationStore = defineStore("location", () => {
const longitudeVal = ref(null) // 经度
const latitudeVal = ref(null) //纬度
function getLocation() {
function getLocation() { // 获取经纬度两个平台
return new Promise((resole, reject) => {
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 (lightAppJssdk.map) {
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('用户位置获取失败')
}
})
} else {
uni.getLocation({
type: 'gcj02',
highAccuracyExpireTime: 3000,
isHighAccuracy: true,
timeout: 2000,
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('用户位置获取失败')
}
});
}
} catch (e) {
longitudeVal.value = 120.382665
latitudeVal.value = 36.066938

View File

@@ -1,283 +1,329 @@
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
// 屏幕检测管理器类
class ScreenDetectionManager {
constructor() {
this.WIDE_SCREEN_CSS_ID = 'wide-screen-css';
this.WIDE_SCREEN_CSS_PATH = '../common/wide-screen.css';
this.resizeTimer = null;
this.resizeListeners = [];
this.cssLink = null;
}
// 获取屏幕宽度
getScreenWidth() {
if (typeof window === 'undefined') return 0;
// 优先使用 uni.getSystemInfo
if (typeof uni !== 'undefined' && uni.getSystemInfo) {
return new Promise((resolve) => {
uni.getSystemInfo({
success: (res) => {
const width = res.screenWidth || res.windowWidth || window.innerWidth;
resolve(width);
},
fail: () => {
resolve(this.getWindowWidth());
}
});
});
}
return Promise.resolve(this.getWindowWidth());
}
// 备用方案:使用 window 对象
getWindowWidth() {
if (typeof window === 'undefined') return 0;
return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
}
// 检测折叠屏
checkVisualViewport() {
if (window.visualViewport?.segments?.length > 1) {
return {
foldable: true,
count: window.visualViewport.segments.length - 1
}
}
else {
return {
foldable: false,
count: 1
}
}
}
// 动态加载 CSS
loadWideScreenCSS() {
if (typeof window === 'undefined' || this.cssLink) return null;
try {
this.cssLink = document.createElement('link');
this.cssLink.id = this.WIDE_SCREEN_CSS_ID;
this.cssLink.rel = 'stylesheet';
this.cssLink.href = this.WIDE_SCREEN_CSS_PATH;
// 添加加载成功/失败监听
this.cssLink.onload = () => {
console.log('🎨 宽屏 CSS 文件加载成功');
};
this.cssLink.onerror = () => {
console.error('❌ 宽屏 CSS 文件加载失败');
this.cssLink = null;
};
document.head.appendChild(this.cssLink);
return this.cssLink;
} catch (error) {
console.error('加载 CSS 失败:', error);
this.cssLink = null;
return null;
}
}
// 移除 CSS
removeWideScreenCSS() {
if (this.cssLink && this.cssLink.parentNode) {
try {
this.cssLink.parentNode.removeChild(this.cssLink);
this.cssLink = null;
console.log('🗑️ 宽屏 CSS 文件已移除');
return true;
} catch (error) {
console.error('移除 CSS 失败:', error);
return false;
}
}
return false;
}
// 更新 CSS 状态
updateWideScreenCSS(isWideScreen) {
if (isWideScreen) {
return this.loadWideScreenCSS();
} else {
return this.removeWideScreenCSS();
}
}
//显示/隐藏默认tabbar
updateTabbar(isWideScreen){
if(isWideScreen) uni.hideTabBar()
else uni.showTabBar()
}
// 添加 resize 监听器
addResizeListener(callback, delay = 250) {
if (typeof window === 'undefined') return () => {};
const handler = () => {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
callback();
}, delay);
};
window.addEventListener('resize', handler);
this.resizeListeners.push({ callback, handler });
// 返回清理函数
return () => this.removeResizeListener(callback);
}
// 移除 resize 监听器
removeResizeListener(callback) {
const index = this.resizeListeners.findIndex(item => item.callback === callback);
if (index > -1) {
const { handler } = this.resizeListeners[index];
window.removeEventListener('resize', handler);
this.resizeListeners.splice(index, 1);
}
}
}
// 创建屏幕状态 store
const useScreenStore = defineStore('screen', () => {
// 状态
const screenWidth = ref(0);
const isInitialized = ref(false);
const isLoading = ref(false);
const foldFeature = ref(false);
const foldCount = ref(0);
const isWideScreen = computed(() => {
return screenWidth.value >= 1080;
});
// 管理器实例
const manager = new ScreenDetectionManager();
// 初始化屏幕检测
const initScreenDetection = async () => {
if (isLoading.value || isInitialized.value) return;
isLoading.value = true;
try {
// 检测屏幕状态
const width = await manager.getScreenWidth();
const { foldable, count } = manager.checkVisualViewport();
foldFeature.value = foldable;
foldCount.value = count;
screenWidth.value = width;
isInitialized.value = true;
console.log(`📱 屏幕检测完成: ${width}px, 宽屏: ${isWideScreen.value}`);
console.log(`是否为折叠屏: ${foldFeature.value},折叠屏数量:${foldCount.value}`);
// 根据状态加载或移除 CSS
manager.updateWideScreenCSS(isWideScreen.value);
manager.updateTabbar(isWideScreen.value);
// 设置 resize 监听
setupResizeListener();
return {
screenWidth: width,
isWideScreen: isWideScreen.value,
foldable,
count
};
} catch (error) {
console.error('初始化屏幕检测失败:', error);
return null;
} finally {
isLoading.value = false;
}
};
// 手动更新屏幕状态
const updateScreenStatus = async () => {
try {
const width = await manager.getScreenWidth();
const { foldable, count } = manager.checkVisualViewport();
// 保存旧状态
const oldWidth = screenWidth.value;
const oldIsWideScreen = isWideScreen.value;
const oldFoldable = foldFeature.value;
const oldFoldCount = foldCount.value;
// 更新状态
screenWidth.value = width;
foldFeature.value = foldable;
foldCount.value = count;
// 检查宽屏状态是否发生变化
if (oldIsWideScreen !== isWideScreen.value) {
console.log(`🔄 屏幕状态变化: ${oldIsWideScreen ? '宽屏' : '非宽屏'} -> ${isWideScreen.value ? '宽屏' : '非宽屏'}`);
console.log(`屏幕宽度变化: ${oldWidth}px -> ${width}px`);
manager.updateWideScreenCSS(isWideScreen.value);
manager.updateTabbar(isWideScreen.value);
}
// 检查折叠屏状态是否发生变化
if (oldFoldable !== foldable || oldFoldCount !== count) {
console.log(`折叠屏状态变化: ${oldFoldable ? '是' : '否'}->${foldable ? '是' : '否'}, 折叠数: ${oldFoldCount}->${count}`);
}
return {
screenWidth: width,
isWideScreen: isWideScreen.value,
foldable,
count
};
} catch (error) {
console.error('更新屏幕状态失败:', error);
return null;
}
};
// 设置 resize 监听
let cleanupResizeListener = () => {};
const setupResizeListener = () => {
if (typeof window === 'undefined') return;
cleanupResizeListener = manager.addResizeListener(async () => {
await updateScreenStatus();
}, 300);
};
// 手动触发屏幕检测
const detectScreen = () => {
return updateScreenStatus();
};
return {
// 状态
foldFeature,
foldCount,
screenWidth,
isInitialized,
isLoading,
// 响应式计算属性
isWideScreen,
// 方法
initScreenDetection,
updateScreenStatus,
detectScreen,
};
});
import {
defineStore
} from 'pinia';
import {
ref,
computed
} from 'vue';
import wideScreenStyles from '../common/wide-screen.css?inline';
// 屏幕检测管理器类
class ScreenDetectionManager {
constructor() {
this.WIDE_SCREEN_CSS_ID = 'wide-screen-css';
this.WIDE_SCREEN_CSS_PATH = '../common/wide-screen.css';
this.resizeTimer = null;
this.resizeListeners = [];
this.cssLink = null;
this.STYLE_ID = 'wide-screen-style-tag';
this.styleTag = null;
}
// 获取屏幕宽度
getScreenWidth() {
if (typeof window === 'undefined') return 0;
// 优先使用 uni.getSystemInfo
if (typeof uni !== 'undefined' && uni.getSystemInfo) {
return new Promise((resolve) => {
uni.getSystemInfo({
success: (res) => {
const width = res.screenWidth || res.windowWidth || window.innerWidth;
resolve(width);
},
fail: () => {
resolve(this.getWindowWidth());
}
});
});
}
return Promise.resolve(this.getWindowWidth());
}
// 备用方案:使用 window 对象
getWindowWidth() {
if (typeof window === 'undefined') return 0;
return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
}
// 检测折叠屏
checkVisualViewport() {
if (window.visualViewport?.segments?.length > 1) {
return {
foldable: true,
count: window.visualViewport.segments.length - 1
}
} else {
return {
foldable: false,
count: 1
}
}
}
// 动态加载 CSS
loadWideScreenCSS() {
if (typeof window === 'undefined' || this.cssLink) return null;
try {
this.cssLink = document.createElement('link');
this.cssLink.id = this.WIDE_SCREEN_CSS_ID;
this.cssLink.rel = 'stylesheet';
this.cssLink.href = this.WIDE_SCREEN_CSS_PATH;
// 添加加载成功/失败监听
this.cssLink.onload = () => {
console.log('🎨 宽屏 CSS 文件加载成功');
};
this.cssLink.onerror = () => {
console.error('❌ 宽屏 CSS 文件加载失败');
this.cssLink = null;
};
document.head.appendChild(this.cssLink);
return this.cssLink;
} catch (error) {
console.error('加载 CSS 失败:', error);
this.cssLink = null;
return null;
}
}
// 移除 CSS
removeWideScreenCSS() {
if (this.cssLink && this.cssLink.parentNode) {
try {
this.cssLink.parentNode.removeChild(this.cssLink);
this.cssLink = null;
console.log('🗑️ 宽屏 CSS 文件已移除');
return true;
} catch (error) {
console.error('移除 CSS 失败:', error);
return false;
}
}
return false;
}
// 更新 CSS 状态
// updateWideScreenCSS(isWideScreen) {
// if (isWideScreen) {
// return this.loadWideScreenCSS();
// } else {
// return this.removeWideScreenCSS();
// }
// }
updateWideScreenCSS(isWideScreen) {
if (typeof document === 'undefined') return;
if (isWideScreen) {
// 如果已经是宽屏且标签已存在,则不重复创建
if (document.getElementById(this.STYLE_ID)) return;
this.styleTag = document.createElement('style');
this.styleTag.id = this.STYLE_ID;
this.styleTag.innerHTML = wideScreenStyles; // 将 CSS 文本注入
document.head.appendChild(this.styleTag);
console.log('宽屏样式已通过内联方式注入');
} else {
// 非宽屏时移除标签
const existingTag = document.getElementById(this.STYLE_ID);
if (existingTag) {
existingTag.remove();
this.styleTag = null;
console.log('宽屏样式已移除');
}
}
}
//显示/隐藏默认tabbar
updateTabbar(isWideScreen) {
if (isWideScreen) uni.hideTabBar()
else uni.showTabBar()
}
// 添加 resize 监听器
addResizeListener(callback, delay = 250) {
if (typeof window === 'undefined') return () => {};
const handler = () => {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
callback();
}, delay);
};
window.addEventListener('resize', handler);
this.resizeListeners.push({
callback,
handler
});
// 返回清理函数
return () => this.removeResizeListener(callback);
}
// 移除 resize 监听器
removeResizeListener(callback) {
const index = this.resizeListeners.findIndex(item => item.callback === callback);
if (index > -1) {
const {
handler
} = this.resizeListeners[index];
window.removeEventListener('resize', handler);
this.resizeListeners.splice(index, 1);
}
}
}
// 创建屏幕状态 store
const useScreenStore = defineStore('screen', () => {
// 状态
const screenWidth = ref(0);
const isInitialized = ref(false);
const isLoading = ref(false);
const foldFeature = ref(false);
const foldCount = ref(0);
const isWideScreen = computed(() => {
return screenWidth.value >= 900;
});
// 管理器实例
const manager = new ScreenDetectionManager();
// 初始化屏幕检测
const initScreenDetection = async () => {
if (isLoading.value || isInitialized.value) return;
isLoading.value = true;
try {
// 检测屏幕状态
const width = await manager.getScreenWidth();
const {
foldable,
count
} = manager.checkVisualViewport();
foldFeature.value = foldable;
foldCount.value = count;
screenWidth.value = width;
isInitialized.value = true;
console.log(`📱 屏幕检测完成: ${width}px, 宽屏: ${isWideScreen.value}`);
console.log(`是否为折叠屏: ${foldFeature.value},折叠屏数量:${foldCount.value}`);
// 根据状态加载或移除 CSS
manager.updateWideScreenCSS(isWideScreen.value);
manager.updateTabbar(isWideScreen.value);
// 设置 resize 监听
setupResizeListener();
return {
screenWidth: width,
isWideScreen: isWideScreen.value,
foldable,
count
};
} catch (error) {
console.error('初始化屏幕检测失败:', error);
return null;
} finally {
isLoading.value = false;
}
};
// 手动更新屏幕状态
const updateScreenStatus = async () => {
try {
const width = await manager.getScreenWidth();
const {
foldable,
count
} = manager.checkVisualViewport();
// 保存旧状态
const oldWidth = screenWidth.value;
const oldIsWideScreen = isWideScreen.value;
const oldFoldable = foldFeature.value;
const oldFoldCount = foldCount.value;
// 更新状态
screenWidth.value = width;
foldFeature.value = foldable;
foldCount.value = count;
// 检查宽屏状态是否发生变化
if (oldIsWideScreen !== isWideScreen.value) {
console.log(
`🔄 屏幕状态变化: ${oldIsWideScreen ? '宽屏' : '非宽屏'} -> ${isWideScreen.value ? '宽屏' : '非宽屏'}`
);
console.log(`屏幕宽度变化: ${oldWidth}px -> ${width}px`);
manager.updateWideScreenCSS(isWideScreen.value);
manager.updateTabbar(isWideScreen.value);
}
// 检查折叠屏状态是否发生变化
if (oldFoldable !== foldable || oldFoldCount !== count) {
console.log(
`折叠屏状态变化: ${oldFoldable ? '是' : '否'}->${foldable ? '是' : '否'}, 折叠数: ${oldFoldCount}->${count}`
);
}
return {
screenWidth: width,
isWideScreen: isWideScreen.value,
foldable,
count
};
} catch (error) {
console.error('更新屏幕状态失败:', error);
return null;
}
};
// 设置 resize 监听
let cleanupResizeListener = () => {};
const setupResizeListener = () => {
if (typeof window === 'undefined') return;
cleanupResizeListener = manager.addResizeListener(async () => {
await updateScreenStatus();
}, 300);
};
// 手动触发屏幕检测
const detectScreen = () => {
return updateScreenStatus();
};
return {
// 状态
foldFeature,
foldCount,
screenWidth,
isInitialized,
isLoading,
// 响应式计算属性
isWideScreen,
// 方法
initScreenDetection,
updateScreenStatus,
detectScreen,
};
});
export default useScreenStore;

View File

@@ -18,6 +18,7 @@ import {
msg,
$api,
} from '../common/globalFunction';
import baseDB from '@/utils/db.js';
// 简历完成度计算
function getResumeCompletionPercentage(resume) {
@@ -59,6 +60,7 @@ const useUserStore = defineStore("user", () => {
const seesionId = ref('')
const counts = ref({})
const isMiniProgram = ref(false)
const isMachineEnv = ref(false)
const login = (value) => {
hasLogin.value = true;
@@ -84,6 +86,20 @@ const useUserStore = defineStore("user", () => {
});
}
const logOutApp = () => {
hasLogin.value = false;
token.value = ''
resume.value = {}
userInfo.value = {}
role.value = {}
uni.clearStorageSync('userInfo')
uni.clearStorageSync('token')
uni.switchTab({
url: '/pages/index/index',
});
}
const getUserInfo = () => {
return new Promise((reslove, reject) => {
createRequest('/getInfo', {}, 'get').then((userInfo) => {
@@ -110,7 +126,9 @@ const useUserStore = defineStore("user", () => {
// 获取消息列表
useReadMsg().fetchMessages()
// 获取用户信息
return getUserResume()
const resume = await getUserResume()
await baseDB.init(); // 登录成功初始化
return resume
}
const setUserInfo = (values) => {
@@ -145,6 +163,9 @@ const useUserStore = defineStore("user", () => {
isMiniProgram.value = val
}
function changMachineEnv(val) {
isMachineEnv.value = val
}
// 导入
return {
@@ -154,6 +175,7 @@ const useUserStore = defineStore("user", () => {
resume,
login,
logOut,
logOutApp,
loginSetToken,
getUserResume,
initSeesionId,
@@ -162,7 +184,9 @@ const useUserStore = defineStore("user", () => {
getUserstatistics,
counts,
isMiniProgram,
changMiniProgramAppStatus
changMiniProgramAppStatus,
changMachineEnv,
isMachineEnv
}
}, {
unistorage: true,

View File

@@ -34,11 +34,12 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
const chatSessionID = ref('')
const lastDateRef = ref('')
async function init() {
async function init() { // 初始化数据,有则用,无则更新
// 获取所有数据
setTimeout(async () => {
if (!baseDB.isDBReady) await baseDB.initDB();
const result = await baseDB.db.getAll(tableName.value);
console.log('result', result)
// 1、判断是否有数据没数据请求服务器
if (result.length) {
console.warn('本地数据库存在数据')
@@ -216,7 +217,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
userBadFeedback: content,
}
$api.chatRequest('/stepped', parmas, 'POST').then((res) => {
$api.createRequest('/stepped', parmas, 'POST').then((res) => {
baseDB.db.update(massageName.value, dbData) // 更新本地数据库
messages.value.forEach((item) => {
if (item.id === dbData.id) {
@@ -230,7 +231,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
// 云端数据
function getHistory() {
$api.chatRequest('/app/chat/getHistory').then((res) => {
$api.createRequest('/app/chat/getHistory').then((res) => {
if (!res.data.list.length) return
let tabel = parseHistory(res.data.list)
if (tabel && tabel.length) {
@@ -250,14 +251,14 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
const params = {
sessionId: chatSessionID.value
}
$api.chatRequest('/app/chat/detail', params, 'GET', loading).then((res) => {
$api.createRequest('/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) => {
messages.value = listAddId(list, ids)
});
}
console.log('解析后:', list)
// console.log('解析后:', list)
}).catch(() => {
msg('请求出现异常,请联系工作人员')
})

View File

@@ -0,0 +1,76 @@
// utils/GlobalInactivityManager.js
export class GlobalInactivityManager {
constructor(callback, timeout = 60000) {
this.callback = callback
this.timeout = timeout
this.timer = null
this.isPaused = false
this.lastActivityTime = 0
this.THROTTLE_DELAY = 300
this.lowFreqEvents = ['mousedown', 'click', 'keydown', 'touchstart']
this.highFreqEvents = ['mousemove', 'scroll', 'wheel', 'pointermove']
}
resetTimer = () => {
if (this.isPaused) return
if (this.timer) clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.callback()
this.pause()
}, this.timeout)
}
handleUserActivity = () => {
const now = Date.now()
if (now - this.lastActivityTime > this.THROTTLE_DELAY) {
this.lastActivityTime = now
this.resetTimer()
}
}
handleVisibilityChange = () => {
if (document.hidden) {
this.pause()
} else {
this.resume()
}
}
start() {
if (this.isPaused) return
this.resetTimer()
this.lowFreqEvents.forEach(event => {
document.addEventListener(event, this.resetTimer, true)
})
this.highFreqEvents.forEach(event => {
document.addEventListener(event, this.handleUserActivity, true)
})
document.addEventListener('visibilitychange', this.handleVisibilityChange)
}
pause() {
this.isPaused = true
if (this.timer) clearTimeout(this.timer)
this.lowFreqEvents.forEach(event => {
document.removeEventListener(event, this.resetTimer, true)
})
this.highFreqEvents.forEach(event => {
document.removeEventListener(event, this.handleUserActivity, true)
})
document.removeEventListener('visibilitychange', this.handleVisibilityChange)
}
resume() {
this.isPaused = false
this.start() // 现在 this 指向正确,事件能正常绑定和触发
}
destroy() {
this.pause()
this.callback = null
}
}

View File

@@ -1,24 +1,50 @@
// 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'
dbName = 'AppMainDB'
initPromise = null
constructor() {
this.initPromise = this.checkAndInitDB()
// 不再自动执行初始化
}
async getDB() {
if (!this.initPromise) {
this.initPromise = this.checkAndInitDB();
/**
* 手动初始化入口、建立连接、如果版本号不对付才会清理
* 建议在登录成功后调用await baseDB.init()
*/
async init() {
// 如果已经有初始化任务在进行了,直接返回该任务
if (this.initPromise) {
return this.initPromise;
}
// 创建初始化任务
this.initPromise = this.checkAndInitDB();
try {
await this.initPromise;
console.log('数据库初始化成功');
} catch (error) {
this.initPromise = null; // 初始化失败允许下次重试
console.error('数据库初始化失败:', error);
throw error;
}
return this.db;
}
/**
* 获取数据库实例
* 如果没初始化,会强制触发一次 init
*/
async getDB() {
if (!this.isDBReady) {
return await this.init();
}
await this.initPromise; // 等待初始化完成
return this.db;
}
@@ -28,30 +54,28 @@ class BaseStore {
}
const localVersion = uni.getStorageSync('indexedDBVersion') || 1
console.log('DBVersion: ', localVersion, config.DBversion)
if (localVersion === config.DBversion) {
return this.initDB() // 🟢 记得加 return
if (Number(localVersion) === Number(config.DBversion)) {
return this.initDB()
} else {
console.log('清空本地数据库')
await this.clearDB() // 🟢 建议用 await
console.log('检测到版本更新,清空重置旧数据库')
await this.clearDB()
uni.setStorageSync('indexedDBVersion', config.DBversion);
return this.initDB(); // 🟢 记得加 return
return this.initDB();
}
}
initDB() {
// // #ifdef H5
this.db = new IndexedDBHelper(this.dbName, config.DBversion);
// // #endif
return this.db.openDB([{
name: 'record',
keyPath: "id",
autoIncrement: true,
autoIncrement: true
},
{
name: 'messageGroup',
keyPath: "id",
autoIncrement: true,
autoIncrement: true
},
{
name: 'messages',
@@ -65,12 +89,12 @@ class BaseStore {
},
{
name: 'api_cache',
keyPath: "cacheKey", // 使用 URL+参数 作为主键
keyPath: "cacheKey",
indexes: []
}
]).then(async () => {
// 这里原来的逻辑保留
if (useChatGroupDBStore) {
// 确保 Pinia Store 已准备好后再初始化子项
useChatGroupDBStore().init()
}
this.isDBReady = true
@@ -79,13 +103,44 @@ class BaseStore {
}
async clearDB() {
return new Promise((resolve, rejetc) => {
new IndexedDBHelper().deleteDB(this.dbName).then(() => {
resolve()
})
})
// 修正拼写错误并优化 Promise 写法
return new IndexedDBHelper().deleteDB(this.dbName);
}
/**
* 彻底清空数据库并重新初始化
* 适用于切换账号或手动清理缓存
* 非特殊情况不要重置!!!!!!!!!!!!!!!
*/
async resetAndReinit() {
console.warn('开始执行数据库重置...');
if (this.db && this.db.db) {
this.db.db.close();
}
try {
await this.clearDB();
// 3. 重置内部状态
this.db = null;
this.isDBReady = false;
this.initPromise = null;
uni.setStorageSync('indexedDBVersion', config.DBversion);
// 5. 重新调用初始化流程
await this.init();
console.log('数据库重置及初始化完成');
return true;
} catch (error) {
console.error('数据库重置失败:', error);
throw error;
}
}
}
// 导出实例
const baseDB = new BaseStore()
export default baseDB

46
utils/modal.js Normal file
View File

@@ -0,0 +1,46 @@
/**
* 全局弹窗调用工具
* @param {Object} options - 配置项 (title, content, type, success, fail, confirmColor...)
*/
export const $confirm = (options = {}) => {
return new Promise((resolve) => {
// 1. 稍微延迟发送,确保注入的页面组件已完成 uni.$on 监听挂载
// 如果是用户点击触发,延迟可以设为 0如果是拦截器自动触发建议保留 200ms
setTimeout(() => {
uni.$emit('show-global-popup', {
title: options.title || '提示',
content: options.content || '',
type: options.type || 'info',
// 核心:重新包装 resolve 逻辑
resolve: (isConfirm) => {
if (isConfirm) {
// 执行成功回调
if (typeof options.success === 'function') {
options.success({
confirm: true,
cancel: false
});
}
resolve(true);
} else {
// 执行失败/取消回调
if (typeof options.fail === 'function') {
options.fail({
confirm: false,
cancel: true
});
}
resolve(false);
}
},
});
}, options.delay || 200);
});
};
// // 页面中使用
// import {
// $confirm
// } from '@/utils/modal.js';
// const ok = await $confirm({
// title: '测试'
// });

66
vite-inject-popup.js Normal file
View File

@@ -0,0 +1,66 @@
import fs from 'fs';
import path from 'path';
// 读取并解析 pages.json
function getPagesConfig() {
const pagesJsonPath = path.resolve(__dirname, 'pages.json');
let rawContent = fs.readFileSync(pagesJsonPath, 'utf8');
// 核心修复:使用正则去除 JSON 中的 // 和 /* */ 注释
const jsonContent = rawContent.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, "");
const pagesJson = JSON.parse(jsonContent);
const pagePaths = [];
// 1. 提取主包页面
if (pagesJson.pages) {
pagesJson.pages.forEach(p => pagePaths.push(p.path));
}
// 2. 提取分包页面
if (pagesJson.subPackages) {
pagesJson.subPackages.forEach(sub => {
if (sub.pages) {
sub.pages.forEach(p => {
pagePaths.push(`${sub.root}/${p.path}`);
});
}
});
}
return pagePaths;
}
const allPages = getPagesConfig();
export default function viteInjectPopup() {
return {
name: 'vite-inject-popup',
enforce: 'pre',
transform(code, id) {
// 将绝对路径转为相对项目的路径,便于匹配
// 例如: /Users/.../pages/index/index.vue -> pages/index/index
const relativePath = id.replace(/\\/g, '/').split('.vue')[0];
// 校验当前文件是否在 pages.json 的定义中
const isTargetPage = allPages.some(pagePath => relativePath.endsWith(pagePath));
if (isTargetPage && !code.includes('global-popup')) {
const lastTemplateEndIndex = code.lastIndexOf('</template>');
if (lastTemplateEndIndex !== -1) {
const newCode =
code.slice(0, lastTemplateEndIndex) +
'\n <global-popup />\n' +
code.slice(lastTemplateEndIndex);
console.log(`[精准注入]: ${relativePath}`);
return {
code: newCode,
map: null
};
}
}
return null;
}
};
}

13
vite.config.js Normal file
View File

@@ -0,0 +1,13 @@
import {
defineConfig
} from 'vite';
import uni from '@dcloudio/vite-plugin-uni';
import viteInjectPopup from './vite-inject-popup.js'; // 你的自定义插件
export default defineConfig({
plugins: [
// 只保留这一个自定义插件,它只负责改 template 字符串
viteInjectPopup(),
uni(),
]
});