flat:4.7暂存

This commit is contained in:
史典卓
2025-04-07 09:10:55 +08:00
parent 2992cb200e
commit b98e1d5405
186 changed files with 1090 additions and 631 deletions

View File

@@ -37,9 +37,14 @@
</view>
</scroll-view>
<view class="drawer-user">
<image class="drawer-user-img" src="/static/icon/boy.png"></image>
<text>用户123</text>
<image class="drawer-user-setting" src="/static/icon/setting.png" mode=""></image>
<image class="drawer-user-img" v-if="userInfo.age === '0'" src="/static/icon/boy.png"></image>
<image class="drawer-user-img" v-else src="/static/icon/girl.png"></image>
<text>{{ userInfo.name || '暂无用户名' }}</text>
<image
class="drawer-user-setting button-click"
src="/static/icon/setting.png"
@click="updateSetting"
></image>
</view>
</view>
</view>
@@ -64,11 +69,13 @@
<script setup>
import { ref, inject, nextTick, computed } from 'vue';
const { $api, navTo, insertSortData } = inject('globalFunction');
import { onLoad, onShow } from '@dcloudio/uni-app';
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import useChatGroupDBStore from '@/stores/userChatGroupStore';
import useUserStore from '@/stores/useUserStore';
import aiPaging from './components/ai-paging.vue';
import { storeToRefs } from 'pinia';
const { isTyping, tabeList, chatSessionID } = storeToRefs(useChatGroupDBStore());
const { userInfo } = storeToRefs(useUserStore());
const isDrawerOpen = ref(false);
const scrollIntoView = ref(false);
import config from '@/config';
@@ -94,12 +101,17 @@ onShow(() => {
});
});
onHide(() => {
paging.value?.handleTouchCancel();
});
const toggleDrawer = () => {
isDrawerOpen.value = !isDrawerOpen.value;
};
const addNewDialogue = () => {
$api.msg('新对话');
paging.value?.changeQueries();
useChatGroupDBStore().addNewDialogue();
};
@@ -113,6 +125,9 @@ const changeDialogue = (item) => {
});
}
};
function updateSetting() {
$api.msg('该功能正在开发中,敬请期待后续更新!');
}
</script>
<style lang="stylus" scoped>
@@ -190,13 +205,14 @@ const changeDialogue = (item) => {
position: relative
margin-bottom: constant(safe-area-inset-bottom); /*兼容 IOS<11.2*/
margin-bottom: env(safe-area-inset-bottom); /*兼容 IOS>11.2*/
color: #000000
.drawer-user-img
width: 57.2rpx;
height: 57.2rpx
margin-right: 20rpx
.drawer-user-setting
width: 57.2rpx;
height: 57.2rpx
width: 48rpx
height: 48rpx
position: absolute
top: 50%
right: 28rpx
@@ -267,7 +283,7 @@ const changeDialogue = (item) => {
display: flex;
align-items: center;
justify-content: space-between
font-size: 28rpx
font-size: 32rpx
color: #000000
padding: 0 30rpx;
font-weight: bold

View File

@@ -0,0 +1,273 @@
<template>
<view class="wave-container active" :style="{ background }">
<!-- 波形显示区域 -->
<view class="wave">
<view
v-for="(bar, index) in waveBars"
:key="index"
class="wave-bar"
:style="{
height: `${bar.height}px`,
backgroundColor: bar.color,
borderRadius: `${bar.borderRadius}px`,
}"
></view>
</view>
</view>
</template>
<script setup>
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
const props = defineProps({
// 是否激活显示
isActive: {
type: Boolean,
default: false,
},
// 音频数据数组 (0-1之间的值)
audioData: {
type: Array,
default: () => [],
},
// 是否显示录音信息
showInfo: {
type: Boolean,
default: false,
},
// 录音时间 (秒)
recordingTime: {
type: Number,
default: 0,
},
// 是否显示取消提示
showCancelTip: {
type: Boolean,
default: false,
},
// 是否处于取消状态
isCanceling: {
type: Boolean,
default: false,
},
background: {
type: String,
default: 'linear-gradient(to right, #377dff, #9a60ff)',
},
});
const emit = defineEmits(['update:audioData']);
// 波形条数据
const waveBars = ref([]);
// 中心条索引
const centerIndex = ref(0);
// 动画帧ID
let animationId = null;
// 格式化显示时间
const formattedTime = computed(() => {
const mins = Math.floor(props.recordingTime / 60)
.toString()
.padStart(2, '0');
const secs = (props.recordingTime % 60).toString().padStart(2, '0');
return `${mins}:${secs}`;
});
// 录音提示文本
const recordingTip = computed(() => {
return props.isCanceling ? '松开取消' : '松手发送';
});
// 初始化波形条
const initWaveBars = () => {
waveBars.value = [];
// 创建31个波形条奇数个确保有真正的中心点
const barCount = 31;
centerIndex.value = Math.floor(barCount / 2);
for (let i = 0; i < barCount; i++) {
// 设置初始状态(中心条最高,向两侧递减)
const distanceFromCenter = Math.abs(i - centerIndex.value);
const initialHeight = Math.max(2, 20 - distanceFromCenter * 3);
waveBars.value.push({
height: initialHeight,
color: '#FFFFFF',
borderRadius: 2,
});
}
};
// 更新波形显示
const updateWaveform = () => {
if (!props.isActive) return;
// 如果没有传入音频数据,则使用模拟数据
const audioData =
props.audioData.length > 0
? props.audioData
: Array(centerIndex.value + 1)
.fill(0)
.map(() => Math.random() * 0.5 + 0.2);
// 从中间向两侧处理
for (let i = 0; i <= centerIndex.value; i++) {
// 左侧条索引
const leftIndex = centerIndex.value - i;
// 右侧条索引
const rightIndex = centerIndex.value + i;
// 获取音频数据值 (归一化到0-1)
const value = audioData[i] || 0;
// 更新左侧条
if (leftIndex >= 0) {
updateWaveBar(leftIndex, value);
}
// 更新右侧条(避免重复更新中心条)
if (rightIndex < waveBars.value.length && rightIndex !== leftIndex) {
updateWaveBar(rightIndex, value);
}
}
// 继续动画
animationId = requestAnimationFrame(updateWaveform);
};
// 更新单个波形条
const updateWaveBar = (index, value) => {
// 动态高度 (4rpx到200rpx之间)
const height = 2 + value * 98;
// // 动态颜色
// let color;
// if (props.isCanceling) {
// color = '#F44336'; // 取消状态显示红色
// } else {
// const intensity = Math.min(1, value * 1.5);
// const r = Math.floor(7 + intensity * 200);
// const g = Math.floor(193 - intensity * 50);
// const b = Math.floor(96 - intensity * 30);
// color = `rgb(${r}, ${g}, ${b})`;
// }
let color = '#FFFFFF';
// 动态圆角
const borderRadius = Math.min(4, height * 0.4);
waveBars.value[index] = {
height,
color,
borderRadius,
};
};
// 开始动画
const startAnimation = () => {
if (!animationId) {
animationId = requestAnimationFrame(updateWaveform);
}
};
// 停止动画
const stopAnimation = () => {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
};
// 监听激活状态变化
watch(
() => props.isActive,
(newVal) => {
if (newVal) {
startAnimation();
} else {
stopAnimation();
}
}
);
// 组件挂载时初始化
onMounted(() => {
initWaveBars();
if (props.isActive) {
startAnimation();
}
});
// 组件卸载时清理
onUnmounted(() => {
stopAnimation();
});
</script>
<style lang="scss">
.wave-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
padding: 16rpx;
border-radius: 36rpx;
height: calc(102rpx - 40rpx);
gap: 4rpx;
box-shadow: 0rpx 8rpx 40rpx 0rpx rgba(0, 54, 170, 0.15);
overflow: hidden;
&.active {
opacity: 1;
}
}
.wave {
width: 100%;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
}
.wave-bar {
width: 6rpx;
min-height: 4rpx;
border-radius: 4rpx;
margin: 0 3rpx;
transition: height 0.3s ease-out, background-color 0.2s;
transform-origin: center bottom;
}
.wave-bar:nth-child(3n) {
opacity: 0.7;
}
.recording-info {
position: absolute;
bottom: -80rpx;
width: 100%;
text-align: center;
font-size: 32rpx;
color: #666;
}
.recording-tip {
font-size: 28rpx;
color: #07c160;
}
.recording-time {
font-size: 28rpx;
color: #07c160;
margin-top: 10rpx;
}
.cancel-tip {
position: absolute;
bottom: -120rpx;
width: 100%;
text-align: center;
font-size: 28rpx;
color: #f44336;
}
</style>

View File

@@ -8,8 +8,12 @@
我可以根据您的简历和求职需求帮你精准匹配青岛市互联网招聘信息对比招聘信息的优缺点提供面试指导等请把你的任务交给我吧~
</view>
<view class="back-rowh3">猜你所想</view>
<view class="back-rowmsg">我希望找青岛的IT行业岗位薪资能否在12000以上</view>
<view class="back-rowmsg">我有三年的工作经验能否推荐一些适合我的青岛的国企 岗位</view>
<view class="back-rowmsg" v-for="item in queries" @click="sendMessageGuess(item)">
{{ item }}
</view>
<view class="chat-item self" v-if="isRecording">
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
</view>
</view>
</FadeView>
<scroll-view class="chat-list scrollView" :scroll-top="scrollTop" :scroll-y="true" scroll-with-animation>
@@ -25,7 +29,7 @@
<view class="message" v-if="msg.self">
<view class="msg-filecontent" v-if="msg.files.length">
<view
class="msg-files"
class="msg-files btn-light"
v-for="(file, vInex) in msg.files"
:key="vInex"
@click="jumpUrl(file)"
@@ -40,7 +44,39 @@
<!-- {{ msg.displayText }} -->
<view class="message-markdown">
<md-render :content="msg.displayText"></md-render>
<view class="message-controll"></view>
<view class="message-controll" v-show="showControll(index)">
<view class="controll-left">
<image
class="controll-icon btn-light"
src="/static/icon/copy.png"
@click="copyMarkdown(msg.displayText)"
></image>
<image
class="controll-icon mar_le10 btn-light"
src="/static/icon/feedback.png"
@click="userGoodFeedback(msg)"
></image>
<image
v-if="isSpeaking && !isPaused && speechIndex === index"
class="controll-icon mar_le10 btn-light"
src="/static/icon/stop.png"
@click="stopMarkdown(msg.displayText, index)"
></image>
<image
class="controll-icon mar_le10 btn-light"
src="/static/icon/broadcast.png"
@click="readMarkdown(msg.displayText, index)"
v-else
></image>
</view>
<view class="controll-right">
<image
class="controll-icon mar_ri10 btn-light"
src="/static/icon/refresh.png"
@click="refreshMarkdown(index)"
></image>
</view>
</view>
</view>
<!-- guess -->
<view
@@ -60,6 +96,9 @@
</view>
</view>
</view>
<view class="chat-item self" v-if="isRecording">
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
</view>
<view v-if="isTyping" :class="{ self: true }">
<text class="message msg-loading">
<span class="ai-loading"></span>
@@ -70,7 +109,12 @@
</scroll-view>
<view class="vio_container" :class="status" v-if="status !== 'idle'">
<view class="record-tip">{{ statusText }}</view>
<AudioWave :background="audiowaveStyle" />
<WaveDisplay
:background="audiowaveStyle"
:isActive="isRecording"
:audioData="audioDataForDisplay"
:showInfo="isRecording"
/>
</view>
<view class="input-area" v-else>
<view class="areatext">
@@ -82,7 +126,7 @@
:disabled="isTyping"
:adjust-position="false"
placeholder="请输入您的职位名称、薪资要求、岗位地址"
v-if="!isVoice"
v-show="!isVoice"
/>
<view
class="input_vio"
@@ -90,8 +134,8 @@
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@touchcancel="handleTouchCancel"
v-show="isVoice"
type="default"
v-else
>
按住说话
</view>
@@ -114,8 +158,8 @@
<view class="btn-box" v-else-if="!isTyping" @click="changeVoice">
<image class="send-btn" src="/static/icon/send4.png"></image>
</view>
<view class="btn-box" v-else>
<image class="send-btn" src="/static/icon/send2xx.png"></image>
<view class="btn-box purple" v-else>
<view class="btn-box-round"></view>
</view>
</view>
<!-- btn -->
@@ -153,15 +197,19 @@
:src="file.url"
></image>
<view class="file-doc" @click="jumpUrl(file)" v-else>
<view style="width: 100%">
<FileIcon class="doc-icon" :type="file.type"></FileIcon>
<view class="doc-con">
<view class="filename-text">{{ file.name }}</view>
<view class="filename-size">{{ file.size }}</view>
<view class="filerow">
<FileText :type="file.type"></FileText>
<view class="row-x"></view>
<view class="filename-size">{{ file.size }}</view>
</view>
</view>
<FileIcon :type="file.type"></FileIcon>
<!-- <image class="file-icon" @click="jumpUrl(file)" :src=""></image> -->
</view>
<view class="file-del" catchtouchmove="true" @click="delfile(file)">
<uni-icons type="closeempty" color="#4B4B4B" size="10"></uni-icons>
<uni-icons type="closeempty" color="#FFFFFF" size="10"></uni-icons>
</view>
</view>
</view>
@@ -176,20 +224,36 @@ import { ref, inject, nextTick, defineProps, defineEmits, onMounted, toRaw, reac
import { storeToRefs } from 'pinia';
import config from '@/config.js';
import useChatGroupDBStore from '@/stores/userChatGroupStore';
const { $api, navTo, throttle } = inject('globalFunction');
const emit = defineEmits(['onConfirm']);
import MdRender from '@/components/md-render/md-render.vue';
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
import CollapseTransition from '@/components/CollapseTransition/CollapseTransition.vue';
import FadeView from '@/components/FadeView/FadeView.vue';
import AudioWave from './AudioWave.vue';
import WaveDisplay from './WaveDisplay.vue';
import FileIcon from './fileIcon.vue';
import FileText from './fileText.vue';
import { useSpeechReader } from '@/hook/useSpeechReader';
import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
// 全局
const { $api, navTo, throttle } = inject('globalFunction');
const emit = defineEmits(['onConfirm']);
const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
const { isRecording, recognizedText, startRecording, stopRecording, cancelRecording } = useAudioRecorder(
config.vioceBaseURl
);
// hook
const {
isRecording,
startRecording,
stopRecording,
cancelRecording,
audioDataForDisplay,
volumeLevel,
recognizedText,
lastFinalText,
} = useAudioRecorder(config.vioceBaseURl);
const { speak, pause, resume, isSpeaking, isPaused } = useSpeechReader();
// state
const queries = ref([]);
const guessList = ref([]);
const scrollTop = ref(0);
const showGuess = ref(false);
@@ -200,18 +264,62 @@ const isVoice = ref(false);
const status = ref('idle'); // idle | recording | cancel
const startY = ref(0);
const cancelThreshold = 100;
let recordingTimer = null;
const speechIndex = ref(0);
const isAudioPermission = ref(false);
const state = reactive({
uploadFileTips: '请根据以上附件,帮我推荐岗位。',
});
onMounted(() => {
scrollToBottom();
const statusText = computed(() => {
switch (status.value) {
case 'recording':
return '松手发送,上划取消';
case 'cancel':
return '松手取消';
default:
return '按住说话';
}
});
const sendMessage = () => {
const values = textInput.value;
const audiowaveStyle = computed(() => {
return status.value === 'cancel'
? '#f54545'
: status.value === 'recording'
? 'linear-gradient(to right, #377dff, #9a60ff)'
: '#f1f1f1';
});
onMounted(async () => {
changeQueries();
scrollToBottom();
isAudioPermission.value = await requestMicPermission();
});
const requestMicPermission = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
console.log('✅ 麦克风权限已授权');
// 立刻停止所有音轨,释放麦克风
stream.getTracks().forEach((track) => track.stop());
return true;
} catch (err) {
console.warn('❌ 用户拒绝麦克风权限或不支持:', err);
return false;
}
};
function showControll(index) {
if (index === messages.value.length - 1 && isTyping.value) {
return false;
}
return true;
}
const sendMessage = (text) => {
const values = textInput.value || text;
showfile.value = false;
showGuess.value = false;
if (values.trim()) {
@@ -222,7 +330,9 @@ const sendMessage = () => {
const newMsg = { text: values, self: true, displayText: values, files: normalArr };
useChatGroupDBStore().addMessage(newMsg);
useChatGroupDBStore()
.getStearm(values, normalArr, scrollToBottom)
.getStearm(values, normalArr, scrollToBottom, {
onComplete: () => console.log('Display complete'),
})
.then(() => {
getGuess();
scrollToBottom();
@@ -276,19 +386,17 @@ const delfile = (file) => {
const scrollToBottom = throttle(function () {
nextTick(() => {
try {
setTimeout(() => {
const query = uni.createSelectorQuery();
query.select('.scrollView').boundingClientRect();
query.select('.list-content').boundingClientRect();
query.exec((res) => {
const scrollViewHeight = res[0].height;
const scrollContentHeight = res[1].height;
if (scrollContentHeight > scrollViewHeight) {
const scrolldistance = scrollContentHeight - scrollViewHeight;
scrollTop.value = scrolldistance;
}
});
}, 500);
const query = uni.createSelectorQuery();
query.select('.scrollView').boundingClientRect();
query.select('.list-content').boundingClientRect();
query.exec((res) => {
const scrollViewHeight = res[0].height;
const scrollContentHeight = res[1].height;
if (scrollContentHeight > scrollViewHeight) {
const scrolldistance = scrollContentHeight - scrollViewHeight;
scrollTop.value = scrolldistance;
}
});
} catch (err) {
console.warn(err);
}
@@ -386,14 +494,29 @@ function getUploadFile(type = 'camera') {
});
}
const handleTouchStart = (e) => {
const tipsPermisson = () => {
uni.showToast({
title: '需要授权麦克风权限才能使用语音功能',
icon: 'none',
});
};
const handleTouchStart = async (e) => {
if (!isAudioPermission.value) {
return tipsPermisson();
}
console.log('handleTouchStart');
startY.value = e.touches[0].clientY;
status.value = 'recording';
showfile.value = false;
startRecording();
$api.sleep(1000).then(() => {
scrollToBottom();
});
};
const handleTouchMove = (e) => {
console.log('handleTouchMove');
const moveY = e.touches[0].clientY;
if (startY.value - moveY > cancelThreshold) {
status.value = 'cancel';
@@ -403,40 +526,32 @@ const handleTouchMove = (e) => {
};
const handleTouchEnd = () => {
console.log('handleTouchEnd');
if (status.value === 'cancel') {
console.log('取消发送');
cancelRecording();
} else {
console.log('stopRecording');
stopRecording();
console.log('发送语音');
$api.sleep(1000).then(() => {
if (isAudioPermission.value) {
if (recognizedText.value) {
sendMessage(recognizedText.value);
} else {
$api.msg('说话时长太短');
}
}
});
}
status.value = 'idle';
};
const handleTouchCancel = () => {
console.log('handleTouchCancel');
stopRecording();
status.value = 'idle';
};
const statusText = computed(() => {
switch (status.value) {
case 'recording':
return '松手发送,上划取消';
case 'cancel':
return '松手取消';
default:
return '按住说话';
}
});
const audiowaveStyle = computed(() => {
return status.value === 'cancel'
? '#f54545'
: status.value === 'recording'
? 'linear-gradient(to right, #377dff, #9a60ff)'
: '#f1f1f1';
});
function closeGuess() {
showGuess.value = false;
}
@@ -452,7 +567,74 @@ function changeShowFile() {
function colseFile() {
showfile.value = false;
}
defineExpose({ scrollToBottom, closeGuess, colseFile });
function copyMarkdown(value) {
$api.copyText(value);
}
function userGoodFeedback(msg) {
$api.msg('该功能正在开发中,敬请期待后续更新!');
// const params = {
// dataId: msg.dataId,
// sessionId: msg.parentGroupId,
// userGoodFeedback: 'no',
// };
}
function readMarkdown(value, index) {
speechIndex.value = index;
if (speechIndex.value !== index) {
speak(value);
return;
}
if (isPaused.value) {
resume();
} else {
speak(value);
}
}
function stopMarkdown(value, index) {
pause(value);
speechIndex.value = index;
}
function refreshMarkdown(index) {
if (isTyping.value) {
$api.msg('正在生成');
} else {
const text = messages.value[index - 1].text;
sendMessageGuess(text);
}
}
const jobSearchQueries = [
'青岛有哪些薪资 12K 以上的岗位适合我?',
'青岛 3 年工作经验能找到哪些 12K 以上的工作?',
'青岛哪些公司在招聘,薪资范围在 12K 以上?',
'青岛有哪些企业提供 15K 以上的岗位?',
'青岛哪些公司在招 3-5 年经验的岗位?',
'我有三年的工作经验,能否推荐一些适合我的青岛的国企 岗位?',
'青岛国企目前在招聘哪些岗位?',
'青岛有哪些适合 3 年经验的国企岗位?',
'青岛国企招聘的岗位待遇如何?',
'青岛国企岗位的薪资水平是多少?',
'青岛哪些国企支持双休 & 五险一金完善?',
'青岛有哪些公司支持远程办公?',
'青岛有哪些外企的岗位,薪资 12K 以上的多吗?',
'青岛哪些企业在招聘 Web3.0 相关岗位?',
'青岛哪些岗位支持海外远程?薪资如何?',
'青岛招聘 AI/大数据相关岗位的公司有哪些?',
];
function changeQueries(value) {
queries.value = getRandomJobQueries(jobSearchQueries);
}
function getRandomJobQueries(queries, count = 2) {
const shuffled = queries.sort(() => 0.5 - Math.random()); // 随机打乱数组
return shuffled.slice(0, count); // 取前 count 条
}
defineExpose({ scrollToBottom, closeGuess, colseFile, changeQueries, handleTouchCancel });
</script>
<style lang="stylus" scoped>
@@ -536,11 +718,11 @@ image-margin-top = 40rpx
flex-direction: column
justify-content: flex-start
align-items: center
width: calc(100% - 88rpx)
.backlogo
width: 313rpx;
height: 190rpx;
.back-rowTitle
width: 100%;
height: 56rpx;
font-weight: bold;
font-size: 40rpx;
@@ -550,27 +732,31 @@ image-margin-top = 40rpx
.back-rowText
margin-top: 28rpx
width: 100%;
height: 144rpx;
height: 148rpx;
font-weight: 400;
font-size: 28rpx;
color: #333333;
border-bottom: 2rpx dashed rgba(0, 0, 0, 0.2);
line-height: 40rpx
// border-bottom: 2rpx dashed rgba(0, 0, 0, 0.2);
border-bottom: 2rpx solid transparent;
background-image: linear-gradient(to right, rgba(0, 0, 0, 0.2) 50%, transparent 50%);
background-size: 10rpx 2rpx; /* 调整虚线宽度和间距 */
background-repeat: repeat-x;
background-position: 0 148rpx
.back-rowh3
width: 100%;
height: 30rpx;
font-weight: 500;
font-size: 22rpx;
font-size: 28rpx;
color: #000000;
margin-top: 24rpx
margin-top: 28rpx
.back-rowmsg
width: 630rpx
margin-top: 20rpx
width: calc(100% - 32rpx)
margin-top: 24rpx
font-weight: 500;
font-size: 24rpx;
color: #333333;
color: rgba(51, 51, 51, 1);
line-height: 28rpx;
font-size: 24rpx
background: #F6F6F6;
background: rgba(246, 246, 246, 1);
border-radius: 8rpx 8rpx 8rpx 8rpx;
padding: 32rpx 18rpx;
@@ -586,6 +772,7 @@ image-margin-top = 40rpx
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
width: 100%;
}
.chat-item.self {
justify-content: flex-end;
@@ -602,6 +789,19 @@ image-margin-top = 40rpx
border-radius: 0 20rpx 20rpx 20rpx;
padding: 20rpx 20rpx 0 20rpx;
background: #F6F6F6;
.message-controll
display: flex
justify-content: space-between
align-items: center
border-top: 2rpx solid #EAEAEA
padding: 24rpx 0
margin-top: -10rpx
.controll-left
.controll-right
.controll-icon
width: 60rpx;
height: 60rpx;
.messageNull
display: none
.msg-loading{
@@ -703,6 +903,11 @@ image-margin-top = 40rpx
transition: transform 0.5s ease;
width: 38rpx;
height: 38rpx;
.btn-box-round
width: 22rpx
height: 22rpx
border-radius: 4rpx
background: #FFFFFF
.purple
background: linear-gradient( 225deg, #9E74FD 0%, #256BFA 100%);
.add-file-btn{
@@ -725,7 +930,7 @@ image-margin-top = 40rpx
text
font-size: 24rpx
font-weight: 500
color: #000000
color: rgba(0,0,0,.5)
padding-top: 8rpx
.card-img
height: 56rpx
@@ -736,38 +941,45 @@ image-margin-top = 40rpx
.area-uploadfiles
position: absolute
top: -180rpx
width: calc(100% - 40rpx)
width: calc(100% - 30rpx)
background: #FFFFFF
left: 0
padding: 10rpx 20rpx
padding: 10rpx 0 10rpx 30rpx
box-shadow: 0rpx -4rpx 10rpx 0rpx rgba(11,44,112,0.06);
.uploadfiles-scroll
height: 100%
.uploadfiles-list
height: 100%
display: flex
margin-right: 28rpx
flex-wrap: nowrap
.file-doc
display: flex
flex-direction: column
align-items: flex-start
justify-content: space-between
align-items: center
justify-content: flex-start
padding: 16rpx 20rpx 18rpx 20rpx
height: calc(100% - 40rpx)
.doc-icon
width: 60rpx
height: 76rpx
margin-right: 20rpx
.doc-con
flex: 1
width: 0
.file-uploadsend
margin: 10rpx 18rpx 0 10rpx;
margin: 10rpx 18rpx 0 0;
height: 100%
border-radius: 30rpx
font-size: 24rpx
position: relative
width: 360rpx;
min-width: 460rpx;
height: 160rpx;
border-radius: 12rpx 12rpx 12rpx 12rpx;
border: 2rpx solid #E2E2E2;
overflow: hidden
.file-del
position: absolute
right: 0
top: 0
right: 25rpx
top: 18rpx
z-index: 9
border-radius: 50%
display: flex
@@ -775,10 +987,10 @@ image-margin-top = 40rpx
justify-content: center
transform: translate(50%, -10rpx)
height: 40rpx
width: 23rpx;
height: 23rpx;
background: #FFFFFF;
border: 2rpx solid #E2E2E2;
width: 37rpx;
height: 37rpx;
background: #4B4B4B;
border: 2rpx solid #FFFFFF;
.file-del:active
background: #e8e8e8
.filename-text
@@ -787,7 +999,6 @@ image-margin-top = 40rpx
white-space: nowrap
color: #333333
font-size: 24rpx
flex: 1
font-weight: 500
max-width: 100%
.filename-size
@@ -795,18 +1006,21 @@ image-margin-top = 40rpx
text-overflow: ellipsis
white-space: nowrap
color: #7B7B7B;
flex: 1
max-width: 100%
.file-icon
height: 40rpx
width: 40rpx
.file-iconImg
height: 100%
width: 100%
border-radius: 15rpx
.filerow
display: flex
align-items: center
margin-top: 7rpx
.row-x
margin: 0 18rpx
height: 20rpx
width: 2rpx
background: rgba(226, 226, 226, .9)
.file-border
width: 160rpx;
border: 0
width: 160rpx !important;
@keyframes ai-circle {
0% {

View File

@@ -46,5 +46,3 @@ const props = defineProps({
const type = props.type;
</script>
<style></style>

View File

@@ -0,0 +1,38 @@
<template>
<view class="file-type-box">
{{ fileAbbreviation }}
</view>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
type: String,
});
const fileAbbreviation = computed(() => {
const typeMap = {
'application/pdf': 'PDF',
'application/msword': 'DOC',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX',
'application/vnd.ms-powerpoint': 'PPT',
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'PPTX',
'application/vnd.ms-excel': 'XLS',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX',
'text/markdown': 'MD',
'text/plain': 'TXT',
'text/html': 'HTML',
};
return typeMap[props.type] || 'OTHER';
});
</script>
<style scoped>
.file-type-box {
font-weight: 400;
font-size: 24rpx;
color: #7b7b7b;
line-height: 28rpx;
}
</style>

View File

@@ -328,9 +328,9 @@ function getJobList(type = 'add') {
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
if (rows.length < pageState.pageSize) {
loadmoreRef.value.change('noMore');
loadmoreRef.value?.change('noMore');
} else {
loadmoreRef.value.change('more');
loadmoreRef.value?.change('more');
}
});
}

View File

@@ -56,16 +56,12 @@
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore';
const userInfo = ref({});
const Completion = ref({});
const popup = ref(null);
onShow(() => {
userInfo.value = useUserStore().userInfo;
Completion.value = useUserStore().Completion;
});
const { userInfo, Completion } = storeToRefs(useUserStore());
function logOut() {
popup.value.open();