8 Commits

Author SHA1 Message Date
Apcallover
2b1a87b65e flat: 未知 2025-12-10 11:20:05 +08:00
Apcallover
67f5dbbfb0 flat: 演示版 2025-12-09 11:13:32 +08:00
Apcallover
f24d95cedf flat: 缓动 2025-12-08 21:46:55 +08:00
Apcallover
c173fdd6a8 flat: 资源压缩 2025-12-08 11:16:43 +08:00
Apcallover
e0c4f18da0 flat: 语音合成、语音识别 2025-12-07 17:06:20 +08:00
Apcallover
a2ca068669 flat: 性能优化 2025-12-07 14:18:51 +08:00
Apcallover
d01f153c6a flat: 暂存 2025-12-07 12:56:02 +08:00
Apcallover
2a5681f8e0 flat: 优化+ 跳转 2025-12-06 15:19:40 +08:00
108 changed files with 1408 additions and 856 deletions

25
App.vue
View File

@@ -4,7 +4,7 @@ import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
import useUserStore from './stores/useUserStore'; import useUserStore from './stores/useUserStore';
import usePageAnimation from './hook/usePageAnimation'; import usePageAnimation from './hook/usePageAnimation';
import useDictStore from './stores/useDictStore'; import useDictStore from './stores/useDictStore';
const { $api, navTo, appendScriptTagElement, aes_Decrypt, sm2_Decrypt } = inject('globalFunction'); const { $api, navTo, appendScriptTagElement, aes_Decrypt, sm2_Decrypt, safeReLaunch } = inject('globalFunction');
import config from '@/config.js'; import config from '@/config.js';
usePageAnimation(); usePageAnimation();
const appword = 'aKd20dbGdFvmuwrt'; // 固定值 const appword = 'aKd20dbGdFvmuwrt'; // 固定值
@@ -14,8 +14,10 @@ onLaunch((options) => {
useDictStore().getDictData(); useDictStore().getDictData();
try { try {
getUserInfo(); getUserInfo();
useUserStore().changMiniProgramAppStatus(false);
} catch { } catch {
console.log('不是爱山东平台,使用测试登陆'); console.log('不是爱山东平台,使用测试登陆');
useUserStore().changMiniProgramAppStatus(true);
useUserStore().initSeesionId(); //更新 useUserStore().initSeesionId(); //更新
let token = uni.getStorageSync('token') || ''; // 同步获取 缓存信息 let token = uni.getStorageSync('token') || ''; // 同步获取 缓存信息
if (token) { if (token) {
@@ -25,9 +27,10 @@ onLaunch((options) => {
$api.msg('登录成功'); $api.msg('登录成功');
}); });
} else { } else {
uni.redirectTo({ safeReLaunch('/pages/login/login');
url: '/pages/login/login', // uni.redirectTo({
}); // url: '/pages/login/login',
// });
} }
} }
}); });
@@ -108,13 +111,15 @@ function loginCallback(userInfo) {
.then((resume) => { .then((resume) => {
if (resume.data.jobTitleId) { if (resume.data.jobTitleId) {
useUserStore().initSeesionId(); useUserStore().initSeesionId();
uni.reLaunch({ // uni.reLaunch({
url: '/pages/index/index', // url: '/pages/index/index',
}); // });
safeReLaunch('/pages/index/index');
} else { } else {
uni.redirectTo({ safeReLaunch('/pages/login/login');
url: '/pages/login/login', // uni.redirectTo({
}); // url: '/pages/login/login',
// });
} }
}); });
}); });

View File

@@ -33,12 +33,17 @@ html {
overflow-x: hidden; overflow-x: hidden;
} }
li {
list-style: none;
}
/* 布局调整 */ /* 布局调整 */
/* 点击动效 */ /* 点击动效 */
/* 缩小 */ /* 缩小 */
.button-click { .button-click {
transition: transform 0.1s ease; /* transition: transform 0.1s ease; */
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
} }
.button-click:active { .button-click:active {
@@ -67,7 +72,8 @@ html {
} }
.btn-feel { .btn-feel {
transition: transform 0.15s ease; transition: transform 0.5s ease;
/* transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); */
transform-style: preserve-3d; transform-style: preserve-3d;
} }
@@ -84,7 +90,8 @@ html {
border: none; border: none;
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
transition: transform 0.1s ease, box-shadow 0.1s ease; /* transition: transform 0.4s ease, box-shadow 0.1s ease; */
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
/* box-shadow: 0 4px 0 #2C3E50; */ /* box-shadow: 0 4px 0 #2C3E50; */
} }
@@ -478,3 +485,153 @@ html {
.pointEveNone { .pointEveNone {
pointer-events: none; pointer-events: none;
} }
/*
*
* TransitionGroup 动画
* stagger 错峰
* fade-up-stagger --i 淡入上滑(经典错峰)
* pop-in-stagger --i 旋转缩放弹入(活泼)
* slide-left-stagger 从左侧滑入(消息流风格)
* blur-fade-stagger --i 渐显 + 模糊(毛玻璃感)
* bounce-up-stagger 从底部弹跳入场(物理感)
*
*/
/* 淡入上滑(经典错峰) */
.fade-up-stagger-enter-active {
transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28);
transition-delay: calc(var(--i) * 0.1s);
}
.fade-up-stagger-enter-from {
opacity: 0;
transform: translateY(20px);
}
.fade-up-stagger-leave-active {
transition: opacity 0.25s ease;
}
.fade-up-stagger-leave-to {
opacity: 0;
}
/* */
.pop-in-stagger-enter-active {
transition: all 0.45s cubic-bezier(0.68, -0.55, 0.27, 1.55);
transition-delay: calc(var(--i) * 0.08s);
}
.pop-in-stagger-enter-from {
opacity: 0;
transform: scale(0.7) rotate(-10deg);
}
.pop-in-stagger-leave-active {
transition: all 0.2s ease;
}
.pop-in-stagger-leave-to {
opacity: 0;
transform: scale(0.9) rotate(5deg);
}
.slide-left-stagger-enter-active {
transition: all 0.35s ease-out;
transition-delay: calc(var(--i) * 0.07s);
}
.slide-left-stagger-enter-from {
opacity: 0;
transform: translateX(-100%);
}
.slide-left-stagger-leave-active {
transition: all 0.25s ease;
}
.slide-left-stagger-leave-to {
opacity: 0;
transform: translateX(50%);
}
/* 错峰动画 */
.stagger-enter-active {
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition-delay: calc(var(--i) * 0.42s);
/* 关键120ms 间隔 */
}
.stagger-enter-from {
opacity: 0;
transform: translateY(30px) scale(0.95);
/* 更大位移 + 轻微缩放 */
}
.stagger-leave-active {
transition: all 0.25s ease;
}
.stagger-leave-to {
opacity: 0;
transform: translateX(20px);
}
.stagger-move {
transition: transform 0.4s ease;
}
.blur-fade-stagger-enter-active {
transition: all 0.5s ease;
transition-delay: calc(var(--i) * 0.09s);
}
.blur-fade-stagger-enter-from {
opacity: 0;
filter: blur(8px);
transform: scale(1.02);
}
.blur-fade-stagger-leave-active {
transition: all 0.2s ease;
}
.blur-fade-stagger-leave-to {
opacity: 0;
filter: blur(4px);
}
.bounce-up-stagger-enter-active {
animation: stagger-bounce 0.6s forwards;
animation-delay: calc(var(--i) * 0.12s);
}
.bounce-up-stagger-leave-active {
transition: all 0.25s ease;
}
.bounce-up-stagger-leave-to {
opacity: 0;
transform: translateY(30px);
}
@keyframes stagger-bounce {
0% {
opacity: 0;
transform: translateY(100px) scale(0.8);
}
60% {
transform: translateY(-10px) scale(1.05);
}
80% {
transform: translateY(5px) scale(0.98);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}

View File

@@ -51,6 +51,23 @@ const prePage = () => {
} }
export function safeReLaunch(url) {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// 移除传入url开头的斜杠用于对比
const cleanUrl = url.startsWith('/') ? url.slice(1) : url;
if (currentPage && currentPage.route === cleanUrl) {
console.log('已在当前页');
return;
}
uni.reLaunch({
url
});
}
/** /**
@@ -285,7 +302,9 @@ function deg2rad(deg) {
} }
function vacanciesTo(vacancies) { function vacanciesTo(vacancies) {
if (vacancies >= 0) { if (vacancies === null) {
return '-人'
} else if (vacancies >= 0) {
return vacancies + "人" return vacancies + "人"
} else { } else {
return '不限人数' return '不限人数'
@@ -625,7 +644,8 @@ export const $api = {
sendingMiniProgramMessage, sendingMiniProgramMessage,
copyText, copyText,
aes_Decrypt, aes_Decrypt,
createRequestWithCache createRequestWithCache,
safeReLaunch
} }
@@ -658,5 +678,6 @@ export default {
sm4Decrypt, sm4Decrypt,
aes_Decrypt, aes_Decrypt,
sm2_Decrypt, sm2_Decrypt,
sm2_Encrypt sm2_Encrypt,
safeReLaunch
} }

View File

@@ -1,5 +1,6 @@
<template> <template>
<view v-for="job in listData" :key="job.id"> <transition-group name="blur-fade-stagger" tag="view">
<view v-for="(job, index) in dataSource" :key="job.id" :style="{ '--i': 2 }">
<view class="cards" @click="nextDetail(job)"> <view class="cards" @click="nextDetail(job)">
<view class="card-company"> <view class="card-company">
<text class="company line_1">{{ job.gsmc }}</text> <text class="company line_1">{{ job.gsmc }}</text>
@@ -32,17 +33,20 @@
</view> </view>
</view> </view>
</view> </view>
</transition-group>
</template> </template>
<script setup> <script setup>
import { inject, computed, toRaw } from 'vue'; import { inject, computed, toRaw, watch, ref, nextTick } from 'vue';
const { insertSortData, navTo, vacanciesTo } = inject('globalFunction'); const { insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js'; import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore(); const recommedIndexDb = useRecommedIndexedDBStore();
const dataSource = ref([]);
const props = defineProps({ const props = defineProps({
list: { list: {
type: Array, type: Array,
default: '标题', default: () => [],
}, },
longitude: { longitude: {
type: Number, type: Number,
@@ -61,13 +65,49 @@ const props = defineProps({
default: '', default: '',
}, },
}); });
const processedIds = new Set();
watch(
() => props.list,
(newList) => {
if (!Array.isArray(newList)) return;
const listData = computed(() => { let shouldReset = false;
return props.list;
if (dataSource.value.length > newList.length) {
shouldReset = true;
} else if (dataSource.value.length > 0 && newList.length > 0) {
// 注意:这里沿用你代码中使用的 item.id 作为唯一标识
const oldId = dataSource.value[0].id;
const newId = newList[0].id;
if (oldId !== newId) {
shouldReset = true;
}
}
if (shouldReset) {
dataSource.value = [];
processedIds.clear();
}
const newItems = newList.filter((item) => !processedIds.has(item.id));
if (newItems.length === 0) return;
newItems.forEach((item) => processedIds.add(item.id));
const delay = 50;
newItems.forEach((item, index) => {
setTimeout(() => {
dataSource.value.push(item);
}, index * delay);
}); });
},
{ immediate: true, deep: true }
);
function nextDetail(company) { function nextDetail(company) {
navTo(`/packageA/pages/UnitDetails/UnitDetails?companyId=${company.gsID}&companyName=${company.gsmc}&zphId=${props.zphId}&dataType=2`); navTo(
`/packageA/pages/UnitDetails/UnitDetails?companyId=${company.gsID}&companyName=${company.gsmc}&zphId=${props.zphId}&dataType=2`
);
} }
</script> </script>

View File

@@ -1,10 +1,14 @@
<template> <template>
<view v-for="job in listData" :key="job.id"> <transition-group name="blur-fade-stagger" tag="view">
<view v-for="job in dataSource" :key="job.id || job.jobId" :style="{ '--i': 2 }">
<view v-if="!job.isTitle" class="cards" @click="nextDetail(job)"> <view v-if="!job.isTitle" class="cards" @click="nextDetail(job)">
<view class="card-company"> <view class="card-company">
<text class="company">{{ job.jobTitle }}</text> <text class="company">{{ job.jobTitle }}</text>
<view class="salary"> <view class="salary">
<Salary-Expectation :max-salary="job.maxSalary" :min-salary="job.minSalary"></Salary-Expectation> <Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view> </view>
</view> </view>
<view class="card-companyName">{{ job.gwmc }}</view> <view class="card-companyName">{{ job.gwmc }}</view>
@@ -36,13 +40,15 @@
{{ job.title }} {{ job.title }}
</view> </view>
</view> </view>
</transition-group>
</template> </template>
<script setup> <script setup>
import { inject, computed, toRaw } from 'vue'; import { inject, computed, toRaw, ref, watch } from 'vue';
const { insertSortData, navTo, vacanciesTo } = inject('globalFunction'); const { insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js'; import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore(); const recommedIndexDb = useRecommedIndexedDBStore();
const dataSource = ref([]);
const props = defineProps({ const props = defineProps({
list: { list: {
type: Array, type: Array,
@@ -71,6 +77,51 @@ const listData = computed(() => {
return props.list; return props.list;
}); });
const processedIds = new Set();
watch(
() => props.list,
(newList) => {
if (!Array.isArray(newList)) return;
// --- 新增逻辑开始 ---
// 判断是否需要重置数据 (例如点击了搜索、切换了Tab、或下拉刷新)
let shouldReset = false;
// 场景1: 新列表长度比当前渲染的列表短说明发生了重置如从20条变成了10条
if (dataSource.value.length > newList.length) {
shouldReset = true;
}
// 场景2: 列表不为空且第一条数据的ID发生了变化说明是全新的搜索结果
else if (dataSource.value.length > 0 && newList.length > 0) {
const oldId = dataSource.value[0].id || dataSource.value[0].jobId;
const newId = newList[0].id || newList[0].jobId;
if (oldId !== newId) {
shouldReset = true;
}
}
// 如果判定为重置则清空现有数据和ID记录
if (shouldReset) {
dataSource.value = [];
processedIds.clear();
}
// --- 新增逻辑结束 ---
const newItems = newList.filter((item) => !processedIds.has(item.id || item.jobId));
if (newItems.length === 0) return;
newItems.forEach((item) => processedIds.add(item.id || item.jobId));
const delay = 50;
newItems.forEach((item, index) => {
setTimeout(() => {
dataSource.value.push(item);
}, index * delay);
});
},
{ immediate: true, deep: true } // 建议加上 deep虽然这里监听的是数组引用变化
);
function nextDetail(job) { function nextDetail(job) {
// 记录岗位类型,用作数据分析 // 记录岗位类型,用作数据分析
if (job.jobCategory) { if (job.jobCategory) {

View File

@@ -1,10 +1,14 @@
<template> <template>
<view v-for="job in listData" :key="job.id"> <transition-group name="blur-fade-stagger" tag="view">
<view v-for="job in dataSource" :key="job.id" :style="{ '--i': 2 }">
<view v-if="!job.isTitle" class="cards" @click="nextDetail(job)"> <view v-if="!job.isTitle" class="cards" @click="nextDetail(job)">
<view class="card-company"> <view class="card-company">
<text class="company">{{ job.gwmc }}</text> <text class="company">{{ job.gwmc }}</text>
<view class="salary"> <view class="salary">
<Salary-Expectation :max-salary="job.maxSalary" :min-salary="job.minSalary"></Salary-Expectation> <Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
></Salary-Expectation>
</view> </view>
</view> </view>
<view class="card-companyName">{{ job.gsmc }}</view> <view class="card-companyName">{{ job.gsmc }}</view>
@@ -36,13 +40,15 @@
{{ job.title }} {{ job.title }}
</view> </view>
</view> </view>
</transition-group>
</template> </template>
<script setup> <script setup>
import { inject, computed, toRaw } from 'vue'; import { inject, computed, toRaw, ref, watch } from 'vue';
const { insertSortData, navTo, vacanciesTo } = inject('globalFunction'); const { insertSortData, navTo, vacanciesTo } = inject('globalFunction');
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js'; import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore(); const recommedIndexDb = useRecommedIndexedDBStore();
const dataSource = ref([]);
const props = defineProps({ const props = defineProps({
list: { list: {
type: Array, type: Array,
@@ -71,6 +77,24 @@ const listData = computed(() => {
return props.list; return props.list;
}); });
const processedIds = new Set();
watch(
() => props.list,
(newList) => {
if (!Array.isArray(newList)) return;
const newItems = newList.filter((item) => !processedIds.has(item.id));
if (newItems.length === 0) return;
newItems.forEach((item) => processedIds.add(item.id));
const delay = 50;
newItems.forEach((item, index) => {
setTimeout(() => {
dataSource.value.push(item);
}, index * delay);
});
},
{ immediate: true }
);
function nextDetail(job) { function nextDetail(job) {
// 记录岗位类型,用作数据分析 // 记录岗位类型,用作数据分析
if (job.jobCategory) { if (job.jobCategory) {

View File

@@ -1,18 +1,26 @@
export default { export default {
// baseUrl: 'https://fw.rc.qingdao.gov.cn/rgpp-api/api', // 内网 baseUrl: 'https://fw.rc.qingdao.gov.cn/rgpp-api/api', // 内网
baseUrl: 'https://qd.zhaopinzao8dian.com/api', // 测试 // baseUrl: 'https://qd.zhaopinzao8dian.com/api', // 测试
// baseUrl: 'http://192.168.3.29:8081', // baseUrl: 'http://192.168.3.29:8081',
// baseUrl: 'http://10.213.6.207:19010/api', // baseUrl: 'http://10.213.6.207:19010/api',
// 语音转文字 // 语音转文字
// vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/speech-recognition', // vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/system/asr/connect', // 自定义
vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/system/asr/connect', // 自定义 vioceBaseURl: 'wss://fw.rc.qingdao.gov.cn/rgpp-api/api/system/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: 'wss://resource.zhuoson.com/synthesis/', //直接替换即可
// indexedDB // indexedDB
DBversion: 2, DBversion: 3,
// 只使用本地缓寸的数据 // 只使用本地缓寸的数据
OnlyUseCachedDB: false, OnlyUseCachedDB: false,
// 素质测评URL
Quality_assessment_URL: 'https://web1.isdapp.shandong.gov.cn/jmopen_files/unzip/3511c4a8b32c468489ace780d40f6d92/zycpvhyjw/index.html#/pages/evaluation_record/evaluation_record?uuid=2f19e4d676df4650b6fb7edf87461571',
// 职业指导
Career_guidance: 'https://web1.isdapp.shandong.gov.cn/jmopen_files/unzip/927b0032dfe3405ab0124fda9282ebdd/zyzd/index.html',
// ai 模拟面试
mock_interview: 'https://web1.isdapp.shandong.gov.cn/jmopen_files/unzip/4a21b3b6efec4f8db2c3d3d5fa51edc9/szjx-rgzn-xnsc/#/pages/interview/schedule',
// VR虚拟招聘会
virtualJobFair: 'https://web1.isdapp.shandong.gov.cn/jmopen_files/unzip/4a21b3b6efec4f8db2c3d3d5fa51edc9/szjx-rgzn-xnsc/#/pages/metaverse/job_fair',
// 使用模拟定位 // 使用模拟定位
UsingSimulatedPositioning: true, UsingSimulatedPositioning: true,
// 应用信息 // 应用信息
@@ -78,5 +86,12 @@ export default {
mode: 'ECB', // default mode: 'ECB', // default
iv: 'UISwD9fW6cFh9SNS', // default is null iv: 'UISwD9fW6cFh9SNS', // default is null
cipherType: 'base64' // default is base64 cipherType: 'base64' // default is base64
} },
animations: [ //动画
'fade-up-stagger',
'pop-in-stagger',
'slide-left-stagger',
'blur-fade-stagger',
'bounce-up-stagger'
]
} }

214
hook/piper-sdk.js Normal file
View File

@@ -0,0 +1,214 @@
/**
* PiperTTS SDK - 兼容移动端的流式语音合成客户端
* 特性:
* 1. Web Audio API 实时调度,解决移动端不支持 MSE 的问题
* 2. 头部注入 (Header Injection) 技术,解决分片解码错误
* 3. 自动状态管理与事件回调
*/
export class PiperTTS {
constructor(config = {}) {
this.baseUrl = config.baseUrl || 'http://localhost:5001';
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; // 下一段音频的预定播放时间
this.audioHeader = null; // 保存WAV/MP3头部
this.chunkQueue = []; // 数据缓冲队列
this.queueSize = 0; // 当前缓冲区字节数
this.analyser = null; // 可视化分析器节点
// 配置参数
this.flushThreshold = 8 * 1024; // 8KB 阈值
}
/**
* [重要] 初始化音频引擎
* 必须在用户点击事件click/touch中调用一次否则手机上没声音
*/
async init() {
if (this.audioCtx.state === 'suspended') {
await this.audioCtx.resume();
this.onStatus('音频引擎已激活', 'success');
}
}
/**
* 绑定可视化分析器
* @param {AnalyserNode} analyserNode - Web Audio Analyser节点
*/
attachVisualizer(analyserNode) {
this.analyser = analyserNode;
}
/**
* 开始合成并播放
* @param {string} text - 要合成的文本
* @param {object} options - 可选参数 {speaker_id, noise_scale, etc.}
*/
speak(text, options = {}) {
if (!text) return;
this.stop(); // 清理上一次播放
this.onStatus('正在建立连接...', 'processing');
try {
const wsUrl = this.baseUrl.replace(/^http/, 'ws') + '/ws/synthesize';
this.ws = new WebSocket(wsUrl);
this.ws.binaryType = 'arraybuffer';
this.ws.onopen = () => {
this.onStatus('连接成功,请求生成...', 'processing');
// 初始化时间轴:当前时间 + 缓冲延迟
this.nextTime = this.audioCtx.currentTime + 0.1;
this.onStart();
this.ws.send(
JSON.stringify({
text: text,
speaker_id: options.speakerId || null,
length_scale: options.lengthScale || 1.0,
noise_scale: options.noiseScale || 0.667,
})
);
};
this.ws.onmessage = (event) => this._handleMessage(event);
this.ws.onclose = async () => {
// 处理剩余残余数据
if (this.chunkQueue.length > 0) {
await this._processQueue(true);
}
this.onStatus('播放结束', 'success');
this.onEnd();
};
this.ws.onerror = (err) => {
console.error(err);
this.onStatus('连接发生错误', 'error');
};
} catch (e) {
this.onStatus(`启动失败: ${e.message}`, 'error');
}
}
/**
* 停止播放并重置状态
*/
stop() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
// 重置缓冲
this.chunkQueue = [];
this.queueSize = 0;
this.audioHeader = null;
// 注意Web Audio API 很难"立即停止"已经在 flight 中的 node
// 除非我们追踪所有的 sourceNode 并调用 .stop()。
// 简单实现suspend 再 resume 或者关闭 context (不推荐频繁关闭)。
// 这里的 stop 主要停止数据接收。
}
// --- 内部私有方法 ---
async _handleMessage(event) {
if (!(event.data instanceof ArrayBuffer)) return;
const chunk = event.data;
// 1. 捕获头部 (Header Injection 核心)
if (!this.audioHeader) {
// 截取前100字节作为通用头
this.audioHeader = chunk.slice(0, 100);
}
// 2. 入队
this.chunkQueue.push(chunk);
this.queueSize += chunk.byteLength;
// 3. 达到阈值则解码播放
if (this.queueSize >= this.flushThreshold) {
await this._processQueue();
}
}
async _processQueue(isLast = false) {
if (this.chunkQueue.length === 0) return;
// 1. 合并 Buffer
const rawData = new Uint8Array(this.queueSize);
let offset = 0;
for (const chunk of this.chunkQueue) {
rawData.set(new Uint8Array(chunk), offset);
offset += chunk.byteLength;
}
// 清空队列
this.chunkQueue = [];
this.queueSize = 0;
try {
// 2. 构造带头部的 Buffer
let decodeTarget;
// 简单的头部检测逻辑,如果没有头,就拼上去
if (this.audioHeader && !this._hasHeader(rawData)) {
const newBuffer = new Uint8Array(this.audioHeader.byteLength + rawData.byteLength);
newBuffer.set(new Uint8Array(this.audioHeader), 0);
newBuffer.set(rawData, this.audioHeader.byteLength);
decodeTarget = newBuffer.buffer;
} else {
decodeTarget = rawData.buffer;
}
// 3. 解码
const decodedBuffer = await this.audioCtx.decodeAudioData(decodeTarget);
// 4. 播放调度
this._scheduleBuffer(decodedBuffer);
} catch (err) {
// 解码失败处理:如果是中间数据,放回队列头部等待拼接
if (!isLast) {
this.chunkQueue.unshift(rawData);
this.queueSize += rawData.byteLength;
} else {
console.warn('最后一段数据解码失败,丢弃', err);
}
}
}
_scheduleBuffer(decodedBuffer) {
const source = this.audioCtx.createBufferSource();
source.buffer = decodedBuffer;
// 连接可视化
if (this.analyser) {
source.connect(this.analyser);
this.analyser.connect(this.audioCtx.destination);
} else {
source.connect(this.audioCtx.destination);
}
// 计算播放时间:如果发生卡顿,立即播放;否则无缝衔接
const scheduleTime = Math.max(this.audioCtx.currentTime, this.nextTime);
source.start(scheduleTime);
// 更新下一段的开始时间
this.nextTime = scheduleTime + decodedBuffer.duration;
}
_hasHeader(uint8Arr) {
if (uint8Arr.byteLength < 4) return false;
// Check "RIFF" (WAV)
if (uint8Arr[0] === 82 && uint8Arr[1] === 73 && uint8Arr[2] === 70) return true;
// Check "ID3" (MP3)
if (uint8Arr[0] === 73 && uint8Arr[1] === 68 && uint8Arr[2] === 51) return true;
// Check MP3 Sync Word (Simplify)
if (uint8Arr[0] === 0xff && (uint8Arr[1] & 0xe0) === 0xe0) return true;
return false;
}
}

View File

@@ -1,285 +1,168 @@
import { import {
ref, ref,
onUnmounted, onUnmounted,
onMounted onMounted,
watch
} from 'vue' } from 'vue'
// 如果是 uni-app 环境,保留这些导入;如果是纯 Web Vue3可以移除
import { import {
onHide, onHide,
onUnload onUnload
} from '@dcloudio/uni-app' } from '@dcloudio/uni-app'
import config from '@/config' import config from '@/config'
// 请确保 piper-sdk.js 已经正确 export class PiperTTS
import {
PiperTTS
} from './piper-sdk.js'
/**
* Piper TTS 播放钩子 (WebSocket MSE 流式版 - 含 cancelAudio)
* 依赖: 后端必须去除 MP3 ID3 标签 (-map_metadata -1)
*/
export function useTTSPlayer() { export function useTTSPlayer() {
// 状态管理 // UI 状态
const isSpeaking = ref(false) const isSpeaking = ref(false)
const isPaused = ref(false) const isPaused = ref(false)
const isLoading = ref(false) const isLoading = ref(false)
// 核心对象 // SDK 实例
let audio = null let piper = null
let mediaSource = null
let sourceBuffer = null
let ws = null
// 缓冲队列管理 /**
let bufferQueue = [] * 初始化 SDK 实例
let isAppending = false * 每次 stop 后 piper 会被置空,这里会重新创建
let isStreamEnded = false */
const initPiper = () => {
if (piper) return
// 初始化 Audio 监听器 (只运行一次) let baseUrl = config.speechSynthesis2 || ''
const initAudioElement = () => { baseUrl = baseUrl.replace(/\/$/, '')
if (!audio && typeof window !== 'undefined') {
audio = new Audio()
// 错误监听 piper = new PiperTTS({
audio.addEventListener('error', (e) => { baseUrl: baseUrl,
// 如果是手动停止导致的 error (src 被置空),忽略 onStatus: (msg, type) => {
if (!audio.src) return if (type === 'error') {
console.error('Audio Player Error:', e) console.error('[TTS Error]', msg)
// 出错时不重置状态,交给用户手动处理或结束事件处理
resetState() resetState()
})
// 播放结束监听
audio.addEventListener('ended', () => {
resetState()
})
} }
},
onStart: () => {
isLoading.value = false
isSpeaking.value = true
isPaused.value = false
},
onEnd: () => {
resetState()
}
})
} }
/** /**
* 核心朗读方法 (WebSocket) * 核心朗读方法
* @param {string} text - 要朗读的文本
*/ */
const speak = async (text) => { const speak = async (text) => {
if (!text) return if (!text) return
// 1. 提取文本
const processedText = extractSpeechText(text) const processedText = extractSpeechText(text)
if (!processedText) return if (!processedText) return
// 2. 彻底清理旧状态 // 1. 【关键修改】先彻底停止并销毁旧实例
cancelAudio() // 这会断开 socket 并且 close AudioContext确保上一个声音立即消失
initAudioElement() await stop()
// 2. 初始化新实例 (因为 stop() 把 piper 设为了 null)
initPiper()
// 3. 更新 UI 为加载中
isLoading.value = true isLoading.value = true
isSpeaking.value = true
isPaused.value = false isPaused.value = false
isStreamEnded = false isSpeaking.value = true // 预先设为 true防止按钮闪烁
// 3. 检查环境 try {
if (!window.MediaSource || !window.WebSocket) { // 4. 激活音频引擎 (移动端防静音关键)
console.error('当前环境不支持 MediaSource 或 WebSocket') await piper.init()
// 5. 发送请求
piper.speak(processedText, {
speakerId: 0,
noiseScale: 0.667,
lengthScale: 1.0
})
} catch (e) {
console.error('TTS Speak Error:', e)
resetState() resetState()
return
}
try {
// 4. 初始化 MSE
mediaSource = new MediaSource()
// 绑定 MSE 到 Audio
audio.src = URL.createObjectURL(mediaSource)
// 监听 MSE 打开事件
mediaSource.addEventListener('sourceopen', () => {
// 防止多次触发
if (mediaSource.sourceBuffers.length > 0) return
startWebSocketStream(processedText)
})
// 尝试播放 (处理浏览器自动播放策略)
const playPromise = audio.play()
if (playPromise !== undefined) {
playPromise.catch(e => {
console.warn('自动播放被拦截 (需用户交互):', e)
// 保持 isSpeaking 为 trueUI 显示播放按钮,用户点击后调用 resume() 即可
})
}
} catch (err) {
console.error('TTS Initialization Failed:', err)
cancelAudio()
} }
} }
// 启动 WebSocket 流程 /**
const startWebSocketStream = (text) => { * 暂停
const mime = 'audio/mpeg' */
const pause = async () => {
// 4.1 创建 SourceBuffer if (piper && piper.audioCtx && piper.audioCtx.state === 'running') {
try { await piper.audioCtx.suspend()
sourceBuffer = mediaSource.addSourceBuffer(mime)
sourceBuffer.addEventListener('updateend', () => {
isAppending = false
processQueue()
})
} catch (e) {
console.error('SourceBuffer Create Failed:', e)
return
}
// 4.2 计算 WebSocket 地址
let baseUrl = config.speechSynthesis2 || ''
baseUrl = baseUrl.replace(/\/$/, '')
const wsUrl = baseUrl.replace(/^http/, 'ws') + '/ws/synthesize'
// 4.3 建立连接
ws = new WebSocket(wsUrl)
ws.binaryType = 'arraybuffer' // 关键
ws.onopen = () => {
// console.log('WS Open')
ws.send(JSON.stringify({
text: text,
speaker_id: 0,
length_scale: 1.0,
noise_scale: 0.667
}))
isLoading.value = false
}
ws.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
bufferQueue.push(event.data)
processQueue()
}
}
ws.onerror = (e) => {
console.error('WS Error:', e)
cancelAudio()
}
ws.onclose = () => {
// console.log('WS Closed')
isStreamEnded = true
// 检查是否需要结束 MSE 流
checkEndOfStream()
}
}
// 处理缓冲队列
const processQueue = () => {
if (!sourceBuffer || sourceBuffer.updating || bufferQueue.length === 0) {
// 如果队列空了,且流已结束,尝试结束 MSE
if (bufferQueue.length === 0 && isStreamEnded && !sourceBuffer.updating) {
checkEndOfStream()
}
return
}
isAppending = true
const chunk = bufferQueue.shift()
try {
sourceBuffer.appendBuffer(chunk)
} catch (e) {
// console.error('AppendBuffer Error:', e)
isAppending = false
}
}
// 结束 MSE 流
const checkEndOfStream = () => {
if (mediaSource && mediaSource.readyState === 'open' && bufferQueue.length === 0 && !sourceBuffer
?.updating) {
try {
mediaSource.endOfStream()
} catch (e) {}
}
}
const pause = () => {
if (audio && !audio.paused) {
audio.pause()
isPaused.value = true isPaused.value = true
isSpeaking.value = false
} }
} }
const resume = () => { /**
if (audio && audio.paused) { * 恢复
audio.play() */
const resume = async () => {
if (piper && piper.audioCtx && piper.audioCtx.state === 'suspended') {
await piper.audioCtx.resume()
isPaused.value = false isPaused.value = false
isSpeaking.value = true isSpeaking.value = true
} }
} }
// === 新增/核心方法:取消并停止 === /**
const cancelAudio = () => { * 停止并重置 (核打击模式)
// 1. 断开 WebSocket (停止数据接收) */
if (ws) { const stop = async () => {
// 移除监听器防止报错 if (piper) {
ws.onclose = null // 1. 断开 WebSocket
ws.onerror = null piper.stop()
ws.onmessage = null
ws.close()
ws = null
}
// 2. 停止音频播放 // 2. 【关键】关闭 AudioContext
if (audio) { // Web Audio API 中,已经 schedule 的 buffer 很难单独取消
audio.pause() // 最直接的方法是关闭整个 Context
// 释放 Blob URL 内存 if (piper.audioCtx && piper.audioCtx.state !== 'closed') {
if (audio.src) {
URL.revokeObjectURL(audio.src)
audio.removeAttribute('src')
}
audio.currentTime = 0
}
// 3. 清理 MSE 对象
if (mediaSource) {
try { try {
if (mediaSource.readyState === 'open') { await piper.audioCtx.close()
mediaSource.endOfStream() } catch (e) {
console.warn('AudioContext close failed', e)
} }
} catch (e) {}
mediaSource = null
} }
sourceBuffer = null // 3. 销毁实例引用
bufferQueue = [] piper = null
isAppending = false }
isStreamEnded = false
// 4. 重置 UI 状态
resetState() resetState()
} }
// 只是重置 UI 变量的辅助函数 // UI 状态重置
const resetState = () => { const resetState = () => {
isSpeaking.value = false isSpeaking.value = false
isPaused.value = false isPaused.value = false
isLoading.value = false isLoading.value = false
} }
// 别名 stop -> cancelAudio (保持兼容性)
const stop = cancelAudio
// === 生命周期 === // === 生命周期 ===
onMounted(() => { onMounted(() => {
initAudioElement() // 预初始化可以不做,等到点击时再做,避免空闲占用 AudioContext 资源
// initPiper()
}) })
onUnmounted(() => { onUnmounted(() => {
cancelAudio() stop()
audio = null
}) })
if (typeof onHide === 'function') onHide(cancelAudio) // Uniapp 生命周期
if (typeof onUnload === 'function') onUnload(cancelAudio) if (typeof onHide === 'function') onHide(stop)
if (typeof onUnload === 'function') onUnload(stop)
return { return {
speak, speak,
pause, pause,
resume, resume,
stop, stop,
cancelAudio, // 新增导出 cancelAudio: stop,
isSpeaking, isSpeaking,
isPaused, isPaused,
isLoading isLoading
@@ -287,7 +170,7 @@ export function useTTSPlayer() {
} }
/** /**
* 提取文本逻辑 * 提取文本逻辑 (保持不变)
*/ */
function extractSpeechText(markdown) { function extractSpeechText(markdown) {
if (!markdown || markdown.indexOf('job-json') === -1) { if (!markdown || markdown.indexOf('job-json') === -1) {

View File

@@ -22,11 +22,11 @@
<script> <script>
eruda.init(); eruda.init();
</script> --> </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> <script>
// VConsole 默认会挂载到 `window.VConsole` 上 // VConsole 默认会挂载到 `window.VConsole` 上
var vConsole = new window.VConsole(); var vConsole = new window.VConsole();
</script> </script> -->
<!-- 爱山东jssdk 本sdk存在性能问题 --> <!-- 爱山东jssdk 本sdk存在性能问题 -->
<script type="text/javascript" src="https://isdapp.shandong.gov.cn/jmopen/jssdk/index.js"></script> <script type="text/javascript" src="https://isdapp.shandong.gov.cn/jmopen/jssdk/index.js"></script>
<!-- 只在内网有效 --> <!-- 只在内网有效 -->

View File

@@ -80,7 +80,7 @@
"locale": "zh-Hans", "locale": "zh-Hans",
"h5": { "h5": {
"router": { "router": {
"base": "/app/", "base": "./",
"mode": "hash" "mode": "hash"
}, },
"title": "青岛智慧就业服务", "title": "青岛智慧就业服务",

View File

@@ -21,6 +21,7 @@ import { storeToRefs } from 'pinia';
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const userStore = useUserStore(); const userStore = useUserStore();
const { isMiniProgram } = storeToRefs(useUserStore());
const state = reactive({}); const state = reactive({});
const pageState = reactive({ const pageState = reactive({
page: 0, page: 0,
@@ -37,6 +38,15 @@ onLoad(() => {
getJobList(); getJobList();
}); });
onMounted(() => {
// #ifdef H5
if (!isMiniProgram.value) {
const a = document.getElementsByClassName('uni-page-head-hd')[0];
a.style.display = 'none';
}
// #endif
});
onReachBottom(() => { onReachBottom(() => {
getJobList(); getJobList();
}); });

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="" :use-scroll-view="false"> <AppLayout title="" :use-scroll-view="false">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btnback"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -75,6 +75,8 @@ import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app'; import { onLoad, onShow } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue'; import dictLabel from '@/components/dict-Label/dict-Label.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const { isMiniProgram } = storeToRefs(useUserStore());
import useUserStore from '@/stores/useUserStore';
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction'); const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction');
@@ -220,7 +222,8 @@ function handleJobsListResponse(type, rows, total, pageKey) {
const str = pageState.pageSize * (pageState[pageKey] - 1); const str = pageState.pageSize * (pageState[pageKey] - 1);
const end = pageState.list.length; const end = pageState.list.length;
const reslist = rows; const reslist = rows;
pageState.list.splice(str, end, ...reslist); pageState.list = [...pageState.list, ...rows];
// pageState.list.splice(str, end, ...reslist);
} else { } else {
pageState.list = rows; pageState.list = rows;
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="添加岗位"> <AppLayout title="添加岗位">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btn"> <view class="btn">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -74,7 +74,7 @@ const { $api, navBack } = inject('globalFunction');
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const { getUserResume } = useUserStore(); const { getUserResume } = useUserStore();
const { userInfo } = storeToRefs(useUserStore()); const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const popup = ref(null); const popup = ref(null);
const selectJobsModel = ref(null); const selectJobsModel = ref(null);

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="我的浏览" :show-bg-image="false" :use-scroll-view="false"> <AppLayout title="我的浏览" :show-bg-image="false" :use-scroll-view="false">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btnback"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -46,7 +46,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const { $api, navTo, navBack } = inject('globalFunction'); const { $api, navTo, navBack } = inject('globalFunction');
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
const { userInfo } = storeToRefs(useUserStore()); const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const userStore = useUserStore(); const userStore = useUserStore();
const browseDate = ref(''); const browseDate = ref('');
@@ -123,6 +123,7 @@ function getJobList(type = 'add', loading = true) {
reslist.shift(); reslist.shift();
} }
} }
// pageState.list = [...pageState.list, ...rows];
pageState.list.splice(str, end, ...reslist); pageState.list.splice(str, end, ...reslist);
pageState.lastDate = lastDate; pageState.lastDate = lastDate;
} else { } else {

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="精选企业"> <AppLayout title="精选企业">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btn"> <view class="btn">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -28,7 +28,7 @@ const { $api, navBack, navTo } = inject('globalFunction');
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const { getUserResume } = useUserStore(); const { getUserResume } = useUserStore();
const { userInfo } = storeToRefs(useUserStore()); const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const list = ref([]); const list = ref([]);
onLoad(() => { onLoad(() => {

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout :title="title" :show-bg-image="false" @onScrollBottom="getDataList('add')"> <AppLayout :title="title" :show-bg-image="false" @onScrollBottom="getDataList('add')">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btnback"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -39,6 +39,8 @@ import { storeToRefs } from 'pinia';
const { $api, navTo, navBack } = inject('globalFunction'); const { $api, navTo, navBack } = inject('globalFunction');
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
// state // state
const title = ref('事业单位'); const title = ref('事业单位');
const cardInfo = ref({}); const cardInfo = ref({});

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="我的收藏" :show-bg-image="false" :use-scroll-view="false"> <AppLayout title="我的收藏" :show-bg-image="false" :use-scroll-view="false">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btn"> <view class="btn">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -61,6 +61,8 @@ import { storeToRefs } from 'pinia';
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const { $api, navBack } = inject('globalFunction'); const { $api, navBack } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
const type = ref(0); const type = ref(0);
const pageState = reactive({ const pageState = reactive({

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="" :use-scroll-view="false"> <AppLayout title="" :use-scroll-view="false">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btnback"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -83,7 +83,7 @@
:longitude="longitudeVal" :longitude="longitudeVal"
:latitude="latitudeVal" :latitude="latitudeVal"
></renderCompanysOutData> ></renderCompanysOutData>
<empty v-else is-position></empty> <empty v-if="!pageState.list.length" is-position></empty>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
@@ -110,6 +110,8 @@ import useLocationStore from '@/stores/useLocationStore';
const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction'); const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction');
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
const isExpanded = ref(false); const isExpanded = ref(false);
const fairInfo = ref({}); const fairInfo = ref({});
@@ -163,7 +165,8 @@ function getCompanyList(type = 'add') {
const str = pageState.pageSize * (pageState.current - 1); const str = pageState.pageSize * (pageState.current - 1);
const end = pageState.list.length; const end = pageState.list.length;
const reslist = rows; const reslist = rows;
pageState.list.splice(str, end, ...reslist); pageState.list = [...pageState.list, ...rows];
// pageState.list.splice(str, end, ...reslist);
} else { } else {
pageState.list = rows; pageState.list = rows;
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="我的简历" title-color="#FFFFFF" back-gorund-color="#F4F4F4"> <AppLayout title="我的简历" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btn"> <view class="btn">
<image src="@/static/icon/back-white.png" @click="navBack"></image> <image src="@/static/icon/back-white.png" @click="navBack"></image>
</view> </view>
@@ -42,7 +42,7 @@
</view> </view>
<view class="subName"> <view class="subName">
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label> <dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
<text class="mar_ri10">{{ userInfo.age }}</text> <text class="mar_ri10">{{ userInfo.age || '-' }}</text>
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label> <dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
<dict-Label <dict-Label
class="mar_ri10" class="mar_ri10"
@@ -50,7 +50,7 @@
:value="userInfo.politicalAffiliation" :value="userInfo.politicalAffiliation"
></dict-Label> ></dict-Label>
</view> </view>
<view class="subName">{{ userInfo.phone }}</view> <view class="subName">{{ userInfo.phone || '未知手机号' }}</view>
</view> </view>
<view class="tops-right"> <view class="tops-right">
<view class="right-imghead"> <view class="right-imghead">
@@ -137,7 +137,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore'; import useDictStore from '@/stores/useDictStore';
const { userInfo } = storeToRefs(useUserStore()); const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const { getUserResume } = useUserStore(); const { getUserResume } = useUserStore();
const { getDictData, oneDictData } = useDictStore(); const { getDictData, oneDictData } = useDictStore();
import config from '@/config.js'; import config from '@/config.js';

View File

@@ -42,7 +42,7 @@ const { $api, navTo, debounce, customSystem } = inject('globalFunction');
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore()); const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const pageState = reactive({ const pageState = reactive({
@@ -58,6 +58,15 @@ const state = reactive({
tabIndex: 'all', tabIndex: 'all',
}); });
onMounted(() => {
// #ifdef H5
if (!isMiniProgram.value) {
const a = document.getElementsByClassName('uni-page-head-hd')[0];
a.style.display = 'none';
}
// #endif
});
onLoad(() => { onLoad(() => {
getList('refresh'); getList('refresh');
}); });

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="" backGorundColor="#F4F4F4"> <AppLayout title="" backGorundColor="#F4F4F4">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btnback"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -183,6 +183,9 @@ import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow, onHide } from '@dcloudio/uni-app'; import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue'; import dictLabel from '@/components/dict-Label/dict-Label.vue';
import RadarMap from './component/radarMap.vue'; import RadarMap from './component/radarMap.vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction'); const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import config from '@/config.js'; import config from '@/config.js';

View File

@@ -60,7 +60,7 @@ import Countdown from './component/countdown.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore()); const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const pageState = reactive({ const pageState = reactive({
@@ -92,6 +92,15 @@ onLoad(() => {
getList(); getList();
}); });
onMounted(() => {
// #ifdef H5
if (!isMiniProgram.value) {
const a = document.getElementsByClassName('uni-page-head-hd')[0];
a.style.display = 'none';
}
// #endif
});
function chnageRanOption(item) { function chnageRanOption(item) {
ranItem.value = item; ranItem.value = item;
getList(); getList();

View File

@@ -1,7 +1,7 @@
<template> <template>
<AppLayout title="选择日期" :use-scroll-view="false" back-gorund-color="#FAFAFA"> <AppLayout title="选择日期" :use-scroll-view="false" back-gorund-color="#FAFAFA">
<template #headerleft> <template #headerleft>
<view class="btn"> <view class="btn" v-if="isMiniProgram">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
</template> </template>
@@ -51,6 +51,9 @@ const { $api, navTo, navBack } = inject('globalFunction');
const weekMap = ['日', '一', '二', '三', '四', '五', '六']; const weekMap = ['日', '一', '二', '三', '四', '五', '六'];
const calendarData = ref([]); const calendarData = ref([]);
const current = ref({}); const current = ref({});
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
import { Solar, Lunar } from '@/lib/lunar-javascript@1.7.2.js'; import { Solar, Lunar } from '@/lib/lunar-javascript@1.7.2.js';
const isRecord = ref(false); const isRecord = ref(false);
@@ -62,7 +65,6 @@ const pages = reactive({
const hasZphDateArray = ref([]); const hasZphDateArray = ref([]);
onLoad((options) => { onLoad((options) => {
updateDateArray();
if (options.date) { if (options.date) {
current.value = { current.value = {
date: options?.date || null, date: options?.date || null,

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="video-container"> <div class="video-container">
<view class="back-box"> <view class="back-box" v-if="isMiniProgram">
<view class="btn"> <view class="btn">
<uni-icons type="left" size="26" color="#FFFFFF" @click="navBack"></uni-icons> <uni-icons type="left" size="26" color="#FFFFFF" @click="navBack"></uni-icons>
</view> </view>
@@ -31,6 +31,8 @@ import mTikTok from '@/components/TikTok/TikTok.vue';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js'; import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore(); const recommedIndexDb = useRecommedIndexedDBStore();
import { storeToRefs } from 'pinia';
const { isMiniProgram } = storeToRefs(useUserStore());
const state = reactive({ const state = reactive({
videoList: [], videoList: [],
}); });
@@ -95,7 +97,7 @@ const change = (e) => {
position: absolute; position: absolute;
left: 24rpx; left: 24rpx;
right: 24rpx; right: 24rpx;
bottom: 30rpx; bottom: calc( var(--window-bottom));
color: #fff; color: #fff;
.title{ .title{
font-weight: 500; font-weight: 500;

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="电子名片" title-color="#FFFFFF" back-gorund-color="#F4F4F4"> <AppLayout title="电子名片" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btn"> <view class="btn">
<image src="@/static/icon/back-white.png" @click="navBack"></image> <image src="@/static/icon/back-white.png" @click="navBack"></image>
</view> </view>
@@ -15,11 +15,11 @@
<image v-else src="@/static/icon/girl.png"></image> <image v-else src="@/static/icon/girl.png"></image>
</view> </view>
<view class="info-right"> <view class="info-right">
<view class="name">{{ userInfo.name || "编辑用户名" }}</view> <view class="name">{{ userInfo.name || '编辑用户名' }}</view>
<view class="des"> <view class="des">
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label> <dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
<text class="mar_ri10">|</text> <text class="mar_ri10">|</text>
<text class="mar_ri10">{{ userInfo.age }}</text> <text class="mar_ri10">{{ userInfo.age || '-' }}</text>
<text class="mar_ri10">|</text> <text class="mar_ri10">|</text>
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label> <dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
<!-- <text class="mar_ri10">|</text> <!-- <text class="mar_ri10">|</text>
@@ -27,23 +27,28 @@
</view> </view>
<view class="phone"> <view class="phone">
<image class="call-icon" src="@/static/icon/call.png" /> <image class="call-icon" src="@/static/icon/call.png" />
<view>{{ userInfo.phone }}</view> <view>{{ userInfo.phone || '未知手机号' }}</view>
</view> </view>
</view> </view>
</view> </view>
<view class="info-bottom"> <view class="info-bottom">
<!-- <view>到岗2025-11-02</view> --> <!-- <view>到岗2025-11-02</view> -->
<view></view> <view></view>
<view>地点青岛市-<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label></view> <view>
地点青岛市-
<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label>
</view>
</view> </view>
<view class="des-card" style="margin-top: 24rpx"> <view class="des-card" style="margin-top: 24rpx">
<view class="fl_box fl_justbet"> <view class="fl_box fl_justbet">
<view style="white-space: nowrap">求职意向岗位</view> <view style="white-space: nowrap">求职意向岗位</view>
<view class="line_1" style="padding-left:40rpx" >{{ userInfo.jobIntention || userInfo.jobTitle?.join(',') || '-' }}</view> <view class="line_1" style="padding-left: 40rpx">
{{ userInfo.jobIntention || userInfo.jobTitle?.join(',') || '--' }}
</view>
</view> </view>
<view class="fl_box fl_justbet"> <view class="fl_box fl_justbet">
<view>毕业学校</view> <view>毕业学校</view>
<view>{{ userInfo.graduationSchool || "-" }}</view> <view>{{ userInfo.graduationSchool || '--' }}</view>
</view> </view>
<!-- <view class="fl_box fl_justbet"> <!-- <view class="fl_box fl_justbet">
<view>当前状态</view> <view>当前状态</view>
@@ -71,7 +76,9 @@
<view class="text">关键经历</view> <view class="text">关键经历</view>
</view> </view>
<view class="exp-box"> <view class="exp-box">
<view class="exp-item" v-for="(item, index) in userInfo?.workExp" :key="item.id">{{ index + 1 + "." }}{{ item.duty }}</view> <view class="exp-item" v-for="(item, index) in userInfo?.workExp" :key="item.id">
{{ index + 1 + '.' }}{{ item.duty }}
</view>
</view> </view>
<view v-if="!userInfo?.workExp?.length" class="empty-box"> <view v-if="!userInfo?.workExp?.length" class="empty-box">
<image class="img" src="@/static/icon/empty.png" mode="widthFix"></image> <image class="img" src="@/static/icon/empty.png" mode="widthFix"></image>
@@ -85,7 +92,9 @@
</view> </view>
<ul class="certificate-box"> <ul class="certificate-box">
<li class="certificate-item" v-for="(item, index) in userInfo?.certificateList" :key="item.id">{{ item.name }}</li> <li class="certificate-item" v-for="(item, index) in userInfo?.certificateList" :key="item.id">
{{ item.name }}
</li>
</ul> </ul>
<view v-if="!userInfo?.certificateList?.length" class="empty-box"> <view v-if="!userInfo?.certificateList?.length" class="empty-box">
<image class="img" src="@/static/icon/empty.png" mode="widthFix"></image> <image class="img" src="@/static/icon/empty.png" mode="widthFix"></image>
@@ -97,13 +106,13 @@
</template> </template>
<script setup> <script setup>
import { reactive, inject, watch, ref, onMounted, computed } from "vue"; import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
const { $api, navTo, navBack } = inject("globalFunction"); const { $api, navTo, navBack } = inject('globalFunction');
import { onLoad, onShow } from "@dcloudio/uni-app"; import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from "pinia"; import { storeToRefs } from 'pinia';
import useUserStore from "@/stores/useUserStore"; import useUserStore from '@/stores/useUserStore';
import useDictStore from "@/stores/useDictStore"; import useDictStore from '@/stores/useDictStore';
const { userInfo } = storeToRefs(useUserStore()); const { userInfo, isMiniProgram } = storeToRefs(useUserStore());
const { getUserResume } = useUserStore(); const { getUserResume } = useUserStore();
const { getDictData, oneDictData } = useDictStore(); const { getDictData, oneDictData } = useDictStore();
</script> </script>
@@ -194,6 +203,9 @@ image {
align-items: center; align-items: center;
padding: 24rpx; padding: 24rpx;
.avatar { .avatar {
background: #e8e8e8;
border-radius: 50%;
overflow: hidden;
width: 160rpx; width: 160rpx;
height: 160rpx; height: 160rpx;
margin-right: 24rpx; margin-right: 24rpx;

View File

@@ -19,7 +19,12 @@
<view class="input-titile">时间</view> <view class="input-titile">时间</view>
<view class="flex-box"> <view class="flex-box">
<view class="input-box btn-feel" @click="changestartTime"> <view class="input-box btn-feel" @click="changestartTime">
<input v-model="fromValue.startTime" class="input-con triangle" disabled placeholder="开始时间" /> <input
v-model="fromValue.startTime"
class="input-con triangle"
disabled
placeholder="开始时间"
/>
<image class="icon" src="@/static/icon/arrow-down.png" /> <image class="icon" src="@/static/icon/arrow-down.png" />
</view> </view>
<view class="gap">-</view> <view class="gap">-</view>
@@ -45,24 +50,24 @@
</template> </template>
<script setup> <script setup>
import { reactive, inject, watch, ref, onMounted, computed } from "vue"; import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow } from "@dcloudio/uni-app"; import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo, navBack } = inject("globalFunction"); const { $api, navTo, navBack } = inject('globalFunction');
import { storeToRefs } from "pinia"; import { storeToRefs } from 'pinia';
import useUserStore from "@/stores/useUserStore"; import useUserStore from '@/stores/useUserStore';
import useDictStore from "@/stores/useDictStore"; import useDictStore from '@/stores/useDictStore';
import DatePicker from "@/components/DatePicker/DatePicker.vue"; import DatePicker from '@/components/DatePicker/DatePicker.vue';
const { userInfo } = storeToRefs(useUserStore()); const { userInfo } = storeToRefs(useUserStore());
const { getUserResume } = useUserStore(); const { getUserResume } = useUserStore();
const { dictLabel, oneDictData } = useDictStore(); const { dictLabel, oneDictData } = useDictStore();
// 初始化数据 // 初始化数据
const fromValue = reactive({ const fromValue = reactive({
position: "", position: '',
company: "", company: '',
startTime: "", startTime: '',
endTime: "", endTime: '',
duty: "", duty: '',
id: undefined, id: undefined,
}); });
@@ -76,43 +81,43 @@ onLoad((e) => {
const confirm = async () => { const confirm = async () => {
// 验证必填字段 // 验证必填字段
if (!fromValue.company) { if (!fromValue.company) {
return $api.msg("请输入公司名称"); return $api.msg('请输入公司名称');
} }
if (!fromValue.position) { if (!fromValue.position) {
return $api.msg("请输入岗位"); return $api.msg('请输入岗位');
} }
if (!fromValue.startTime) { if (!fromValue.startTime) {
return $api.msg("请选择开始时间"); return $api.msg('请选择开始时间');
} }
// 验证时间逻辑:结束时间不能早于开始时间 // 验证时间逻辑:结束时间不能早于开始时间
if (fromValue.endTime && new Date(fromValue.endTime) < new Date(fromValue.startTime)) { if (fromValue.endTime && new Date(fromValue.endTime) < new Date(fromValue.startTime)) {
return $api.msg("结束时间不能早于开始时间"); return $api.msg('结束时间不能早于开始时间');
} }
let res; let res;
try { try {
if (fromValue.id) { if (fromValue.id) {
res = await $api.createRequest("/app/user/experience/edit", fromValue, "post"); res = await $api.createRequest('/app/user/experience/edit', fromValue, 'post');
} else { } else {
res = await $api.createRequest("/app/user/experience/save", fromValue, "post"); res = await $api.createRequest('/app/user/experience/save', fromValue, 'post');
} }
$api.msg("保存成功"); $api.msg('保存成功');
getUserResume().then(() => { getUserResume().then(() => {
navBack(); navBack();
}); });
} catch (error) { } catch (error) {
$api.msg("保存失败"); $api.msg('保存失败');
} }
}; };
function delCurrent() { function delCurrent() {
uni.showModal({ uni.showModal({
title: "提示", title: '提示',
content: "确认要删除此条工作经历吗?", content: '确认要删除此条工作经历吗?',
showCancel: true, showCancel: true,
success: async ({ confirm, cancel }) => { success: async ({ confirm, cancel }) => {
if (confirm) { if (confirm) {
await $api.createRequest("/app/user/experience/delete", { id: fromValue.id }, "post"); await $api.createRequest('/app/user/experience/delete', { id: fromValue.id }, 'post');
$api.msg("删除成功"); $api.msg('删除成功');
getUserResume().then(() => { getUserResume().then(() => {
navBack(); navBack();
}); });
@@ -123,13 +128,12 @@ function delCurrent() {
function initLoad(id) { function initLoad(id) {
if (!id) return; if (!id) return;
$api $api.createRequest(`/app/user/experience/getSingle/${id}`, {}, 'get')
.createRequest(`/app/user/experience/getSingle/${id}`, {}, "get")
.then((res) => { .then((res) => {
Object.assign(fromValue, res.data); Object.assign(fromValue, res.data);
}) })
.catch((err) => { .catch((err) => {
console.error("获取工作经历失败:", err); console.error('获取工作经历失败:', err);
}); });
} }
@@ -137,7 +141,7 @@ function initLoad(id) {
const changestartTime = () => { const changestartTime = () => {
console.log(1); console.log(1);
datePicker.value.open({ datePicker.value.open({
title: "选择开始时间", title: '选择开始时间',
defaultDate: fromValue.startTime, defaultDate: fromValue.startTime,
success: (selectedDate) => { success: (selectedDate) => {
fromValue.startTime = selectedDate; fromValue.startTime = selectedDate;
@@ -148,14 +152,14 @@ const changestartTime = () => {
// 选择结束时间 // 选择结束时间
const changeendTime = () => { const changeendTime = () => {
datePicker.value.open({ datePicker.value.open({
title: "选择结束时间", title: '选择结束时间',
defaultDate: fromValue.endTime, defaultDate: fromValue.endTime,
success: (selectedDate) => { success: (selectedDate) => {
fromValue.endTime = selectedDate; fromValue.endTime = selectedDate;
// 如果结束时间早于新的开始时间,清空结束时间 // 如果结束时间早于新的开始时间,清空结束时间
if (fromValue.startTime && new Date(fromValue.startTime) > new Date(selectedDate)) { if (fromValue.startTime && new Date(fromValue.startTime) > new Date(selectedDate)) {
fromValue.endTime = ""; fromValue.endTime = '';
$api.msg("结束时间不能小于开始时间!"); $api.msg('结束时间不能小于开始时间!');
} }
}, },
}); });

View File

@@ -45,10 +45,12 @@
<view class="container-main"> <view class="container-main">
<scroll-view scroll-y class="main-scroll" @scrolltolower="handleScrollToLower"> <scroll-view scroll-y class="main-scroll" @scrolltolower="handleScrollToLower">
<view class="cards"> <view class="cards">
<transition-group name="stagger" tag="view" :css="true">
<view <view
class="card press-button" class="card press-button"
v-for="(item, index) in fairList" v-for="(item, index) in fairList"
:key="index" :key="item.zphID"
:style="{ '--i': index }"
@click=" @click="
navTo( navTo(
'/packageA/pages/exhibitors/exhibitors?jobFairId=' + '/packageA/pages/exhibitors/exhibitors?jobFairId=' +
@@ -93,8 +95,9 @@
<view class="recommend-card-line"></view> <view class="recommend-card-line"></view>
<view class="card-footer">内容简介{{ item.zphjj }}</view> <view class="card-footer">内容简介{{ item.zphjj }}</view>
</view> </view>
</transition-group>
</view> </view>
<empty v-if="!fairList.length"></empty> <empty v-if="!fairList.length" content="暂时没有结果,下一天也许就有惊喜"></empty>
</scroll-view> </scroll-view>
</view> </view>
<!-- <Tabbar :currentpage="1"></Tabbar> --> <!-- <Tabbar :currentpage="1"></Tabbar> -->
@@ -108,6 +111,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
import Tabbar from '@/components/tabbar/midell-box.vue'; import Tabbar from '@/components/tabbar/midell-box.vue';
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import config from '@/config';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const { $api, navTo, cloneDeep, debounce } = inject('globalFunction'); const { $api, navTo, cloneDeep, debounce } = inject('globalFunction');
const weekList = ref([]); const weekList = ref([]);
@@ -140,6 +144,12 @@ onLoad(() => {
getFair('refresh'); getFair('refresh');
}); });
const handleItemClick = (item) => {
lightAppJssdk.navigation.hide({
url: config.virtualJobFair,
});
};
function toSelectDate() { function toSelectDate() {
navTo('/packageA/pages/selectDate/selectDate', { navTo('/packageA/pages/selectDate/selectDate', {
query: { query: {
@@ -171,7 +181,9 @@ function changeSwiperMsgType(e) {
function seemsg(index) { function seemsg(index) {
if (index === 1) { if (index === 1) {
return $api.msg('功能确定中'); handleItemClick();
// $api.msg('功能确定中');
return;
} }
state.current = index; state.current = index;
} }
@@ -201,7 +213,7 @@ function getFair(type = 'add') {
if (currentDay.value?.fullDate) { if (currentDay.value?.fullDate) {
params.zphjbsj = currentDay.value.fullDate.replace(/-/g, ''); params.zphjbsj = currentDay.value.fullDate.replace(/-/g, '');
} }
$api.createRequest('/app/internal/jobFairThirdPart', params, 'GET', true).then((resData) => { $api.createRequest('/app/internal/jobFairThirdPart', params, 'GET', false).then((resData) => {
const { rows, total } = resData; const { rows, total } = resData;
if (type === 'add') { if (type === 'add') {
// const str = pageState.pageSize * (pageState.page - 1); // const str = pageState.pageSize * (pageState.page - 1);

View File

@@ -111,16 +111,16 @@ onHide(() => {
paging.value?.handleTouchCancel(); paging.value?.handleTouchCancel();
if (isDrawerOpen.value) { if (isDrawerOpen.value) {
isDrawerOpen.value = false; isDrawerOpen.value = false;
// uni.showTabBar(); uni.showTabBar();
} }
}); });
const toggleDrawer = () => { const toggleDrawer = () => {
isDrawerOpen.value = !isDrawerOpen.value; isDrawerOpen.value = !isDrawerOpen.value;
if (isDrawerOpen.value) { if (isDrawerOpen.value) {
// uni.hideTabBar(); uni.hideTabBar();
} else { } else {
// uni.showTabBar(); uni.showTabBar();
} }
}; };

View File

@@ -27,7 +27,7 @@
<view class="title">人工智能专区</view> <view class="title">人工智能专区</view>
<view class="item-box"> <view class="item-box">
<view class="box-l"> <view class="box-l">
<view class="item1 button-click" @click="handleItemClick"> <view class="item1 button-click" @click="handleItemClick('素质测评')">
<view class="title">AI素质测评</view> <view class="title">AI素质测评</view>
<view class="des">提高个人素质</view> <view class="des">提高个人素质</view>
<image class="bg-text" mode="widthFix" src="@/static/icon/item-bg-text.png"></image> <image class="bg-text" mode="widthFix" src="@/static/icon/item-bg-text.png"></image>
@@ -35,13 +35,13 @@
</view> </view>
</view> </view>
<view class="box-r"> <view class="box-r">
<view class="item2 button-click" @click="handleItemClick"> <view class="item2 button-click" @click="handleItemClick('就业指导')">
<view class="title">就业指导</view> <view class="title">就业指导</view>
<view class="des">根据个人建议</view> <view class="des">根据个人建议</view>
<image class="bg" src="@/static/icon/top-card-bg.png" /> <image class="bg" src="@/static/icon/top-card-bg.png" />
<image class="bg-img" src="@/static/icon/item-bg-img2.png"></image> <image class="bg-img" src="@/static/icon/item-bg-img2.png"></image>
</view> </view>
<view class="item3 button-click" @click="handleItemClick"> <view class="item3 button-click" @click="handleItemClick('模拟面试')">
<view class="title">AI模拟面试</view> <view class="title">AI模拟面试</view>
<view class="des">提高面试成功率</view> <view class="des">提高面试成功率</view>
<image class="bg" src="@/static/icon/bottom-card-bg.png" /> <image class="bg" src="@/static/icon/bottom-card-bg.png" />
@@ -49,7 +49,7 @@
</view> </view>
</view> </view>
</view> </view>
<view class="tip" @click="navTo('/packageA/pages/myResume/myResume')"> <view class="tip button-click" @click="navTo('/packageA/pages/myResume/myResume')">
<image class="icon" src="@/static/icon/leart-gold.png" /> <image class="icon" src="@/static/icon/leart-gold.png" />
<view class="text">使用人工智能需要先完成素质测评完善简历 ></view> <view class="text">使用人工智能需要先完成素质测评完善简历 ></view>
</view> </view>
@@ -233,7 +233,7 @@
<loadmore ref="loadmoreRef"></loadmore> <loadmore ref="loadmoreRef"></loadmore>
<view v-if="showScrollBottom" style="height: 200px"></view> <view v-if="showScrollBottom" style="height: 200px"></view>
</view> </view>
<empty v-else pdTop="200"></empty> <empty v-else is-position></empty>
</view> </view>
</view> </view>
</view> </view>
@@ -269,6 +269,7 @@ import { useScrollDirection } from '@/hook/useScrollDirection';
import { useColumnCount } from '@/hook/useColumnCount'; import { useColumnCount } from '@/hook/useColumnCount';
const { isScrollingDown, handleScroll } = useScrollDirection(); const { isScrollingDown, handleScroll } = useScrollDirection();
const recommedIndexDb = useRecommedIndexedDBStore(); const recommedIndexDb = useRecommedIndexedDBStore();
import config from '@/config';
import AIMatch from './AIMatch.vue'; import AIMatch from './AIMatch.vue';
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
@@ -386,12 +387,30 @@ function closeVideoTip() {
const handleTagClick = (tagInfo) => { const handleTagClick = (tagInfo) => {
console.log('点击的标签信息:', tagInfo); console.log('点击的标签信息:', tagInfo);
$api.msg('暂未开放') $api.msg('暂未开放');
}; };
const handleItemClick = ()=>{ const handleItemClick = (item) => {
$api.msg('暂未开放') switch (item) {
case '素质测评':
lightAppJssdk.navigation.hide({
url: config.Quality_assessment_URL,
});
break;
case '就业指导':
lightAppJssdk.navigation.hide({
url: config.Career_guidance,
});
break;
case '模拟面试':
lightAppJssdk.navigation.hide({
url: config.mock_interview,
});
break;
default:
$api.msg('暂未开放');
} }
};
const hexToRgba = (hex, opacity) => { const hexToRgba = (hex, opacity) => {
const r = parseInt(hex.slice(1, 3), 16); const r = parseInt(hex.slice(1, 3), 16);
@@ -1199,6 +1218,8 @@ defineExpose({ loadData });
.falls-scroll .falls-scroll
width: 100% width: 100%
height: 100% height: 100%
min-height: 100vh;
position: relative
.falls .falls
padding: 28rpx 28rpx; padding: 28rpx 28rpx;
.item,.m-height{ .item,.m-height{

View File

@@ -91,7 +91,9 @@ onLoad(() => {
uni.preloadPage({ url: '/pages/nearby/nearby' }); uni.preloadPage({ url: '/pages/nearby/nearby' });
uni.preloadPage({ url: '/pages/chat/chat' }); uni.preloadPage({ url: '/pages/chat/chat' });
uni.preloadPage({ url: '/packageA/pages/choiceness/choiceness' }); uni.preloadPage({ url: '/packageA/pages/choiceness/choiceness' });
}, 3000); uni.preloadPage({ url: '/packageA/pages/reservation/reservation' });
uni.preloadPage({ url: '/packageA/pages/Intendedposition/Intendedposition' });
}, 1000);
}); });
onShow(() => { onShow(() => {

View File

@@ -1,5 +1,5 @@
<template> <template>
<AppLayout title="AI+就业服务程序"> <AppLayout title="就业服务程序">
<tabcontrolVue :current="tabCurrent"> <tabcontrolVue :current="tabCurrent">
<template v-slot:tab0> <template v-slot:tab0>
<view class="login-content"> <view class="login-content">
@@ -109,9 +109,10 @@
</template> </template>
</tabcontrolVue> </tabcontrolVue>
<SelectJobs ref="selectJobsModel"></SelectJobs> <SelectJobs ref="selectJobsModel"></SelectJobs>
<view class="backdoor" @click="loginbackdoor"> <!-- 后门 -->
<!-- <view class="backdoor" @click="loginbackdoor">
<uni-icons type="gift-filled" size="30"></uni-icons> <uni-icons type="gift-filled" size="30"></uni-icons>
</view> </view> -->
</AppLayout> </AppLayout>
</template> </template>
@@ -153,6 +154,7 @@ const fromValue = reactive({
onLoad((parmas) => { onLoad((parmas) => {
getTreeselect(); getTreeselect();
$api.msg('请完善微简历');
}); });
onMounted(() => {}); onMounted(() => {});
@@ -283,7 +285,7 @@ function loginTest() {
password: 'test', password: 'test',
}; };
$api.createRequest('/app/login', params, 'post').then((resData) => { $api.createRequest('/app/login', params, 'post').then((resData) => {
$api.msg('模拟帐号密码测试登录成功'); $api.msg('模拟帐号密码测试登录成功 测试环境使用模拟定位');
loginSetToken(resData.token).then((resume) => { loginSetToken(resData.token).then((resume) => {
if (resume.data.jobTitleId) { if (resume.data.jobTitleId) {
// 设置推荐列表,每次退出登录都需要更新 // 设置推荐列表,每次退出登录都需要更新
@@ -312,8 +314,8 @@ function complete() {
<style lang="stylus" scoped> <style lang="stylus" scoped>
.backdoor{ .backdoor{
position: fixed; position: fixed;
left: 0; left: 24rpx;
bottom: 300rpx; top: 10rpx;
} }
.input-nx .input-nx
position: relative position: relative

View File

@@ -56,7 +56,7 @@
</view> </view>
<view class="row-right">已认证</view> <view class="row-right">已认证</view>
</view> </view>
<view class="main-row btn-feel"> <view class="main-row btn-feel" @click="handleItemClick('素质测评')">
<view class="row-left"> <view class="row-left">
<image class="left-img" src="@/static/icon/server2.png"></image> <image class="left-img" src="@/static/icon/server2.png"></image>
<text class="left-text">素质测评</text> <text class="left-text">素质测评</text>
@@ -65,7 +65,7 @@
<uni-icons color="#909090" type="right" size="14"></uni-icons> <uni-icons color="#909090" type="right" size="14"></uni-icons>
</view> </view>
</view> </view>
<view class="main-row btn-feel"> <view class="main-row btn-feel" @click="handleItemClick('模拟面试')">
<view class="row-left"> <view class="row-left">
<image class="left-img" src="@/static/icon/server3.png"></image> <image class="left-img" src="@/static/icon/server3.png"></image>
<text class="left-text">AI面试</text> <text class="left-text">AI面试</text>
@@ -82,7 +82,7 @@
<view class="row-right">已开启</view> <view class="row-right">已开启</view>
</view> </view>
</view> </view>
<view class="card-back button-click" @click="logOut">退出登录</view> <!-- <view class="card-back button-click" @click="logOut">退出登录</view> -->
<uni-popup ref="popup" type="dialog"> <uni-popup ref="popup" type="dialog">
<uni-popup-dialog <uni-popup-dialog
mode="base" mode="base"
@@ -107,6 +107,7 @@ import { storeToRefs } from 'pinia';
import Tabbar from '@/components/tabbar/midell-box.vue'; import Tabbar from '@/components/tabbar/midell-box.vue';
import { onLoad, onShow } from '@dcloudio/uni-app'; import { onLoad, onShow } from '@dcloudio/uni-app';
import FileUploader from '@/utils/FileUploader.js'; import FileUploader from '@/utils/FileUploader.js';
import config from '@/config';
const { $api, navTo } = inject('globalFunction'); const { $api, navTo } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const popup = ref(null); const popup = ref(null);
@@ -137,6 +138,28 @@ function selectFile() {
// }); // });
} }
function chooseFileUploadTest(pam) {} function chooseFileUploadTest(pam) {}
const handleItemClick = (item) => {
switch (item) {
case '素质测评':
lightAppJssdk.navigation.hide({
url: config.Quality_assessment_URL,
});
break;
case '就业指导':
lightAppJssdk.navigation.hide({
url: config.Career_guidance,
});
break;
case '模拟面试':
lightAppJssdk.navigation.hide({
url: config.mock_interview,
});
break;
default:
$api.msg('暂未开放');
}
};
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
@@ -280,6 +303,7 @@ function chooseFileUploadTest(pam) {}
overflow: hidden overflow: hidden
border-radius: 50% border-radius: 50%
margin-right: 22rpx; margin-right: 22rpx;
background: #e8e8e8;
.userindo-head-img .userindo-head-img
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@@ -1,6 +1,6 @@
<template> <template>
<AppLayout title="附近" :use-scroll-view="false" :show-bg-image="false"> <AppLayout title="附近" :use-scroll-view="false" :show-bg-image="false">
<template #headerleft> <template #headerleft v-if="isMiniProgram">
<view class="btnback"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
@@ -50,6 +50,9 @@ import threeComponent from './components/three.vue';
import fourComponent from './components/four.vue'; import fourComponent from './components/four.vue';
import { reactive, inject, watch, ref, onMounted } from 'vue'; import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app'; import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
const { $api, debounce, throttle, navBack } = inject('globalFunction'); const { $api, debounce, throttle, navBack } = inject('globalFunction');
const loadedMap = reactive([false, false, false, false]); const loadedMap = reactive([false, false, false, false]);
const swiperRefs = [ref(null), ref(null), ref(null), ref(null)]; const swiperRefs = [ref(null), ref(null), ref(null), ref(null)];

View File

@@ -2,7 +2,12 @@
<view class="container"> <view class="container">
<view> <view>
<view class="top"> <view class="top">
<image class="btnback button-click" src="@/static/icon/back.png" @click="navBack"></image> <image
v-if="isMiniProgram"
class="btnback button-click"
src="@/static/icon/back.png"
@click="navBack"
></image>
<view class="search-box"> <view class="search-box">
<uni-icons <uni-icons
class="iconsearch" class="iconsearch"
@@ -91,6 +96,8 @@ import { useColumnCount } from '@/hook/useColumnCount';
import { usePagination } from '@/hook/usePagination'; import { usePagination } from '@/hook/usePagination';
import img from '@/static/icon/filter.png'; import img from '@/static/icon/filter.png';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
const searchValue = ref(''); const searchValue = ref('');
const historyList = ref([]); const historyList = ref([]);
const listCom = ref([]); const listCom = ref([]);
@@ -253,9 +260,10 @@ function getJobList(type = 'add') {
const str = pageState.pageSize * (pageState.page - 1); const str = pageState.pageSize * (pageState.page - 1);
const end = listCom.value.length; const end = listCom.value.length;
const reslist = rows; const reslist = rows;
listCom.value.splice(str, end, ...reslist); // listCom.value.splice(str, end, ...reslist);
listCom.value = [...listCom.value, ...reslist];
} else { } else {
listCom.value = rows; listCom.value = [...rows];
} }
pageState.total = resData.total; pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize); pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 B

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.4 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: 751 B

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 B

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 987 B

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.7 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: 740 B

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 B

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 B

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 B

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 B

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 B

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 603 B

Some files were not shown because too many files have changed in this diff Show More