flat: 暂存
This commit is contained in:
BIN
components/.DS_Store
vendored
BIN
components/.DS_Store
vendored
Binary file not shown.
@@ -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">
|
<view class="header-title" :style="{ color: titleColor }">
|
||||||
<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,12 +45,15 @@
|
|||||||
<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,
|
||||||
@@ -110,11 +113,13 @@ 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', sans-serif;
|
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei',
|
||||||
|
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, sans-serif;
|
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial,
|
||||||
|
sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<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 btn-shaky">
|
<view class="content_top">
|
||||||
|
<!-- <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>
|
||||||
|
|||||||
@@ -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: 32rpx;
|
font-size: 30rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
.salary{
|
.salary{
|
||||||
font-family: DIN-Medium;
|
font-family: DIN-Medium;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 28rpx;
|
font-size: 26rpx;
|
||||||
color: #4C6EFB;
|
color: #4C6EFB;
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
line-height: 48rpx
|
line-height: 48rpx
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ 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) => {
|
||||||
@@ -161,6 +162,7 @@ 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', '区域'));
|
||||||
}
|
}
|
||||||
|
|||||||
345
components/selectFilter/selectFilter2col.vue
Normal file
345
components/selectFilter/selectFilter2col.vue
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<template>
|
||||||
|
<uni-popup
|
||||||
|
ref="popup"
|
||||||
|
type="bottom"
|
||||||
|
borderRadius="10px 10px 0 0"
|
||||||
|
background-color="#FFFFFF"
|
||||||
|
@maskClick="maskClickFn"
|
||||||
|
:mask-click="maskClick"
|
||||||
|
class="popup-fix"
|
||||||
|
>
|
||||||
|
<view class="popup-content">
|
||||||
|
<view class="popup-header">
|
||||||
|
<view class="btn-cancel" @click="cancel">取消</view>
|
||||||
|
<view class="title">
|
||||||
|
<text>{{ title }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="btn-confirm"></view>
|
||||||
|
</view>
|
||||||
|
<view class="popup-list">
|
||||||
|
<view class="content-wrapper">
|
||||||
|
<scroll-view class="filter-nav" scroll-y>
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in filterOptions"
|
||||||
|
:key="index"
|
||||||
|
class="nav-item button-click"
|
||||||
|
:class="{ active: activeTab === item.key }"
|
||||||
|
@click="scrollTo(item.key)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<scroll-view class="filter-content" :scroll-into-view="activeTab" scroll-y>
|
||||||
|
<template v-for="(item, index) in filterOptions" :key="index">
|
||||||
|
<view class="content-item">
|
||||||
|
<view class="item-title" :id="item.key">{{ item.label }}</view>
|
||||||
|
<view class="check-content">
|
||||||
|
<view
|
||||||
|
v-for="option in item.options"
|
||||||
|
:key="option.value"
|
||||||
|
class="checkbox-item button-click"
|
||||||
|
:class="{
|
||||||
|
checkedstyle: activeValue === option.value,
|
||||||
|
}"
|
||||||
|
@click="handleItemClick(option)"
|
||||||
|
>
|
||||||
|
<text class="option-label">{{ option.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, nextTick, onBeforeMount } from 'vue';
|
||||||
|
import useDictStore from '@/stores/useDictStore';
|
||||||
|
const { getTransformChildren } = useDictStore();
|
||||||
|
|
||||||
|
const area = ref(true);
|
||||||
|
const maskClick = ref(false);
|
||||||
|
const maskClickFn = ref(null);
|
||||||
|
|
||||||
|
const title = ref('标题');
|
||||||
|
const confirmCallback = ref(null);
|
||||||
|
const cancelCallback = ref(null);
|
||||||
|
const changeCallback = ref(null);
|
||||||
|
const popup = ref(null);
|
||||||
|
// MODIFIED: 新增 ref,用于存储当前激活的选项值
|
||||||
|
const activeValue = ref(null);
|
||||||
|
|
||||||
|
const activeTab = ref('');
|
||||||
|
const filterOptions = ref([]);
|
||||||
|
const listData = ref([]);
|
||||||
|
|
||||||
|
// MODIFIED: open 方法增加一个 currentValue 参数
|
||||||
|
const open = (newConfig = {}) => {
|
||||||
|
const {
|
||||||
|
title: configTitle,
|
||||||
|
success,
|
||||||
|
cancel,
|
||||||
|
change,
|
||||||
|
data,
|
||||||
|
maskClick: configMaskClick = false,
|
||||||
|
currentValue, // MODIFIED: 接收父组件传入的当前值
|
||||||
|
} = newConfig;
|
||||||
|
|
||||||
|
// reset();
|
||||||
|
|
||||||
|
if (configTitle) title.value = configTitle;
|
||||||
|
if (typeof success === 'function') confirmCallback.value = success;
|
||||||
|
if (typeof cancel === 'function') cancelCallback.value = cancel;
|
||||||
|
if (typeof change === 'function') changeCallback.value = change;
|
||||||
|
if (Array.isArray(data)) listData.value = data;
|
||||||
|
|
||||||
|
// MODIFIED: 将父组件传入的值
|
||||||
|
activeValue.value = currentValue;
|
||||||
|
|
||||||
|
if (configMaskClick) {
|
||||||
|
maskClick.value = configMaskClick;
|
||||||
|
maskClickFn.value = cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
getoptions();
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
popup.value?.open();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
popup.value?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
handleClick(cancelCallback.value, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = async (callback, selectedItem) => {
|
||||||
|
if (typeof callback !== 'function') {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await callback(selectedItem);
|
||||||
|
if (result !== false) close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Callback execution error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleItemClick = (option) => {
|
||||||
|
// MODIFIED: 点击时,更新本地的 activeValue
|
||||||
|
activeValue.value = option.value;
|
||||||
|
// 立即调用回调并传递所选的完整 option
|
||||||
|
handleClick(confirmCallback.value, option);
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollTo = (key) => {
|
||||||
|
activeTab.value = key;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getoptions() {
|
||||||
|
filterOptions.value = transformRegionalData(listData.value);
|
||||||
|
activeTab.value = listData.value[0].key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
maskClick.value = false;
|
||||||
|
confirmCallback.value = null;
|
||||||
|
cancelCallback.value = null;
|
||||||
|
changeCallback.value = null;
|
||||||
|
// MODIFIED: 重置时也清空 activeValue
|
||||||
|
activeValue.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function transformRegionalData(sourceData) {
|
||||||
|
const options = sourceData.map((region, index) => {
|
||||||
|
const ls = region.areaList.map((commercial) => ({
|
||||||
|
...commercial,
|
||||||
|
text: commercial.commercialAreaName,
|
||||||
|
label: commercial.commercialAreaName,
|
||||||
|
value: commercial.commercialAreaId,
|
||||||
|
key: commercial.commercialAreaId,
|
||||||
|
listClass: 'default',
|
||||||
|
status: 'default',
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
label: region.regionalName,
|
||||||
|
key: 'lx' + region.regionalId,
|
||||||
|
options: ls,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 样式表无变化,保持原样即可 */
|
||||||
|
.popup-fix {
|
||||||
|
position: fixed !important;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.popup-content {
|
||||||
|
color: #000000;
|
||||||
|
height: 80vh;
|
||||||
|
}
|
||||||
|
.popup-bottom {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
height: calc(80vh - 100rpx);
|
||||||
|
overflow: hidden;
|
||||||
|
.picker-view {
|
||||||
|
width: 100%;
|
||||||
|
height: 500rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
.uni-picker-view-mask {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
line-height: 84rpx;
|
||||||
|
height: 84rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
|
.item-active {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.uni-picker-view-indicator:after {
|
||||||
|
border-color: #e3e3e3;
|
||||||
|
}
|
||||||
|
.uni-picker-view-indicator:before {
|
||||||
|
border-color: #e3e3e3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40rpx 40rpx 10rpx 40rpx;
|
||||||
|
.title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #333333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.btn-cancel {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #666d7f;
|
||||||
|
line-height: 38rpx;
|
||||||
|
}
|
||||||
|
.btn-confirm {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #256bfa;
|
||||||
|
min-width: 60rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-nav {
|
||||||
|
width: 200rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
height: 100rpx;
|
||||||
|
line-height: 100rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666d7f;
|
||||||
|
&.active {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #256bfa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20rpx;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
|
||||||
|
.content-item {
|
||||||
|
margin-top: 30rpx;
|
||||||
|
.item-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 15rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-item:first-child {
|
||||||
|
margin-top: 0rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 16rpx;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(180rpx, 1fr));
|
||||||
|
place-items: stretch;
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
background: #e8eaee;
|
||||||
|
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||||
|
|
||||||
|
.option-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 这个样式现在会根据 activeValue 动态应用 */
|
||||||
|
.checkedstyle {
|
||||||
|
height: 76rpx;
|
||||||
|
background: rgba(37, 107, 250, 0.06);
|
||||||
|
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||||
|
border: 2rpx solid #256bfa;
|
||||||
|
color: #256bfa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import {
|
|
||||||
ref,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onMounted
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
onHide,
|
|
||||||
onUnload
|
|
||||||
} from '@dcloudio/uni-app'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function useSpeechReader() {
|
|
||||||
const isSpeaking = ref(false)
|
|
||||||
const isPaused = ref(false)
|
|
||||||
let utterance = null
|
|
||||||
|
|
||||||
const cleanMarkdown = (text) => {
|
|
||||||
return formatTextForSpeech(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
const speak = (text, options = {
|
|
||||||
lang: 'zh-CN',
|
|
||||||
rate: 0.9,
|
|
||||||
pitch: 1.2
|
|
||||||
}) => {
|
|
||||||
cancelAudio() // 重置之前的
|
|
||||||
// const voices = speechSynthesis.getVoices()
|
|
||||||
// const chineseVoices = voices.filter(v => v.lang.includes('zh'))
|
|
||||||
const speechText = extractSpeechText(text);
|
|
||||||
utterance = new SpeechSynthesisUtterance(speechText)
|
|
||||||
// utterance.lang = options.lang || 'zh'
|
|
||||||
utterance.rate = options.rate || 1
|
|
||||||
utterance.pitch = options.pitch || 1.1 // 音调(0 - 2,偏高比较柔和)
|
|
||||||
|
|
||||||
utterance.onend = () => {
|
|
||||||
isSpeaking.value = false
|
|
||||||
isPaused.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
speechSynthesis.speak(utterance)
|
|
||||||
isSpeaking.value = true
|
|
||||||
isPaused.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const pause = () => {
|
|
||||||
if (isSpeaking.value && !isPaused.value) {
|
|
||||||
speechSynthesis.pause()
|
|
||||||
isPaused.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resume = () => {
|
|
||||||
if (isSpeaking.value && isPaused.value) {
|
|
||||||
speechSynthesis.resume()
|
|
||||||
isPaused.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelAudio = () => {
|
|
||||||
speechSynthesis.cancel()
|
|
||||||
isSpeaking.value = false
|
|
||||||
isPaused.value = false
|
|
||||||
}
|
|
||||||
// 页面刷新/关闭时
|
|
||||||
onMounted(() => {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.addEventListener('beforeunload', cancelAudio)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
cancelAudio()
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.removeEventListener('beforeunload', cancelAudio)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onHide(cancelAudio)
|
|
||||||
onUnload(cancelAudio)
|
|
||||||
|
|
||||||
return {
|
|
||||||
speak,
|
|
||||||
pause,
|
|
||||||
resume,
|
|
||||||
cancelAudio,
|
|
||||||
isSpeaking,
|
|
||||||
isPaused,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractSpeechText(markdown) {
|
|
||||||
const jobRegex = /``` job-json\s*({[\s\S]*?})\s*```/g;
|
|
||||||
const jobs = [];
|
|
||||||
let match;
|
|
||||||
let lastJobEndIndex = 0;
|
|
||||||
let firstJobStartIndex = -1;
|
|
||||||
|
|
||||||
// 提取岗位 json 数据及前后位置
|
|
||||||
while ((match = jobRegex.exec(markdown)) !== null) {
|
|
||||||
const jobStr = match[1];
|
|
||||||
try {
|
|
||||||
const job = JSON.parse(jobStr);
|
|
||||||
jobs.push(job);
|
|
||||||
if (firstJobStartIndex === -1) {
|
|
||||||
firstJobStartIndex = match.index;
|
|
||||||
}
|
|
||||||
lastJobEndIndex = jobRegex.lastIndex;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('JSON 解析失败', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取引导语(第一个 job-json 之前的文字)
|
|
||||||
const guideText = firstJobStartIndex > 0 ?
|
|
||||||
markdown.slice(0, firstJobStartIndex).trim() :
|
|
||||||
'';
|
|
||||||
|
|
||||||
// 提取结束语(最后一个 job-json 之后的文字)
|
|
||||||
const endingText = lastJobEndIndex < markdown.length ?
|
|
||||||
markdown.slice(lastJobEndIndex).trim() :
|
|
||||||
'';
|
|
||||||
|
|
||||||
// 岗位信息格式化为语音文本
|
|
||||||
const jobTexts = jobs.map((job, index) => {
|
|
||||||
return `第 ${index + 1} 个岗位,岗位名称是:${job.jobTitle},公司是:${job.companyName},薪资:${job.salary},地点:${job.location},学历要求:${job.education},经验要求:${job.experience}。`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 拼接总语音内容
|
|
||||||
const finalTextParts = [];
|
|
||||||
if (guideText) finalTextParts.push(guideText);
|
|
||||||
finalTextParts.push(...jobTexts);
|
|
||||||
if (endingText) finalTextParts.push(endingText);
|
|
||||||
|
|
||||||
return finalTextParts.join('\n');
|
|
||||||
}
|
|
||||||
157
hook/useSystemPlayer.js
Normal file
157
hook/useSystemPlayer.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import {
|
||||||
|
ref,
|
||||||
|
onUnmounted,
|
||||||
|
readonly
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
const defaultExtractSpeechText = (text) => text;
|
||||||
|
|
||||||
|
|
||||||
|
export function useTTSPlayer() {
|
||||||
|
const synth = window.speechSynthesis;
|
||||||
|
const isSpeaking = ref(false);
|
||||||
|
const isPaused = ref(false);
|
||||||
|
const utteranceRef = ref(null);
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
isSpeaking.value = false;
|
||||||
|
isPaused.value = false;
|
||||||
|
utteranceRef.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text - The text to be spoken.
|
||||||
|
* @param {object} [options] - Optional settings for the speech.
|
||||||
|
* @param {string} [options.lang] - Language (e.g., 'en-US', 'es-ES').
|
||||||
|
* @param {number} [options.rate] - Speed (0.1 to 10, default 1).
|
||||||
|
* @param {number} [options.pitch] - Pitch (0 to 2, default 1).
|
||||||
|
* @param {SpeechSynthesisVoice} [options.voice] - A specific voice object.
|
||||||
|
* @param {function(string): string} [options.extractSpeechText] - A function to filter/clean the text before speaking.
|
||||||
|
*/
|
||||||
|
const speak = (text, options = {}) => {
|
||||||
|
if (!synth) {
|
||||||
|
console.error('SpeechSynthesis API is not supported in this browser.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSpeaking.value) {
|
||||||
|
synth.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredText = extractSpeechText(text);
|
||||||
|
|
||||||
|
if (!filteredText || typeof filteredText !== 'string' || filteredText.trim() === '') {
|
||||||
|
console.warn('Text to speak is empty after filtering.');
|
||||||
|
cleanup(); // Ensure state is clean
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUtterance = new SpeechSynthesisUtterance(filteredText); // Use filtered text
|
||||||
|
utteranceRef.value = newUtterance;
|
||||||
|
|
||||||
|
newUtterance.rate = options.rate || 1;
|
||||||
|
newUtterance.pitch = options.pitch || 1;
|
||||||
|
if (options.voice) {
|
||||||
|
newUtterance.voice = options.voice;
|
||||||
|
}
|
||||||
|
|
||||||
|
newUtterance.onstart = () => {
|
||||||
|
isSpeaking.value = true;
|
||||||
|
isPaused.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
newUtterance.onpause = () => {
|
||||||
|
isPaused.value = true;
|
||||||
|
};
|
||||||
|
newUtterance.onresume = () => {
|
||||||
|
isPaused.value = false;
|
||||||
|
};
|
||||||
|
newUtterance.onend = () => {
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
newUtterance.onerror = (event) => {
|
||||||
|
console.error('SpeechSynthesis Error:', event.error);
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
synth.speak(newUtterance);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pause = () => {
|
||||||
|
if (synth && isSpeaking.value && !isPaused.value) {
|
||||||
|
synth.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resume = () => {
|
||||||
|
if (synth && isPaused.value) {
|
||||||
|
synth.resume();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelAudio = () => {
|
||||||
|
if (synth) {
|
||||||
|
synth.cancel();
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancelAudio();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
speak,
|
||||||
|
pause,
|
||||||
|
resume,
|
||||||
|
cancelAudio,
|
||||||
|
isSpeaking: readonly(isSpeaking),
|
||||||
|
isPaused: readonly(isPaused),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractSpeechText(markdown) {
|
||||||
|
const jobRegex = /``` job-json\s*({[\s\S]*?})\s*```/g;
|
||||||
|
const jobs = [];
|
||||||
|
let match;
|
||||||
|
let lastJobEndIndex = 0;
|
||||||
|
let firstJobStartIndex = -1;
|
||||||
|
|
||||||
|
// 提取岗位 json 数据及前后位置
|
||||||
|
while ((match = jobRegex.exec(markdown)) !== null) {
|
||||||
|
const jobStr = match[1];
|
||||||
|
try {
|
||||||
|
const job = JSON.parse(jobStr);
|
||||||
|
jobs.push(job);
|
||||||
|
if (firstJobStartIndex === -1) {
|
||||||
|
firstJobStartIndex = match.index;
|
||||||
|
}
|
||||||
|
lastJobEndIndex = jobRegex.lastIndex;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('JSON 解析失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取引导语(第一个 job-json 之前的文字)
|
||||||
|
const guideText = firstJobStartIndex > 0 ?
|
||||||
|
markdown.slice(0, firstJobStartIndex).trim() :
|
||||||
|
'';
|
||||||
|
|
||||||
|
// 提取结束语(最后一个 job-json 之后的文字)
|
||||||
|
const endingText = lastJobEndIndex < markdown.length ?
|
||||||
|
markdown.slice(lastJobEndIndex).trim() :
|
||||||
|
'';
|
||||||
|
|
||||||
|
// 岗位信息格式化为语音文本
|
||||||
|
const jobTexts = jobs.map((job, index) => {
|
||||||
|
return `第 ${index + 1} 个岗位,岗位名称是:${job.jobTitle},公司是:${job.companyName},薪资:${job.salary},地点:${job.location},学历要求:${job.education},经验要求:${job.experience}。`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拼接总语音内容
|
||||||
|
const finalTextParts = [];
|
||||||
|
if (guideText) finalTextParts.push(guideText);
|
||||||
|
finalTextParts.push(...jobTexts);
|
||||||
|
if (endingText) finalTextParts.push(endingText);
|
||||||
|
|
||||||
|
return finalTextParts.join('\n');
|
||||||
|
}
|
||||||
203
hook/useSystemSpeechReader.js
Normal file
203
hook/useSystemSpeechReader.js
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import {
|
||||||
|
ref,
|
||||||
|
readonly,
|
||||||
|
onUnmounted
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
// 检查 API 兼容性
|
||||||
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||||
|
const isApiSupported = !!SpeechRecognition && !!navigator.mediaDevices && !!window.AudioContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {string} [options.lang] - Language code (e.g., 'zh-CN', 'en-US')
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function useAudioRecorder(options = {}) {
|
||||||
|
const lang = options.lang || 'zh-CN'; // 默认使用中文
|
||||||
|
|
||||||
|
const isRecording = ref(false);
|
||||||
|
const recognizedText = ref(''); // 完整的识别文本(包含临时的)
|
||||||
|
const lastFinalText = ref(''); // 最后一段已确定的文本
|
||||||
|
const volumeLevel = ref(0); // 音量 (0-100)
|
||||||
|
const audioDataForDisplay = ref(new Uint8Array()); // 波形数据
|
||||||
|
|
||||||
|
let recognition = null;
|
||||||
|
let audioContext = null;
|
||||||
|
let analyser = null;
|
||||||
|
let mediaStreamSource = null;
|
||||||
|
let mediaStream = null;
|
||||||
|
let dataArray = null; // 用于音量和波形
|
||||||
|
let animationFrameId = null;
|
||||||
|
|
||||||
|
if (!isApiSupported) {
|
||||||
|
console.warn(
|
||||||
|
'此浏览器不支持Web语音API或Web音频API。钩子无法正常工作。'
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
isRecording: readonly(isRecording),
|
||||||
|
startRecording: () => console.error('Audio recording not supported.'),
|
||||||
|
stopRecording: () => {},
|
||||||
|
cancelRecording: () => {},
|
||||||
|
audioDataForDisplay: readonly(audioDataForDisplay),
|
||||||
|
volumeLevel: readonly(volumeLevel),
|
||||||
|
recognizedText: readonly(recognizedText),
|
||||||
|
lastFinalText: readonly(lastFinalText),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupRecognition = () => {
|
||||||
|
recognition = new SpeechRecognition();
|
||||||
|
recognition.lang = lang;
|
||||||
|
recognition.continuous = true; // 持续识别
|
||||||
|
recognition.interimResults = true; // 返回临时结果
|
||||||
|
|
||||||
|
recognition.onstart = () => {
|
||||||
|
isRecording.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
recognition.onend = () => {
|
||||||
|
isRecording.value = false;
|
||||||
|
stopAudioAnalysis(); // 语音识别停止时,也停止音频分析
|
||||||
|
};
|
||||||
|
|
||||||
|
recognition.onerror = (event) => {
|
||||||
|
console.error('SpeechRecognition Error:', event.error);
|
||||||
|
isRecording.value = false;
|
||||||
|
stopAudioAnalysis();
|
||||||
|
};
|
||||||
|
|
||||||
|
recognition.onresult = (event) => {
|
||||||
|
let interim = '';
|
||||||
|
let final = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < event.results.length; i++) {
|
||||||
|
const transcript = event.results[i][0].transcript;
|
||||||
|
if (event.results[i].isFinal) {
|
||||||
|
final += transcript;
|
||||||
|
lastFinalText.value = transcript; // 存储最后一段确定的文本
|
||||||
|
} else {
|
||||||
|
interim += transcript;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recognizedText.value = final + interim; // 组合为完整文本
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAudioAnalysis = async () => {
|
||||||
|
try {
|
||||||
|
mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: true
|
||||||
|
});
|
||||||
|
audioContext = new AudioContext();
|
||||||
|
analyser = audioContext.createAnalyser();
|
||||||
|
mediaStreamSource = audioContext.createMediaStreamSource(mediaStream);
|
||||||
|
|
||||||
|
// 设置 Analyser
|
||||||
|
analyser.fftSize = 512; // 必须是 2 的幂
|
||||||
|
const bufferLength = analyser.frequencyBinCount;
|
||||||
|
dataArray = new Uint8Array(bufferLength); // 用于波形
|
||||||
|
|
||||||
|
// 连接节点
|
||||||
|
mediaStreamSource.connect(analyser);
|
||||||
|
|
||||||
|
// 开始循环分析
|
||||||
|
updateAudioData();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to get media stream or setup AudioContext:', err);
|
||||||
|
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
||||||
|
alert('麦克风权限被拒绝。请在浏览器设置中允许访问麦克风。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAudioData = () => {
|
||||||
|
if (!isRecording.value) return; // 如果停止了就退出循环
|
||||||
|
|
||||||
|
// 获取时域数据 (波形)
|
||||||
|
analyser.getByteTimeDomainData(dataArray);
|
||||||
|
audioDataForDisplay.value = new Uint8Array(dataArray); // 复制数组以触发响应式
|
||||||
|
|
||||||
|
// 计算音量 (RMS)
|
||||||
|
let sumSquares = 0.0;
|
||||||
|
for (const amplitude of dataArray) {
|
||||||
|
const normalized = (amplitude / 128.0) - 1.0; // 转换为 -1.0 到 1.0
|
||||||
|
sumSquares += normalized * normalized;
|
||||||
|
}
|
||||||
|
const rms = Math.sqrt(sumSquares / dataArray.length);
|
||||||
|
volumeLevel.value = Math.min(100, Math.floor(rms * 250)); // 放大 RMS 值到 0-100 范围
|
||||||
|
|
||||||
|
animationFrameId = requestAnimationFrame(updateAudioData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopAudioAnalysis = () => {
|
||||||
|
if (animationFrameId) {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
animationFrameId = null;
|
||||||
|
}
|
||||||
|
// 停止麦克风轨道
|
||||||
|
mediaStream?.getTracks().forEach((track) => track.stop());
|
||||||
|
// 关闭 AudioContext
|
||||||
|
audioContext?.close().catch((e) => console.error('Error closing AudioContext', e));
|
||||||
|
|
||||||
|
mediaStream = null;
|
||||||
|
audioContext = null;
|
||||||
|
analyser = null;
|
||||||
|
mediaStreamSource = null;
|
||||||
|
volumeLevel.value = 0;
|
||||||
|
audioDataForDisplay.value = new Uint8Array();
|
||||||
|
};
|
||||||
|
|
||||||
|
const startRecording = async () => {
|
||||||
|
if (isRecording.value) return;
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
recognizedText.value = '';
|
||||||
|
lastFinalText.value = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 必须先启动音频分析以获取麦克风权限
|
||||||
|
await startAudioAnalysis();
|
||||||
|
|
||||||
|
// 如果音频启动成功 (mediaStream 存在),则启动语音识别
|
||||||
|
if (mediaStream) {
|
||||||
|
setupRecognition();
|
||||||
|
recognition.start();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error starting recording:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopRecording = () => {
|
||||||
|
if (!isRecording.value || !recognition) return;
|
||||||
|
recognition.stop(); // 这将触发 onend 事件,自动停止音频分析
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelRecording = () => {
|
||||||
|
if (!recognition) return;
|
||||||
|
isRecording.value = false; // 立即设置状态
|
||||||
|
recognition.abort(); // 这也会触发 onend
|
||||||
|
recognizedText.value = '';
|
||||||
|
lastFinalText.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (recognition) {
|
||||||
|
recognition.abort();
|
||||||
|
}
|
||||||
|
stopAudioAnalysis();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isRecording: readonly(isRecording),
|
||||||
|
startRecording,
|
||||||
|
stopRecording,
|
||||||
|
cancelRecording,
|
||||||
|
audioDataForDisplay: readonly(audioDataForDisplay),
|
||||||
|
volumeLevel: readonly(volumeLevel),
|
||||||
|
recognizedText: readonly(recognizedText),
|
||||||
|
lastFinalText: readonly(lastFinalText),
|
||||||
|
isApiSupported, // 导出支持状态
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -23,6 +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>
|
||||||
|
|||||||
@@ -1,75 +1,116 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="mys-container">
|
<AppLayout title="我的简历" title-color="#FFFFFF" back-gorund-color="#F4F4F4">
|
||||||
<!-- 个人信息 -->
|
<template #headerleft>
|
||||||
<view class="mys-tops btn-feel">
|
<view class="btn">
|
||||||
<view class="tops-left">
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
<view class="name">
|
</view>
|
||||||
<text>{{ userInfo.name || '编辑用户名' }}</text>
|
</template>
|
||||||
<view class="edit-icon mar_le10">
|
<view class="mys-container">
|
||||||
<image
|
<!-- 个人信息 -->
|
||||||
class="button-click"
|
<view class="card-top" style="margin-top: 12rpx; padding: 0">
|
||||||
src="@/static/icon/edit1.png"
|
<view class="mys-tops btn-feel">
|
||||||
@click="navTo('/packageA/pages/personalInfo/personalInfo')"
|
<view class="tops-left">
|
||||||
></image>
|
<view class="name">
|
||||||
|
<text>{{ userInfo.name || '编辑用户名' }}</text>
|
||||||
|
<view class="edit-icon mar_le10">
|
||||||
|
<image
|
||||||
|
class="button-click"
|
||||||
|
src="@/static/icon/edit1.png"
|
||||||
|
@click="navTo('/packageA/pages/personalInfo/personalInfo')"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="subName">
|
||||||
|
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
|
||||||
|
<text class="mar_ri10">{{ userInfo.age }}岁</text>
|
||||||
|
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
|
||||||
|
<dict-Label
|
||||||
|
class="mar_ri10"
|
||||||
|
dictType="affiliation"
|
||||||
|
:value="userInfo.politicalAffiliation"
|
||||||
|
></dict-Label>
|
||||||
|
</view>
|
||||||
|
<view class="subName">{{ userInfo.phone }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="tops-right">
|
||||||
|
<view class="right-imghead">
|
||||||
|
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy.png"></image>
|
||||||
|
<image v-else src="@/static/icon/girl.png"></image>
|
||||||
|
</view>
|
||||||
|
<view class="right-sex">
|
||||||
|
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy1.png"></image>
|
||||||
|
<image v-else src="@/static/icon/girl1.png"></image>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="subName">
|
<!-- 求职期望 -->
|
||||||
<dict-Label class="mar_ri10" dictType="sex" :value="userInfo.sex"></dict-Label>
|
<view class="mys-line"></view>
|
||||||
<text class="mar_ri10">{{ userInfo.age }}岁</text>
|
<view class="mys-info">
|
||||||
<dict-Label class="mar_ri10" dictType="education" :value="userInfo.education"></dict-Label>
|
<view class="mys-h4">
|
||||||
<dict-Label
|
<text>求职期望</text>
|
||||||
class="mar_ri10"
|
<view class="mys-edit-icon">
|
||||||
dictType="affiliation"
|
<image
|
||||||
:value="userInfo.politicalAffiliation"
|
class="button-click"
|
||||||
></dict-Label>
|
src="@/static/icon/edit1.png"
|
||||||
|
@click="navTo('/packageA/pages/jobExpect/jobExpect')"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mys-text">
|
||||||
|
<text>期望薪资:</text>
|
||||||
|
<text>{{ userInfo.salaryMin / 1000 }}k-{{ userInfo.salaryMax / 1000 }}k</text>
|
||||||
|
</view>
|
||||||
|
<view class="mys-text">
|
||||||
|
<text>期望工作地:</text>
|
||||||
|
<text>青岛市-</text>
|
||||||
|
<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label>
|
||||||
|
</view>
|
||||||
|
<view class="mys-list">
|
||||||
|
<view class="cards button-click" v-for="(title, index) in userInfo.jobTitle" :key="index">
|
||||||
|
{{ title }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="subName">{{ userInfo.phone }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="tops-right">
|
<view class="card-top" style="margin-top: 24rpx">
|
||||||
<view class="right-imghead">
|
<view class="mys-info" style="padding: 0">
|
||||||
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy.png"></image>
|
<view class="mys-h4">
|
||||||
<image v-else src="@/static/icon/girl.png"></image>
|
<text>工作经历</text>
|
||||||
</view>
|
<view class="mys-edit-icon">
|
||||||
<view class="right-sex">
|
<image
|
||||||
<image v-if="userInfo.sex === '0'" src="@/static/icon/boy1.png"></image>
|
class="button-click"
|
||||||
<image v-else src="@/static/icon/girl1.png"></image>
|
src="@/static/icon/edit1.png"
|
||||||
|
@click="navTo('/packageA/pages/jobExpect/jobExpect')"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mar_top10" v-for="item in userInfo.workExp" :key="item.id">
|
||||||
|
<view class="fl_box fl_justbet mar_top10">
|
||||||
|
<text class="fs_16">{{ item.company }}</text>
|
||||||
|
<text class="datetext">{{ item.startTime }}--{{ item.endTime || '至今' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="mys-text">
|
||||||
|
<text class="color_333333 fs_14">{{ item.position }}</text>
|
||||||
|
<text></text>
|
||||||
|
</view>
|
||||||
|
<view class="mys-text color_333333">
|
||||||
|
<text>{{ item.duty }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 求职期望 -->
|
<template #footer>
|
||||||
<view class="mys-line"></view>
|
<view class="footer-container">
|
||||||
<view class="mys-info">
|
<view class="footer-button">上传简历</view>
|
||||||
<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>
|
||||||
<view class="mys-text">
|
</template>
|
||||||
<text>期望薪资:</text>
|
</AppLayout>
|
||||||
<text>{{ userInfo.salaryMin / 1000 }}k-{{ userInfo.salaryMax / 1000 }}k</text>
|
|
||||||
</view>
|
|
||||||
<view class="mys-text">
|
|
||||||
<text>期望工作地:</text>
|
|
||||||
<text>青岛市-</text>
|
|
||||||
<dict-Label dictType="area" :value="Number(userInfo.area)"></dict-Label>
|
|
||||||
</view>
|
|
||||||
<view class="mys-list">
|
|
||||||
<view class="cards button-click" v-for="(title, index) in userInfo.jobTitle" :key="index">
|
|
||||||
{{ title }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
</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 } = inject('globalFunction');
|
const { $api, navTo, navBack } = inject('globalFunction');
|
||||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import useUserStore from '@/stores/useUserStore';
|
import useUserStore from '@/stores/useUserStore';
|
||||||
@@ -80,20 +121,52 @@ 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{
|
||||||
|
.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: 52rpx 48rpx
|
padding: 38rpx 44rpx
|
||||||
.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: 44rpx;
|
font-size: 36rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
@@ -108,8 +181,8 @@ image{
|
|||||||
.subName{
|
.subName{
|
||||||
margin-top: 12rpx
|
margin-top: 12rpx
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 32rpx;
|
font-size: 26rpx;
|
||||||
color: #333333;
|
color: #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -143,7 +216,7 @@ image{
|
|||||||
.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: 600;
|
font-weight: 600;
|
||||||
font-size: 32rpx;
|
font-size: 30rpx;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
margin-bottom: 8rpx
|
margin-bottom: 8rpx
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -155,10 +228,15 @@ 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: 28rpx;
|
font-size: 26rpx;
|
||||||
color: #333333;
|
color: #999999;
|
||||||
margin-top: 16rpx
|
margin-top: 16rpx
|
||||||
}
|
}
|
||||||
.mys-list{
|
.mys-list{
|
||||||
|
|||||||
@@ -106,7 +106,8 @@
|
|||||||
"path": "pages/myResume/myResume",
|
"path": "pages/myResume/myResume",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的简历",
|
"navigationBarTitleText": "我的简历",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF"
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"path": "pages/Intendedposition/Intendedposition",
|
"path": "pages/Intendedposition/Intendedposition",
|
||||||
|
|||||||
@@ -113,7 +113,8 @@
|
|||||||
</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">
|
||||||
@@ -268,14 +269,18 @@ 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';
|
||||||
import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
|
// 系统功能hook和阿里云hook
|
||||||
import { useTTSPlayer } from '@/hook/useTTSPlayer.js';
|
// import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
|
||||||
|
import { useAudioRecorder } from '@/hook/useSystemSpeechReader.js';
|
||||||
|
// import { useTTSPlayer } from '@/hook/useTTSPlayer.js';
|
||||||
|
import { useTTSPlayer } from '@/hook/useSystemPlayer.js';
|
||||||
// 全局
|
// 全局
|
||||||
const { $api, navTo, throttle } = inject('globalFunction');
|
const { $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,
|
||||||
@@ -286,7 +291,7 @@ const {
|
|||||||
recognizedText,
|
recognizedText,
|
||||||
lastFinalText,
|
lastFinalText,
|
||||||
} = useAudioRecorder();
|
} = useAudioRecorder();
|
||||||
|
// 语音合成
|
||||||
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio } = useTTSPlayer(config.speechSynthesis);
|
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio } = useTTSPlayer(config.speechSynthesis);
|
||||||
|
|
||||||
// state
|
// state
|
||||||
@@ -629,6 +634,7 @@ 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')">
|
||||||
@@ -563,7 +563,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 +703,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: 32rpx;
|
font-size: 30rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
margin-top: 10rpx
|
margin-top: 10rpx
|
||||||
.falls-card-pay
|
.falls-card-pay
|
||||||
|
|||||||
@@ -327,7 +327,8 @@ 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: url('@/static/icon/background2.png') 0 0 no-repeat;
|
// background: linear-gradient( 180deg, #1677FF 0%, rgba(22,119,255,0) 54%, rgba(22,119,255,0) 100%);
|
||||||
|
// background: url('@/static/icon/background2.png') 0 0 no-repeat;
|
||||||
background-size: 100% 728rpx;
|
background-size: 100% 728rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
<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
|
<view class="head-all">
|
||||||
class="head-item"
|
<text>热门商圈</text>
|
||||||
:class="{ active: state.comId === item.commercialAreaId }"
|
<text class="color_333333 button-click" @click="handleOpenBusinessDistrict">
|
||||||
v-for="(item, index) in state.comlist"
|
更多
|
||||||
:key="item.commercialAreaName"
|
<uni-icons type="forward" color="#333333" size="14"></uni-icons>
|
||||||
@click="clickCommercialArea(item)"
|
</text>
|
||||||
>
|
|
||||||
{{ item.commercialAreaName }}
|
|
||||||
</view>
|
</view>
|
||||||
|
<scroll-view class="scroll-head" :scroll-x="true" :scroll-into-view="activeTab" :show-scrollbar="false">
|
||||||
|
<view class="head-item-content">
|
||||||
|
<view
|
||||||
|
class="head-item"
|
||||||
|
:class="{ active: state.comId === item.commercialAreaId }"
|
||||||
|
v-for="(item, index) in comlistPuted"
|
||||||
|
:key="item.commercialAreaName"
|
||||||
|
@click="clickCommercialArea(item)"
|
||||||
|
>
|
||||||
|
{{ item.commercialAreaName }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
<view class="nearby-list">
|
<view class="nearby-list">
|
||||||
<view class="nav-filter" @touchmove.stop.prevent>
|
<view class="nav-filter" @touchmove.stop.prevent>
|
||||||
@@ -70,11 +81,12 @@
|
|||||||
</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 } from 'vue';
|
import { reactive, inject, watch, ref, onMounted, onBeforeUnmount, computed } 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';
|
||||||
@@ -87,6 +99,7 @@ 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({
|
||||||
@@ -96,12 +109,15 @@ 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,
|
||||||
@@ -114,6 +130,18 @@ 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: '最热' },
|
||||||
@@ -149,7 +177,6 @@ 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: () => {
|
||||||
@@ -201,7 +228,7 @@ function changeArea(area, item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getBusinessDistrict() {
|
function getBusinessDistrict() {
|
||||||
$api.createRequest(`/app/common/commercialArea`).then((resData) => {
|
$api.createRequest(`/app/common/commercialArea/getAllData`).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];
|
||||||
@@ -266,10 +293,49 @@ 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
|
||||||
@@ -277,18 +343,30 @@ defineExpose({ loadData, handleFilterConfirm });
|
|||||||
.two-head
|
.two-head
|
||||||
margin: 22rpx;
|
margin: 22rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap
|
flex-direction: column
|
||||||
|
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;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppLayout title="附近" :use-scroll-view="false">
|
<AppLayout title="附近" :use-scroll-view="false" :show-bg-image="false">
|
||||||
<template #headerleft>
|
<template #headerleft>
|
||||||
<view class="btnback">
|
<view class="btnback">
|
||||||
<image src="@/static/icon/back.png" @click="navBack"></image>
|
<image src="@/static/icon/back.png" @click="navBack"></image>
|
||||||
|
|||||||
BIN
static/.DS_Store
vendored
BIN
static/.DS_Store
vendored
Binary file not shown.
BIN
static/icon/.DS_Store
vendored
BIN
static/icon/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 34 KiB |
BIN
static/icon/background3.png
Normal file
BIN
static/icon/background3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"hash": "5cf4c8aa",
|
"hash": "8cb75570",
|
||||||
"configHash": "0279f4bc",
|
"configHash": "01aaac4c",
|
||||||
"lockfileHash": "5d26acb0",
|
"lockfileHash": "5d26acb0",
|
||||||
"browserHash": "5520a35c",
|
"browserHash": "4b09b340",
|
||||||
"optimized": {},
|
"optimized": {},
|
||||||
"chunks": {}
|
"chunks": {}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user