AI小程序端样式修复

This commit is contained in:
francis_fh
2026-01-22 14:05:22 +08:00
parent 96c89e0210
commit 80bd1ee181
6 changed files with 246 additions and 140 deletions

View File

@@ -1,7 +1,12 @@
<template> <template>
<view class="markdown-body"> <view class="markdown-body">
<!-- 根据不同平台使用不同的渲染方式 -->
<!-- #ifdef MP-WEIXIN -->
<rich-text class="markdownRich" id="markdown-content" :nodes="renderedHtml" @itemclick="handleItemClick" /> <rich-text class="markdownRich" id="markdown-content" :nodes="renderedHtml" @itemclick="handleItemClick" />
<!-- <view class="markdown-body" v-html="renderedHtml"></view> --> <!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="markdown-body" v-html="renderedHtml"></view>
<!-- #endif -->
</view> </view>
</template> </template>
@@ -267,7 +272,7 @@ ol {
</style> </style>
<style lang="stylus"> <style lang="stylus">
.custom-more{ .custom-more
display: flex display: flex
justify-content: center justify-content: center
align-items: center align-items: center
@@ -282,15 +287,16 @@ ol {
transition: all 0.3s ease transition: all 0.3s ease
position: relative position: relative
overflow: hidden overflow: hidden
.more-icon{
width: 32rpx; .more-icon
height: 32rpx; width: 32rpx
background: url('@/static/svg/seemore.svg') center center no-repeat; height: 32rpx
background: url('@/static/svg/seemore.svg') center center no-repeat
background-size: 100% 100% background-size: 100% 100%
margin-left: 12rpx margin-left: 12rpx
filter: brightness(0) invert(1) filter: brightness(0) invert(1)
}
&::before { &::before
content: '' content: ''
position: absolute position: absolute
top: 0 top: 0
@@ -299,93 +305,124 @@ ol {
height: 100% height: 100%
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent) background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent)
transition: left 0.5s ease transition: left 0.5s ease
}
&:active { &:active
transform: translateY(2rpx) transform: translateY(2rpx)
box-shadow: 0rpx 4rpx 16rpx rgba(37, 107, 250, 0.4) box-shadow: 0rpx 4rpx 16rpx rgba(37, 107, 250, 0.4)
}
&:active::before { &:active::before
left: 100% left: 100%
}
} /* 为小程序专门优化的样式 */
/* #ifdef MP-WEIXIN */
.rich-text-container
padding: 0 20rpx
.markdownRich
padding: 0
/* #endif */
.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)
border-radius: 20rpx 20rpx 20rpx 20rpx; border-radius: 20rpx
padding: 28rpx 24rpx; padding: 28rpx 24rpx
font-weight: 400; font-weight: 400
font-size: 28rpx; font-size: 28rpx
color: #333333; color: #333333
margin-bottom: 20rpx; margin-bottom: 20rpx
position: relative; position: relative
display: flex; display: flex
flex-direction: column flex-direction: column
/* 确保在小程序中边距正确应用 */
/* #ifdef MP-WEIXIN */
margin-left: auto
margin-right: auto
width: 100%
box-sizing: border-box
/* #endif */
.card-title .card-title
font-weight: 600; font-weight: 600
display: flex; display: flex
align-items: center; align-items: center
justify-content: space-between justify-content: space-between
margin-bottom: 16rpx
.title-text .title-text
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
max-width: calc(100% - 160rpx); max-width: calc(100% - 160rpx)
overflow: hidden overflow: hidden
text-overflow: ellipsis text-overflow: ellipsis
font-size: 30rpx font-size: 30rpx
line-height: 1.4
.card-salary .card-salary
font-family: DIN-Medium; font-family: DIN-Medium
font-size: 28rpx; font-size: 28rpx
color: #FF6E1C; color: #FF6E1C
line-height: 1.4
.card-company .card-company
margin-top: 16rpx; margin-bottom: 22rpx
max-width: calc(100%); max-width: 100%
overflow: hidden; overflow: hidden
text-overflow: ellipsis text-overflow: ellipsis
color: #6C7282; color: #6C7282
line-height: 1.4
.card-info .card-info
margin-top: 22rpx; display: flex
display: flex; align-items: center
align-items: center; justify-content: space-between
justify-content: space-between; padding-right: 40rpx
padding-right: 40rpx;
.info-item .info-item
display: flex; display: flex
position: relative; position: relative
align-items: center; align-items: center
color: #256BFA;
font-size: 28rpx; &:last-child
color: #256BFA
font-size: 28rpx
padding-right: 10rpx padding-right: 10rpx
.position-nav .position-nav
position: absolute; position: absolute
right: -10rpx; right: -10rpx
top: 50%; top: 50%
transform: translateY(-50%)
.position-nav::before .position-nav::before
position: absolute; position: absolute
left: 0; left: 0
top: -4rpx; top: -4rpx
content: ''; content: ''
width: 4rpx; width: 4rpx
height: 16rpx; height: 16rpx
border-radius: 2rpx border-radius: 2rpx
background: #256BFA; background: #256BFA
transform: translate(0, -50%) rotate(-45deg) ; transform: translate(0, -50%) rotate(-45deg)
.position-nav::after .position-nav::after
position: absolute; position: absolute
left: 0; left: 0
top: -4rpx; top: -4rpx
content: ''; content: ''
width: 4rpx; width: 4rpx
height: 16rpx; height: 16rpx
border-radius: 2rpx border-radius: 2rpx
background: #256BFA; background: #256BFA
transform: rotate(45deg) transform: rotate(45deg)
.card-tag .card-tag
font-weight: 500; font-weight: 500
font-size: 24rpx; font-size: 24rpx
color: #333333; color: #333333
width: fit-content; width: fit-content
background: #F4F4F4; background: #F4F4F4
border-radius: 4rpx 4rpx 4rpx 4rpx; border-radius: 4rpx
padding: 4rpx 20rpx; padding: 4rpx 20rpx
margin-right: 16rpx; margin-right: 16rpx
margin-bottom: 0
</style> </style>

View File

@@ -6,8 +6,8 @@
*/ */
export default { export default {
// baseUrl: 'http://39.98.44.136:8080', // 测试 // baseUrl: 'http://39.98.44.136:8080', // 测试
baseUrl: 'https://www.xjksly.cn/api/ks', // 正式环境 // baseUrl: 'https://www.xjksly.cn/api/ks', // 正式环境
// baseUrl: 'http://ks.zhaopinzao8dian.com/api/ks', // 测试 baseUrl: 'http://ks.zhaopinzao8dian.com/api/ks', // 测试
// LCBaseUrl:'http://10.110.145.145:9100',//内网端口 // LCBaseUrl:'http://10.110.145.145:9100',//内网端口
// LCBaseUrlInner:'http://10.110.145.145:10100',//招聘、培训、帮扶 // LCBaseUrlInner:'http://10.110.145.145:10100',//招聘、培训、帮扶
@@ -22,10 +22,11 @@ export default {
StreamBaseURl: 'https://www.xjksly.cn/api/ks/app/chat', StreamBaseURl: 'https://www.xjksly.cn/api/ks/app/chat',
// StreamBaseURl: 'https://qd.zhaopinzao8dian.com/ai/test', // StreamBaseURl: 'https://qd.zhaopinzao8dian.com/ai/test',
// 语音转文字 // 语音转文字
vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/ks/app/speech/asr', vioceBaseURl: 'https://www.xjksly.cn/api/ks/app/speech/asr',
// vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/speech-recognition', // vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/speech-recognition',
// 语音合成 // 语音合成
speechSynthesis: 'wss://qd.zhaopinzao8dian.com/api/speech-synthesis', speechSynthesis: 'https://www.xjksly.cn/api/ks/app/speech/tts',
// speechSynthesis: 'wss://qd.zhaopinzao8dian.com/api/speech-synthesis',
// indexedDB // indexedDB
DBversion: 2, DBversion: 2,
// 只使用本地缓寸的数据 // 只使用本地缓寸的数据

View File

@@ -292,11 +292,6 @@ export function useTTSPlayer(wsUrl) {
onHide(cancelAudio) onHide(cancelAudio)
onUnload(cancelAudio) onUnload(cancelAudio)
// 只在支持 AudioContext 的环境中初始化 WebSocket
if (audioContext) {
initWebSocket()
}
return { return {
speak, speak,
pause, pause,

View File

@@ -134,9 +134,16 @@
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view> <view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
</view> </view>
<view v-if="isTyping" class="self"> <view v-if="isTyping" class="self">
<text class="message msg-loading"> <view class="message msg-loading">
<span class="ai-loading"></span> <div class="loading-content">
</text> <span class="ai-loading">
<span></span>
<span></span>
<span></span>
</span>
<text class="loading-text">AI正在思考中...</text>
</div>
</view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
@@ -1005,12 +1012,26 @@ image-margin-top = 40rpx
.messageNull .messageNull
display: none display: none
.msg-loading{ .msg-loading{
background: transparent; background: #F6F6F6;
font-size: 24rpx; border-radius: 20rpx 0 20rpx 20rpx;
color: #8f8d8e; padding: 20rpx;
width: fit-content;
display: flex; display: flex;
align-items: flex-end; align-items: center;
justify-content: flex-start; justify-content: center;
.loading-content{
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
}
.loading-text{
font-size: 28rpx;
color: #666666;
font-weight: 500;
}
} }
.loaded{ .loaded{
padding-left: 20rpx padding-left: 20rpx
@@ -1250,25 +1271,55 @@ image-margin-top = 40rpx
.file-border .file-border
width: 160rpx !important; width: 160rpx !important;
@keyframes ai-circle { /* 更美观的loading动画 - 兼容H5和小程序 */
0% { @keyframes ai-loading-dots {
-webkit-transform: rotate(0); 0%, 20%, 80%, 100% {
transform: rotate(0); transform: scale(1);
opacity: 0.6;
} }
100% { 40% {
-webkit-transform: rotate(360deg); transform: scale(1.2);
transform: rotate(360deg); opacity: 1;
} }
} }
.ai-loading
/* 重置默认样式 */
.ai-loading {
display: inline-flex; display: inline-flex;
vertical-align: middle; align-items: center;
width: 28rpx; justify-content: center;
height: 28rpx; gap: 8rpx;
background: 0 0; width: auto;
height: auto;
background: transparent;
border: none;
border-radius: 0;
padding: 0;
margin: 0;
}
/* 三个点的样式 - 使用标准CSS语法不使用嵌套 */
.ai-loading span {
display: inline-block;
width: 12rpx;
height: 12rpx;
border-radius: 50%; border-radius: 50%;
border: 4rpx solid; background-color: #256BFA;
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e; animation: ai-loading-dots 1.4s ease-in-out infinite both;
-webkit-animation: ai-circle 1s linear infinite; margin: 0;
animation: ai-circle 1s linear infinite; padding: 0;
}
/* 为每个点设置不同的动画延迟 */
.ai-loading span:nth-child(1) {
animation-delay: -0.32s;
}
.ai-loading span:nth-child(2) {
animation-delay: -0.16s;
}
.ai-loading span:nth-child(3) {
animation-delay: 0s;
}
</style> </style>

View File

@@ -255,10 +255,10 @@
<uni-icons class="iconsearch" color="#666D7F" type="plusempty" size="18"></uni-icons> <uni-icons class="iconsearch" color="#666D7F" type="plusempty" size="18"></uni-icons>
<text>添加</text> <text>添加</text>
</view> </view>
<!-- <view class="jobs-add button-click" @click="navTo('/pages/city-select/index')" style="padding-right:0;"> <view class="jobs-add button-click" @click="navTo('/pages/city-select/index')" style="padding-right:0;">
<text>{{ selectedCity.name || '地区' }}</text> <text>{{ selectedCity.name || '地区' }}</text>
<image class="right-sx" :class="{ active: showFilter }" src="@/static/icon/shaixun.png"></image> <image class="right-sx" :class="{ active: showFilter }" src="@/static/icon/shaixun.png"></image>
</view> --> </view>
</view> </view>
<view class="filter-bottom"> <view class="filter-bottom">
<view class="btm-left"> <view class="btm-left">

View File

@@ -19,38 +19,20 @@ const md = new MarkdownIt({
const result = safeExtractJson(str); const result = safeExtractJson(str);
if (result) { // json解析成功 if (result) { // json解析成功
const jobId = result.appJobUrl.split('jobId=')[1] const jobId = result.appJobUrl.split('jobId=')[1]
let domContext = ` let domContext = `<a class="custom-card" data-job-id="${jobId}"><div class="card-title"><span class="title-text">${result.jobTitle}</span><div class="card-salary">${result.salary}</div></div><div class="card-company">${result.location}·${result.companyName}</div><div class="card-info"><div class="info-item"><div class="card-tag">${result.education}</div><div class="card-tag">${result.experience}</div></div><div class="info-item">查看详情<div class="position-nav"></div></div></div></a>`
<a class="custom-card" data-job-id="${jobId}">
<div class="card-title">
<span class="title-text" >${result.jobTitle}</span>
<div class="card-salary">${result.salary}</div>
</div>
<div class="card-company">${result.location}·${result.companyName}</div>
<div class="card-info">
<div class="info-item">
<div class="card-tag">${result.education}</div>
<div class="card-tag">${result.experience}</div>
</div>
<div class="info-item">查看详情<div class="position-nav"></div></div>
</div>
</a>
`
if (result.data) { if (result.data) {
jobMoreMap.set(jobId, result.data) jobMoreMap.set(jobId, result.data)
domContext += domContext += `<a class="custom-more" data-job-id="${jobId}">查看更多岗位<div class="more-icon"></div></a>`
`<a class="custom-more" data-job-id="${jobId}">查看更多岗位<div class="more-icon"></div></a>`
} }
return domContext return domContext
} }
} }
// <div class="card-tag">${result.location}</div>
// <div class="info-item">${result.salary}</div>
// 代码块 // 代码块
let preCode = "" let preCode = ""
try { try {
preCode = hljs.highlightAuto(str).value preCode = hljs.highlightAuto(str).value
} catch (err) { } catch (err) {
preCode = markdownIt.utils.escapeHtml(str); preCode = md.utils.escapeHtml(str);
} }
// 以换行进行分割 , 按行拆分代码 // 以换行进行分割 , 按行拆分代码
const lines = preCode.split(/\n/).slice(0, -1); const lines = preCode.split(/\n/).slice(0, -1);
@@ -58,7 +40,7 @@ const md = new MarkdownIt({
.map((line, index) => .map((line, index) =>
line ? line ?
`<li><span class="line-num" data-line="${index + 1}"></span>${line}</li>` : `<li><span class="line-num" data-line="${index + 1}"></span>${line}</li>` :
'' '<li></li>'
) )
.join(''); .join('');
@@ -127,9 +109,49 @@ export function clearJobMoreMap() { // 切换对话清空
export function parseMarkdown(content) { export function parseMarkdown(content) {
if (!content) { if (!content) {
return //处理特殊情况,比如网络异常导致的响应的 content 的值为空 return [] //处理特殊情况,比如网络异常导致的响应的 content 的值为空
} }
// 过滤掉<think>标签及其内容这些是AI内部思考过程不应该显示给用户
// 1. 处理原始标签(支持多行)
content = content.replace(/<\s*think\s*>[\s\S]*?<\s*\/\s*think\s*>/gi, '')
// 2. 处理HTML编码的标签
content = content.replace(/&lt;\s*think\s*&gt;[\s\S]*?&lt;\s*\/\s*think\s*&gt;/gi, '')
// 3. 处理部分编码的标签
content = content.replace(/&lt;\s*think\s*>/gi, '')
content = content.replace(/<\s*\/\s*think\s*&gt;/gi, '')
codeDataList = [] codeDataList = []
const unsafeHtml = md.render(content || '') const unsafeHtml = md.render(content || '')
return unsafeHtml
// 在markdown渲染后再次过滤确保没有遗漏
let filteredHtml = unsafeHtml
// 1. 处理原始标签(支持多行)
filteredHtml = filteredHtml.replace(/<\s*think\s*>[\s\S]*?<\s*\/\s*think\s*>/gi, '')
// 2. 处理HTML编码的标签
filteredHtml = filteredHtml.replace(/&lt;\s*think\s*&gt;[\s\S]*?&lt;\s*\/\s*think\s*&gt;/gi, '')
// 3. 处理部分编码的标签
filteredHtml = filteredHtml.replace(/&lt;\s*think\s*>/gi, '')
filteredHtml = filteredHtml.replace(/<\s*\/\s*think\s*&gt;/gi, '')
// 4. 单独处理剩余的think标签对
filteredHtml = filteredHtml.replace(/&lt;think&gt;/gi, '')
filteredHtml = filteredHtml.replace(/&lt;\/think&gt;/gi, '')
filteredHtml = filteredHtml.replace(/<think>/gi, '')
filteredHtml = filteredHtml.replace(/<\/think>/gi, '')
// 根据平台返回不同的内容格式
// 微信小程序返回rich-text组件支持的nodes格式
// H5直接返回HTML字符串避免HTML解析错误
if (process.env.UNI_PLATFORM === 'mp-weixin') {
try {
return parseHtml(filteredHtml)
} catch (error) {
console.error('HTML解析失败:', error)
// 解析失败时返回空数组,避免页面崩溃
return []
}
} else {
// H5端直接返回HTML字符串
return filteredHtml
}
} }