Compare commits
1 Commits
a03b54a406
...
guantao
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af3ff2db18 |
21
.gitignore
vendored
@@ -1,22 +1 @@
|
|||||||
# 编译/打包输出目录
|
|
||||||
/unpackage/
|
/unpackage/
|
||||||
|
|
||||||
# 依赖包目录
|
|
||||||
/node_modules/
|
|
||||||
|
|
||||||
# IDE/编辑器配置
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# macOS 系统文件
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Windows 系统文件
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# 日志文件
|
|
||||||
npm-debug.log
|
|
||||||
yarn-debug.log
|
|
||||||
|
|
||||||
# HBuilderX 运行时生成的文件
|
|
||||||
.hbuilderx
|
|
||||||
16
.hbuilderx/launch.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||||
|
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||||
|
"version": "0.0",
|
||||||
|
"configurations": [{
|
||||||
|
"default" :
|
||||||
|
{
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"mp-weixin" :
|
||||||
|
{
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"type" : "uniCloud"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
App.vue
@@ -4,10 +4,8 @@ import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
|
|||||||
import useUserStore from './stores/useUserStore';
|
import useUserStore from './stores/useUserStore';
|
||||||
import useDictStore from './stores/useDictStore';
|
import useDictStore from './stores/useDictStore';
|
||||||
const { $api, navTo, appendScriptTagElement } = inject('globalFunction');
|
const { $api, navTo, appendScriptTagElement } = inject('globalFunction');
|
||||||
import config from '@/config.js';
|
|
||||||
|
|
||||||
onLaunch((options) => {
|
onLaunch((options) => {
|
||||||
useUserStore().initSeesionId(); //更新
|
|
||||||
useDictStore().getDictData();
|
useDictStore().getDictData();
|
||||||
// uni.hideTabBar();
|
// uni.hideTabBar();
|
||||||
|
|
||||||
@@ -28,10 +26,15 @@ onLaunch((options) => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-WEIXIN
|
||||||
appendScriptTagElement('https://qd.zhaopinzao8dian.com/file/csn/jweixin-1.4.0.js').then(() => {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
appendScriptTagElement('./static/js/jweixin-1.4.0.js').then(() => {
|
||||||
console.log('✅ 微信 JSSDK 加载完成');
|
console.log('✅ 微信 JSSDK 加载完成');
|
||||||
// signatureFn();
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
appendScriptTagElement('/static/js/jweixin-1.4.0.js').then(() => {
|
||||||
|
console.log('✅ 微信 JSSDK 加载完成');
|
||||||
|
});
|
||||||
|
}
|
||||||
// #endif
|
// #endif
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,19 +81,19 @@ uni-modal,
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: PingFangSC-Regular;
|
font-family: PingFangSC-Regular;
|
||||||
src: url('https://qd.zhaopinzao8dian.com/file/csn/PingFangSC-Regular.woff2') format('woff2');
|
src: url('/static/font/PingFangSC-Regular.woff2') format('woff2');
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: PingFangSC-Medium;
|
font-family: PingFangSC-Medium;
|
||||||
src: url('https://qd.zhaopinzao8dian.com/file/csn/PingFangSC-Medium.woff2') format('woff2');
|
src: url('/static/font/PingFangSC-Medium.woff2') format('woff2');
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: DIN-Medium;
|
font-family: DIN-Medium;
|
||||||
src: url('https://qd.zhaopinzao8dian.com/file/csn/DIN-Medium.woff2') format('woff2');
|
src: url('/static/font/DIN-Medium.woff2') format('woff2');
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
common/.DS_Store
vendored
Normal file
@@ -543,7 +543,6 @@ function isEmptyObject(obj) {
|
|||||||
return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0;
|
return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const $api = {
|
export const $api = {
|
||||||
msg,
|
msg,
|
||||||
prePage,
|
prePage,
|
||||||
@@ -585,5 +584,5 @@ export default {
|
|||||||
appendScriptTagElement,
|
appendScriptTagElement,
|
||||||
insertSortData,
|
insertSortData,
|
||||||
isInWechatMiniProgramWebview,
|
isInWechatMiniProgramWebview,
|
||||||
isEmptyObject,
|
isEmptyObject
|
||||||
}
|
}
|
||||||
BIN
components/.DS_Store
vendored
Normal file
@@ -12,7 +12,7 @@
|
|||||||
<view class="header-btnLf">
|
<view class="header-btnLf">
|
||||||
<slot name="headerleft"></slot>
|
<slot name="headerleft"></slot>
|
||||||
</view>
|
</view>
|
||||||
<view class="header-title" :style="{ color: titleColor }">
|
<view class="header-title">
|
||||||
<view>{{ title }}</view>
|
<view>{{ title }}</view>
|
||||||
<view v-show="subTitle" class="subtitle-text">{{ subTitle }}</view>
|
<view v-show="subTitle" class="subtitle-text">{{ subTitle }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -45,15 +45,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import img from '@/static/icon/background2.png';
|
import img from '@/static/icon/background2.png';
|
||||||
const emit = defineEmits(['onScrollBottom']);
|
const emit = defineEmits(['onScrollBottom']);
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '标题',
|
default: '标题',
|
||||||
},
|
},
|
||||||
titleColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#333333',
|
|
||||||
},
|
|
||||||
border: {
|
border: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -113,13 +110,11 @@ const handleScrollToLower = () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 7rpx 3rpx;
|
padding: 7rpx 3rpx;
|
||||||
.header-title {
|
.header-title {
|
||||||
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei',
|
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
|
||||||
sans-serif;
|
|
||||||
color: #000000;
|
color: #000000;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
.subtitle-text {
|
.subtitle-text {
|
||||||
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial,
|
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
sans-serif;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="empty" :style="{ background: bgcolor, marginTop: mrTop + 'rpx' }">
|
<view class="empty" :style="{ background: bgcolor, marginTop: mrTop + 'rpx' }">
|
||||||
<view class="ty_content" :style="{ paddingTop: pdTop + 'rpx' }">
|
<view class="ty_content" :style="{ paddingTop: pdTop + 'rpx' }">
|
||||||
<view class="content_top">
|
<view class="content_top btn-shaky">
|
||||||
<!-- <view class="content_top btn-shaky"> -->
|
|
||||||
<image v-if="pictrue" :src="pictrue" mode=""></image>
|
<image v-if="pictrue" :src="pictrue" mode=""></image>
|
||||||
<image v-else src="@/static/icon/empty.png" mode=""></image>
|
<image v-else src="@/static/icon/empty.png" mode=""></image>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -24,13 +24,11 @@ const renderedHtml = computed(() => parseMarkdown(props.content));
|
|||||||
|
|
||||||
const handleItemClick = (e) => {
|
const handleItemClick = (e) => {
|
||||||
let { attrs } = e.detail.node;
|
let { attrs } = e.detail.node;
|
||||||
console.log(attrs);
|
|
||||||
let { 'data-copy-index': codeDataIndex, 'data-job-id': jobId, class: className } = attrs;
|
let { 'data-copy-index': codeDataIndex, 'data-job-id': jobId, class: className } = attrs;
|
||||||
switch (className) {
|
switch (className) {
|
||||||
case 'custom-card':
|
case 'custom-card':
|
||||||
return navTo('/packageA/pages/post/post?jobId=' + jobId);
|
navTo('/packageA/pages/post/post?jobId=' + jobId);
|
||||||
case 'custom-more':
|
return;
|
||||||
return navTo('/packageA/pages/moreJobs/moreJobs?jobId=' + jobId);
|
|
||||||
case 'copy-btn':
|
case 'copy-btn':
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: codeDataList[codeDataIndex],
|
data: codeDataList[codeDataIndex],
|
||||||
@@ -42,7 +40,6 @@ const handleItemClick = (e) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -261,19 +258,6 @@ ol {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
.custom-more{
|
|
||||||
display: flex
|
|
||||||
justify-content: flex-end
|
|
||||||
color: #256BFA
|
|
||||||
padding-top: 5rpx
|
|
||||||
padding-bottom: 14rpx
|
|
||||||
.more-icon{
|
|
||||||
width: 60rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
background: url('@/static/svg/seemore.svg') center center no-repeat;
|
|
||||||
background-size: 100% 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.custom-card
|
.custom-card
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
|
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
|
||||||
|
|||||||
@@ -101,13 +101,13 @@ function nextDetail(job) {
|
|||||||
.company{
|
.company{
|
||||||
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;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 30rpx;
|
font-size: 32rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
.salary{
|
.salary{
|
||||||
font-family: DIN-Medium;
|
font-family: DIN-Medium;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
color: #4C6EFB;
|
color: #4C6EFB;
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
line-height: 48rpx
|
line-height: 48rpx
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ const cleanup = () => {
|
|||||||
Object.keys(selectedValues).forEach((key) => {
|
Object.keys(selectedValues).forEach((key) => {
|
||||||
delete selectedValues[key];
|
delete selectedValues[key];
|
||||||
});
|
});
|
||||||
count.value = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollTo = (key) => {
|
const scrollTo = (key) => {
|
||||||
@@ -162,7 +161,6 @@ function getoptions() {
|
|||||||
getTransformChildren('experience', '工作经验'),
|
getTransformChildren('experience', '工作经验'),
|
||||||
getTransformChildren('scale', '公司规模'),
|
getTransformChildren('scale', '公司规模'),
|
||||||
];
|
];
|
||||||
console.log(arr);
|
|
||||||
if (area.value) {
|
if (area.value) {
|
||||||
arr.push(getTransformChildren('area', '区域'));
|
arr.push(getTransformChildren('area', '区域'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,345 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -58,9 +58,9 @@ const state = reactive({
|
|||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// onMounted(() => {
|
onMounted(() => {
|
||||||
// serchforIt();
|
serchforIt();
|
||||||
// });
|
});
|
||||||
|
|
||||||
// 统一处理二维数组格式
|
// 统一处理二维数组格式
|
||||||
const processedListData = computed(() => {
|
const processedListData = computed(() => {
|
||||||
@@ -82,11 +82,11 @@ const open = (newConfig = {}) => {
|
|||||||
rowLabel: configRowLabel = 'label',
|
rowLabel: configRowLabel = 'label',
|
||||||
rowKey: configRowKey = 'value',
|
rowKey: configRowKey = 'value',
|
||||||
maskClick: configMaskClick = false,
|
maskClick: configMaskClick = false,
|
||||||
defaultId = '',
|
defaultIndex = [],
|
||||||
} = newConfig;
|
} = newConfig;
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
serchforIt(defaultId);
|
serchforIt();
|
||||||
|
|
||||||
if (configTitle) title.value = configTitle;
|
if (configTitle) title.value = configTitle;
|
||||||
if (typeof success === 'function') confirmCallback.value = success;
|
if (typeof success === 'function') confirmCallback.value = success;
|
||||||
@@ -143,13 +143,11 @@ const handleClick = async (callback) => {
|
|||||||
console.error('confirmCallback 执行出错:', error);
|
console.error('confirmCallback 执行出错:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function serchforIt(defaultId) {
|
function serchforIt() {
|
||||||
if (state.stations.length) {
|
if (state.stations.length) {
|
||||||
const ids = defaultId
|
const ids = userInfo.value.jobTitleId.split(',').map((id) => Number(id));
|
||||||
? defaultId.split(',').map((id) => Number(id))
|
|
||||||
: userInfo.value.jobTitleId.split(',').map((id) => Number(id));
|
|
||||||
count.value = ids.length;
|
count.value = ids.length;
|
||||||
state.jobTitleId = defaultId ? defaultId : userInfo.value.jobTitleId;
|
state.jobTitleId = userInfo.value.jobTitleId;
|
||||||
setCheckedNodes(state.stations, ids);
|
setCheckedNodes(state.stations, ids);
|
||||||
state.visible = true;
|
state.visible = true;
|
||||||
return;
|
return;
|
||||||
@@ -168,14 +166,14 @@ function serchforIt(defaultId) {
|
|||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
maskClick.value = false;
|
maskClick.value = false;
|
||||||
|
confirmCallback.value = null;
|
||||||
|
cancelCallback.value = null;
|
||||||
changeCallback.value = null;
|
changeCallback.value = null;
|
||||||
listData.value = [];
|
listData.value = [];
|
||||||
selectedIndex.value = [0, 0, 0];
|
selectedIndex.value = [0, 0, 0];
|
||||||
rowLabel.value = 'label';
|
rowLabel.value = 'label';
|
||||||
rowKey.value = 'value';
|
rowKey.value = 'value';
|
||||||
selectedItems.value = [];
|
selectedItems.value = [];
|
||||||
JobsIdsValue.value = '';
|
|
||||||
JobsLabelValue.value = '';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
>
|
>
|
||||||
<image :src="currentItem == item.id ? item.selectedIconPath : item.iconPath"></image>
|
<image :src="currentItem == item.id ? item.selectedIconPath : item.iconPath"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="badge" v-if="item.badge">{{ item.badge }}</view>
|
|
||||||
<view class="item-bottom" :class="[currentItem == item.id ? 'item-active' : '']">
|
<view class="item-bottom" :class="[currentItem == item.id ? 'item-active' : '']">
|
||||||
<text>{{ item.text }}</text>
|
<text>{{ item.text }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -18,19 +17,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script>
|
||||||
import { ref, defineProps, onMounted, computed } from 'vue';
|
export default {
|
||||||
import { useReadMsg } from '@/stores/useReadMsg';
|
data() {
|
||||||
const props = defineProps({
|
return {
|
||||||
currentpage: {
|
currentItem: 0,
|
||||||
type: Number,
|
tabbarList: [
|
||||||
required: true,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const readMsg = useReadMsg();
|
|
||||||
const currentItem = ref(0);
|
|
||||||
const tabbarList = computed(() => [
|
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
text: '首页',
|
text: '首页',
|
||||||
@@ -38,73 +30,56 @@ const tabbarList = computed(() => [
|
|||||||
iconPath: '../../static/tabbar/calendar.png',
|
iconPath: '../../static/tabbar/calendar.png',
|
||||||
selectedIconPath: '../../static/tabbar/calendared.png',
|
selectedIconPath: '../../static/tabbar/calendared.png',
|
||||||
centerItem: false,
|
centerItem: false,
|
||||||
badge: readMsg.badges[0].count,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
text: '招聘会',
|
text: '精选',
|
||||||
path: '/pages/careerfair/careerfair',
|
path: '/pages/careerfair/careerfair',
|
||||||
iconPath: '../../static/tabbar/post.png',
|
iconPath: '../../static/tabbar/post.png',
|
||||||
selectedIconPath: '../../static/tabbar/posted.png',
|
selectedIconPath: '../../static/tabbar/posted.png',
|
||||||
centerItem: false,
|
centerItem: false,
|
||||||
badge: readMsg.badges[1].count,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
text: '',
|
|
||||||
path: '/pages/chat/chat',
|
|
||||||
iconPath: '../../static/tabbar/logo3.png',
|
|
||||||
selectedIconPath: '../../static/tabbar/logo3.png',
|
|
||||||
centerItem: true,
|
|
||||||
badge: readMsg.badges[2].count,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
text: '消息',
|
text: '消息',
|
||||||
path: '/pages/msglog/msglog',
|
path: '/pages/msglog/msglog',
|
||||||
iconPath: '../../static/tabbar/chat4.png',
|
iconPath: '../../static/tabbar/chat4.png',
|
||||||
selectedIconPath: '../../static/tabbar/chat4ed.png',
|
selectedIconPath: '../../static/tabbar/chat4ed.png',
|
||||||
centerItem: false,
|
centerItem: false,
|
||||||
badge: readMsg.badges[3].count,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 3,
|
||||||
text: '我的',
|
text: '我的',
|
||||||
path: '/pages/mine/mine',
|
path: '/pages/mine/mine',
|
||||||
iconPath: '../../static/tabbar/mine.png',
|
iconPath: '../../static/tabbar/mine.png',
|
||||||
selectedIconPath: '../../static/tabbar/mined.png',
|
selectedIconPath: '../../static/tabbar/mined.png',
|
||||||
centerItem: false,
|
centerItem: false,
|
||||||
badge: readMsg.badges[4].count,
|
|
||||||
},
|
},
|
||||||
]);
|
],
|
||||||
|
};
|
||||||
onMounted(() => {
|
},
|
||||||
|
props: {
|
||||||
|
currentpage: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.currentItem = this.currentpage;
|
||||||
uni.hideTabBar();
|
uni.hideTabBar();
|
||||||
currentItem.value = props.currentpage;
|
},
|
||||||
});
|
methods: {
|
||||||
|
changeItem(item) {
|
||||||
const changeItem = (item) => {
|
|
||||||
uni.switchTab({
|
uni.switchTab({
|
||||||
url: item.path,
|
url: item.path,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 4rpx;
|
|
||||||
right: 20rpx;
|
|
||||||
min-width: 30rpx;
|
|
||||||
height: 30rpx;
|
|
||||||
background-color: red;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 18rpx;
|
|
||||||
border-radius: 15rpx;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 30rpx;
|
|
||||||
padding: 0 10rpx;
|
|
||||||
}
|
|
||||||
.tabbar_container {
|
.tabbar_container {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default {
|
|||||||
// 只使用本地缓寸的数据
|
// 只使用本地缓寸的数据
|
||||||
OnlyUseCachedDB: true,
|
OnlyUseCachedDB: true,
|
||||||
// 使用模拟定位
|
// 使用模拟定位
|
||||||
UsingSimulatedPositioning: true,
|
UsingSimulatedPositioning: false,
|
||||||
// 应用信息
|
// 应用信息
|
||||||
appInfo: {
|
appInfo: {
|
||||||
// 应用名称
|
// 应用名称
|
||||||
@@ -63,11 +63,5 @@ export default {
|
|||||||
experience: 0.3, //经验
|
experience: 0.3, //经验
|
||||||
salary: 0.5, // 薪资
|
salary: 0.5, // 薪资
|
||||||
areas: 0.5 // 区域
|
areas: 0.5 // 区域
|
||||||
},
|
|
||||||
shareConfig: {
|
|
||||||
baseUrl: 'https://qd.zhaopinzao8dian.com',
|
|
||||||
title: '找工作,用 AI 更高效|青岛市智能求职平台',
|
|
||||||
desc: '融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!',
|
|
||||||
imgUrl: 'https://qd.zhaopinzao8dian.com/file/csn/qd_shareLogo.jpg',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
hook/.DS_Store
vendored
Normal file
@@ -49,7 +49,7 @@ export function usePagination(
|
|||||||
|
|
||||||
const fetchData = async (type = 'refresh') => {
|
const fetchData = async (type = 'refresh') => {
|
||||||
if (loading.value) return Promise.resolve()
|
if (loading.value) return Promise.resolve()
|
||||||
console.log(type)
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = false
|
error.value = false
|
||||||
|
|
||||||
@@ -77,17 +77,17 @@ export function usePagination(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
...pageState.search,
|
|
||||||
[pageField]: pageState.page,
|
[pageField]: pageState.page,
|
||||||
[sizeField]: pageState.pageSize,
|
[sizeField]: pageState.pageSize,
|
||||||
|
...pageState.search
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await requestFn(params)
|
const res = await requestFn(params)
|
||||||
|
|
||||||
const rawData = res[dataKey]
|
const rawData = res[dataKey]
|
||||||
const total = res[totalKey] || 99999999
|
const total = res[totalKey]
|
||||||
console.log(total, rawData)
|
|
||||||
const data = typeof transformFn === 'function' ? transformFn(rawData) : rawData
|
const data = typeof transformFn === 'function' ? transformFn(rawData) : rawData
|
||||||
|
|
||||||
if (type === 'refresh') {
|
if (type === 'refresh') {
|
||||||
@@ -137,10 +137,10 @@ export function usePagination(
|
|||||||
if (autoWatchSearch && isRef(search)) {
|
if (autoWatchSearch && isRef(search)) {
|
||||||
watch(search, (newVal) => {
|
watch(search, (newVal) => {
|
||||||
pageState.search = newVal
|
pageState.search = newVal
|
||||||
clearTimeout(debounceTimer)
|
// clearTimeout(debounceTimer)
|
||||||
debounceTimer = setTimeout(() => {
|
// debounceTimer = setTimeout(() => {
|
||||||
refresh()
|
// refresh()
|
||||||
}, debounceTime)
|
// }, debounceTime)
|
||||||
}, {
|
}, {
|
||||||
deep: true
|
deep: true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,246 +1,387 @@
|
|||||||
import {
|
import {
|
||||||
ref,
|
ref,
|
||||||
onUnmounted
|
onUnmounted
|
||||||
} from 'vue'
|
} from 'vue';
|
||||||
import {
|
|
||||||
$api,
|
|
||||||
|
|
||||||
} from '../common/globalFunction';
|
function mergeText(prevText, newText) {
|
||||||
|
if (newText.startsWith(prevText)) {
|
||||||
import config from '@/config'
|
return newText; // 直接替换,避免重复拼接
|
||||||
|
}
|
||||||
export function useAudioRecorder() {
|
return prevText + newText; // 兼容意外情况
|
||||||
const isRecording = ref(false)
|
|
||||||
const isStopping = ref(false)
|
|
||||||
const isSocketConnected = ref(false)
|
|
||||||
const recordingDuration = ref(0)
|
|
||||||
|
|
||||||
const audioDataForDisplay = ref(new Array(16).fill(0))
|
|
||||||
const volumeLevel = ref(0)
|
|
||||||
|
|
||||||
const recognizedText = ref('')
|
|
||||||
const lastFinalText = ref('')
|
|
||||||
|
|
||||||
let audioStream = null
|
|
||||||
let audioContext = null
|
|
||||||
let audioInput = null
|
|
||||||
let scriptProcessor = null
|
|
||||||
let websocket = null
|
|
||||||
let durationTimer = null
|
|
||||||
|
|
||||||
const generateUUID = () => {
|
|
||||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11)
|
|
||||||
.replace(/[018]/g, c =>
|
|
||||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
|
||||||
).replace(/-/g, '')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchWsUrl = async () => {
|
export function useAudioRecorder(wsUrl) {
|
||||||
const res = await $api.createRequest('/app/speech/getToken')
|
// 状态变量
|
||||||
if (res.code !== 200) throw new Error('无法获取语音识别 wsUrl')
|
const isRecording = ref(false);
|
||||||
const wsUrl = res.msg
|
const isStopping = ref(false);
|
||||||
return wsUrl
|
const isSocketConnected = ref(false);
|
||||||
|
const recordingDuration = ref(0);
|
||||||
|
const audioDataForDisplay = ref(new Array(16).fill(0.01));
|
||||||
|
const volumeLevel = ref(0);
|
||||||
|
|
||||||
|
// 音频相关
|
||||||
|
const audioContext = ref(null);
|
||||||
|
const mediaStream = ref(null);
|
||||||
|
const workletNode = ref(null);
|
||||||
|
const analyser = ref(null);
|
||||||
|
|
||||||
|
// 网络相关
|
||||||
|
const socket = ref(null);
|
||||||
|
|
||||||
|
// 配置常量
|
||||||
|
const SAMPLE_RATE = 16000;
|
||||||
|
const SILENCE_THRESHOLD = 0.02; // 静音阈值 (0-1)
|
||||||
|
const SILENCE_DURATION = 100; // 静音持续时间(ms)后切片
|
||||||
|
const MIN_SOUND_DURATION = 200; // 最小有效声音持续时间(ms)
|
||||||
|
|
||||||
|
// 音频处理变量
|
||||||
|
const lastSoundTime = ref(0);
|
||||||
|
const audioChunks = ref([]);
|
||||||
|
const currentChunkStartTime = ref(0);
|
||||||
|
const silenceStartTime = ref(0);
|
||||||
|
|
||||||
|
// 语音识别结果
|
||||||
|
const recognizedText = ref('');
|
||||||
|
const lastFinalText = ref(''); // 保存最终确认的文本
|
||||||
|
|
||||||
|
// AudioWorklet处理器代码
|
||||||
|
const workletProcessorCode = `
|
||||||
|
class AudioProcessor extends AudioWorkletProcessor {
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
this.silenceThreshold = options.processorOptions.silenceThreshold;
|
||||||
|
this.sampleRate = options.processorOptions.sampleRate;
|
||||||
|
this.samplesPerChunk = Math.floor(this.sampleRate * 0.05); // 50ms的块
|
||||||
|
this.buffer = new Int16Array(this.samplesPerChunk);
|
||||||
|
this.index = 0;
|
||||||
|
this.lastUpdate = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractWsParams(wsUrl) {
|
calculateVolume(inputs) {
|
||||||
const url = new URL(wsUrl)
|
const input = inputs[0];
|
||||||
const appkey = url.searchParams.get('appkey')
|
if (!input || input.length === 0) return 0;
|
||||||
const token = url.searchParams.get('token')
|
|
||||||
return {
|
let sum = 0;
|
||||||
appkey,
|
const inputChannel = input[0];
|
||||||
token
|
for (let i = 0; i < inputChannel.length; i++) {
|
||||||
|
sum += inputChannel[i] * inputChannel[i];
|
||||||
}
|
}
|
||||||
|
return Math.sqrt(sum / inputChannel.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process(inputs) {
|
||||||
|
const now = currentTime;
|
||||||
|
const volume = this.calculateVolume(inputs);
|
||||||
|
|
||||||
const connectWebSocket = async () => {
|
// 每50ms发送一次分析数据
|
||||||
const wsUrl = await fetchWsUrl()
|
if (now - this.lastUpdate > 0.05) {
|
||||||
const {
|
this.lastUpdate = now;
|
||||||
appkey,
|
|
||||||
token
|
// 简单的频率分析 (模拟16个频段)
|
||||||
} = extractWsParams(wsUrl)
|
const simulatedFreqData = [];
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
simulatedFreqData.push(
|
||||||
|
Math.min(1, volume * 10 + (Math.random() * 0.2 - 0.1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.port.postMessage({
|
||||||
|
type: 'analysis',
|
||||||
|
volume: volume,
|
||||||
|
frequencyData: simulatedFreqData,
|
||||||
|
isSilent: volume < this.silenceThreshold,
|
||||||
|
timestamp: now
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原始音频处理
|
||||||
|
const input = inputs[0];
|
||||||
|
if (input && input.length > 0) {
|
||||||
|
const inputChannel = input[0];
|
||||||
|
for (let i = 0; i < inputChannel.length; i++) {
|
||||||
|
this.buffer[this.index++] = Math.max(-32768, Math.min(32767, inputChannel[i] * 32767));
|
||||||
|
|
||||||
|
if (this.index >= this.samplesPerChunk) {
|
||||||
|
this.port.postMessage({
|
||||||
|
type: 'audio',
|
||||||
|
audioData: this.buffer.buffer,
|
||||||
|
timestamp: now
|
||||||
|
}, [this.buffer.buffer]);
|
||||||
|
|
||||||
|
this.buffer = new Int16Array(this.samplesPerChunk);
|
||||||
|
this.index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerProcessor('audio-processor', AudioProcessor);
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 初始化WebSocket连接
|
||||||
|
const initSocket = (wsUrl) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
websocket = new WebSocket(wsUrl)
|
socket.value = new WebSocket(wsUrl);
|
||||||
websocket.binaryType = 'arraybuffer'
|
|
||||||
|
|
||||||
websocket.onopen = () => {
|
socket.value.onopen = () => {
|
||||||
isSocketConnected.value = true
|
console.log('open')
|
||||||
|
isSocketConnected.value = true;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
// 发送 StartTranscription 消息(参考 demo.html)
|
socket.value.onerror = (error) => {
|
||||||
const startTranscriptionMessage = {
|
reject(error);
|
||||||
header: {
|
};
|
||||||
appkey: appkey, // 不影响使用,可留空或由 wsUrl 带入
|
|
||||||
namespace: 'SpeechTranscriber',
|
|
||||||
name: 'StartTranscription',
|
|
||||||
task_id: generateUUID(),
|
|
||||||
message_id: generateUUID()
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
format: 'pcm',
|
|
||||||
sample_rate: 16000,
|
|
||||||
enable_intermediate_result: true,
|
|
||||||
enable_punctuation_prediction: true,
|
|
||||||
enable_inverse_text_normalization: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
websocket.send(JSON.stringify(startTranscriptionMessage))
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
websocket.onerror = (e) => {
|
socket.value.onclose = () => {
|
||||||
isSocketConnected.value = false
|
isSocketConnected.value = false;
|
||||||
reject(e)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
websocket.onclose = () => {
|
socket.value.onmessage = handleMessage;
|
||||||
isSocketConnected.value = false
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
websocket.onmessage = (e) => {
|
const handleMessage = (values) => {
|
||||||
const msg = JSON.parse(e.data)
|
|
||||||
const name = msg?.header?.name
|
|
||||||
const payload = msg?.payload
|
|
||||||
|
|
||||||
switch (name) {
|
|
||||||
case 'TranscriptionResultChanged': {
|
|
||||||
// 中间识别文本(可选:使用 stash_result.unfixedText 更精确)
|
|
||||||
const text = payload?.unfixed_result || payload?.result || ''
|
|
||||||
lastFinalText.value = text
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'SentenceBegin': {
|
|
||||||
// 可选:开始新的一句,重置状态
|
|
||||||
// console.log('开始新的句子识别')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'SentenceEnd': {
|
|
||||||
const text = payload?.result || ''
|
|
||||||
const confidence = payload?.confidence || 0
|
|
||||||
if (text && confidence > 0.5) {
|
|
||||||
recognizedText.value += text
|
|
||||||
lastFinalText.value = ''
|
|
||||||
// console.log('识别完成:', {
|
|
||||||
// text,
|
|
||||||
// confidence
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'TranscriptionStarted': {
|
|
||||||
// console.log('识别任务已开始')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'TranscriptionCompleted': {
|
|
||||||
lastFinalText.value = ''
|
|
||||||
// console.log('识别全部完成')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'TaskFailed': {
|
|
||||||
console.error('识别失败:', msg?.header?.status_text)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
console.log('未知消息类型:', name, msg)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const startRecording = async () => {
|
|
||||||
if (isRecording.value) return
|
|
||||||
try {
|
try {
|
||||||
recognizedText.value = ''
|
const data = JSON.parse(event.data);
|
||||||
lastFinalText.value = ''
|
if (data.text) {
|
||||||
await connectWebSocket()
|
const {
|
||||||
|
asrEnd,
|
||||||
audioStream = await navigator.mediaDevices.getUserMedia({
|
text
|
||||||
audio: true
|
} = data
|
||||||
})
|
if (asrEnd === 'true') {
|
||||||
audioContext = new(window.AudioContext || window.webkitAudioContext)({
|
recognizedText.value += data.text;
|
||||||
sampleRate: 16000
|
} else {
|
||||||
})
|
lastFinalText.value = '';
|
||||||
audioInput = audioContext.createMediaStreamSource(audioStream)
|
|
||||||
scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1)
|
|
||||||
|
|
||||||
scriptProcessor.onaudioprocess = (event) => {
|
|
||||||
const input = event.inputBuffer.getChannelData(0)
|
|
||||||
const pcm = new Int16Array(input.length)
|
|
||||||
let sum = 0
|
|
||||||
for (let i = 0; i < input.length; ++i) {
|
|
||||||
const s = Math.max(-1, Math.min(1, input[i]))
|
|
||||||
pcm[i] = s * 0x7FFF
|
|
||||||
sum += s * s
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
volumeLevel.value = Math.sqrt(sum / input.length)
|
} catch (error) {
|
||||||
audioDataForDisplay.value = Array(16).fill(volumeLevel.value)
|
console.error('解析识别结果失败:', error);
|
||||||
|
|
||||||
if (websocket?.readyState === WebSocket.OPEN) {
|
|
||||||
websocket.send(pcm.buffer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audioInput.connect(scriptProcessor)
|
// 处理音频切片
|
||||||
scriptProcessor.connect(audioContext.destination)
|
const processAudioChunk = (isSilent) => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
isRecording.value = true
|
if (!isSilent) {
|
||||||
recordingDuration.value = 0
|
// 检测到声音
|
||||||
durationTimer = setInterval(() => recordingDuration.value++, 1000)
|
lastSoundTime.value = now;
|
||||||
} catch (err) {
|
|
||||||
console.error('启动失败:', err)
|
if (silenceStartTime.value > 0) {
|
||||||
cleanup()
|
// 从静音恢复到有声音
|
||||||
|
silenceStartTime.value = 0;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 静音状态
|
||||||
|
if (silenceStartTime.value === 0) {
|
||||||
|
silenceStartTime.value = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopRecording = () => {
|
// 检查是否达到静音切片条件
|
||||||
if (!isRecording.value || isStopping.value) return
|
if (now - silenceStartTime.value >= SILENCE_DURATION &&
|
||||||
isStopping.value = true
|
now - currentChunkStartTime.value >= MIN_SOUND_DURATION) {
|
||||||
|
sendCurrentChunk();
|
||||||
if (websocket?.readyState === WebSocket.OPEN) {
|
|
||||||
websocket.send(JSON.stringify({
|
|
||||||
header: {
|
|
||||||
namespace: 'SpeechTranscriber',
|
|
||||||
name: 'StopTranscription',
|
|
||||||
message_id: generateUUID()
|
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
websocket.close()
|
};
|
||||||
|
|
||||||
|
// 发送当前音频块
|
||||||
|
const sendCurrentChunk = () => {
|
||||||
|
if (audioChunks.value.length === 0 || !socket.value || socket.value.readyState !== WebSocket.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 合并所有块
|
||||||
|
const totalBytes = audioChunks.value.reduce((total, chunk) => total + chunk.byteLength, 0);
|
||||||
|
const combined = new Int16Array(totalBytes / 2);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
audioChunks.value.forEach(chunk => {
|
||||||
|
const samples = new Int16Array(chunk);
|
||||||
|
combined.set(samples, offset);
|
||||||
|
offset += samples.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送合并后的数据
|
||||||
|
socket.value.send(combined.buffer);
|
||||||
|
audioChunks.value = [];
|
||||||
|
|
||||||
|
// 记录新块的开始时间
|
||||||
|
currentChunkStartTime.value = Date.now();
|
||||||
|
silenceStartTime.value = 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送音频数据时出错:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始录音
|
||||||
|
const startRecording = async () => {
|
||||||
|
if (isRecording.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 重置状态
|
||||||
|
recognizedText.value = '';
|
||||||
|
lastFinalText.value = '';
|
||||||
|
// 重置状态
|
||||||
|
recordingDuration.value = 0;
|
||||||
|
audioChunks.value = [];
|
||||||
|
lastSoundTime.value = 0;
|
||||||
|
currentChunkStartTime.value = Date.now();
|
||||||
|
silenceStartTime.value = 0;
|
||||||
|
|
||||||
|
// 初始化WebSocket
|
||||||
|
await initSocket(wsUrl);
|
||||||
|
|
||||||
|
// 获取音频流
|
||||||
|
mediaStream.value = await navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: {
|
||||||
|
sampleRate: SAMPLE_RATE,
|
||||||
|
channelCount: 1,
|
||||||
|
echoCancellation: true,
|
||||||
|
noiseSuppression: true,
|
||||||
|
autoGainControl: false
|
||||||
|
},
|
||||||
|
video: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建音频上下文
|
||||||
|
audioContext.value = new(window.AudioContext || window.webkitAudioContext)({
|
||||||
|
sampleRate: SAMPLE_RATE
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注册AudioWorklet
|
||||||
|
const blob = new Blob([workletProcessorCode], {
|
||||||
|
type: 'application/javascript'
|
||||||
|
});
|
||||||
|
const workletUrl = URL.createObjectURL(blob);
|
||||||
|
await audioContext.value.audioWorklet.addModule(workletUrl);
|
||||||
|
URL.revokeObjectURL(workletUrl);
|
||||||
|
|
||||||
|
// 创建AudioWorkletNode
|
||||||
|
workletNode.value = new AudioWorkletNode(audioContext.value, 'audio-processor', {
|
||||||
|
processorOptions: {
|
||||||
|
silenceThreshold: SILENCE_THRESHOLD,
|
||||||
|
sampleRate: SAMPLE_RATE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理音频数据
|
||||||
|
workletNode.value.port.onmessage = (e) => {
|
||||||
|
if (e.data.type === 'audio') {
|
||||||
|
audioChunks.value.push(e.data.audioData);
|
||||||
|
} else if (e.data.type === 'analysis') {
|
||||||
|
audioDataForDisplay.value = e.data.frequencyData;
|
||||||
|
volumeLevel.value = e.data.volume;
|
||||||
|
processAudioChunk(e.data.isSilent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 连接音频节点
|
||||||
|
const source = audioContext.value.createMediaStreamSource(mediaStream.value);
|
||||||
|
source.connect(workletNode.value);
|
||||||
|
workletNode.value.connect(audioContext.value.destination);
|
||||||
|
|
||||||
|
isRecording.value = true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启动录音失败:', error);
|
||||||
|
cleanup();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 停止录音
|
||||||
|
const stopRecording = async () => {
|
||||||
|
if (!isRecording.value || isStopping.value) return;
|
||||||
|
|
||||||
|
isStopping.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 发送最后一个音频块(无论是否静音)
|
||||||
|
sendCurrentChunk();
|
||||||
|
|
||||||
|
// 发送结束标记
|
||||||
|
if (socket.value?.readyState === WebSocket.OPEN) {
|
||||||
|
socket.value.send(JSON.stringify({
|
||||||
|
action: 'end',
|
||||||
|
duration: recordingDuration.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
await new Promise(resolve => {
|
||||||
|
if (socket.value.bufferedAmount === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (socket.value.bufferedAmount === 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup()
|
cleanup();
|
||||||
isStopping.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelRecording = () => {
|
} catch (error) {
|
||||||
if (!isRecording.value || isStopping.value) return
|
console.error('停止录音时出错:', error);
|
||||||
isStopping.value = true
|
throw error;
|
||||||
websocket?.close()
|
} finally {
|
||||||
cleanup()
|
isStopping.value = false;
|
||||||
isStopping.value = false
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
clearInterval(durationTimer)
|
if (mediaStream.value) {
|
||||||
|
mediaStream.value.getTracks().forEach(track => track.stop());
|
||||||
scriptProcessor?.disconnect()
|
mediaStream.value = null;
|
||||||
audioInput?.disconnect()
|
|
||||||
audioStream?.getTracks().forEach(track => track.stop())
|
|
||||||
audioContext?.close()
|
|
||||||
|
|
||||||
audioStream = null
|
|
||||||
audioContext = null
|
|
||||||
audioInput = null
|
|
||||||
scriptProcessor = null
|
|
||||||
websocket = null
|
|
||||||
|
|
||||||
isRecording.value = false
|
|
||||||
isSocketConnected.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (workletNode.value) {
|
||||||
|
workletNode.value.disconnect();
|
||||||
|
workletNode.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioContext.value && audioContext.value.state !== 'closed') {
|
||||||
|
audioContext.value.close();
|
||||||
|
audioContext.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioChunks.value = [];
|
||||||
|
isRecording.value = false;
|
||||||
|
isSocketConnected.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 取消录音
|
||||||
|
const cancelRecording = async () => {
|
||||||
|
if (!isRecording.value || isStopping.value) return;
|
||||||
|
isStopping.value = true;
|
||||||
|
try {
|
||||||
|
if (socket.value?.readyState === WebSocket.OPEN) {
|
||||||
|
console.log('发送结束标记...');
|
||||||
|
socket.value.send(JSON.stringify({
|
||||||
|
action: 'cancel'
|
||||||
|
}));
|
||||||
|
socket.value.close();
|
||||||
|
}
|
||||||
|
cleanup()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消录音时出错:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
isStopping.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (isRecording.value) stopRecording()
|
if (isRecording.value) {
|
||||||
})
|
stopRecording();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isRecording,
|
isRecording,
|
||||||
@@ -249,10 +390,10 @@ export function useAudioRecorder() {
|
|||||||
recordingDuration,
|
recordingDuration,
|
||||||
audioDataForDisplay,
|
audioDataForDisplay,
|
||||||
volumeLevel,
|
volumeLevel,
|
||||||
recognizedText,
|
|
||||||
lastFinalText,
|
|
||||||
startRecording,
|
startRecording,
|
||||||
stopRecording,
|
stopRecording,
|
||||||
|
recognizedText,
|
||||||
|
lastFinalText,
|
||||||
cancelRecording
|
cancelRecording
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
136
hook/useSpeechReader.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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');
|
||||||
|
}
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
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, // 导出支持状态
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -23,10 +23,8 @@
|
|||||||
var vConsole = new window.VConsole();
|
var vConsole = new window.VConsole();
|
||||||
vConsole.destroy();
|
vConsole.destroy();
|
||||||
</script> -->
|
</script> -->
|
||||||
<!-- 爱山东jssdk -->
|
|
||||||
<script type="text/javascript" src="https://isdapp.shandong.gov.cn/jmopen/jssdk/index.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<!-- <body> -->
|
<body>
|
||||||
<div id="app"><!--app-html--></div>
|
<div id="app"><!--app-html--></div>
|
||||||
<script type="module" src="/main.js"></script>
|
<script type="module" src="/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
BIN
lib/.DS_Store
vendored
Normal file
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLayout title="" :use-scroll-view="false">
|
<AppLayout title="" :use-scroll-view="false">
|
||||||
<template #headerleft>
|
<template #headerleft>
|
||||||
<view class="btnback">
|
<view class="btn">
|
||||||
<image src="@/static/icon/back.png" @click="navBack"></image>
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -143,16 +143,12 @@ function expand() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.btnback{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
}
|
|
||||||
.btn {
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 52rpx;
|
width: 60rpx;
|
||||||
height: 52rpx;
|
height: 60rpx;
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<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>
|
||||||
<view class="btnback">
|
<view class="btn">
|
||||||
<image src="@/static/icon/back.png" @click="navBack"></image>
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -152,16 +152,12 @@ function getPreviousDay(dateStr) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.btnback{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
}
|
|
||||||
.btn {
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 52rpx;
|
width: 60rpx;
|
||||||
height: 52rpx;
|
height: 60rpx;
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<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>
|
||||||
<view class="btnback">
|
<view class="btn">
|
||||||
<image src="@/static/icon/back.png" @click="navBack"></image>
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -101,16 +101,12 @@ function getDataList(type = 'add') {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.btnback{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
}
|
|
||||||
.btn {
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 52rpx;
|
width: 60rpx;
|
||||||
height: 52rpx;
|
height: 60rpx;
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLayout title="" :use-scroll-view="false">
|
<AppLayout title="" :use-scroll-view="false">
|
||||||
<template #headerleft>
|
<template #headerleft>
|
||||||
<view class="btnback">
|
<view class="btn">
|
||||||
<image src="@/static/icon/back.png" @click="navBack"></image>
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -219,16 +219,12 @@ function getHoursBetween(startTimeStr, endTimeStr) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.btnback{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
}
|
|
||||||
.btn {
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 52rpx;
|
width: 60rpx;
|
||||||
height: 52rpx;
|
height: 60rpx;
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -145,17 +145,10 @@ function changeArea() {
|
|||||||
function changeJobs() {
|
function changeJobs() {
|
||||||
selectJobsModel.value?.open({
|
selectJobsModel.value?.open({
|
||||||
title: '添加岗位',
|
title: '添加岗位',
|
||||||
defaultId: fromValue.jobTitleId,
|
|
||||||
success: (ids, labels) => {
|
success: (ids, labels) => {
|
||||||
console.log(ids, labels);
|
|
||||||
fromValue.jobTitleId = ids;
|
fromValue.jobTitleId = ids;
|
||||||
state.jobsText = labels.split(',');
|
state.jobsText = labels.split(',');
|
||||||
},
|
},
|
||||||
cancel: (ids, labels) => {
|
|
||||||
console.log(ids, labels);
|
|
||||||
// fromValue.jobTitleId = ids;
|
|
||||||
// state.jobsText = labels.split(',');
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,9 +262,10 @@ function getFormCompletionPercent(form) {
|
|||||||
display: flex
|
display: flex
|
||||||
flex-wrap: wrap
|
flex-wrap: wrap
|
||||||
.nx-item
|
.nx-item
|
||||||
margin: 12rpx 12rpx 0 0;
|
padding: 20rpx 28rpx
|
||||||
padding: 12rpx 25rpx;
|
width: fit-content
|
||||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||||
border: 2rpx solid #E8EAEE;
|
border: 2rpx solid #E8EAEE;
|
||||||
|
margin-right: 24rpx
|
||||||
|
margin-top: 24rpx
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="collection-content">
|
|
||||||
<renderJobs :list="list" :longitude="longitudeVal" :latitude="latitudeVal"></renderJobs>
|
|
||||||
<loadmore ref="loadmoreRef"></loadmore>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import dictLabel from '@/components/dict-Label/dict-Label.vue';
|
|
||||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
|
||||||
import { onLoad, onShow, onReachBottom } from '@dcloudio/uni-app';
|
|
||||||
import useUserStore from '@/stores/useUserStore';
|
|
||||||
const { $api, navTo, navBack, vacanciesTo } = inject('globalFunction');
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import useLocationStore from '@/stores/useLocationStore';
|
|
||||||
import { usePagination } from '@/hook/usePagination';
|
|
||||||
import { jobMoreMap } from '@/utils/markdownParser';
|
|
||||||
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
|
|
||||||
const loadmoreRef = ref(null);
|
|
||||||
|
|
||||||
// 响应式搜索条件(可以被修改)
|
|
||||||
const searchParams = ref({});
|
|
||||||
const pageSize = ref(10);
|
|
||||||
const { list, loading, refresh, loadMore } = usePagination(
|
|
||||||
(params) => $api.createRequest('/app/job/list', params, 'GET', true),
|
|
||||||
null, // 转换函数
|
|
||||||
{
|
|
||||||
pageSize: pageSize,
|
|
||||||
search: searchParams,
|
|
||||||
autoWatchSearch: true,
|
|
||||||
onBeforeRequest: () => {
|
|
||||||
loadmoreRef.value?.change('loading');
|
|
||||||
},
|
|
||||||
onAfterRequest: () => {
|
|
||||||
loadmoreRef.value?.change('more');
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onLoad((options) => {
|
|
||||||
let params = jobMoreMap.get(options.jobId);
|
|
||||||
if (params) {
|
|
||||||
uni.setStorageSync('jobMoreMap', params);
|
|
||||||
} else {
|
|
||||||
params = uni.getStorageSync('jobMoreMap');
|
|
||||||
}
|
|
||||||
const objs = removeNullProperties(params);
|
|
||||||
searchParams.value = objs;
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
function removeNullProperties(obj) {
|
|
||||||
for (const key in obj) {
|
|
||||||
if (obj.hasOwnProperty(key) && obj[key] === null) {
|
|
||||||
delete obj[key]; // 删除值为 null 的属性
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
onReachBottom(() => {
|
|
||||||
loadMore();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.collection-content{
|
|
||||||
padding: 1rpx 28rpx 20rpx 28rpx;
|
|
||||||
background: #F4F4F4;
|
|
||||||
height: 100%
|
|
||||||
min-height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLayout title="我的简历" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
|
|
||||||
<template #headerleft>
|
|
||||||
<view class="btn">
|
|
||||||
<image src="@/static/icon/back-white.png" @click="navBack"></image>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<view class="mys-container">
|
<view class="mys-container">
|
||||||
<!-- 个人信息 -->
|
<!-- 个人信息 -->
|
||||||
<view class="card-top" style="margin-top: 12rpx; padding: 0; background: none">
|
|
||||||
<view class="mys-tops btn-feel">
|
<view class="mys-tops btn-feel">
|
||||||
<view class="tops-left">
|
<view class="tops-left">
|
||||||
<view class="name">
|
<view class="name">
|
||||||
<text>{{ userInfo.name || "编辑用户名" }}</text>
|
<text>{{ userInfo.name || '编辑用户名' }}</text>
|
||||||
<view class="edit-icon mar_le10">
|
<view class="edit-icon mar_le10">
|
||||||
<image class="button-click" src="@/static/icon/edit1.png" @click="navTo('/packageA/pages/personalInfo/personalInfo')"></image>
|
<image
|
||||||
|
class="button-click"
|
||||||
|
src="@/static/icon/edit1.png"
|
||||||
|
@click="navTo('/packageA/pages/personalInfo/personalInfo')"
|
||||||
|
></image>
|
||||||
</view>
|
</view>
|
||||||
</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 class="mar_ri10" dictType="affiliation" :value="userInfo.politicalAffiliation"></dict-Label>
|
<dict-Label
|
||||||
|
class="mar_ri10"
|
||||||
|
dictType="affiliation"
|
||||||
|
:value="userInfo.politicalAffiliation"
|
||||||
|
></dict-Label>
|
||||||
</view>
|
</view>
|
||||||
<view class="subName">{{ userInfo.phone }}</view>
|
<view class="subName">{{ userInfo.phone }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -36,14 +37,16 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 求职期望 -->
|
<!-- 求职期望 -->
|
||||||
<view class="mys-line">
|
<view class="mys-line"></view>
|
||||||
<view class="line"></view>
|
|
||||||
</view>
|
|
||||||
<view class="mys-info">
|
<view class="mys-info">
|
||||||
<view class="mys-h4">
|
<view class="mys-h4">
|
||||||
<text>求职期望</text>
|
<text>求职期望</text>
|
||||||
<view class="mys-edit-icon">
|
<view class="mys-edit-icon">
|
||||||
<image class="button-click" src="@/static/icon/edit1.png" @click="navTo('/packageA/pages/jobExpect/jobExpect')"></image>
|
<image
|
||||||
|
class="button-click"
|
||||||
|
src="@/static/icon/edit1.png"
|
||||||
|
@click="navTo('/packageA/pages/jobExpect/jobExpect')"
|
||||||
|
></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="mys-text">
|
<view class="mys-text">
|
||||||
@@ -62,99 +65,35 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="card-top" style="margin-top: 24rpx">
|
|
||||||
<view class="mys-info" style="padding: 0">
|
|
||||||
<view class="mys-h4">
|
|
||||||
<text>工作经历</text>
|
|
||||||
<view class="mys-edit-icon">
|
|
||||||
<image class="button-click" src="@/static/icon/edit1.png" @click="navTo('/packageA/pages/jobExpect/jobExpect')"></image>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="exp-item" v-for="item in userInfo.workExp" :key="item.id">
|
|
||||||
<view class="fl_box fl_justbet mar_top10">
|
|
||||||
<text class="fs_16">{{ item.company }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="mys-text fl_box fl_justbet">
|
|
||||||
<text class="color_333333 fs_14">{{ item.position }}</text>
|
|
||||||
<text class="datetext">{{ item.startTime }}--{{ item.endTime || "至今" }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="mys-text">
|
|
||||||
<text>{{ item.duty }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<template #footer>
|
|
||||||
<view class="footer-container">
|
|
||||||
<view class="footer-button">上传简历</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</AppLayout>
|
|
||||||
</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 } = 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 } = storeToRefs(useUserStore());
|
||||||
const { getUserResume } = useUserStore();
|
const { getUserResume } = useUserStore();
|
||||||
const { getDictData, oneDictData } = useDictStore();
|
const { getDictData, oneDictData } = useDictStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<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{
|
image{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%
|
height: 100%
|
||||||
}
|
}
|
||||||
.mys-container{
|
.mys-container{
|
||||||
padding-bottom:20rpx;
|
|
||||||
.card-top{
|
|
||||||
background: #FFFFFF;
|
|
||||||
margin: 0 28rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
padding: 24rpx
|
|
||||||
}
|
|
||||||
.mys-tops{
|
.mys-tops{
|
||||||
display: flex
|
display: flex
|
||||||
justify-content: space-between
|
justify-content: space-between
|
||||||
padding: 38rpx 44rpx
|
padding: 52rpx 48rpx
|
||||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8) , rgba(255, 255, 255, 1));
|
|
||||||
border-radius: 8rpx 8rpx 0 0 ;
|
|
||||||
.tops-left{
|
.tops-left{
|
||||||
.name{
|
.name{
|
||||||
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;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 36rpx;
|
font-size: 44rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
@@ -169,8 +108,8 @@ image{
|
|||||||
.subName{
|
.subName{
|
||||||
margin-top: 12rpx
|
margin-top: 12rpx
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 26rpx;
|
font-size: 32rpx;
|
||||||
color: #999999;
|
color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -193,20 +132,18 @@ image{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.mys-line{
|
.mys-line{
|
||||||
background: #ffffff;
|
margin: 0 28rpx
|
||||||
padding: 0 24rpx
|
height: 0rpx;
|
||||||
.line{
|
border-radius: 0rpx 0rpx 0rpx 0rpx;
|
||||||
border: 2rpx dashed #eeeeee;
|
border: 2rpx dashed #000000;
|
||||||
}
|
opacity: 0.16;
|
||||||
}
|
}
|
||||||
.mys-info{
|
.mys-info{
|
||||||
padding: 28rpx
|
padding: 28rpx
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 0 0 8rpx 8rpx ;
|
|
||||||
.mys-h4{
|
.mys-h4{
|
||||||
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;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
font-size: 30rpx;
|
font-size: 32rpx;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
margin-bottom: 8rpx
|
margin-bottom: 8rpx
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -218,15 +155,10 @@ image{
|
|||||||
height: 40rpx
|
height: 40rpx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.datetext{
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.mys-text{
|
.mys-text{
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
color: #999999;
|
color: #333333;
|
||||||
margin-top: 16rpx
|
margin-top: 16rpx
|
||||||
}
|
}
|
||||||
.mys-list{
|
.mys-list{
|
||||||
@@ -234,24 +166,17 @@ image{
|
|||||||
align-items: center
|
align-items: center
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
.cards{
|
.cards{
|
||||||
margin: 12rpx 12rpx 0 0
|
margin: 28rpx 28rpx 0 0
|
||||||
padding: 12rpx 25rpx;
|
height: 80rpx;
|
||||||
|
padding: 0 38rpx;
|
||||||
width: fit-content
|
width: fit-content
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
justify-content: center
|
justify-content: center
|
||||||
border-radius:10rpx;
|
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||||
border: 2rpx solid #E8EAEE;
|
border: 2rpx solid #E8EAEE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.exp-item{
|
|
||||||
padding-bottom: 28rpx;
|
|
||||||
border-bottom: 2rpx dashed #EEEEEE;
|
|
||||||
|
|
||||||
}
|
|
||||||
.exp-item:nth-last-child(1){
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLayout title="个人信息" :sub-title="`完成度${percent}`" border back-gorund-color="#ffffff" :show-bg-image="false">
|
<AppLayout
|
||||||
|
title="个人信息"
|
||||||
|
:sub-title="`完成度${percent}`"
|
||||||
|
border
|
||||||
|
back-gorund-color="#ffffff"
|
||||||
|
:show-bg-image="false"
|
||||||
|
>
|
||||||
<template #headerleft>
|
<template #headerleft>
|
||||||
<view class="btn mar_le20 button-click" @click="navBack">取消</view>
|
<view class="btn mar_le20 button-click" @click="navBack">取消</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -7,14 +13,6 @@
|
|||||||
<view class="btn mar_ri20 button-click" @click="confirm">确认</view>
|
<view class="btn mar_ri20 button-click" @click="confirm">确认</view>
|
||||||
</template>
|
</template>
|
||||||
<view class="content">
|
<view class="content">
|
||||||
<view class="content-avatar">
|
|
||||||
<view class="avatar-title">编辑头像</view>
|
|
||||||
<view @click="selectAvatar">
|
|
||||||
<image class="avatar" v-if="fromValue.avatar" :src="fromValue.avatar" />
|
|
||||||
<image class="avatar" v-else-if="fromValue.sex == '0'" src="@/static/icon/boy.png" />
|
|
||||||
<image class="avatar" v-else="fromValue.sex == '1'" src="@/static/icon/girl.png" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="content-input">
|
<view class="content-input">
|
||||||
<view class="input-titile">姓名</view>
|
<view class="input-titile">姓名</view>
|
||||||
<input class="input-con" v-model="fromValue.name" placeholder="请输入您的姓名" />
|
<input class="input-con" v-model="fromValue.name" placeholder="请输入您的姓名" />
|
||||||
@@ -22,13 +20,22 @@
|
|||||||
<view class="content-sex">
|
<view class="content-sex">
|
||||||
<view class="sex-titile">性别</view>
|
<view class="sex-titile">性别</view>
|
||||||
<view class="sext-ri">
|
<view class="sext-ri">
|
||||||
<view class="sext-box" :class="{ 'sext-boxactive': fromValue.sex === 0 }" @click="changeSex(0)"> 男 </view>
|
<view class="sext-box" :class="{ 'sext-boxactive': fromValue.sex === 0 }" @click="changeSex(0)">
|
||||||
<view class="sext-box" :class="{ 'sext-boxactive': fromValue.sex === 1 }" @click="changeSex(1)"> 女 </view>
|
男
|
||||||
|
</view>
|
||||||
|
<view class="sext-box" :class="{ 'sext-boxactive': fromValue.sex === 1 }" @click="changeSex(1)">
|
||||||
|
女
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="content-input" @click="changeDateBirt">
|
<view class="content-input" @click="changeDateBirt">
|
||||||
<view class="input-titile">出生年月</view>
|
<view class="input-titile">出生年月</view>
|
||||||
<input class="input-con triangle" v-model="fromValue.birthDate" disabled placeholder="请选择您的出生年月" />
|
<input
|
||||||
|
class="input-con triangle"
|
||||||
|
v-model="fromValue.birthDate"
|
||||||
|
disabled
|
||||||
|
placeholder="请选择您的出生年月"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view class="content-input" @click="changeEducation">
|
<view class="content-input" @click="changeEducation">
|
||||||
<view class="input-titile">学历</view>
|
<view class="input-titile">学历</view>
|
||||||
@@ -36,7 +43,12 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="content-input" @click="changePoliticalAffiliation">
|
<view class="content-input" @click="changePoliticalAffiliation">
|
||||||
<view class="input-titile">政治面貌</view>
|
<view class="input-titile">政治面貌</view>
|
||||||
<input class="input-con triangle" v-model="state.politicalAffiliationText" disabled placeholder="请选择您的政治面貌" />
|
<input
|
||||||
|
class="input-con triangle"
|
||||||
|
v-model="state.politicalAffiliationText"
|
||||||
|
disabled
|
||||||
|
placeholder="请选择您的政治面貌"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view class="content-input">
|
<view class="content-input">
|
||||||
<view class="input-titile">手机号码</view>
|
<view class="input-titile">手机号码</view>
|
||||||
@@ -47,29 +59,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
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';
|
||||||
const { $api, navTo, navBack, checkingPhoneRegExp } = inject("globalFunction");
|
const { $api, navTo, navBack, checkingPhoneRegExp } = 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';
|
||||||
const { userInfo } = storeToRefs(useUserStore());
|
const { userInfo } = storeToRefs(useUserStore());
|
||||||
const { getUserResume } = useUserStore();
|
const { getUserResume } = useUserStore();
|
||||||
const { dictLabel, oneDictData } = useDictStore();
|
const { dictLabel, oneDictData } = useDictStore();
|
||||||
const openSelectPopup = inject("openSelectPopup");
|
const openSelectPopup = inject('openSelectPopup');
|
||||||
|
|
||||||
const percent = ref("0%");
|
const percent = ref('0%');
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
educationText: "",
|
educationText: '',
|
||||||
politicalAffiliationText: "",
|
politicalAffiliationText: '',
|
||||||
});
|
});
|
||||||
const fromValue = reactive({
|
const fromValue = reactive({
|
||||||
name: "",
|
name: '',
|
||||||
sex: 0,
|
sex: 0,
|
||||||
birthDate: "",
|
birthDate: '',
|
||||||
education: "",
|
education: '',
|
||||||
politicalAffiliation: "",
|
politicalAffiliation: '',
|
||||||
avatar: "",
|
|
||||||
});
|
});
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
initLoad();
|
initLoad();
|
||||||
@@ -90,35 +101,34 @@ function initLoad() {
|
|||||||
fromValue.birthDate = userInfo.value.birthDate;
|
fromValue.birthDate = userInfo.value.birthDate;
|
||||||
fromValue.education = userInfo.value.education;
|
fromValue.education = userInfo.value.education;
|
||||||
fromValue.politicalAffiliation = userInfo.value.politicalAffiliation;
|
fromValue.politicalAffiliation = userInfo.value.politicalAffiliation;
|
||||||
fromValue.avatar = userInfo.value.avatar;
|
|
||||||
// 回显
|
// 回显
|
||||||
state.educationText = dictLabel("education", userInfo.value.education);
|
state.educationText = dictLabel('education', userInfo.value.education);
|
||||||
state.politicalAffiliationText = dictLabel("affiliation", userInfo.value.politicalAffiliation);
|
state.politicalAffiliationText = dictLabel('affiliation', userInfo.value.politicalAffiliation);
|
||||||
const result = getFormCompletionPercent(fromValue);
|
const result = getFormCompletionPercent(fromValue);
|
||||||
percent.value = result;
|
percent.value = result;
|
||||||
}
|
}
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
if (!fromValue.name) {
|
if (!fromValue.name) {
|
||||||
return $api.msg("请输入姓名");
|
return $api.msg('请输入姓名');
|
||||||
}
|
}
|
||||||
if (!fromValue.birthDate) {
|
if (!fromValue.birthDate) {
|
||||||
return $api.msg("请选择出生年月");
|
return $api.msg('请选择出生年月');
|
||||||
}
|
}
|
||||||
if (!fromValue.education) {
|
if (!fromValue.education) {
|
||||||
return $api.msg("请选择学历");
|
return $api.msg('请选择学历');
|
||||||
}
|
}
|
||||||
if (!fromValue.politicalAffiliation) {
|
if (!fromValue.politicalAffiliation) {
|
||||||
return $api.msg("请选择政治面貌");
|
return $api.msg('请选择政治面貌');
|
||||||
|
}
|
||||||
|
if (!checkingPhoneRegExp(fromValue.phone)) {
|
||||||
|
return $api.msg('请输入正确手机号');
|
||||||
}
|
}
|
||||||
// if (!checkingPhoneRegExp(fromValue.phone)) {
|
|
||||||
// return $api.msg('请输入正确手机号');
|
|
||||||
// }
|
|
||||||
const params = {
|
const params = {
|
||||||
...fromValue,
|
...fromValue,
|
||||||
age: calculateAge(fromValue.birthDate),
|
age: calculateAge(fromValue.birthDate),
|
||||||
};
|
};
|
||||||
$api.createRequest("/app/user/resume", params, "post").then((resData) => {
|
$api.createRequest('/app/user/resume', params, 'post').then((resData) => {
|
||||||
$api.msg("完成");
|
$api.msg('完成');
|
||||||
state.disbleDate = true;
|
state.disbleDate = true;
|
||||||
getUserResume().then(() => {
|
getUserResume().then(() => {
|
||||||
navBack();
|
navBack();
|
||||||
@@ -130,7 +140,7 @@ const changeDateBirt = () => {
|
|||||||
const datearray = generateDatePickerArrays();
|
const datearray = generateDatePickerArrays();
|
||||||
const defaultIndex = getDatePickerIndexes(fromValue.birthDate);
|
const defaultIndex = getDatePickerIndexes(fromValue.birthDate);
|
||||||
openSelectPopup({
|
openSelectPopup({
|
||||||
title: "年龄段",
|
title: '年龄段',
|
||||||
maskClick: true,
|
maskClick: true,
|
||||||
data: datearray,
|
data: datearray,
|
||||||
defaultIndex,
|
defaultIndex,
|
||||||
@@ -140,16 +150,16 @@ const changeDateBirt = () => {
|
|||||||
if (isValidDate(dateStr)) {
|
if (isValidDate(dateStr)) {
|
||||||
fromValue.birthDate = dateStr;
|
fromValue.birthDate = dateStr;
|
||||||
} else {
|
} else {
|
||||||
$api.msg("没有这一天");
|
$api.msg('没有这一天');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const changeEducation = () => {
|
const changeEducation = () => {
|
||||||
openSelectPopup({
|
openSelectPopup({
|
||||||
title: "学历",
|
title: '学历',
|
||||||
maskClick: true,
|
maskClick: true,
|
||||||
data: [oneDictData("education")],
|
data: [oneDictData('education')],
|
||||||
success: (_, [value]) => {
|
success: (_, [value]) => {
|
||||||
fromValue.education = value.value;
|
fromValue.education = value.value;
|
||||||
state.educationText = value.label;
|
state.educationText = value.label;
|
||||||
@@ -162,9 +172,9 @@ const changeSex = (sex) => {
|
|||||||
|
|
||||||
const changePoliticalAffiliation = () => {
|
const changePoliticalAffiliation = () => {
|
||||||
openSelectPopup({
|
openSelectPopup({
|
||||||
title: "政治面貌",
|
title: '政治面貌',
|
||||||
maskClick: true,
|
maskClick: true,
|
||||||
data: [oneDictData("affiliation")],
|
data: [oneDictData('affiliation')],
|
||||||
success: (_, [value]) => {
|
success: (_, [value]) => {
|
||||||
fromValue.politicalAffiliation = value.value;
|
fromValue.politicalAffiliation = value.value;
|
||||||
state.politicalAffiliationText = value.label;
|
state.politicalAffiliationText = value.label;
|
||||||
@@ -181,17 +191,17 @@ function generateDatePickerArrays(startYear = 1975, endYear = new Date().getFull
|
|||||||
years.push(y.toString());
|
years.push(y.toString());
|
||||||
}
|
}
|
||||||
for (let m = 1; m <= 12; m++) {
|
for (let m = 1; m <= 12; m++) {
|
||||||
months.push(m.toString().padStart(2, "0"));
|
months.push(m.toString().padStart(2, '0'));
|
||||||
}
|
}
|
||||||
for (let d = 1; d <= 31; d++) {
|
for (let d = 1; d <= 31; d++) {
|
||||||
days.push(d.toString().padStart(2, "0"));
|
days.push(d.toString().padStart(2, '0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [years, months, days];
|
return [years, months, days];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidDate(dateString) {
|
function isValidDate(dateString) {
|
||||||
const [year, month, day] = dateString.split("-").map(Number);
|
const [year, month, day] = dateString.split('-').map(Number);
|
||||||
|
|
||||||
const date = new Date(year, month - 1, day); // 月份从0开始
|
const date = new Date(year, month - 1, day); // 月份从0开始
|
||||||
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
||||||
@@ -218,22 +228,22 @@ function getFormCompletionPercent(form) {
|
|||||||
|
|
||||||
for (const key in form) {
|
for (const key in form) {
|
||||||
const value = form[key];
|
const value = form[key];
|
||||||
if (value !== "" && value !== null && value !== undefined) {
|
if (value !== '' && value !== null && value !== undefined) {
|
||||||
if (typeof value === "number") {
|
if (typeof value === 'number') {
|
||||||
filled += 1;
|
filled += 1;
|
||||||
} else if (typeof value === "string" && value.trim() !== "") {
|
} else if (typeof value === 'string' && value.trim() !== '') {
|
||||||
filled += 1;
|
filled += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total === 0) return "0%";
|
if (total === 0) return '0%';
|
||||||
const percent = (filled / total) * 100;
|
const percent = (filled / total) * 100;
|
||||||
return percent.toFixed(0) + "%"; // 取整,不要小数点
|
return percent.toFixed(0) + '%'; // 取整,不要小数点
|
||||||
}
|
}
|
||||||
// 主函数
|
// 主函数
|
||||||
function getDatePickerIndexes(dateStr) {
|
function getDatePickerIndexes(dateStr) {
|
||||||
const [year, month, day] = dateStr.split("-");
|
const [year, month, day] = dateStr.split('-');
|
||||||
|
|
||||||
const [years, months, days] = generateDatePickerArrays();
|
const [years, months, days] = generateDatePickerArrays();
|
||||||
|
|
||||||
@@ -243,19 +253,6 @@ function getDatePickerIndexes(dateStr) {
|
|||||||
|
|
||||||
return [yearIndex, monthIndex, dayIndex];
|
return [yearIndex, monthIndex, dayIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAvatar() {
|
|
||||||
uni.chooseImage({
|
|
||||||
sizeType: ["original", "compressed"],
|
|
||||||
sourceType: ["album", "camera"],
|
|
||||||
count: 1,
|
|
||||||
success: ({ tempFilePaths, tempFiles }) => {
|
|
||||||
console.log(`选择的图片:${tempFilePaths}`);
|
|
||||||
console.warn("没有做后续上传逻辑!!!!!!!");
|
|
||||||
},
|
|
||||||
fail: (error) => {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@@ -270,22 +267,6 @@ function selectAvatar() {
|
|||||||
height: calc(100% - 120rpx)
|
height: calc(100% - 120rpx)
|
||||||
|
|
||||||
}
|
}
|
||||||
.content-avatar{
|
|
||||||
margin-bottom: 52rpx;
|
|
||||||
padding-bottom: 28rpx
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 2rpx solid #EBEBEB
|
|
||||||
.avatar-title{
|
|
||||||
font-size: 30rpx;
|
|
||||||
color #333;
|
|
||||||
}
|
|
||||||
.avatar{
|
|
||||||
width:110rpx;
|
|
||||||
height: 110rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content-input
|
.content-input
|
||||||
margin-bottom: 52rpx
|
margin-bottom: 52rpx
|
||||||
.input-titile
|
.input-titile
|
||||||
@@ -321,12 +302,12 @@ function selectAvatar() {
|
|||||||
background: #697279;
|
background: #697279;
|
||||||
transform: rotate(45deg)
|
transform: rotate(45deg)
|
||||||
.content-sex
|
.content-sex
|
||||||
|
height: 110rpx;
|
||||||
display: flex
|
display: flex
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
border-bottom: 2rpx solid #EBEBEB
|
border-bottom: 2rpx solid #EBEBEB
|
||||||
margin-bottom: 52rpx
|
margin-bottom: 52rpx
|
||||||
padding-bottom: 28rpx
|
|
||||||
.sex-titile
|
.sex-titile
|
||||||
line-height: 80rpx;
|
line-height: 80rpx;
|
||||||
.sext-ri
|
.sext-ri
|
||||||
|
|||||||
BIN
packageA/pages/post/.DS_Store
vendored
Normal file
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLayout title="" backGorundColor="#F4F4F4">
|
<AppLayout title="" backGorundColor="#F4F4F4">
|
||||||
<template #headerleft>
|
<template #headerleft>
|
||||||
<view class="btnback">
|
<view class="btn">
|
||||||
<image src="@/static/icon/back.png" @click="navBack"></image>
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<template #headerright>
|
<template #headerright>
|
||||||
<!-- <view class="btnshare">
|
|
||||||
<image src="@/static/icon/share.png" @click="shareJob"></image>
|
|
||||||
</view> -->
|
|
||||||
<view class="btn mar_ri10">
|
<view class="btn mar_ri10">
|
||||||
<image src="@/static/icon/collect3.png" v-if="!jobInfo.isCollection" @click="jobCollection"></image>
|
<image src="@/static/icon/collect3.png" v-if="!jobInfo.isCollection" @click="jobCollection"></image>
|
||||||
<image src="@/static/icon/collect2.png" v-else @click="jobCollection"></image>
|
<image src="@/static/icon/collect2.png" v-else @click="jobCollection"></image>
|
||||||
@@ -140,11 +137,10 @@
|
|||||||
import point from '@/static/icon/point.png';
|
import point from '@/static/icon/point.png';
|
||||||
import VideoPlayer from './component/videoPlayer.vue';
|
import VideoPlayer from './component/videoPlayer.vue';
|
||||||
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
|
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
|
||||||
import { onLoad, onShow, onHide } 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 RadarMap from './component/radarMap.vue';
|
|
||||||
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
|
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
|
||||||
import config from '@/config.js';
|
import RadarMap from './component/radarMap.vue';
|
||||||
const matchingDegree = ref(['一般', '良好', '优秀', '极好']);
|
const matchingDegree = ref(['一般', '良好', '优秀', '极好']);
|
||||||
const currentStep = ref(1);
|
const currentStep = ref(1);
|
||||||
const companyCount = ref(0);
|
const companyCount = ref(0);
|
||||||
@@ -186,11 +182,9 @@ function seeExplain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDetail(jobId) {
|
function getDetail(jobId) {
|
||||||
return new Promise((reslove, reject) => {
|
|
||||||
$api.createRequest(`/app/job/${jobId}`).then((resData) => {
|
$api.createRequest(`/app/job/${jobId}`).then((resData) => {
|
||||||
const { latitude, longitude, companyName, companyId } = resData.data;
|
const { latitude, longitude, companyName, companyId } = resData.data;
|
||||||
jobInfo.value = resData.data;
|
jobInfo.value = resData.data;
|
||||||
reslove(resData.data);
|
|
||||||
getCompanyIsAJobs(companyId);
|
getCompanyIsAJobs(companyId);
|
||||||
getCompetivetuveness(jobId);
|
getCompetivetuveness(jobId);
|
||||||
if (latitude && longitude) {
|
if (latitude && longitude) {
|
||||||
@@ -213,7 +207,6 @@ function getDetail(jobId) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCompanyIsAJobs(companyId) {
|
function getCompanyIsAJobs(companyId) {
|
||||||
@@ -285,21 +278,12 @@ function getClass(index) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.btnback{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
}
|
|
||||||
.btn {
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 52rpx;
|
width: 60rpx;
|
||||||
height: 52rpx;
|
height: 60rpx;
|
||||||
}
|
|
||||||
.btnshare {
|
|
||||||
width: 48rpx;
|
|
||||||
height: 48rpx;
|
|
||||||
margin-right: 46rpx;
|
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,261 +0,0 @@
|
|||||||
<template>
|
|
||||||
<AppLayout title="电子名片" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
|
|
||||||
<template #headerleft>
|
|
||||||
<view class="btn">
|
|
||||||
<image src="@/static/icon/back-white.png" @click="navBack"></image>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<view class="mys-container">
|
|
||||||
<!-- 个人信息 -->
|
|
||||||
<view class="card-top">
|
|
||||||
<view class="info">
|
|
||||||
<view class="avatar">
|
|
||||||
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy.png"></image>
|
|
||||||
<image v-else src="@/static/icon/girl.png"></image>
|
|
||||||
</view>
|
|
||||||
<view class="info-right">
|
|
||||||
<view class="name">{{ userInfo.name || "编辑用户名" }}</view>
|
|
||||||
<view class="des">
|
|
||||||
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
|
|
||||||
<text class="mar_ri10">|</text>
|
|
||||||
<text class="mar_ri10">{{ userInfo.age }}岁</text>
|
|
||||||
<text class="mar_ri10">|</text>
|
|
||||||
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
|
|
||||||
<!-- <text class="mar_ri10">|</text>
|
|
||||||
<dict-Label class="mar_ri10" dictType="affiliation" :value="userInfo.politicalAffiliation"></dict-Label> -->
|
|
||||||
</view>
|
|
||||||
<view class="phone">
|
|
||||||
<image class="call-icon" src="@/static/icon/call.png" />
|
|
||||||
<view>{{ userInfo.phone }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="info-bottom">
|
|
||||||
<view>到岗:2025-11-02</view>
|
|
||||||
<view>地点:青岛市-<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label></view>
|
|
||||||
</view>
|
|
||||||
<view class="des-card" style="margin-top: 24rpx">
|
|
||||||
<view class="fl_box fl_justbet">
|
|
||||||
<view>求职意向岗位</view>
|
|
||||||
<view>{{ userInfo.jobIntention || "-" }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="fl_box fl_justbet">
|
|
||||||
<view>毕业学校</view>
|
|
||||||
<view>{{ userInfo.graduationSchool || "-" }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="fl_box fl_justbet">
|
|
||||||
<view>当前状态</view>
|
|
||||||
<view>在职 看工作机会</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="card">
|
|
||||||
<view class="title">
|
|
||||||
<image class="bg" src="@/static/icon/title-bg.png" />
|
|
||||||
<view class="text">个人技能</view>
|
|
||||||
</view>
|
|
||||||
<view class="skill-box">
|
|
||||||
<view class="skill-item" v-for="item in userInfo?.skillList" :key="item.id">{{ item.skill }}</view>
|
|
||||||
</view>
|
|
||||||
<view v-if="!userInfo?.skillList?.length" class="empty-box">
|
|
||||||
<image class="img" src="@/static/icon/empty.png" mode="widthFix"></image>
|
|
||||||
<view class="content">暂无个人技能</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="card">
|
|
||||||
<view class="title">
|
|
||||||
<image class="bg" src="@/static/icon/title-bg.png" />
|
|
||||||
<view class="text">关键经历</view>
|
|
||||||
</view>
|
|
||||||
<view class="exp-box">
|
|
||||||
<view class="exp-item" v-for="(item, index) in userInfo?.workExp" :key="item.id">{{ index + 1 + "." }}{{ item.duty }}</view>
|
|
||||||
</view>
|
|
||||||
<view v-if="!userInfo?.workExp?.length" class="empty-box">
|
|
||||||
<image class="img" src="@/static/icon/empty.png" mode="widthFix"></image>
|
|
||||||
<view class="content">暂无关键经历</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="card">
|
|
||||||
<view class="title">
|
|
||||||
<image class="bg" src="@/static/icon/title-bg.png" />
|
|
||||||
<view class="text">荣誉及证书情况</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<ul class="certificate-box">
|
|
||||||
<li class="certificate-item" v-for="(item, index) in userInfo?.certificateList" :key="item.id">{{ item.name }}</li>
|
|
||||||
</ul>
|
|
||||||
<view v-if="!userInfo?.certificateList?.length" class="empty-box">
|
|
||||||
<image class="img" src="@/static/icon/empty.png" mode="widthFix"></image>
|
|
||||||
<view class="content">暂无荣誉证书</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</AppLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { reactive, inject, watch, ref, onMounted, computed } from "vue";
|
|
||||||
const { $api, navTo, navBack } = inject("globalFunction");
|
|
||||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import useUserStore from "@/stores/useUserStore";
|
|
||||||
import useDictStore from "@/stores/useDictStore";
|
|
||||||
const { userInfo } = storeToRefs(useUserStore());
|
|
||||||
const { getUserResume } = useUserStore();
|
|
||||||
const { getDictData, oneDictData } = useDictStore();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.btn {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
image {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.card-top {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.9));
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: #fff;
|
|
||||||
padding: 24rpx;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
.title {
|
|
||||||
position: relative;
|
|
||||||
.bg {
|
|
||||||
width: 108rpx;
|
|
||||||
height: 16rpx;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
margin-left: 20rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.skill-box {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 16rpx;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
.skill-item {
|
|
||||||
padding: 8rpx 20rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #333;
|
|
||||||
background: #e7f1ff;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.exp-box {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 24rpx;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
.exp-item {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.certificate-box {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
padding-inline-start: 40rpx !important;
|
|
||||||
.certificate-item {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.mys-container {
|
|
||||||
padding: 28rpx;
|
|
||||||
.info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 24rpx;
|
|
||||||
.avatar {
|
|
||||||
width: 160rpx;
|
|
||||||
height: 160rpx;
|
|
||||||
margin-right: 24rpx;
|
|
||||||
}
|
|
||||||
.info-right {
|
|
||||||
height: 160rpx;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
.name {
|
|
||||||
font-size: 40rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.des {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.phone {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999999;
|
|
||||||
.call-icon {
|
|
||||||
width: 24rpx;
|
|
||||||
height: 24rpx;
|
|
||||||
margin-right: 5rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.info-bottom {
|
|
||||||
background: linear-gradient(to right, #91b6ff, #87afff, #b7adff);
|
|
||||||
border-radius: 0 0 8rpx 8rpx;
|
|
||||||
padding: 12rpx 24rpx;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.des-card {
|
|
||||||
padding: 24rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #666;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12rpx;
|
|
||||||
}
|
|
||||||
.empty-box {
|
|
||||||
padding: 80rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
.img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
86
pages.json
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -48,18 +47,18 @@
|
|||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
"path": "pages/chat/chat",
|
// "path": "pages/chat/chat",
|
||||||
"style": {
|
// "style": {
|
||||||
"navigationBarTitleText": "AI+",
|
// "navigationBarTitleText": "AI+",
|
||||||
"navigationBarBackgroundColor": "#4778EC",
|
// "navigationBarBackgroundColor": "#4778EC",
|
||||||
"navigationBarTextStyle": "white",
|
// "navigationBarTextStyle": "white",
|
||||||
"enablePullDownRefresh": false,
|
// "enablePullDownRefresh": false,
|
||||||
// #ifdef H5
|
// // #ifdef H5
|
||||||
"navigationStyle": "custom"
|
// "navigationStyle": "custom"
|
||||||
//#endif
|
// //#endif
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
"path": "pages/search/search",
|
"path": "pages/search/search",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -67,12 +66,11 @@
|
|||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
"subpackages": [
|
"subpackages": [{
|
||||||
{
|
|
||||||
"root": "packageA",
|
"root": "packageA",
|
||||||
"pages": [
|
"pages": [{
|
||||||
{
|
|
||||||
"path": "pages/choiceness/choiceness",
|
"path": "pages/choiceness/choiceness",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "精选",
|
"navigationBarTitleText": "精选",
|
||||||
@@ -80,8 +78,7 @@
|
|||||||
"navigationBarTextStyle": "white",
|
"navigationBarTextStyle": "white",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"path": "pages/post/post",
|
"path": "pages/post/post",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "职位详情",
|
"navigationBarTitleText": "职位详情",
|
||||||
@@ -89,8 +86,7 @@
|
|||||||
"navigationBarTextStyle": "white",
|
"navigationBarTextStyle": "white",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"path": "pages/UnitDetails/UnitDetails",
|
"path": "pages/UnitDetails/UnitDetails",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "单位详情",
|
"navigationBarTitleText": "单位详情",
|
||||||
@@ -98,8 +94,7 @@
|
|||||||
"navigationBarTextStyle": "white",
|
"navigationBarTextStyle": "white",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"path": "pages/exhibitors/exhibitors",
|
"path": "pages/exhibitors/exhibitors",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "参展单位",
|
"navigationBarTitleText": "参展单位",
|
||||||
@@ -107,31 +102,19 @@
|
|||||||
"navigationBarTextStyle": "white",
|
"navigationBarTextStyle": "white",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"path": "pages/myResume/myResume",
|
"path": "pages/myResume/myResume",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的简历",
|
"navigationBarTitleText": "我的简历",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF"
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"path": "pages/vCard/vCard",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "点子名片",
|
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/Intendedposition/Intendedposition",
|
"path": "pages/Intendedposition/Intendedposition",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "投递记录",
|
"navigationBarTitleText": "投递记录",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF"
|
"navigationBarBackgroundColor": "#FFFFFF"
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
"path": "pages/collection/collection",
|
"path": "pages/collection/collection",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的收藏",
|
"navigationBarTitleText": "我的收藏",
|
||||||
@@ -211,17 +194,9 @@
|
|||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/moreJobs/moreJobs",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "更多岗位",
|
|
||||||
"navigationBarBackgroundColor": "#FFFFFF"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}],
|
||||||
],
|
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"custom": true,
|
"custom": true,
|
||||||
"display": "none",
|
"display": "none",
|
||||||
@@ -234,8 +209,7 @@
|
|||||||
"height": "50px",
|
"height": "50px",
|
||||||
"backgroundImage": "static/tabbar/logo2copy.png"
|
"backgroundImage": "static/tabbar/logo2copy.png"
|
||||||
},
|
},
|
||||||
"list": [
|
"list": [{
|
||||||
{
|
|
||||||
"pagePath": "pages/index/index",
|
"pagePath": "pages/index/index",
|
||||||
"iconPath": "static/tabbar/calendar.png",
|
"iconPath": "static/tabbar/calendar.png",
|
||||||
"selectedIconPath": "static/tabbar/calendared.png",
|
"selectedIconPath": "static/tabbar/calendared.png",
|
||||||
@@ -247,11 +221,11 @@
|
|||||||
"selectedIconPath": "static/tabbar/posted.png",
|
"selectedIconPath": "static/tabbar/posted.png",
|
||||||
"text": "招聘会"
|
"text": "招聘会"
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
"pagePath": "pages/chat/chat",
|
// "pagePath": "pages/chat/chat",
|
||||||
"iconPath": "static/tabbar/logo3.png",
|
// "iconPath": "static/tabbar/logo3.png",
|
||||||
"selectedIconPath": "static/tabbar/logo3.png"
|
// "selectedIconPath": "static/tabbar/logo3.png"
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
"pagePath": "pages/msglog/msglog",
|
"pagePath": "pages/msglog/msglog",
|
||||||
"iconPath": "static/tabbar/chat4.png",
|
"iconPath": "static/tabbar/chat4.png",
|
||||||
|
|||||||
BIN
pages/.DS_Store
vendored
Normal file
@@ -113,8 +113,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="chat-item self" v-if="isRecording">
|
<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>
|
||||||
<view v-if="isTyping" class="self">
|
<view v-if="isTyping" class="self">
|
||||||
<text class="message msg-loading">
|
<text class="message msg-loading">
|
||||||
@@ -269,18 +268,14 @@ import AudioWave from './AudioWave.vue';
|
|||||||
import WaveDisplay from './WaveDisplay.vue';
|
import WaveDisplay from './WaveDisplay.vue';
|
||||||
import FileIcon from './fileIcon.vue';
|
import FileIcon from './fileIcon.vue';
|
||||||
import FileText from './fileText.vue';
|
import FileText from './fileText.vue';
|
||||||
// 系统功能hook和阿里云hook
|
import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
|
||||||
// import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
|
import { useTTSPlayer } from '@/hook/useTTSPlayer.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 { $api, navTo, throttle } = inject('globalFunction');
|
||||||
const emit = defineEmits(['onConfirm']);
|
const emit = defineEmits(['onConfirm']);
|
||||||
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
|
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
|
||||||
import successIcon from '@/static/icon/success.png';
|
import successIcon from '@/static/icon/success.png';
|
||||||
// hook
|
// hook
|
||||||
// 语音识别
|
|
||||||
const {
|
const {
|
||||||
isRecording,
|
isRecording,
|
||||||
startRecording,
|
startRecording,
|
||||||
@@ -290,9 +285,9 @@ const {
|
|||||||
volumeLevel,
|
volumeLevel,
|
||||||
recognizedText,
|
recognizedText,
|
||||||
lastFinalText,
|
lastFinalText,
|
||||||
} = useAudioRecorder();
|
} = useAudioRecorder(config.vioceBaseURl);
|
||||||
// 语音合成
|
|
||||||
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio } = useTTSPlayer(config.speechSynthesis);
|
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio, audioUrl } = useTTSPlayer(config.speechSynthesis);
|
||||||
|
|
||||||
// state
|
// state
|
||||||
const queries = ref([]);
|
const queries = ref([]);
|
||||||
@@ -634,7 +629,6 @@ function readMarkdown(value, index) {
|
|||||||
if (isPaused.value) {
|
if (isPaused.value) {
|
||||||
resume();
|
resume();
|
||||||
} else {
|
} else {
|
||||||
console.log(value, speechIndex.value, index, isPaused.value)
|
|
||||||
speak(value);
|
speak(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<uni-icons class="iconsearch" color="#666666" type="search" size="18"></uni-icons>
|
<uni-icons class="iconsearch" color="#666666" type="search" size="18"></uni-icons>
|
||||||
<text class="inpute">职位名称、薪资要求等</text>
|
<text class="inpute">职位名称、薪资要求等</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- <view class="chart button-click">职业图谱</view> -->
|
<view class="chart button-click">职业图谱</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="cards">
|
<view class="cards">
|
||||||
<view class="card press-button" @click="navTo('/pages/nearby/nearby')">
|
<view class="card press-button" @click="navTo('/pages/nearby/nearby')">
|
||||||
@@ -390,13 +390,11 @@ function getJobRecommend(type = 'add') {
|
|||||||
list.value = dataToImg(data);
|
list.value = dataToImg(data);
|
||||||
}
|
}
|
||||||
// 切换状态
|
// 切换状态
|
||||||
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
|
|
||||||
if (data.length < pageState.pageSize) {
|
if (data.length < pageState.pageSize) {
|
||||||
loadmoreRef.value.change('noMore');
|
loadmoreRef.value.change('noMore');
|
||||||
} else {
|
} else {
|
||||||
loadmoreRef.value.change('more');
|
loadmoreRef.value.change('more');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// 当没有岗位,刷新sessionId重新啦
|
// 当没有岗位,刷新sessionId重新啦
|
||||||
if (!data.length) {
|
if (!data.length) {
|
||||||
useUserStore().initSeesionId();
|
useUserStore().initSeesionId();
|
||||||
@@ -412,8 +410,7 @@ function getJobList(type = 'add') {
|
|||||||
list.value = [];
|
list.value = [];
|
||||||
pageState.page = 1;
|
pageState.page = 1;
|
||||||
pageState.maxPage = 2;
|
pageState.maxPage = 2;
|
||||||
// waterfallsFlowRef.value.refresh();
|
waterfallsFlowRef.value.refresh();
|
||||||
if (waterfallsFlowRef.value) waterfallsFlowRef.value.refresh();
|
|
||||||
}
|
}
|
||||||
let params = {
|
let params = {
|
||||||
current: pageState.page,
|
current: pageState.page,
|
||||||
@@ -434,14 +431,11 @@ function getJobList(type = 'add') {
|
|||||||
}
|
}
|
||||||
pageState.total = resData.total;
|
pageState.total = resData.total;
|
||||||
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
||||||
// 切换状态
|
|
||||||
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
|
|
||||||
if (rows.length < pageState.pageSize) {
|
if (rows.length < pageState.pageSize) {
|
||||||
loadmoreRef.value?.change('noMore');
|
loadmoreRef.value?.change('noMore');
|
||||||
} else {
|
} else {
|
||||||
loadmoreRef.value?.change('more');
|
loadmoreRef.value?.change('more');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,7 +557,7 @@ defineExpose({ loadData });
|
|||||||
width: 100%
|
width: 100%
|
||||||
height: 80rpx;
|
height: 80rpx;
|
||||||
line-height: 80rpx
|
line-height: 80rpx
|
||||||
// margin-right: 24rpx
|
margin-right: 24rpx
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
border-radius: 75rpx 75rpx 75rpx 75rpx;
|
border-radius: 75rpx 75rpx 75rpx 75rpx;
|
||||||
.iconsearch
|
.iconsearch
|
||||||
@@ -703,7 +697,7 @@ defineExpose({ loadData });
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
word-break:break-all
|
word-break:break-all
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 30rpx;
|
font-size: 32rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
margin-top: 10rpx
|
margin-top: 10rpx
|
||||||
.falls-card-pay
|
.falls-card-pay
|
||||||
@@ -753,22 +747,20 @@ defineExpose({ loadData });
|
|||||||
.falls-card-pepleNumber
|
.falls-card-pepleNumber
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap
|
align-items: center;
|
||||||
margin-top: 10rpx;
|
margin-top: 20rpx;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
line-height: 46rpx
|
|
||||||
view
|
view
|
||||||
display:flex
|
display:flex
|
||||||
align-items: center
|
align-items: center
|
||||||
white-space: nowrap;
|
|
||||||
.point2
|
.point2
|
||||||
margin: 0rpx 6rpx 0 2rpx
|
margin: 4rpx 6rpx 0 2rpx
|
||||||
height: 22rpx
|
height: 22rpx
|
||||||
width: 22rpx
|
width: 22rpx
|
||||||
.point3
|
.point3
|
||||||
margin: 0rpx 4rpx 0 0
|
margin: 4rpx 4rpx 0 0
|
||||||
height: 28rpx
|
height: 28rpx
|
||||||
width: 28rpx
|
width: 28rpx
|
||||||
.falls-card-matchingrate
|
.falls-card-matchingrate
|
||||||
@@ -780,7 +772,7 @@ defineExpose({ loadData });
|
|||||||
color: #4778EC;
|
color: #4778EC;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
.falls-card-company2
|
.falls-card-company2
|
||||||
margin-top: 4rpx;
|
margin-top: 8rpx;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ const maskFristEntry = ref(false);
|
|||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
// 判断浏览器是否有 fristEntry 第一次进入
|
// 判断浏览器是否有 fristEntry 第一次进入
|
||||||
let fristEntry = uni.getStorageSync('fristEntry') === false ? false : true; // 默认未读
|
// let fristEntry = uni.getStorageSync('fristEntry') === false ? false : true; // 默认未读
|
||||||
maskFristEntry.value = fristEntry;
|
// maskFristEntry.value = fristEntry ;
|
||||||
// maskFristEntry.value = true;
|
maskFristEntry.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
|||||||
@@ -296,10 +296,12 @@ function complete() {
|
|||||||
display: flex
|
display: flex
|
||||||
flex-wrap: wrap
|
flex-wrap: wrap
|
||||||
.nx-item
|
.nx-item
|
||||||
margin: 12rpx 12rpx 0 0;
|
padding: 20rpx 28rpx
|
||||||
padding: 12rpx 25rpx;
|
width: fit-content
|
||||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||||
border: 2rpx solid #E8EAEE;
|
border: 2rpx solid #E8EAEE;
|
||||||
|
margin-right: 24rpx
|
||||||
|
margin-top: 24rpx
|
||||||
.nx-item::before
|
.nx-item::before
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20rpx;
|
right: 20rpx;
|
||||||
@@ -325,8 +327,7 @@ function complete() {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
|
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
|
||||||
position: fixed;
|
position: fixed;
|
||||||
// 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: url('@/static/icon/background2.png') 0 0 no-repeat;
|
|
||||||
background-size: 100% 728rpx;
|
background-size: 100% 728rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="mini-cards">
|
<view class="mini-cards">
|
||||||
<view class="card-top btn-feel" @click="navTo('/packageA/pages/vCard/vCard')">
|
<view class="card-top btn-feel">
|
||||||
<view class="top-title line_1">
|
<view class="top-title line_1">
|
||||||
<text>{{ userInfo.name || '暂无用户名' }}</text>
|
<text>{{ userInfo.name || '暂无用户名' }}</text>
|
||||||
|
|
|
|
||||||
@@ -44,8 +44,8 @@
|
|||||||
<text v-if="userInfo.jobTitle.length - 1 !== index">|</text>
|
<text v-if="userInfo.jobTitle.length - 1 !== index">|</text>
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="top-btn button-click" >
|
<view class="top-btn button-click" @click="navTo('/packageA/pages/personalInfo/personalInfo')">
|
||||||
电子名片
|
修改简历
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="card-main">
|
<view class="card-main">
|
||||||
|
|||||||
@@ -1,27 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<scroll-view :scroll-y="true" class="nearby-scroll" @scrolltolower="scrollBottom">
|
<scroll-view :scroll-y="true" class="nearby-scroll" @scrolltolower="scrollBottom">
|
||||||
<view class="two-head">
|
<view class="two-head">
|
||||||
<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
|
<view
|
||||||
class="head-item"
|
class="head-item"
|
||||||
:class="{ active: state.comId === item.commercialAreaId }"
|
:class="{ active: state.comId === item.commercialAreaId }"
|
||||||
v-for="(item, index) in comlistPuted"
|
v-for="(item, index) in state.comlist"
|
||||||
:key="item.commercialAreaName"
|
:key="item.commercialAreaName"
|
||||||
@click="clickCommercialArea(item)"
|
@click="clickCommercialArea(item)"
|
||||||
>
|
>
|
||||||
{{ item.commercialAreaName }}
|
{{ item.commercialAreaName }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
|
||||||
</view>
|
|
||||||
<view class="nearby-list">
|
<view class="nearby-list">
|
||||||
<view class="nav-filter" @touchmove.stop.prevent>
|
<view class="nav-filter" @touchmove.stop.prevent>
|
||||||
<view class="filter-top">
|
<view class="filter-top">
|
||||||
@@ -81,12 +70,11 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 筛选 -->
|
<!-- 筛选 -->
|
||||||
<select-filter ref="selectFilterModel"></select-filter>
|
<select-filter ref="selectFilterModel"></select-filter>
|
||||||
<select-filter2-col ref="selectFilter2ColModel"></select-filter2-col>
|
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, inject, watch, ref, onMounted, onBeforeUnmount, computed } from 'vue';
|
import { reactive, inject, watch, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||||
const { $api, navTo, debounce, customSystem } = inject('globalFunction');
|
const { $api, navTo, debounce, customSystem } = inject('globalFunction');
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -99,7 +87,6 @@ const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
|
|||||||
import point2 from '@/static/icon/point2.png';
|
import point2 from '@/static/icon/point2.png';
|
||||||
import LocationPng from '@/static/icon/Location.png';
|
import LocationPng from '@/static/icon/Location.png';
|
||||||
import selectFilter from '@/components/selectFilter/selectFilter.vue';
|
import selectFilter from '@/components/selectFilter/selectFilter.vue';
|
||||||
import selectFilter2Col from '@/components/selectFilter/selectFilter2Col.vue';
|
|
||||||
|
|
||||||
const emit = defineEmits(['onFilter']);
|
const emit = defineEmits(['onFilter']);
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
@@ -109,15 +96,12 @@ const state = reactive({
|
|||||||
comId: 0,
|
comId: 0,
|
||||||
areaInfo: {},
|
areaInfo: {},
|
||||||
});
|
});
|
||||||
const commercialAreaList = ref([]);
|
|
||||||
const isLoaded = ref(false);
|
const isLoaded = ref(false);
|
||||||
const showFilter = ref(false);
|
const showFilter = ref(false);
|
||||||
const selectFilterModel = ref();
|
const selectFilterModel = ref();
|
||||||
const selectFilter2ColModel = ref();
|
|
||||||
const fromValue = reactive({
|
const fromValue = reactive({
|
||||||
area: 0,
|
area: 0,
|
||||||
});
|
});
|
||||||
const activeTab = ref('');
|
|
||||||
const loadmoreRef = ref(null);
|
const loadmoreRef = ref(null);
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
page: 0,
|
page: 0,
|
||||||
@@ -130,18 +114,6 @@ const pageState = reactive({
|
|||||||
});
|
});
|
||||||
const list = ref([]);
|
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([
|
const rangeOptions = ref([
|
||||||
{ value: 0, text: '推荐' },
|
{ value: 0, text: '推荐' },
|
||||||
{ value: 1, text: '最热' },
|
{ value: 1, text: '最热' },
|
||||||
@@ -177,6 +149,7 @@ function openFilter() {
|
|||||||
pageState.search[key] = value.join(',');
|
pageState.search[key] = value.join(',');
|
||||||
}
|
}
|
||||||
showFilter.value = false;
|
showFilter.value = false;
|
||||||
|
console.log(pageState.search);
|
||||||
getJobList('refresh');
|
getJobList('refresh');
|
||||||
},
|
},
|
||||||
cancel: () => {
|
cancel: () => {
|
||||||
@@ -228,7 +201,7 @@ function changeArea(area, item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getBusinessDistrict() {
|
function getBusinessDistrict() {
|
||||||
$api.createRequest(`/app/common/commercialArea/getAllData`).then((resData) => {
|
$api.createRequest(`/app/common/commercialArea`).then((resData) => {
|
||||||
if (resData.data.length) {
|
if (resData.data.length) {
|
||||||
state.comlist = resData.data;
|
state.comlist = resData.data;
|
||||||
state.areaInfo = resData.data[0];
|
state.areaInfo = resData.data[0];
|
||||||
@@ -268,13 +241,11 @@ function getJobList(type = 'add') {
|
|||||||
}
|
}
|
||||||
pageState.total = resData.total;
|
pageState.total = resData.total;
|
||||||
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
||||||
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
|
|
||||||
if (rows.length < pageState.pageSize) {
|
if (rows.length < pageState.pageSize) {
|
||||||
loadmoreRef.value.change('noMore');
|
loadmoreRef.value.change('noMore');
|
||||||
} else {
|
} else {
|
||||||
loadmoreRef.value.change('more');
|
loadmoreRef.value.change('more');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,49 +264,10 @@ function handleFilterConfirm(val) {
|
|||||||
getJobList('refresh');
|
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 });
|
defineExpose({ loadData, handleFilterConfirm });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.scroll-head
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
.tabchecked
|
.tabchecked
|
||||||
color: #4778EC !important
|
color: #4778EC !important
|
||||||
.nearby-scroll
|
.nearby-scroll
|
||||||
@@ -343,30 +275,18 @@ defineExpose({ loadData, handleFilterConfirm });
|
|||||||
.two-head
|
.two-head
|
||||||
margin: 22rpx;
|
margin: 22rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column
|
flex-wrap: wrap
|
||||||
flex-wrap: no-wrap
|
|
||||||
// grid-template-columns: repeat(4, 1fr);
|
// grid-template-columns: repeat(4, 1fr);
|
||||||
// grid-column-gap: 10rpx;
|
// grid-column-gap: 10rpx;
|
||||||
// grid-row-gap: 24rpx;
|
// grid-row-gap: 24rpx;
|
||||||
border-radius: 17rpx 17rpx 17rpx 17rpx;
|
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
|
.head-item
|
||||||
padding: 0 10rpx
|
|
||||||
margin: 10rpx
|
margin: 10rpx
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
// min-width: 156rpx
|
min-width: 156rpx
|
||||||
line-height: 64rpx
|
line-height: 64rpx
|
||||||
text-align: center;
|
text-align: center;
|
||||||
// width: fit-content;
|
width: fit-content;
|
||||||
font-size: 21rpx;
|
font-size: 21rpx;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
|
|||||||
@@ -292,13 +292,11 @@ function getJobList(type = 'add') {
|
|||||||
}
|
}
|
||||||
pageState.total = resData.total;
|
pageState.total = resData.total;
|
||||||
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
||||||
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
|
|
||||||
if (rows.length < pageState.pageSize) {
|
if (rows.length < pageState.pageSize) {
|
||||||
loadmoreRef.value.change('noMore');
|
loadmoreRef.value.change('noMore');
|
||||||
} else {
|
} else {
|
||||||
loadmoreRef.value.change('more');
|
loadmoreRef.value.change('more');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -317,13 +317,11 @@ function getJobList(type = 'add') {
|
|||||||
}
|
}
|
||||||
pageState.total = resData.total;
|
pageState.total = resData.total;
|
||||||
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
||||||
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
|
|
||||||
if (rows.length < pageState.pageSize) {
|
if (rows.length < pageState.pageSize) {
|
||||||
loadmoreRef.value.change('noMore');
|
loadmoreRef.value.change('noMore');
|
||||||
} else {
|
} else {
|
||||||
loadmoreRef.value.change('more');
|
loadmoreRef.value.change('more');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -222,13 +222,11 @@ function getJobList(type = 'add') {
|
|||||||
}
|
}
|
||||||
pageState.total = resData.total;
|
pageState.total = resData.total;
|
||||||
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
||||||
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
|
|
||||||
if (rows.length < pageState.pageSize) {
|
if (rows.length < pageState.pageSize) {
|
||||||
loadmoreRef.value.change('noMore');
|
loadmoreRef.value.change('noMore');
|
||||||
} else {
|
} else {
|
||||||
loadmoreRef.value.change('more');
|
loadmoreRef.value.change('more');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLayout title="附近" :use-scroll-view="false" :show-bg-image="false">
|
<AppLayout title="附近" :use-scroll-view="false">
|
||||||
<template #headerleft>
|
<template #headerleft>
|
||||||
<view class="btnback">
|
<view class="btn">
|
||||||
<image src="@/static/icon/back.png" @click="navBack"></image>
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -83,16 +83,12 @@ function handleTabChange(index) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.btnback{
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
}
|
|
||||||
.btn {
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 52rpx;
|
width: 60rpx;
|
||||||
height: 52rpx;
|
height: 60rpx;
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ const searchParams = ref({});
|
|||||||
const pageSize = ref(10);
|
const pageSize = ref(10);
|
||||||
|
|
||||||
const { list, loading, refresh, loadMore } = usePagination(
|
const { list, loading, refresh, loadMore } = usePagination(
|
||||||
(params) => $api.createRequest('/app/job/littleVideo', params, 'GET', true),
|
(params) => $api.createRequest('/app/job/littleVideo', params),
|
||||||
dataToImg, // 转换函数
|
dataToImg, // 转换函数
|
||||||
{
|
{
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
@@ -122,9 +122,6 @@ const { list, loading, refresh, loadMore } = usePagination(
|
|||||||
onBeforeRequest: () => {
|
onBeforeRequest: () => {
|
||||||
loadmoreRef.value?.change('loading');
|
loadmoreRef.value?.change('loading');
|
||||||
},
|
},
|
||||||
onAfterRequest: () => {
|
|
||||||
loadmoreRef.value?.change('more');
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -247,7 +244,7 @@ function getJobList(type = 'add') {
|
|||||||
jobTitle: searchValue.value,
|
jobTitle: searchValue.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
$api.createRequest('/app/job/list', params, 'GET', true).then((resData) => {
|
$api.createRequest('/app/job/list', params).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);
|
||||||
|
|||||||
BIN
static/.DS_Store
vendored
Normal file
BIN
static/font/.DS_Store
vendored
Normal file
BIN
static/font/DIN-Medium.woff2
Normal file
BIN
static/font/PingFangSC-Medium.woff2
Normal file
BIN
static/font/PingFangSC-Regular.woff2
Normal file
BIN
static/gif/.DS_Store
vendored
Normal file
BIN
static/icon/.DS_Store
vendored
Normal file
|
Before Width: | Height: | Size: 187 B |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,36 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>找工作,用 AI 更高效|青岛市智能求职平台</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<!-- 微信分享卡片标签(动态填充) -->
|
|
||||||
<meta property="og:title" content="找工作,用 AI 更高效|青岛市智能求职平台" />
|
|
||||||
<meta property="og:description" content="融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!" />
|
|
||||||
<meta property="og:image" content="https://qd.zhaopinzao8dian.com/file/csn/qd_shareLogo.jpg" />
|
|
||||||
<meta property="og:url" content="https://qd.zhaopinzao8dian.com" />
|
|
||||||
<meta name="description" content="融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!">
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const params = new URLSearchParams(location.search)
|
|
||||||
const jobId = params.get('jobId')
|
|
||||||
// document.querySelector('meta[property="og:url"]').setAttribute('content', location.href)
|
|
||||||
|
|
||||||
// 延迟跳转到 Vue 页面
|
|
||||||
setTimeout(() => {
|
|
||||||
if (jobId) {
|
|
||||||
window.location.href = `/#/packageA/pages/post/post?jobId=${jobId}`
|
|
||||||
} else {
|
|
||||||
window.location.href = '/#/'
|
|
||||||
}
|
|
||||||
}, 300)
|
|
||||||
// 测试使用 分享等形式打开
|
|
||||||
// http://localhost:5173/app/static/share.html?jobId=MTE4MzQ4NTE4
|
|
||||||
// http://localhost:5173/app/static/share.html?jobId=MTE4MzQ4NTE4&_t=1752221704007#/
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>正在加载中...</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753846081356" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1008" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M256 384c38.272 0 72.576 16.768 96 43.392C332.16 449.92 320 479.552 320 512c0 32.448 12.096 62.08 32 84.608A128 128 0 1 1 256 384zM512 384c38.272 0 72.576 16.768 96 43.392C588.16 449.92 576 479.552 576 512c0 32.448 12.096 62.08 32 84.608A128 128 0 1 1 512 384z" fill="#256BFA" p-id="1009"></path><path d="M768 512m-128 0a128 128 0 1 0 256 0 128 128 0 1 0-256 0Z" fill="#256BFA" p-id="1010"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 736 B |
BIN
static/tabbar/.DS_Store
vendored
Normal file
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
stores/.DS_Store
vendored
Normal file
@@ -15,22 +15,6 @@ import {
|
|||||||
// 控制消息
|
// 控制消息
|
||||||
export const useReadMsg = defineStore('readMsg', () => {
|
export const useReadMsg = defineStore('readMsg', () => {
|
||||||
const msgList = ref([])
|
const msgList = ref([])
|
||||||
const badges = ref([{
|
|
||||||
count: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
count: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
count: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
count: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
count: 0
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
// 计算总未读数量,基于 notReadCount 字段
|
// 计算总未读数量,基于 notReadCount 字段
|
||||||
const unreadCount = computed(() =>
|
const unreadCount = computed(() =>
|
||||||
@@ -46,22 +30,14 @@ export const useReadMsg = defineStore('readMsg', () => {
|
|||||||
// 设置 TabBar 角标
|
// 设置 TabBar 角标
|
||||||
function updateTabBarBadge() {
|
function updateTabBarBadge() {
|
||||||
const count = unreadCount.value
|
const count = unreadCount.value
|
||||||
const index = 3
|
|
||||||
const countVal = count > 99 ? '99+' : String(count)
|
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
uni.removeTabBarBadge({
|
uni.removeTabBarBadge({
|
||||||
index
|
index: 3
|
||||||
}) // 替换为你消息页面的 TabBar index
|
}) // 替换为你消息页面的 TabBar index
|
||||||
badges.value[index] = {
|
|
||||||
count: 0
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
badges.value[index] = {
|
|
||||||
count: countVal
|
|
||||||
}
|
|
||||||
uni.setTabBarBadge({
|
uni.setTabBarBadge({
|
||||||
index,
|
index: 3,
|
||||||
text: countVal
|
text: count > 99 ? '99+' : String(count)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +76,6 @@ export const useReadMsg = defineStore('readMsg', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
badges,
|
|
||||||
msgList,
|
msgList,
|
||||||
unreadMsgList,
|
unreadMsgList,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ import {
|
|||||||
UUID
|
UUID
|
||||||
} from '../lib/uuid-min';
|
} from '../lib/uuid-min';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import {
|
|
||||||
clearJobMoreMap
|
|
||||||
} from '@/utils/markdownParser';
|
|
||||||
|
|
||||||
const useChatGroupDBStore = defineStore("messageGroup", () => {
|
const useChatGroupDBStore = defineStore("messageGroup", () => {
|
||||||
const tableName = ref('messageGroup')
|
const tableName = ref('messageGroup')
|
||||||
@@ -60,7 +57,6 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
|
|||||||
if (!baseDB.isDBReady) await baseDB.initDB();
|
if (!baseDB.isDBReady) await baseDB.initDB();
|
||||||
chatSessionID.value = sessionId
|
chatSessionID.value = sessionId
|
||||||
const list = await baseDB.db.queryByField(massageName.value, 'parentGroupId', sessionId);
|
const list = await baseDB.db.queryByField(massageName.value, 'parentGroupId', sessionId);
|
||||||
clearJobMoreMap() // 清空对话 加载更多参数
|
|
||||||
if (list.length) {
|
if (list.length) {
|
||||||
console.log('本地数据库存在该对话数据', list)
|
console.log('本地数据库存在该对话数据', list)
|
||||||
messages.value = list
|
messages.value = list
|
||||||
|
|||||||
BIN
uni_modules/.DS_Store
vendored
Normal file
BIN
unpackage/.DS_Store
vendored
Normal file
BIN
unpackage/dist/.DS_Store
vendored
Normal file
BIN
unpackage/dist/build/.DS_Store
vendored
Normal file
8
unpackage/dist/cache/.vite/deps/_metadata.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"hash": "ab0eb594",
|
||||||
|
"configHash": "554cced5",
|
||||||
|
"lockfileHash": "5d26acb0",
|
||||||
|
"browserHash": "86a09ddc",
|
||||||
|
"optimized": {},
|
||||||
|
"chunks": {}
|
||||||
|
}
|
||||||
3
unpackage/dist/cache/.vite/deps/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"type": "module"
|
||||||
|
}
|
||||||
36
unpackage/dist/dev/mp-weixin/project.config.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"description": "项目配置文件。",
|
||||||
|
"packOptions": {
|
||||||
|
"ignore": []
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": false,
|
||||||
|
"es6": true,
|
||||||
|
"postcss": true,
|
||||||
|
"minified": true,
|
||||||
|
"newFeature": true,
|
||||||
|
"bigPackageSizeSupport": true
|
||||||
|
},
|
||||||
|
"compileType": "miniprogram",
|
||||||
|
"libVersion": "",
|
||||||
|
"appid": "touristappid",
|
||||||
|
"projectname": "qingdao-employment-service",
|
||||||
|
"condition": {
|
||||||
|
"search": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"conversation": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"miniprogram": {
|
||||||
|
"current": -1,
|
||||||
|
"list": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
unpackage/dist/dev/mp-weixin/project.private.config.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
|
||||||
|
"projectname": "qingdao-employment-service",
|
||||||
|
"setting": {
|
||||||
|
"compileHotReLoad": true,
|
||||||
|
"autoAudits": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import parseHtml from '@/lib/html-parser.js';
|
|||||||
// import DOMPurify from '@/lib/dompurify@3.2.4es.js';
|
// import DOMPurify from '@/lib/dompurify@3.2.4es.js';
|
||||||
|
|
||||||
export let codeDataList = []
|
export let codeDataList = []
|
||||||
export let jobMoreMap = new Map()
|
|
||||||
|
|
||||||
const md = new MarkdownIt({
|
const md = new MarkdownIt({
|
||||||
html: true, // 允许 HTML 标签
|
html: true, // 允许 HTML 标签
|
||||||
@@ -17,9 +16,8 @@ const md = new MarkdownIt({
|
|||||||
highlight: function(str, lang) {
|
highlight: function(str, lang) {
|
||||||
if (lang === 'job-json') {
|
if (lang === 'job-json') {
|
||||||
const result = safeExtractJson(str);
|
const result = safeExtractJson(str);
|
||||||
if (result) { // json解析成功
|
|
||||||
const jobId = result.appJobUrl.split('jobId=')[1]
|
const jobId = result.appJobUrl.split('jobId=')[1]
|
||||||
let domContext = `
|
return `
|
||||||
<a class="custom-card" data-job-id="${jobId}">
|
<a class="custom-card" data-job-id="${jobId}">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<span class="title-text" >${result.jobTitle}</span>
|
<span class="title-text" >${result.jobTitle}</span>
|
||||||
@@ -35,13 +33,6 @@ const md = new MarkdownIt({
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
if (result.data) {
|
|
||||||
jobMoreMap.set(jobId, result.data)
|
|
||||||
domContext +=
|
|
||||||
`<a class="custom-more" data-job-id="${jobId}">查看更多岗位<div class="more-icon"></div></a>`
|
|
||||||
}
|
|
||||||
return domContext
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// <div class="card-tag">${result.location}</div>
|
// <div class="card-tag">${result.location}</div>
|
||||||
// <div class="info-item">${result.salary}</div>
|
// <div class="info-item">${result.salary}</div>
|
||||||
@@ -77,54 +68,18 @@ const md = new MarkdownIt({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function extractFirstJson(text) {
|
|
||||||
let stack = [];
|
|
||||||
let startIndex = -1;
|
|
||||||
let endIndex = -1;
|
|
||||||
|
|
||||||
for (let i = 0; i < text.length; i++) {
|
|
||||||
const char = text[i];
|
|
||||||
|
|
||||||
if (char === '{') {
|
|
||||||
if (stack.length === 0) startIndex = i; // 记录第一个 '{' 的位置
|
|
||||||
stack.push(char);
|
|
||||||
} else if (char === '}') {
|
|
||||||
stack.pop();
|
|
||||||
if (stack.length === 0) {
|
|
||||||
endIndex = i; // 找到配对的 '}'
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startIndex !== -1 && endIndex !== -1) {
|
|
||||||
const jsonString = text.slice(startIndex, endIndex + 1);
|
|
||||||
try {
|
|
||||||
const jsonObject = JSON.parse(jsonString);
|
|
||||||
return jsonObject;
|
|
||||||
} catch (e) {
|
|
||||||
return null; // 如果不是有效的 JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null; // 如果没有找到有效的 JSON 对象
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function safeExtractJson(text) {
|
function safeExtractJson(text) {
|
||||||
try {
|
try {
|
||||||
const jsonObject = extractFirstJson(text);
|
const match = text.match(/\{[\s\S]*?\}/); // 提取第一个完整的 JSON 块
|
||||||
return jsonObject
|
if (match) {
|
||||||
|
return JSON.parse(match[0]);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('JSON 解析失败:', e);
|
console.error('JSON 解析失败:', e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearJobMoreMap() { // 切换对话清空
|
|
||||||
jobMoreMap.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseMarkdown(content) {
|
export function parseMarkdown(content) {
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return //处理特殊情况,比如网络异常导致的响应的 content 的值为空
|
return //处理特殊情况,比如网络异常导致的响应的 content 的值为空
|
||||||
|
|||||||
63
utils/wechatShare.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import wx from 'weixin-js-sdk'
|
||||||
|
import config from "@/config.js"
|
||||||
|
|
||||||
|
export function setupWechatShare({
|
||||||
|
title,
|
||||||
|
desc,
|
||||||
|
link,
|
||||||
|
imgUrl
|
||||||
|
}) {
|
||||||
|
// 通过后端接口获取签名(必须)
|
||||||
|
fetch(`${config.baseUrl}/wechat-signature?url=${encodeURIComponent(location.href.split('#')[0])}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(({
|
||||||
|
appId,
|
||||||
|
timestamp,
|
||||||
|
nonceStr,
|
||||||
|
signature
|
||||||
|
}) => {
|
||||||
|
wx.config({
|
||||||
|
debug: false,
|
||||||
|
appId,
|
||||||
|
timestamp,
|
||||||
|
nonceStr,
|
||||||
|
signature,
|
||||||
|
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
|
||||||
|
})
|
||||||
|
|
||||||
|
wx.ready(() => {
|
||||||
|
// 分享给好友
|
||||||
|
wx.updateAppMessageShareData({
|
||||||
|
title,
|
||||||
|
desc,
|
||||||
|
link,
|
||||||
|
imgUrl,
|
||||||
|
success: () => {
|
||||||
|
console.log('分享配置成功')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分享到朋友圈
|
||||||
|
wx.updateTimelineShareData({
|
||||||
|
title,
|
||||||
|
link,
|
||||||
|
imgUrl,
|
||||||
|
success: () => {
|
||||||
|
console.log('朋友圈分享配置成功')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
// import { setupWechatShare } from '@/utils/wechatShare.js'
|
||||||
|
|
||||||
|
// onMounted(() => {
|
||||||
|
// setupWechatShare({
|
||||||
|
// title: '职位推荐:高级前端工程师',
|
||||||
|
// desc: '某知名互联网公司,年薪40W,点击查看详情',
|
||||||
|
// link: location.href,
|
||||||
|
// imgUrl: 'https://yourcdn.com/job-thumbnail.png'
|
||||||
|
// })
|
||||||
|
// })
|
||||||