flat: 暂存

This commit is contained in:
Apcallover
2025-11-07 11:30:07 +08:00
parent 7d2faa6c1b
commit e12241b0e4
24 changed files with 980 additions and 237 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
components/.DS_Store vendored

Binary file not shown.

View File

@@ -12,7 +12,7 @@
<view class="header-btnLf">
<slot name="headerleft"></slot>
</view>
<view class="header-title">
<view class="header-title" :style="{ color: titleColor }">
<view>{{ title }}</view>
<view v-show="subTitle" class="subtitle-text">{{ subTitle }}</view>
</view>
@@ -45,12 +45,15 @@
<script setup>
import img from '@/static/icon/background2.png';
const emit = defineEmits(['onScrollBottom']);
defineProps({
title: {
type: String,
default: '标题',
},
titleColor: {
type: String,
default: '#333333',
},
border: {
type: Boolean,
default: false,
@@ -110,11 +113,13 @@ const handleScrollToLower = () => {
align-items: center;
padding: 7rpx 3rpx;
.header-title {
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei',
sans-serif;
color: #000000;
font-weight: bold;
.subtitle-text {
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
font-weight: 400;
font-size: 28rpx;
color: #333333;

View File

@@ -1,7 +1,8 @@
<template>
<view class="empty" :style="{ background: bgcolor, marginTop: mrTop + 'rpx' }">
<view class="ty_content" :style="{ paddingTop: pdTop + 'rpx' }">
<view class="content_top btn-shaky">
<view class="content_top">
<!-- <view class="content_top btn-shaky"> -->
<image v-if="pictrue" :src="pictrue" mode=""></image>
<image v-else src="@/static/icon/empty.png" mode=""></image>
</view>

View File

@@ -101,13 +101,13 @@ function nextDetail(job) {
.company{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 500;
font-size: 32rpx;
font-size: 30rpx;
color: #333333;
}
.salary{
font-family: DIN-Medium;
font-weight: 500;
font-size: 28rpx;
font-size: 26rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx

View File

@@ -149,6 +149,7 @@ const cleanup = () => {
Object.keys(selectedValues).forEach((key) => {
delete selectedValues[key];
});
count.value = 0;
};
const scrollTo = (key) => {
@@ -161,6 +162,7 @@ function getoptions() {
getTransformChildren('experience', '工作经验'),
getTransformChildren('scale', '公司规模'),
];
console.log(arr);
if (area.value) {
arr.push(getTransformChildren('area', '区域'));
}

View File

@@ -0,0 +1,345 @@
<template>
<uni-popup
ref="popup"
type="bottom"
borderRadius="10px 10px 0 0"
background-color="#FFFFFF"
@maskClick="maskClickFn"
:mask-click="maskClick"
class="popup-fix"
>
<view class="popup-content">
<view class="popup-header">
<view class="btn-cancel" @click="cancel">取消</view>
<view class="title">
<text>{{ title }}</text>
</view>
<view class="btn-confirm"></view>
</view>
<view class="popup-list">
<view class="content-wrapper">
<scroll-view class="filter-nav" scroll-y>
<view
v-for="(item, index) in filterOptions"
:key="index"
class="nav-item button-click"
:class="{ active: activeTab === item.key }"
@click="scrollTo(item.key)"
>
{{ item.label }}
</view>
</scroll-view>
<scroll-view class="filter-content" :scroll-into-view="activeTab" scroll-y>
<template v-for="(item, index) in filterOptions" :key="index">
<view class="content-item">
<view class="item-title" :id="item.key">{{ item.label }}</view>
<view class="check-content">
<view
v-for="option in item.options"
:key="option.value"
class="checkbox-item button-click"
:class="{
checkedstyle: activeValue === option.value,
}"
@click="handleItemClick(option)"
>
<text class="option-label">{{ option.label }}</text>
</view>
</view>
</view>
</template>
</scroll-view>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, reactive, nextTick, onBeforeMount } from 'vue';
import useDictStore from '@/stores/useDictStore';
const { getTransformChildren } = useDictStore();
const area = ref(true);
const maskClick = ref(false);
const maskClickFn = ref(null);
const title = ref('标题');
const confirmCallback = ref(null);
const cancelCallback = ref(null);
const changeCallback = ref(null);
const popup = ref(null);
// MODIFIED: 新增 ref用于存储当前激活的选项值
const activeValue = ref(null);
const activeTab = ref('');
const filterOptions = ref([]);
const listData = ref([]);
// MODIFIED: open 方法增加一个 currentValue 参数
const open = (newConfig = {}) => {
const {
title: configTitle,
success,
cancel,
change,
data,
maskClick: configMaskClick = false,
currentValue, // MODIFIED: 接收父组件传入的当前值
} = newConfig;
// reset();
if (configTitle) title.value = configTitle;
if (typeof success === 'function') confirmCallback.value = success;
if (typeof cancel === 'function') cancelCallback.value = cancel;
if (typeof change === 'function') changeCallback.value = change;
if (Array.isArray(data)) listData.value = data;
// MODIFIED: 将父组件传入的值
activeValue.value = currentValue;
if (configMaskClick) {
maskClick.value = configMaskClick;
maskClickFn.value = cancel;
}
getoptions();
nextTick(() => {
popup.value?.open();
});
};
const close = () => {
popup.value?.close();
};
const cancel = () => {
handleClick(cancelCallback.value, null);
};
const handleClick = async (callback, selectedItem) => {
if (typeof callback !== 'function') {
close();
return;
}
try {
const result = await callback(selectedItem);
if (result !== false) close();
} catch (error) {
console.error('Callback execution error:', error);
}
};
const handleItemClick = (option) => {
// MODIFIED: 点击时,更新本地的 activeValue
activeValue.value = option.value;
// 立即调用回调并传递所选的完整 option
handleClick(confirmCallback.value, option);
};
const scrollTo = (key) => {
activeTab.value = key;
};
function getoptions() {
filterOptions.value = transformRegionalData(listData.value);
activeTab.value = listData.value[0].key;
}
const reset = () => {
maskClick.value = false;
confirmCallback.value = null;
cancelCallback.value = null;
changeCallback.value = null;
// MODIFIED: 重置时也清空 activeValue
activeValue.value = null;
};
function transformRegionalData(sourceData) {
const options = sourceData.map((region, index) => {
const ls = region.areaList.map((commercial) => ({
...commercial,
text: commercial.commercialAreaName,
label: commercial.commercialAreaName,
value: commercial.commercialAreaId,
key: commercial.commercialAreaId,
listClass: 'default',
status: 'default',
}));
return {
label: region.regionalName,
key: 'lx' + region.regionalId,
options: ls,
};
});
return options;
}
// 暴露方法给父组件
defineExpose({
open,
close,
});
</script>
<style lang="scss" scoped>
/* 样式表无变化,保持原样即可 */
.popup-fix {
position: fixed !important;
left: 0;
right: 0;
bottom: 0;
top: 0;
height: 100vh;
z-index: 9999;
}
.popup-content {
color: #000000;
height: 80vh;
}
.popup-bottom {
display: none;
}
.popup-list {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: space-evenly;
height: calc(80vh - 100rpx);
overflow: hidden;
.picker-view {
width: 100%;
height: 500rpx;
margin-top: 20rpx;
.uni-picker-view-mask {
background: rgba(0, 0, 0, 0);
}
.item {
line-height: 84rpx;
height: 84rpx;
text-align: center;
font-weight: 400;
font-size: 32rpx;
color: #cccccc;
}
.item-active {
color: #333333;
}
.uni-picker-view-indicator:after {
border-color: #e3e3e3;
}
.uni-picker-view-indicator:before {
border-color: #e3e3e3;
}
}
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 40rpx 10rpx 40rpx;
.title {
font-weight: 500;
font-size: 36rpx;
color: #333333;
text-align: center;
}
.btn-cancel {
font-weight: 400;
font-size: 32rpx;
color: #666d7f;
line-height: 38rpx;
}
.btn-confirm {
font-weight: 400;
font-size: 32rpx;
color: #256bfa;
min-width: 60rpx;
}
}
.content-wrapper {
flex: 1;
display: flex;
overflow: hidden;
height: 100%;
}
.filter-nav {
width: 200rpx;
background-color: #ffffff;
.nav-item {
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-weight: 400;
font-size: 28rpx;
color: #666d7f;
&.active {
font-weight: 500;
font-size: 28rpx;
color: #256bfa;
}
}
}
.filter-content {
flex: 1;
padding: 20rpx;
background-color: #f6f6f6;
.content-item {
margin-top: 30rpx;
.item-title {
font-weight: 400;
font-size: 28rpx;
color: #333333;
margin-bottom: 15rpx;
}
}
.content-item:first-child {
margin-top: 0rpx;
}
.check-content {
display: grid;
gap: 16rpx;
grid-template-columns: repeat(auto-fill, minmax(180rpx, 1fr));
place-items: stretch;
.checkbox-item {
display: flex;
align-items: center;
text-align: center;
background-color: #d9d9d9;
min-width: 0;
padding: 0 10rpx;
height: 80rpx;
background: #e8eaee;
border-radius: 12rpx 12rpx 12rpx 12rpx;
.option-label {
font-size: 28rpx;
width: 100%;
white-space: nowrap;
overflow: hidden;
}
}
/* 这个样式现在会根据 activeValue 动态应用 */
.checkedstyle {
height: 76rpx;
background: rgba(37, 107, 250, 0.06);
border-radius: 12rpx 12rpx 12rpx 12rpx;
border: 2rpx solid #256bfa;
color: #256bfa;
}
}
}
</style>

View File

@@ -1,136 +0,0 @@
import {
ref,
onBeforeUnmount,
onMounted
} from 'vue'
import {
onHide,
onUnload
} from '@dcloudio/uni-app'
export function useSpeechReader() {
const isSpeaking = ref(false)
const isPaused = ref(false)
let utterance = null
const cleanMarkdown = (text) => {
return formatTextForSpeech(text)
}
const speak = (text, options = {
lang: 'zh-CN',
rate: 0.9,
pitch: 1.2
}) => {
cancelAudio() // 重置之前的
// const voices = speechSynthesis.getVoices()
// const chineseVoices = voices.filter(v => v.lang.includes('zh'))
const speechText = extractSpeechText(text);
utterance = new SpeechSynthesisUtterance(speechText)
// utterance.lang = options.lang || 'zh'
utterance.rate = options.rate || 1
utterance.pitch = options.pitch || 1.1 // 音调0 - 2偏高比较柔和
utterance.onend = () => {
isSpeaking.value = false
isPaused.value = false
}
speechSynthesis.speak(utterance)
isSpeaking.value = true
isPaused.value = false
}
const pause = () => {
if (isSpeaking.value && !isPaused.value) {
speechSynthesis.pause()
isPaused.value = true
}
}
const resume = () => {
if (isSpeaking.value && isPaused.value) {
speechSynthesis.resume()
isPaused.value = false
}
}
const cancelAudio = () => {
speechSynthesis.cancel()
isSpeaking.value = false
isPaused.value = false
}
// 页面刷新/关闭时
onMounted(() => {
if (typeof window !== 'undefined') {
window.addEventListener('beforeunload', cancelAudio)
}
})
onBeforeUnmount(() => {
cancelAudio()
if (typeof window !== 'undefined') {
window.removeEventListener('beforeunload', cancelAudio)
}
})
onHide(cancelAudio)
onUnload(cancelAudio)
return {
speak,
pause,
resume,
cancelAudio,
isSpeaking,
isPaused,
}
}
function extractSpeechText(markdown) {
const jobRegex = /``` job-json\s*({[\s\S]*?})\s*```/g;
const jobs = [];
let match;
let lastJobEndIndex = 0;
let firstJobStartIndex = -1;
// 提取岗位 json 数据及前后位置
while ((match = jobRegex.exec(markdown)) !== null) {
const jobStr = match[1];
try {
const job = JSON.parse(jobStr);
jobs.push(job);
if (firstJobStartIndex === -1) {
firstJobStartIndex = match.index;
}
lastJobEndIndex = jobRegex.lastIndex;
} catch (e) {
console.warn('JSON 解析失败', e);
}
}
// 提取引导语(第一个 job-json 之前的文字)
const guideText = firstJobStartIndex > 0 ?
markdown.slice(0, firstJobStartIndex).trim() :
'';
// 提取结束语(最后一个 job-json 之后的文字)
const endingText = lastJobEndIndex < markdown.length ?
markdown.slice(lastJobEndIndex).trim() :
'';
// 岗位信息格式化为语音文本
const jobTexts = jobs.map((job, index) => {
return `${index + 1} 个岗位,岗位名称是:${job.jobTitle},公司是:${job.companyName},薪资:${job.salary},地点:${job.location},学历要求:${job.education},经验要求:${job.experience}`;
});
// 拼接总语音内容
const finalTextParts = [];
if (guideText) finalTextParts.push(guideText);
finalTextParts.push(...jobTexts);
if (endingText) finalTextParts.push(endingText);
return finalTextParts.join('\n');
}

157
hook/useSystemPlayer.js Normal file
View File

@@ -0,0 +1,157 @@
import {
ref,
onUnmounted,
readonly
} from 'vue';
const defaultExtractSpeechText = (text) => text;
export function useTTSPlayer() {
const synth = window.speechSynthesis;
const isSpeaking = ref(false);
const isPaused = ref(false);
const utteranceRef = ref(null);
const cleanup = () => {
isSpeaking.value = false;
isPaused.value = false;
utteranceRef.value = null;
};
/**
* @param {string} text - The text to be spoken.
* @param {object} [options] - Optional settings for the speech.
* @param {string} [options.lang] - Language (e.g., 'en-US', 'es-ES').
* @param {number} [options.rate] - Speed (0.1 to 10, default 1).
* @param {number} [options.pitch] - Pitch (0 to 2, default 1).
* @param {SpeechSynthesisVoice} [options.voice] - A specific voice object.
* @param {function(string): string} [options.extractSpeechText] - A function to filter/clean the text before speaking.
*/
const speak = (text, options = {}) => {
if (!synth) {
console.error('SpeechSynthesis API is not supported in this browser.');
return;
}
if (isSpeaking.value) {
synth.cancel();
}
const filteredText = extractSpeechText(text);
if (!filteredText || typeof filteredText !== 'string' || filteredText.trim() === '') {
console.warn('Text to speak is empty after filtering.');
cleanup(); // Ensure state is clean
return;
}
const newUtterance = new SpeechSynthesisUtterance(filteredText); // Use filtered text
utteranceRef.value = newUtterance;
newUtterance.rate = options.rate || 1;
newUtterance.pitch = options.pitch || 1;
if (options.voice) {
newUtterance.voice = options.voice;
}
newUtterance.onstart = () => {
isSpeaking.value = true;
isPaused.value = false;
};
newUtterance.onpause = () => {
isPaused.value = true;
};
newUtterance.onresume = () => {
isPaused.value = false;
};
newUtterance.onend = () => {
cleanup();
};
newUtterance.onerror = (event) => {
console.error('SpeechSynthesis Error:', event.error);
cleanup();
};
synth.speak(newUtterance);
};
const pause = () => {
if (synth && isSpeaking.value && !isPaused.value) {
synth.pause();
}
};
const resume = () => {
if (synth && isPaused.value) {
synth.resume();
}
};
const cancelAudio = () => {
if (synth) {
synth.cancel();
}
cleanup();
};
onUnmounted(() => {
cancelAudio();
});
return {
speak,
pause,
resume,
cancelAudio,
isSpeaking: readonly(isSpeaking),
isPaused: readonly(isPaused),
};
}
function extractSpeechText(markdown) {
const jobRegex = /``` job-json\s*({[\s\S]*?})\s*```/g;
const jobs = [];
let match;
let lastJobEndIndex = 0;
let firstJobStartIndex = -1;
// 提取岗位 json 数据及前后位置
while ((match = jobRegex.exec(markdown)) !== null) {
const jobStr = match[1];
try {
const job = JSON.parse(jobStr);
jobs.push(job);
if (firstJobStartIndex === -1) {
firstJobStartIndex = match.index;
}
lastJobEndIndex = jobRegex.lastIndex;
} catch (e) {
console.warn('JSON 解析失败', e);
}
}
// 提取引导语(第一个 job-json 之前的文字)
const guideText = firstJobStartIndex > 0 ?
markdown.slice(0, firstJobStartIndex).trim() :
'';
// 提取结束语(最后一个 job-json 之后的文字)
const endingText = lastJobEndIndex < markdown.length ?
markdown.slice(lastJobEndIndex).trim() :
'';
// 岗位信息格式化为语音文本
const jobTexts = jobs.map((job, index) => {
return `${index + 1} 个岗位,岗位名称是:${job.jobTitle},公司是:${job.companyName},薪资:${job.salary},地点:${job.location},学历要求:${job.education},经验要求:${job.experience}`;
});
// 拼接总语音内容
const finalTextParts = [];
if (guideText) finalTextParts.push(guideText);
finalTextParts.push(...jobTexts);
if (endingText) finalTextParts.push(endingText);
return finalTextParts.join('\n');
}

View File

@@ -0,0 +1,203 @@
import {
ref,
readonly,
onUnmounted
} from 'vue';
// 检查 API 兼容性
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const isApiSupported = !!SpeechRecognition && !!navigator.mediaDevices && !!window.AudioContext;
/**
* @param {object} [options]
* @param {string} [options.lang] - Language code (e.g., 'zh-CN', 'en-US')
* @returns {object}
*/
export function useAudioRecorder(options = {}) {
const lang = options.lang || 'zh-CN'; // 默认使用中文
const isRecording = ref(false);
const recognizedText = ref(''); // 完整的识别文本(包含临时的)
const lastFinalText = ref(''); // 最后一段已确定的文本
const volumeLevel = ref(0); // 音量 (0-100)
const audioDataForDisplay = ref(new Uint8Array()); // 波形数据
let recognition = null;
let audioContext = null;
let analyser = null;
let mediaStreamSource = null;
let mediaStream = null;
let dataArray = null; // 用于音量和波形
let animationFrameId = null;
if (!isApiSupported) {
console.warn(
'此浏览器不支持Web语音API或Web音频API。钩子无法正常工作。'
);
return {
isRecording: readonly(isRecording),
startRecording: () => console.error('Audio recording not supported.'),
stopRecording: () => {},
cancelRecording: () => {},
audioDataForDisplay: readonly(audioDataForDisplay),
volumeLevel: readonly(volumeLevel),
recognizedText: readonly(recognizedText),
lastFinalText: readonly(lastFinalText),
};
}
const setupRecognition = () => {
recognition = new SpeechRecognition();
recognition.lang = lang;
recognition.continuous = true; // 持续识别
recognition.interimResults = true; // 返回临时结果
recognition.onstart = () => {
isRecording.value = true;
};
recognition.onend = () => {
isRecording.value = false;
stopAudioAnalysis(); // 语音识别停止时,也停止音频分析
};
recognition.onerror = (event) => {
console.error('SpeechRecognition Error:', event.error);
isRecording.value = false;
stopAudioAnalysis();
};
recognition.onresult = (event) => {
let interim = '';
let final = '';
for (let i = 0; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
final += transcript;
lastFinalText.value = transcript; // 存储最后一段确定的文本
} else {
interim += transcript;
}
}
recognizedText.value = final + interim; // 组合为完整文本
};
};
const startAudioAnalysis = async () => {
try {
mediaStream = await navigator.mediaDevices.getUserMedia({
audio: true
});
audioContext = new AudioContext();
analyser = audioContext.createAnalyser();
mediaStreamSource = audioContext.createMediaStreamSource(mediaStream);
// 设置 Analyser
analyser.fftSize = 512; // 必须是 2 的幂
const bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength); // 用于波形
// 连接节点
mediaStreamSource.connect(analyser);
// 开始循环分析
updateAudioData();
} catch (err) {
console.error('Failed to get media stream or setup AudioContext:', err);
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
alert('麦克风权限被拒绝。请在浏览器设置中允许访问麦克风。');
}
}
};
const updateAudioData = () => {
if (!isRecording.value) return; // 如果停止了就退出循环
// 获取时域数据 (波形)
analyser.getByteTimeDomainData(dataArray);
audioDataForDisplay.value = new Uint8Array(dataArray); // 复制数组以触发响应式
// 计算音量 (RMS)
let sumSquares = 0.0;
for (const amplitude of dataArray) {
const normalized = (amplitude / 128.0) - 1.0; // 转换为 -1.0 到 1.0
sumSquares += normalized * normalized;
}
const rms = Math.sqrt(sumSquares / dataArray.length);
volumeLevel.value = Math.min(100, Math.floor(rms * 250)); // 放大 RMS 值到 0-100 范围
animationFrameId = requestAnimationFrame(updateAudioData);
};
const stopAudioAnalysis = () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
// 停止麦克风轨道
mediaStream?.getTracks().forEach((track) => track.stop());
// 关闭 AudioContext
audioContext?.close().catch((e) => console.error('Error closing AudioContext', e));
mediaStream = null;
audioContext = null;
analyser = null;
mediaStreamSource = null;
volumeLevel.value = 0;
audioDataForDisplay.value = new Uint8Array();
};
const startRecording = async () => {
if (isRecording.value) return;
// 重置状态
recognizedText.value = '';
lastFinalText.value = '';
try {
// 必须先启动音频分析以获取麦克风权限
await startAudioAnalysis();
// 如果音频启动成功 (mediaStream 存在),则启动语音识别
if (mediaStream) {
setupRecognition();
recognition.start();
}
} catch (error) {
console.error("Error starting recording:", error);
}
};
const stopRecording = () => {
if (!isRecording.value || !recognition) return;
recognition.stop(); // 这将触发 onend 事件,自动停止音频分析
};
const cancelRecording = () => {
if (!recognition) return;
isRecording.value = false; // 立即设置状态
recognition.abort(); // 这也会触发 onend
recognizedText.value = '';
lastFinalText.value = '';
};
onUnmounted(() => {
if (recognition) {
recognition.abort();
}
stopAudioAnalysis();
});
return {
isRecording: readonly(isRecording),
startRecording,
stopRecording,
cancelRecording,
audioDataForDisplay: readonly(audioDataForDisplay),
volumeLevel: readonly(volumeLevel),
recognizedText: readonly(recognizedText),
lastFinalText: readonly(lastFinalText),
isApiSupported, // 导出支持状态
};
}

View File

@@ -23,6 +23,8 @@
var vConsole = new window.VConsole();
vConsole.destroy();
</script> -->
<!-- 爱山东jssdk -->
<script type="text/javascript" src="https://isdapp.shandong.gov.cn/jmopen/jssdk/index.js"></script>
</head>
<!-- <body> -->
<div id="app"><!--app-html--></div>

View File

@@ -1,75 +1,116 @@
<template>
<view class="mys-container">
<!-- 个人信息 -->
<view class="mys-tops btn-feel">
<view class="tops-left">
<view class="name">
<text>{{ userInfo.name || '编辑用户名' }}</text>
<view class="edit-icon mar_le10">
<image
class="button-click"
src="@/static/icon/edit1.png"
@click="navTo('/packageA/pages/personalInfo/personalInfo')"
></image>
<AppLayout title="我的简历" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
<template #headerleft>
<view class="btn">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<view class="mys-container">
<!-- 个人信息 -->
<view class="card-top" style="margin-top: 12rpx; padding: 0">
<view class="mys-tops btn-feel">
<view class="tops-left">
<view class="name">
<text>{{ userInfo.name || '编辑用户名' }}</text>
<view class="edit-icon mar_le10">
<image
class="button-click"
src="@/static/icon/edit1.png"
@click="navTo('/packageA/pages/personalInfo/personalInfo')"
></image>
</view>
</view>
<view class="subName">
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
<text class="mar_ri10">{{ userInfo.age }}</text>
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
<dict-Label
class="mar_ri10"
dictType="affiliation"
:value="userInfo.politicalAffiliation"
></dict-Label>
</view>
<view class="subName">{{ userInfo.phone }}</view>
</view>
<view class="tops-right">
<view class="right-imghead">
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy.png"></image>
<image v-else src="@/static/icon/girl.png"></image>
</view>
<view class="right-sex">
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy1.png"></image>
<image v-else src="@/static/icon/girl1.png"></image>
</view>
</view>
</view>
<view class="subName">
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
<text class="mar_ri10">{{ userInfo.age }}</text>
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
<dict-Label
class="mar_ri10"
dictType="affiliation"
:value="userInfo.politicalAffiliation"
></dict-Label>
<!-- 求职期望 -->
<view class="mys-line"></view>
<view class="mys-info">
<view class="mys-h4">
<text>求职期望</text>
<view class="mys-edit-icon">
<image
class="button-click"
src="@/static/icon/edit1.png"
@click="navTo('/packageA/pages/jobExpect/jobExpect')"
></image>
</view>
</view>
<view class="mys-text">
<text>期望薪资</text>
<text>{{ userInfo.salaryMin / 1000 }}k-{{ userInfo.salaryMax / 1000 }}k</text>
</view>
<view class="mys-text">
<text>期望工作地</text>
<text>青岛市-</text>
<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label>
</view>
<view class="mys-list">
<view class="cards button-click" v-for="(title, index) in userInfo.jobTitle" :key="index">
{{ title }}
</view>
</view>
</view>
<view class="subName">{{ userInfo.phone }}</view>
</view>
<view class="tops-right">
<view class="right-imghead">
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy.png"></image>
<image v-else src="@/static/icon/girl.png"></image>
</view>
<view class="right-sex">
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy1.png"></image>
<image v-else src="@/static/icon/girl1.png"></image>
<view class="card-top" style="margin-top: 24rpx">
<view class="mys-info" style="padding: 0">
<view class="mys-h4">
<text>工作经历</text>
<view class="mys-edit-icon">
<image
class="button-click"
src="@/static/icon/edit1.png"
@click="navTo('/packageA/pages/jobExpect/jobExpect')"
></image>
</view>
</view>
<view class="mar_top10" v-for="item in userInfo.workExp" :key="item.id">
<view class="fl_box fl_justbet mar_top10">
<text class="fs_16">{{ item.company }}</text>
<text class="datetext">{{ item.startTime }}--{{ item.endTime || '至今' }}</text>
</view>
<view class="mys-text">
<text class="color_333333 fs_14">{{ item.position }}</text>
<text></text>
</view>
<view class="mys-text color_333333">
<text>{{ item.duty }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 求职期望 -->
<view class="mys-line"></view>
<view class="mys-info">
<view class="mys-h4">
<text>求职期望</text>
<view class="mys-edit-icon">
<image
class="button-click"
src="@/static/icon/edit1.png"
@click="navTo('/packageA/pages/jobExpect/jobExpect')"
></image>
</view>
<template #footer>
<view class="footer-container">
<view class="footer-button">上传简历</view>
</view>
<view class="mys-text">
<text>期望薪资</text>
<text>{{ userInfo.salaryMin / 1000 }}k-{{ userInfo.salaryMax / 1000 }}k</text>
</view>
<view class="mys-text">
<text>期望工作地</text>
<text>青岛市-</text>
<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label>
</view>
<view class="mys-list">
<view class="cards button-click" v-for="(title, index) in userInfo.jobTitle" :key="index">
{{ title }}
</view>
</view>
</view>
</view>
</template>
</AppLayout>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
const { $api, navTo } = inject('globalFunction');
const { $api, navTo, navBack } = inject('globalFunction');
import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
@@ -80,20 +121,52 @@ const { getDictData, oneDictData } = useDictStore();
</script>
<style lang="stylus" scoped>
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 60rpx;
height: 60rpx;
image {
height: 100%;
width: 100%;
}
}
.footer-container{
background: #FFFFFF;
box-shadow: 0rpx -4rpx 24rpx 0rpx rgba(11,44,112,0.12);
border-radius: 0rpx 0rpx 0rpx 0rpx;
padding: 40rpx 28rpx 20rpx 28rpx
.footer-button{
width: 100%;
height: 90rpx;
background: #1677FF;
border-radius: 8rpx;
color: #FFFFFF;
line-height: 90rpx;
text-align: center;
}
}
image{
width: 100%;
height: 100%
}
.mys-container{
.card-top{
background: #FFFFFF;
margin: 0 28rpx;
border-radius: 8rpx;
padding: 24rpx
}
.mys-tops{
display: flex
justify-content: space-between
padding: 52rpx 48rpx
padding: 38rpx 44rpx
.tops-left{
.name{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 600;
font-size: 44rpx;
font-size: 36rpx;
color: #333333;
display: flex
align-items: center
@@ -108,8 +181,8 @@ image{
.subName{
margin-top: 12rpx
font-weight: 400;
font-size: 32rpx;
color: #333333;
font-size: 26rpx;
color: #999999;
}
}
@@ -143,7 +216,7 @@ image{
.mys-h4{
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
font-weight: 600;
font-size: 32rpx;
font-size: 30rpx;
color: #000000;
margin-bottom: 8rpx
display: flex;
@@ -155,10 +228,15 @@ image{
height: 40rpx
}
}
.datetext{
font-weight: 400;
font-size: 26rpx;
color: #999999;
}
.mys-text{
font-weight: 400;
font-size: 28rpx;
color: #333333;
font-size: 26rpx;
color: #999999;
margin-top: 16rpx
}
.mys-list{

View File

@@ -106,7 +106,8 @@
"path": "pages/myResume/myResume",
"style": {
"navigationBarTitleText": "我的简历",
"navigationBarBackgroundColor": "#FFFFFF"
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
}, {
"path": "pages/Intendedposition/Intendedposition",

View File

@@ -113,7 +113,8 @@
</view>
</view>
<view class="chat-item self" v-if="isRecording">
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
<!-- <view class="message">{{ recognizedText }} {{ lastFinalText }}</view> -->
<view class="message">{{ recognizedText }}</view>
</view>
<view v-if="isTyping" class="self">
<text class="message msg-loading">
@@ -268,14 +269,18 @@ import AudioWave from './AudioWave.vue';
import WaveDisplay from './WaveDisplay.vue';
import FileIcon from './fileIcon.vue';
import FileText from './fileText.vue';
import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
import { useTTSPlayer } from '@/hook/useTTSPlayer.js';
// 系统功能hook和阿里云hook
// import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
import { useAudioRecorder } from '@/hook/useSystemSpeechReader.js';
// import { useTTSPlayer } from '@/hook/useTTSPlayer.js';
import { useTTSPlayer } from '@/hook/useSystemPlayer.js';
// 全局
const { $api, navTo, throttle } = inject('globalFunction');
const emit = defineEmits(['onConfirm']);
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
import successIcon from '@/static/icon/success.png';
// hook
// 语音识别
const {
isRecording,
startRecording,
@@ -286,7 +291,7 @@ const {
recognizedText,
lastFinalText,
} = useAudioRecorder();
// 语音合成
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio } = useTTSPlayer(config.speechSynthesis);
// state
@@ -629,6 +634,7 @@ function readMarkdown(value, index) {
if (isPaused.value) {
resume();
} else {
console.log(value, speechIndex.value, index, isPaused.value)
speak(value);
}
}

View File

@@ -6,7 +6,7 @@
<uni-icons class="iconsearch" color="#666666" type="search" size="18"></uni-icons>
<text class="inpute">职位名称薪资要求等</text>
</view>
<view class="chart button-click">职业图谱</view>
<!-- <view class="chart button-click">职业图谱</view> -->
</view>
<view class="cards">
<view class="card press-button" @click="navTo('/pages/nearby/nearby')">
@@ -563,7 +563,7 @@ defineExpose({ loadData });
width: 100%
height: 80rpx;
line-height: 80rpx
margin-right: 24rpx
// margin-right: 24rpx
background: #FFFFFF;
border-radius: 75rpx 75rpx 75rpx 75rpx;
.iconsearch
@@ -703,7 +703,7 @@ defineExpose({ loadData });
text-align: left;
word-break:break-all
font-weight: 500;
font-size: 32rpx;
font-size: 30rpx;
color: #333333;
margin-top: 10rpx
.falls-card-pay

View File

@@ -327,7 +327,8 @@ function complete() {
width: 100%;
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
position: fixed;
background: url('@/static/icon/background2.png') 0 0 no-repeat;
// background: linear-gradient( 180deg, #1677FF 0%, rgba(22,119,255,0) 54%, rgba(22,119,255,0) 100%);
// background: url('@/static/icon/background2.png') 0 0 no-repeat;
background-size: 100% 728rpx;
display: flex;
flex-direction: column

View File

@@ -1,15 +1,26 @@
<template>
<scroll-view :scroll-y="true" class="nearby-scroll" @scrolltolower="scrollBottom">
<view class="two-head">
<view
class="head-item"
:class="{ active: state.comId === item.commercialAreaId }"
v-for="(item, index) in state.comlist"
:key="item.commercialAreaName"
@click="clickCommercialArea(item)"
>
{{ item.commercialAreaName }}
<view class="head-all">
<text>热门商圈</text>
<text class="color_333333 button-click" @click="handleOpenBusinessDistrict">
更多
<uni-icons type="forward" color="#333333" size="14"></uni-icons>
</text>
</view>
<scroll-view class="scroll-head" :scroll-x="true" :scroll-into-view="activeTab" :show-scrollbar="false">
<view class="head-item-content">
<view
class="head-item"
:class="{ active: state.comId === item.commercialAreaId }"
v-for="(item, index) in comlistPuted"
:key="item.commercialAreaName"
@click="clickCommercialArea(item)"
>
{{ item.commercialAreaName }}
</view>
</view>
</scroll-view>
</view>
<view class="nearby-list">
<view class="nav-filter" @touchmove.stop.prevent>
@@ -70,11 +81,12 @@
</view>
<!-- 筛选 -->
<select-filter ref="selectFilterModel"></select-filter>
<select-filter2-col ref="selectFilter2ColModel"></select-filter2-col>
</scroll-view>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted, onBeforeUnmount } from 'vue';
import { reactive, inject, watch, ref, onMounted, onBeforeUnmount, computed } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo, debounce, customSystem } = inject('globalFunction');
import { storeToRefs } from 'pinia';
@@ -87,6 +99,7 @@ const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
import point2 from '@/static/icon/point2.png';
import LocationPng from '@/static/icon/Location.png';
import selectFilter from '@/components/selectFilter/selectFilter.vue';
import selectFilter2Col from '@/components/selectFilter/selectFilter2Col.vue';
const emit = defineEmits(['onFilter']);
const state = reactive({
@@ -96,12 +109,15 @@ const state = reactive({
comId: 0,
areaInfo: {},
});
const commercialAreaList = ref([]);
const isLoaded = ref(false);
const showFilter = ref(false);
const selectFilterModel = ref();
const selectFilter2ColModel = ref();
const fromValue = reactive({
area: 0,
});
const activeTab = ref('');
const loadmoreRef = ref(null);
const pageState = reactive({
page: 0,
@@ -114,6 +130,18 @@ const pageState = reactive({
});
const list = ref([]);
const comlistPuted = computed(() => {
// const commercialArea = state.comlist.find((item) => item.commercialAreaId === state.comId);
// if (commercialArea) {
// const otherItems = state.comlist.filter((item) => item.commercialAreaId !== state.comId);
// return [commercialArea, ...otherItems];
// } else {
// return [state.areaInfo, ...state.comlist];
// }
// activeTab.value = state.areaInfo.commercialAreaId;
return state.comlist;
});
const rangeOptions = ref([
{ value: 0, text: '推荐' },
{ value: 1, text: '最热' },
@@ -149,7 +177,6 @@ function openFilter() {
pageState.search[key] = value.join(',');
}
showFilter.value = false;
console.log(pageState.search);
getJobList('refresh');
},
cancel: () => {
@@ -201,7 +228,7 @@ function changeArea(area, item) {
}
function getBusinessDistrict() {
$api.createRequest(`/app/common/commercialArea`).then((resData) => {
$api.createRequest(`/app/common/commercialArea/getAllData`).then((resData) => {
if (resData.data.length) {
state.comlist = resData.data;
state.areaInfo = resData.data[0];
@@ -266,10 +293,49 @@ function handleFilterConfirm(val) {
getJobList('refresh');
}
function handleOpenBusinessDistrict() {
if (commercialAreaList.value.length) {
openFilter2Col();
} else {
getBusinessDistrictList();
}
}
function getBusinessDistrictList() {
$api.createRequest(`/app/common/commercialArea`).then((resData) => {
if (resData.data.length) {
commercialAreaList.value = resData.data;
openFilter2Col();
}
});
}
function openFilter2Col() {
selectFilter2ColModel.value?.open({
data: commercialAreaList.value,
title: '商圈',
currentValue: state.comId,
maskClick: true,
success: (values) => {
pageState.search = {
...pageState.search,
latitude: values.latitude,
longitude: values.longitude,
};
state.areaInfo = values;
state.comId = values.value;
getJobList('refresh');
},
});
}
defineExpose({ loadData, handleFilterConfirm });
</script>
<style lang="stylus" scoped>
.scroll-head
width: 100%;
overflow: hidden;
.tabchecked
color: #4778EC !important
.nearby-scroll
@@ -277,18 +343,30 @@ defineExpose({ loadData, handleFilterConfirm });
.two-head
margin: 22rpx;
display: flex;
flex-wrap: wrap
flex-direction: column
flex-wrap: no-wrap
// grid-template-columns: repeat(4, 1fr);
// grid-column-gap: 10rpx;
// grid-row-gap: 24rpx;
border-radius: 17rpx 17rpx 17rpx 17rpx;
.head-all{
display: flex;
justify-content: space-between;
align-items: center
margin-bottom: 16rpx
}
.head-item-content{
display: flex
flex-wrap: nowrap
}
.head-item
padding: 0 10rpx
margin: 10rpx
white-space: nowrap
min-width: 156rpx
// min-width: 156rpx
line-height: 64rpx
text-align: center;
width: fit-content;
// width: fit-content;
font-size: 21rpx;
font-weight: 400;
font-size: 28rpx;
@@ -369,4 +447,4 @@ defineExpose({ loadData, handleFilterConfirm });
height: 26rpx;
.active
transform: rotate(180deg)
</style>
</style>

View File

@@ -1,5 +1,5 @@
<template>
<AppLayout title="附近" :use-scroll-view="false">
<AppLayout title="附近" :use-scroll-view="false" :show-bg-image="false">
<template #headerleft>
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>

BIN
static/.DS_Store vendored

Binary file not shown.

BIN
static/icon/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
static/icon/background3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,8 +1,8 @@
{
"hash": "5cf4c8aa",
"configHash": "0279f4bc",
"hash": "8cb75570",
"configHash": "01aaac4c",
"lockfileHash": "5d26acb0",
"browserHash": "5520a35c",
"browserHash": "4b09b340",
"optimized": {},
"chunks": {}
}