left value. right value. reference. const reference

本文详细解析了C++中lvalue与rvalue的概念及其区别,包括如何判断一个表达式是lvalue还是rvalue,以及这些概念在实际编程中的应用。

以下是一些转载

C++ 03 标准 3.10/1 节上说: "每一个表达式要么是一个 lvalue ,要么就是一个 rvalue 。" 应该谨记 lvalue 跟 rvalue 是针对表达式而言的,而不是对象。

lvalue 是指那些单一表达式结束之后依然存在的持久对象。例如: obj,*ptr, prt[index], ++x 都是 lvalue。

rvalue 是指那些表达式结束时(在分号处)就不复存在了的临时对象。例如: 1729 , x + y , std::string("meow") , 和 x++ 都是 rvalue。

注意 ++x 和 x++ 的区别。当我们写 int x = 0; 时, x 是一个 lvalue,因为它代表一个持久对象。 表达式 ++x 也是一个 lvalue,它修改了 x 的值,但还是代表原来那个持久对象。然而,表达式 x++ 却是一个 rvalue,它只是拷贝一份持久对象的初值,再修改持久对象的值,最后返回那份拷贝,那份拷贝是临时对象。 ++x 和 x++ 都递增了 x,但 ++x 返回持久对象本身,而 x++ 返回临时拷贝。这就是为什么 ++x 之所以是一个 lvalue,而 x++ 是一个 rvalue。 lvalue 与 rvalue 之分不在于表达式做了什么,而在于表达式代表了什么(持久对象或临时产物)。

另一个培养判断一个表达式是不是 lvalue 的直觉感的方法就是自问一下"我能不能对表达式取址?",如果能够,那就是一个 lvalue;如果不能,那就是 一个 rvalue。 例如:&obj , &*ptr , &ptr[index] , 和 &++x 都是合法的(即使其中一些例子很蠢),而 &1729 , &(x + y) , &std::string("meow") , 和 &x++ 是不合法的。为什么这个方法凑效?因为取址操作要求它的"操作数必须是一个 lvalue"(见 C++ 03 5.3.1/2)。为什么要有那样的规定?因为对一个持久对象取址是没问题的,但对一个临时对象取址是极端危险的,因为临时对象很快就会被销毁(译注:就像你有一个指向某个对象的指针,那个对象被释放了,但你还在使用那个指针,鬼知道这时候指针指向的是什么东西)。

前面的例子不考虑操作符重载的情况,它只是普通的函数调用语义。"一个函数调用是一个 lvalue 当且仅当它返回一个引用"(见 C++ 03 5.2.2/10)。因此,给定语句 vercor<int> v(10, 1729); , v[0] 是一个 lvalue,因为操作符 []() 返回 int& (且 &v[0] 是合法可用的); 而给定语句 string s("foo");和 string t("bar");,s + t 是一个rvalue,因为操作符 +() 返回 string(而 &(s + t) 也不合法)。

lvalue 和 rvalue 两者都有非常量(modifiable,也就是说non-const)与常量(const )之分。举例来说:

string one("cute");

const string two("fluffy");

string three() { return "kittens"; }

const string four() { return "are an essential part of a healthy diet"; }

one; // modifiable lvalue

two; // const lvalue

three(); // modifiable rvalue

four(); // const rvalue

Type& 可绑定到非常量 lvalue (可以用这个引用来读取和修改原来的值),但不能绑定到 const lvalue,因为那将违背 const 正确性;也不能把它绑定到非常量 rvalue,这样做极端危险,你用这个引用来修改临时对象,但临时对象早就不存在了,这将导致难以捕捉而令人讨厌的 bug,因此 C++ 明智地禁止这这么做。(我要补充一句:VC 有一个邪恶的扩展允许这么蛮干,但如果你编译的时候加上参数 /W4 ,编译器通常会提示警告"邪恶的扩展被激活了")。也不能把它绑定到 const ravlue,因为那会是双倍的糟糕。(细心的读者应该注意到了我在这里并没有谈及模板参数推导)。

const Type& 可以绑定到: 非常量 lvalues, const lvalues,非常量 rvalues 以及 const values。(然后你就可以用这个引用来观察它们)

引用是具名的,因此一个绑定到 rvalue 的引用,它本身是一个 lvalue(没错!是 L)。(因为只有 const 引用可以绑定到 rvalue,所以它是一个 const lvalue)。这让人费解,(不弄清楚的话)到后面会更难以理解,因此我将进一步解释。给定函数 void observe(const string& str), 在 observe()'s 的实现中, str 是一个 const lvalue,在 observe() 返回之前可以对它取址并使用那个地址。这一点即使我们通过传一个 rvalue 参数来调用 observe()也是成立的 ,就像上面的 three() 和 four()。也可以调用 observe("purr"),它构建一个临时 string 并将 str 绑定到那个临时 string。three() 和 foure() 的返回对象是不具名的,因此他们是 rvalue,但是在 observe()中,str 是具名的,所以它是一个 lvalue。正如前面我说的" lvalue 跟 rvalue 是针对表达式而言的,而不是对象"。当然,因为 str 可以被绑定到一个很快会被销毁的临时对象,所以在 observe() 返回之后我们就不应该在任何地方保存这个临时对象的地址。

你有没有对一个绑定到 rvalue 的 const 引用取址过么?当然,你有过!每当你写一个带自赋值检查的拷贝赋值操作符: Foo& operator=(const Foo& other), if( this != &other) { copy struff;}; 或从一个临时变量来拷贝赋值,像: Foo make_foo(); Foo f; f = make_foo(); 的时候,你就做了这样的事情。

这个时候,你可能会问"那么非常量 rvalues 跟 const rvalues 有什么不同呢?我不能将 Type& 绑定到非常量 rvalue 上,也不能通过赋值等操作来修改 rvalue,那我真的可以修改它们?" 问的很好!在 C++ 98/03 中,这两者存在一些细微的差异: non-constrvalues 可以调用 non-const 成员函数。 C++ 不希望你意外地修改临时对象,但直接在non-const rvalues上调用 non-const 成员函数,这样做是很明显的,所以这是被允许的。在 C++ 0x中,答案有了显著的变化,它能用来实现 move 语意。

恭喜!你已经具备了我所谓的"lvalue/rvalue 观",这样你就能够一眼就判断出一个表达式到底是 lvalue 还是 rvalue。再加上你原来对 const 的认识,你就能完全理解为什么给定语句 void mutate(string& ref) 以及前面的变量定义, mutate(one) 是合法的,而 mutate(two), mutate(three()), mutate(four()), mutate("purr") 都是不合法的。如果你是 C++ 98/03 程序员,你已经可以分辨出这些调用中的哪些是合法的,哪些是不合法的;是你的"本能直觉",而不是你的编译器,告诉你 mutate(three()) 是假冒的。你对 lvalue/rvalue 的新认识让你明确地理解为什么 three() 是一个 rvalue,也知道为什么非常量引用不能绑定到右值。知道这些有用么?对语言律师而言,有用,但对普通程序员来说并不见得。毕竟,你如果不理解关于 lvalues 和 rvalues 一切就要领悟这个还隔得远呢。但是重点来了:与 C++ 98/03 相比, C++ 0x 中的 lvalue 和 rvalue 有着更广泛更强劲的含义(尤其是判断表达式是否是 modifiable / const 的 lvalue/rvalue,并据此做些处理)。要有效地使用 C++ 0x,你也需具备对 lvalue/rvalue 的理解。现在万事具备,我们能继续前行了。

<template> <el-container class="footer"> <div class="left"> <div class="logo"> <img :src="musicStore.getCurrentMusicCover" alt="" class="cover-image"> </div> <div class="name"> <!-- 添加 title 属性,鼠标悬停时显示完整歌名 --> <div style="margin-left:8px;text-align: left; overflow: hidden; white-space: nowrap;" :title="musicStore.getCurrentMusicName"> <span class="song-name">{{ musicStore.getCurrentMusicName }}</span> <span style="color: #ccc;font-size: 14px; display: block;">--{{ musicStore.getCurrentMusicAuthor }}</span> </div> </div> </div> <div class="center"> <div class="audio"> <audio ref="audioPlayer" :src="url" autoplay controls class="audio-player" @timeupdate="updateProgress" @loadedmetadata="onLoadedMetadata" @play="onPlay" @pause="onPause" @ended="onSongEnded" ></audio> </div> <div class="player-controls"> <!-- <i class="iconfont icon-like" :class="{ active: isLiked }" @click="toggleLike"></i> --> <i class="iconfont icon-shangyishou" @click="playPrev"></i> <div class="play-btn" :class="{ playing: isPlaying }" @click="togglePlay"> <i :class="isPlaying ? 'iconfont icon-bofang1' : 'iconfont icon-zantinganniu'"></i> </div> <i class="iconfont icon-xiayishou" @click="playNext"></i> <i class="iconfont" :class="playModeIcon" @click="togglePlayMode"></i> </div> <div class="progress-bar"> <span>{{ formatTime(currentTime) }}</span> <input type="range" min="0" :max="duration" v-model="currentTime" @input="seek" /> <span>{{ formatTime(duration) }}</span> </div> </div> <div class="right"> <el-popover placement="top" :width="500" trigger="click"> <template #reference> <i class="iconfont icon-bofangliebiao" @click=""></i> </template> <div style="max-height: 400px; overflow-y: auto;"> <el-table :data="musicList" > <el-table-column width="250" property="name" label="歌曲" /> <el-table-column width="100" property="author" label="歌手" /> <el-table-column width="100" label="操作" v-slot="scope"> <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button> </el-table-column> </el-table> </div> </el-popover> <i class="iconfont icon-geci24"></i> </div> </el-container> </template> <script setup lang="ts"> import { ref, computed, onMounted, watch, onUnmounted } from 'vue'; import { GetMusicUrl } from '@/servers/musicApi'; import { useMusicStore } from '@/stores/music'; import useFunc from '@/hooks/useFunc'; import { ElMessage } from 'element-plus'; import { type MusicItem } from '@/types/index' import axios from 'axios'; import { deleteSong } from '@/servers/userApi.ts'; import { useRoute } from 'vue-router'; import { getSonglistById, getSongById, getSongsInPlaylist } from '@/servers/userApi' let route = useRoute(); const handleDelete = async (row: MusicItem) => { try { await deleteSong(row.id); ElMessage.success('删除成功'); getMusicInfo(); } catch (error) { ElMessage.error('删除失败'); } }; // let { getAllMusic } = useFunc(); let musicStore = useMusicStore(); let musicid = computed(() => musicStore.getCurrentMusicId); let url = ref(''); let musicList = ref<MusicItem[]>([]); const audioPlayer = ref<HTMLAudioElement | null>(null); let currentMusicIndex = ref(0); const isPlaying = ref(false); const playMode = ref('order'); // 'order' | 'random' const isLiked = ref(false); const currentTime = ref(0); const duration = ref(0); let userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); console.log('userInfo', userInfo); // let userName = userInfo.userName || '未登录'; let userId = userInfo.id || 0; // let passWord = userInfo.passWord || ''; async function getMusicInfo() { try { let songlistname = ''; if (route.query.songlistname) { songlistname = String(route.query.songlistname); } else { // 从路径中获取 const pathParts = route.path.split('/'); if (pathParts.length > 1) { songlistname = pathParts[1]; } } console.log('获取的歌单名:', songlistname); if (!songlistname) { throw new Error('未找到歌单名'); } // 获取用户的所有歌单 const userSonglists = await getSonglistById(userId); // 查找匹配的歌单 const targetSonglist = userSonglists.find((songlist:any) => songlist.songlistname === songlistname ); if (!targetSonglist) { throw new Error(`未找到名为 "${songlistname}" 的歌单`); } // 获取歌单中的歌曲 const songs = await getSongsInPlaylist(targetSonglist.songlistid); // 更新音乐列表和当前索引 musicList.value = songs; currentMusicIndex.value = 0; ElMessage.success('获取歌单成功'); console.log('获取的歌曲列表:', musicList.value); } catch (error: any) { console.error('获取音乐列表失败:', error.message); ElMessage.error('获取音乐列表失败: ' + error.message); } } getMusicInfo(); // onMounted(() => { // console.log(musicList.value); // }); let updateMusicUrl = async () => { url.value = await GetMusicUrl(String(musicid.value)); // console.log('audio src:', url.value); setTimeout(() => { audioPlayer.value?.play().catch(err => { console.error('自动播放失败:', err); }); }, 100); }; watch(musicid, async () => { await updateMusicUrl(); }); console.log(musicStore.getCurrentMusicId, musicStore.getCurrentMusicName, musicStore.getCurrentMusicAuthor, musicStore.getCurrentMusicCover, musicStore.getCurrentCurrentTime, musicStore.getCurrentDuration, musicStore.getCurrentIsPlaying, musicStore.getCurrentLikeFlag, ); updateMusicUrl(); const updateProgress = () => { // console.log(audioPlayer.value?.currentTime, audioPlayer.value?.duration); if (audioPlayer.value) { currentTime.value = audioPlayer.value.currentTime; duration.value = audioPlayer.value.duration || 0; // musicStore.updateCurrentTime(audioPlayer.value.currentTime); // musicStore.updateDuration(audioPlayer.value.duration || 0); musicStore.updateCurrentTime(currentTime.value); } }; //保持原来的播放进度 const onLoadedMetadata = () => { if (audioPlayer.value) { duration.value = audioPlayer.value.duration || 0; // musicStore.updateDuration(audioPlayer.value.duration || 0); musicStore.updateDuration(duration.value); } }; const onPlay = () => { isPlaying.value = true; // musicStore.updatePlayingState(true); musicStore.updatePlayingState(isPlaying.value); }; const onPause = () => { isPlaying.value = false; // musicStore.updatePlayingState(false); musicStore.updatePlayingState(isPlaying.value); }; const onSongEnded = () => { if (musicList.value.length === 0) return; if (playMode.value === 'random') { playRandom(); } else { playNext(); } }; const playModeIcon = computed(() => playMode.value === 'order' ? 'iconfont icon-shunxubofang' : 'iconfont icon-suijibofang'); function togglePlay() { if (audioPlayer.value) { if (isPlaying.value) { audioPlayer.value.pause(); } else { audioPlayer.value.play(); } } } function playPrev() { if (musicList.value.length === 0) return; if (playMode.value === 'random') { playRandom(); return; } currentMusicIndex.value = (currentMusicIndex.value - 1 + musicList.value.length) % musicList.value.length; playByIndex(currentMusicIndex.value); } function playNext() { if (musicList.value.length === 0) return; if (playMode.value === 'random') { playRandom(); return; } currentMusicIndex.value = (currentMusicIndex.value + 1) % musicList.value.length; playByIndex(currentMusicIndex.value); } function playRandom() { let idx = Math.floor(Math.random() * musicList.value.length); if (idx === currentMusicIndex.value && musicList.value.length > 1) { idx = (idx + 1) % musicList.value.length; } currentMusicIndex.value = idx; playByIndex(idx); } function playByIndex(idx: number) { const music = musicList.value[idx]; musicStore.setCurrentMusicId(music.id); musicStore.setCurrentMusicName(music.name); musicStore.setCurrentMusicAuthor(music.author || ''); musicStore.setCurrentMusicCover(music.img1v1Url || ''); updateMusicUrl(); setTimeout(() => { audioPlayer.value?.play(); }, 100); } function togglePlayMode() { playMode.value = playMode.value === 'order' ? 'random' : 'order'; } // function toggleLike() { // isLiked.value = !isLiked.value; // // 这里可以加收藏逻辑 // } function seek(e: Event) { const val = Number((e.target as HTMLInputElement).value); if (audioPlayer.value) { audioPlayer.value.currentTime = val; currentTime.value = val; } } function formatTime(sec: number) { const m = Math.floor(sec / 60).toString().padStart(2, '0'); const s = Math.floor(sec % 60).toString().padStart(2, '0'); return `${m}:${s}`; } onMounted(() => { if (musicStore.getCurrentCurrentTime > 0) { setTimeout(() => { audioPlayer.value?.currentTime = musicStore.getCurrentCurrentTime; currentTime.value = musicStore.getCurrentCurrentTime; }, 100); } }) </script> <style scoped> .footer { width: 100%; display: flex; align-items: center; border-top: 1px solid #ccc; height: 80px; /* 固定footer高度 */ box-sizing: border-box; padding: 0 20px; background-color: #f5f5f5; } .left { display: flex; align-items: center; width: 25%; height: 100%; } .logo { width: 60px; height: 60px; margin-right: 15px; } .cover-image { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; } .name { display: flex; flex-direction: column; justify-content: center; height: 100%; width: calc(100% - 100px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .song-name { font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; /* 添加初始位置 */ transform: translateX(0); animation: marquee 10s linear infinite; } /* .name:hover .song-name { animation: marquee 10s linear infinite; } */ @keyframes marquee { 0% { transform: translateX(0); } 100% { /* 移动到歌名完全显示 */ transform: translateX(calc(-100% + 100% / 2)); } } .center { width: 50%; height: 100%; display: flex; align-items: center; justify-content: center; } .audio { width: 100%; display: flex; justify-content: center; } .audio-player { width: 100%; max-width: 100%; height: 40px; /* 固定音频控件高度 */ /* background-color: #ff3a3a; */ } .right { width: 25%; height: 100%; display: flex; align-items: center; justify-content: flex-end; } .right i { margin: 0 10px; font-size: 24px; } .player-controls { display: flex; align-items: center; justify-content: center; gap: 32px; margin: 10px 0; } .player-controls .iconfont { font-size: 28px; color: #666; cursor: pointer; transition: color 0.2s; } .player-controls .iconfont.active, .player-controls .iconfont:hover { color: #c62f2f; } .play-btn { width: 60px; height: 60px; /* background: #ff3a3a; */ border-radius: 50%; display: flex; align-items: center; justify-content: center; /* box-shadow: 0 2px 8px rgba(198,47,47,0.15); */ cursor: pointer; transition: background 0.2s; } .play-btn .iconfont { color: #ff3a3a; font-size: 50px; } .progress-bar { display: flex; align-items: center; gap: 10px; margin: 0 20px; } .progress-bar input[type=range] { flex: 1; accent-color: #ff3a3a; height: 4px; } .progress-bar span { font-size: 14px; color: #666; width: 45px; text-align: center; } </style>帮我实现在播放音乐时,点击其他页面,播放器的进度条不会重置这个功能
06-26
<template> <div class="vote-container"> <!-- 消息提示区域 (添加在顶部) --> <div v-if="showAlert" :class="['alert', alertType]"> {{ alertMessage }} </div> <!-- 投票人信息 --> <div class="voter-info" v-if="voterName && voterIdCard"> <p>投票人:{{ voterName }}</p> <p>身份证:{{ formattedIdCard }}</p> </div> <!-- 投票统计信息 --> <div class="stats"> <div class="stat"> <h3>经理投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.A / 5) * 100 + '%' }"></div> </div> <p>{{ votes.A }} / 5</p> </div> <div class="stat"> <h3>厂长投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.B / 5) * 100 + '%' }"></div> </div> <p>{{ votes.B }} / 5</p> </div> <div class="stat"> <h3>副厂长投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.C / 15) * 100 + '%' }"></div> </div> <p>{{ votes.C }} / 15</p> </div> <div class="stat"> <h3>总票数</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (totalVotes / 25) * 100 + '%' }"></div> </div> <p>{{ totalVotes }} / 25</p> </div> </div> <!-- 被投票人列表 --> <div class="voters-grid"> <div v-for="voter in voters" :key="voter.id" class="voter-card"> <h4>{{ voter.name }}</h4> <p class="voter-id">ID: {{ voter.id }}</p> <div class="vote-options"> <button @click="castVote(voter, 'A')" :disabled="!canVote(voter, 'A')" :class="{ 'selected': voter.vote === 'A', 'disabled': !canVote(voter, 'A') }" > 经理 </button> <button @click="castVote(voter, 'B')" :disabled="!canVote(voter, 'B')" :class="{ 'selected': voter.vote === 'B', 'disabled': !canVote(voter, 'B') }" > 厂长 </button> <button @click="castVote(voter, 'C')" :disabled="!canVote(voter, 'C')" :class="{ 'selected': voter.vote === 'C', 'disabled': !canVote(voter, 'C') }" > 副厂长 </button> </div> </div> </div> <!-- 操作按钮 --> <div class="action-buttons"> <button @click="submitVotes" :disabled="!hasSelectedCandidates || isSubmitting">提交投票</button> <button @click="resetVotes" :disabled="isSubmitting">重置投票</button> </div> </div> </template> <script setup> import { ref, reactive, computed } from 'vue'; import { useRoute } from 'vue-router'; import { onMounted } from 'vue' const voters = ref([]); //候选人 const activeSurvey = ref({ id: null, //投票ID bt: '', // 标题 qydcl: '', dclx: '', //投票类型 tffs: '' }); console.log() // 添加消息提示状态 const alertMessage = ref(''); const showAlert = ref(false); const alertType = ref(''); // 'success' 或 'error' // 安全序列化函数 function safeStringify(obj) { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { // 检测到循环引用,返回占位符或跳过 return "[Circular Reference Removed]"; } seen.add(value); } return value; }); } // onMounted生命周期钩子 onMounted(async () => { // 从sessionStorage获取投票人信息并立即清除 const voterInfo = sessionStorage.getItem('voterInfo'); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; sessionStorage.removeItem('voterInfo'); } // 加载候选人数据 voters.value = await fetchCandidates(); }); // 获取路由信息 const route = useRoute(); // 添加用于存储投票人信息的变量 const voterName = ref(''); const voterIdCard = ref(''); // 格式化身份证显示(安全脱敏) const formattedIdCard = computed(() => { if (!voterIdCard.value) return ''; // 显示前6位和后4位,中间用*代替 return voterIdCard.value.substring(0, 6) + '******' + voterIdCard.value.substring(voterIdCard.value.length - 4); }); onMounted(() => { // 从sessionStorage获取数据并立即清除 const voterInfo = sessionStorage.getItem('voterInfo'); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; // 关键:立即清除存储防止数据残留 sessionStorage.removeItem('voterInfo'); } }); //获取候选人明细 const fetchCandidates = async () => { try { const response = await fetch('/api/wechat/getInvestigate', { method: 'POST', body: JSON.stringify({ id: '9', dcl: '123' }) }); console.log('API响应:', response); const result = await response.json(); if (!result || !result.root) throw new Error('无效API响应'); // 提取候选人数据 const candidateArray = []; let idCounter = 1; // 自增计数器,名称序号 result.root.forEach(rootItem => { if (!rootItem.childEntList) return; rootItem.childEntList.forEach(candidate => { if (!candidate.dcbt || !candidate.dcxbt) return; candidateArray.push({ originalid: candidate.dcbt, name: candidate.dcxbt, vote: null, id:idCounter++, tmlx: candidate.tmlx, // 投票类型 }); // console.log('投票类型tmlx',candidate.tmlx) // console.log('调查标题dcbt',candidate.dcbt) }); }); return candidateArray; } catch (error) { console.error('获取候选人失败:', error); return []; // 返回空数组保持安全 } }; // 新增计算属性 - 获取已选择的候选人 const selectedCandidates = computed(() => { return voters.value .filter(v => v.vote) // 只过滤已投票的 .map(v => ({ id: v.originalid, // 候选人原始ID voteType: v.vote, // 投票类型(A/B/C) voteValue: v.vote === 'A' ? 17 : // 转换类型值 v.vote === 'B' ? 18 : 19, name: v.name, // 候选人姓名 tmlx: v.tmlx // 原始类型 })); }); // 检查是否有选择的候选人 const hasSelectedCandidates = computed(() => { return selectedCandidates.value.length > 0; }); // 投票统计 const votes = reactive({ A: 0, B: 0, C: 0 }); // 计算总票数 const totalVotes = computed(() => { return votes.A + votes.B + votes.C; }); // 投票方法 const canVote = (voter, type) => { // 情况1:用户取消当前选择的类型(总是允许) if (voter.vote === type) return true; // 情况2:用户从其他类型转换到当前类型 if (voter.vote && voter.vote !== type) { if (type === 'A' && votes.A >= 5) return false; if (type === 'B' && votes.B >= 5) return false; if (type === 'C' && votes.C >= 15) return false; } // 情况3:用户首次投票 if (!voter.vote) { if (type === 'A' && votes.A >= 5) return false; if (type === 'B' && votes.B >= 5) return false; if (type === 'C' && votes.C >= 15) return false; if (totalVotes.value >= 25) return false; } return true; }; // 投票方法 const castVote = (voter, type) => { // 如果已投票且点击相同类型,取消投票 if (voter.vote === type) { voter.vote = null; votes[type]--; return; } // 如果之前有投票,先取消 if (voter.vote !== null) { votes[voter.vote]--; } // 投新票 voter.vote = type; votes[type]++; }; //投票人信息 // 添加投票人信息数据模型 const voterInfo = reactive({ name: '', idNumber: '' }); // // 添加基本信息验证 // const isValid = computed(() => { // return voterInfo.name.trim() !== '' && // voterInfo.idNumber.trim() !== '' && // /^\d{17}[\dXx]$/.test(voterInfo.idNumber); // }); // 提交投票 // 防止重复提交 const isSubmitting = ref(false); // 提交投票到API const submitVotes = async () => { // 添加防御性检查 if (!hasSelectedCandidates.value) { showMessage('请先选择候选人', 'error'); return; } // 防止重复提交 if (isSubmitting.value) return; isSubmitting.value = true; try { // 按接口规范构建JSON数据结构 // const requestData = { // mainData: [ // { fieldName: "bt", fieldValue: activeSurvey.value.bt }, // { fieldName: "tprxm", fieldValue: voterName }, // { fieldName: "tprsfz", fieldValue: voterIdCard }, // { fieldName: "mb", fieldValue: activeSurvey.value.id }, // { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, // { fieldName: "tffs", fieldValue: activeSurvey.value.tffs }, // ], // workflowRequestTableRecords: selectedCandidates.value.map(candidate => ({ // workflowRequestTableFields: [ // { // fieldName: "dcbt", // fieldValue: candidate.originalid // }, // { // fieldName: "tmlx", // fieldValue: candidate.tmlx // }, // { // fieldName: "dcxx", // fieldValue: candidate.voteValue // 使用转换后的值 // } // ] // })) // }; const mainData = [ { fieldName: "bt", fieldValue: activeSurvey.value.bt }, { fieldName: "tprxm", fieldValue: voterName.value }, { fieldName: "tprsfz", fieldValue: voterIdCard.value }, { fieldName: "mb", fieldValue: activeSurvey.value.id }, { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, { fieldName: "tffs", fieldValue: activeSurvey.value.tffs } ]; const workflowRequestTableRecords = selectedCandidates.value.map(candidate => ({ workflowRequestTableFields: [ { fieldName: "dcbt", fieldValue: candidate.dcbt }, { fieldName: "tmlx", fieldValue: candidate.tmlx }, { fieldName: "dcxx", fieldValue: candidate.vote === 'A' ? 17 : candidate.vote === 'B' ? 18 : 19 } ] })); const requestBody = { requestName: activeSurvey.value.bt, // 投票标题 workflowId: 118, // 固定工作流ID mainData, workflowRequestTableRecords }; console.log('标题bt', activeSurvey.value.bt ) console.log('mb', activeSurvey.value.id ) console.log('tmlx', activeSurvey.value.dclx ) // 发送POST请求 const response = await fetch('/api/wechat/addInvestigateWorkflow1', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: safeStringify(requestBody)// 使用安全序列化,避免重复引用 }); const result = await response.json(); // ===== 核心逻辑:根据 api_status 判断 ===== if (result.api_status === true) { // 成功处理 const successMsg = result.msg || '投票提交成功!'; showMessage('投票提交成功!', 'success'); // 存储已投票标识 localStorage.setItem('voted_' + voterIdCard.value, 'true'); } else { // 处理错误情况 if (result.msg === '你已提交或不满足提交条件') { showMessage(result.msg, 'error'); // 特殊处理:用户已投票,存储标识防止重复提交 localStorage.setItem('voted_' + voterIdCard.value, 'true'); } else { // 其他错误情况 const errorMsg = result.msg ? `提交失败: ${result.msg}` : '未知错误,请稍后重试'; showMessage(errorMsg, 'error'); } } } catch (error) { console.error('网络请求失败:', error); showMessage('网络错误,请检查连接后重试', 'error'); } finally { isSubmitting.value = false; } }; // 重置投票 const resetVotes = () => { if (confirm('确定要重置所有投票吗?')) { voters.value.forEach(voter => { voter.vote = null; }); votes.A = 0; votes.B = 0; votes.C = 0; voterInfo.name = ''; voterInfo.idNumber = ''; } }; //后台响应信息showMessage const showMessage = (message, type = 'error') => { // 更新消息提示状态 alertMessage.value = message; showAlert.value = true; alertType.value = type; // 错误提示停留3秒,成功提示停留2秒 const timeout = type === 'error' ? 3000 : 2000; setTimeout(() => { showAlert.value = false; }, timeout); }; </script> <style scoped> /* 移动端垂直布局 */ @media (max-width: 480px) { .input-group { flex-direction: column; } } /* 平板/桌面端水平布局 */ @media (min-width: 768px) { .input-group { flex-direction: row; } } /* 消息提示样式 */ .alert { padding: 15px; margin-bottom: 20px; border-radius: 4px; text-align: center; position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 1000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); min-width: 300px; opacity: 0.95; } .alert.error { background-color: #ffebee; color: #b71c1c; border: 1px solid #ffcdd2; } .alert.success { background-color: #e8f5e9; color: #1b5e20; border: 1px solid #c8e6c9; } .vote-container { max-width: 1200px; margin: 0 auto; padding: 20px; } .stats { display: flex; justify-content: space-between; margin-bottom: 30px; background: #f5f7fa; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .stat { flex: 1; text-align: center; padding: 0 15px; } .progress { height: 20px; background: #e0e0e0; border-radius: 10px; margin: 10px 0; overflow: hidden; } .progress-bar { height: 100%; background: #3498db; transition: width 0.3s; } .voters-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 20px; } .voter-card { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s; } .voter-card:hover { transform: translateY(-5px); } .voter-id { color: #777; font-size: 0.9rem; margin-bottom: 15px; } .vote-options { display: flex; justify-content: space-between; } .vote-options button { flex: 1; margin: 0 5px; padding: 8px 0; border: none; border-radius: 4px; cursor: pointer; transition: all 0.2s; } .vote-options button:not(.selected):hover { opacity: 0.9; transform: scale(1.05); } .vote-options button:first-child { background: #ff6b6b; color: white; } .vote-options button:nth-child(2) { background: #4ecdc4; color: white; } .vote-options button:last-child { background: #ffd166; color: white; } .selected { border: 2px solid #2c3e50 !important; font-weight: bold; box-shadow: 0 0 2 rgba(61, 60, 60, 0.5); } .disabled { opacity: 0.5 !important; cursor: not-allowed !important; } .action-buttons { margin-top: 30px; display: flex; justify-content: center; gap: 20px; } .action-buttons button { padding: 12px 30px; border: none; border-radius: 6px; cursor: pointer; font-size: 1rem; font-weight: 600; transition: all 0.2s; } .action-buttons button:first-child { background: #3498db; color: white; } .action-buttons button:first-child:disabled { background: #bdc3c7; cursor: not-allowed; } .action-buttons button:last-child { background: #e74c3c; color: white; } .action-buttons button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } </style>代码中 const mainData = [ { fieldName: "bt", fieldValue: activeSurvey.value.bt }, { fieldName: "tprxm", fieldValue: voterName.value }, { fieldName: "tprsfz", fieldValue: voterIdCard.value }, { fieldName: "mb", fieldValue: activeSurvey.value.id }, { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, { fieldName: "tffs", fieldValue: activeSurvey.value.tffs } ]; const workflowRequestTableRecords = selectedCandidates.value.map(candidate => ({ workflowRequestTableFields: [ { fieldName: "dcbt", fieldValue: candidate.dcbt }, { fieldName: "tmlx", fieldValue: candidate.tmlx }, { fieldName: "dcxx", fieldValue: candidate.vote === 'A' ? 17 : candidate.vote === 'B' ? 18 : 19 } ] })); const requestBody = { requestName: activeSurvey.value.bt, // 投票标题 workflowId: 118, // 固定工作流ID mainData, workflowRequestTableRecords }; console.log('标题bt', activeSurvey.value.bt ) console.log('mb', activeSurvey.value.id ) console.log('tmlx', activeSurvey.value.dclx )这段代码点击提交打印出来的 bt,mb,tmlx都是空的
06-11
<template> <el-popover placement="bottom-start" trigger="click" width="200px"> <el-tabs v-model="activeName"> <el-tab-pane label="字段选择" name="fieldsChosen"> <el-row align="middle" justify="space-between"> <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange" >全部</el-checkbox > <el-button size="small" @click="reset"> 重置 </el-button> </el-row> <el-checkbox-group v-model="checkedColumns" @change="handleCheckedChange"> <el-scrollbar height="200px"> <vue-draggable :list="customTableData" ghost-class="ghost" chosen-class="chosen-class" animation="300" @start="onStart" @end="onEnd" item-key="index" > <template #item="{ element }"> <el-checkbox style="width: 100%" :label="element.title" :value="element.title" :disabled="element.alwaysShow" > {{ element.title }} </el-checkbox> </template> </vue-draggable> </el-scrollbar> </el-checkbox-group> </el-tab-pane> <el-tab-pane label="调整列宽" name="fieldsWidth"> <el-row align="middle" justify="space-between"> <div /> <el-button size="small" @click="reset"> 重置 </el-button> </el-row> <el-scrollbar height="200px"> <vue-draggable :list="customTableData" ghost-class="ghost" chosen-class="chosen-class" animation="300" @start="onStart" @end="onEnd" item-key="index" > <template #item="{ element, index }"> <div class="element"> <span class="title">{{ element.title }}</span> <el-slider class="width" v-model="element.width" v-bind:debounce="500" @change="debounceSliderChange(element.width, index)" size="small" :max="1000" :min="50" /> </div> </template> </vue-draggable> </el-scrollbar> </el-tab-pane> </el-tabs> <template #reference> <div> <el-tooltip effect="dark" content="列设置" placement="top"> <el-button plain type="primary" size="small"> <el-icon><i-ep-setting /></el-icon> </el-button> </el-tooltip ></div> </template> </el-popover> </template> <script setup lang="ts" name="CustomTableSetting"> import VueDraggable from 'vuedraggable' import useStore from '@/store' import { CostomTableStore } from '#/store' const props = defineProps({ /** 列表名 */ tableName: { type: String as PropType<string & keyof CostomTableStore>, default: '', }, filter: { type: Array as PropType<string[]>, default: () => [], }, }) const emits = defineEmits<{ (e: 'reset', ...args: any[]): void }>() const { customTableStore } = useStore() // 选项值 const checkedColumns = ref<string[]>([]) const activeName = ref('fieldsChosen') const isIndeterminate = ref(false) const checkAll = ref(false) // 列表数据 const customTableData = computed(() => { let data = customTableStore.get(props.tableName) checkedColumns.value = [] if (data) { if (props.filter.length > 0) { data = data.filter(it => !props.filter.includes(it.dataKey as string)) } data.forEach((it, index) => { it.width = it.width ? parseInt(it.width) : 120 it.index = index + 1 if (it.show && it.title) { checkedColumns.value.push(it.title) } }) } return data ?? [] }) const debounceSliderChange = useDebounceFn((value, index) => { customWidthChange(value, index) }, 500) onMounted(() => { console.log('customTableData.value', customTableData.value) checkAll.value = customTableData.value.every(it => it.show) isIndeterminate.value = !checkAll.value }) function customWidthChange(width: number, index: number) { // customTableStore.reset(props.tableName!) customTableData.value[index].width = width customTableStore.save(props.tableName, customTableData.value) } function onStart() { //console.log('开始拖拽') } //拖拽结束的事件 function onEnd() { customTableStore.save(props.tableName!, customTableData.value) } // 全选 function handleCheckAllChange(val: boolean) { if (val) { customTableData.value.forEach(it => { if (!it.alwaysShow) { it.show = true } }) checkAll.value = true isIndeterminate.value = false } else { customTableData.value.forEach(it => { if (!it.alwaysShow) { it.show = false } }) checkAll.value = false isIndeterminate.value = true } customTableStore.save(props.tableName!, customTableData.value) } function handleCheckedChange(value: string[]) { // 选中状态变化 保存 checkedColumns.value = value customTableData.value.forEach(it => { it.show = typeof it.title !== 'undefined' && value.includes(it.title) }) customTableStore.save(props.tableName!, customTableData.value) const checkedCount = value.length checkAll.value = checkedCount === customTableData.value.length isIndeterminate.value = checkedCount > 0 && checkedCount < customTableData.value.length } // 重置 function reset() { console.log('123') customTableStore.reset(props.tableName!) checkAll.value = customTableData.value.every(it => it.show) isIndeterminate.value = !checkAll.value // emits('reset') } defineExpose({ customWidthChange }) </script> <style scoped lang="scss"> .ghost { border: solid 1px var(--el-color-primary); } .chosen-class { background-color: #f1f1f1; } .element { display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 10px; .title { flex: 6; } .width { flex: 4; margin-right: 10px; margin-left: 10px; } } </style> 第一次更改选中状态或者调整列宽重置不能恢复默认,第二次就可以是为什么
最新发布
09-11
<template> <div class="flex flex-col h-full overflow-hidden" v-loading="loading"> <div class="flex-1 h-full option-container"> <div class="container"> <el-form :model="formData" label-width="120px" class="w-full" ref="formRef" > <div class="tablle-top-view" v-if="step == 1"> <table class="mt-7" :class="{ 'disabled-style': !is_show }" :style="{ backgroundColor: !is_show ? '#fff' : '#fffdec' }" > <tbody> <tr class="flex"> <td class="flex-1"> <div class="label">领用单号</div> <div class="title">{{ formData.order_no }}</div> </td> <td class="flex-1"> <div class="label">创建日期</div> <div class="title">{{ formData.create_time }}</div> </td> <td class="flex-1"> <div class="label">门店</div> <div class="title">{{ formData.store }}</div> </td> </tr> <tr class="flex"> <td class="flex-1"> <div class="label">领用人</div> <div class="title">{{ formData.receive_name }}</div> </td> <td class="flex-1"> <div class="label">领入库房</div> <div class="title"> {{ formData.in_repertory_name }} </div> </td> <td class="flex-1"> <div class="label">领出库房</div> <div class="title"> {{ formData.out_repertory_name }} </div> </td> </tr> <tr> <td class="flex-1"> <div class="label">备注</div> <input type="text" name="source" v-model="formData.remark" placeholder="" :style="{ backgroundColor: !is_show ? '#fff' : '#fffdec', }" /> </td> </tr> </tbody> </table> </div> <div class="table-center-view" v-if="step == 1"> <!-- 搜索框 --> <!-- <DrugSearch v-if="Number(formData.receive_status) === 0" :in_repertory_id="formData.in_repertory_id" :out_repertory_id="formData.out_repertory_id" @add-drug="handleAddDrug" /> --> <!-- 表格 --> <div class="table-container"> <div class="table-wrapper"> <table border style="width: 100%; border-collapse: collapse" ref="drugTableRef" > <thead> <tr> <th width="40"> <input type="checkbox" v-model="isAllSelected" @change="toggleAllSelection" /> </th> <th>编码</th> <th>药品名称</th> <th>批次</th> <th>生产批号</th> <th>效期</th> <th>进价</th> <th>金额</th> <th>领用数量</th> <th>可退数量</th> <th>退回数量</th> </tr> </thead> <tbody> <template v-for="(row, index) in tableData" :key="index"> <tr> <td> <input type="checkbox" v-model="row.selected" /> </td> <!-- 药品编号 --> <td width="80"> <span type="text">{{ row.commodity_no }}</span> </td> <!-- 药品名称 --> <td> <div type="text">{{ row.commodity_name }}</div> <div type="text" style="color: #999; font-size: 11px"> {{ row.size }} </div> </td> <!-- 批次 --> <td> <div v-if="!row.commodityRelateList" type="text"> {{ row.serial_no }} </div> <div v-else class="select-view"> <el-popover placement="bottom-start" trigger="click" width="700" :ref="(el) => setPopoverRef3(el, index)" > <!-- @selection-change=" (selection) => handleBatchSelection(selection, row, index) " --> <el-table :data="row.commodityRelateList" border :ref="(el) => setTableRef(el, index)" > <el-table-column type="selection" width="40" ></el-table-column> <el-table-column label="批次" prop="serial_no" ></el-table-column> <el-table-column label="生产批号" prop="batch_no" ></el-table-column> <el-table-column label="入库时间" prop="in_storage_date" ></el-table-column> <el-table-column label="效期" prop="expiry_date" ></el-table-column> <el-table-column label="进价" prop="pur_price" ></el-table-column> <el-table-column label="账面数量" prop="show_memory_num" ></el-table-column> </el-table> <!-- 确定和取消 --> <div class="dialog-footer" style="text-align: right; margin-top: 10px" > <el-button size="small" type="primary" @click="confirmBatchSelection(index)" >确定</el-button > <el-button size="small" @click="closePopover3(index)" >取消</el-button > </div> <template #reference> <!-- 显示已选批次 --> <div class="reference-item flex justify-between items-center" > <div v-if=" row.selectedBatches && row.selectedBatches.length " > 已选择批次({{ row.selectedBatches.length }}) </div> <div v-else link type="primary" size="small" style="margin-right: 10px; color: #999" > 不指定批次 </div> <bc-icon name="ele-ArrowDown" class="icon-grow" ></bc-icon> </div> </template> </el-popover> </div> </td> <!-- 生产批号 --> <td> <div type="text">{{ row.batch_no }}</div> </td> <!-- 效期 --> <td width="120"> <div type="text">{{ row.expiry_date }}</div> </td> <!--进价 --> <td> <div type="text">{{ row.pur_price }}</div> </td> <td> <div type="text">{{ row.sale_price }}</div> </td> <td> <div type="text"> {{ row.show_commodity_num }} <!-- <span class="text" v-if="Number(row.commodity_num) > 0" >{{ row.commodity_num }}{{ row.unit }}</span > <span class="text" v-if="Number(row.commodity_split_num) > 0" >{{ row.commodity_split_num }}{{ row.min_dose_unit }}</span > --> </div> </td> <td> <div type="text"> {{ row.show_returnable_num }} <!-- <span class="text" v-if="Number(row.returnable_num) > 0" >{{ row.returnable_num }}{{ row.unit }}</span > <span class="text" v-if="Number(row.returnable_split_num) > 0" >{{ row.returnable_split_num }}{{ row.min_dose_unit }}</span > --> </div> </td> <!-- 退回数量 --> <td width="170"> <div v-if=" row.selectedBatches && row.selectedBatches.length " class="relateList" > <span class="text" v-if="Number(row.application_num) > 0" >{{ row.application_num }}{{ row.unit }}</span > <span class="text" v-if="Number(row.application_split_num) > 0" >{{ row.application_split_num }}{{ row.min_dose_unit }}</span > </div> <div v-else class="input-group"> <el-input v-model="row.application_num" class="custom-input" @change="handle_stock_num(row, 'application_num')" > <template #append> <div class="unit">{{ row.unit || '' }}</div> </template> </el-input> <el-input v-model="row.application_split_num" class="custom-input" @change=" handle_stock_num(row, 'application_split_num') " > <template #append> <div class="unit"> {{ row.min_dose_unit || '' }} </div> </template> </el-input> </div> </td> </tr> <!-- 子行 --> <template v-for="(rowItem, subIndex) in row?.selectedBatches" :key="subIndex + '-sub'" > <tr> <td></td> <td></td> <td></td> <td> <div type="text">{{ rowItem.serial_no }}</div> </td> <td> <div type="text">{{ rowItem.batch_no }}</div> </td> <td> <div type="text">{{ rowItem.expiry_date }}</div> </td> <!--进价 --> <td> <div type="text">{{ rowItem.pur_price }}</div> </td> <td> <div type="text">{{ rowItem.sale_price }}</div> </td> <td> <div type="text"> {{ rowItem.show_commodity_num }} </div> </td> <td> <div type="text"> {{ rowItem.show_returnable_num }} </div> </td> <td width="170"> <div class="input-group"> <el-input v-model="rowItem.application_num" class="custom-input" @change=" handle_stock_num(rowItem, 'application_num'); updateTotalNumForRow(row); " > <template #append> <div class="unit"> {{ rowItem.unit || '' }} </div> </template> </el-input> <el-input v-model="rowItem.application_split_num" class="custom-input" @change=" handle_stock_num( rowItem, 'application_split_num' ); updateTotalNumForRow(row); " > <template #append> <div class="unit"> {{ rowItem.min_dose_unit || '' }} </div> </template> </el-input> </div> </td> </tr> </template> </template> </tbody> </table> </div> <div class="table-foot-view"> <div class="foot-view"> <span>品种</span> {{ tableData?.length || 0 }}<span>, 数量</span>{{ totalQuantity || 0 }} </div> </div> </div> <!-- 表格 --> </div> <div class="tablle-top-view" v-if="step == 2"> <table class="mt-7" :class="{ 'disabled-style': !is_show }" :style="{ backgroundColor: !is_show ? '#fff' : '#fffdec' }" > <tbody> <tr class="flex"> <td class="flex-1"> <div class="label">门店</div> <div class="title">{{ formData.store }}</div> </td> <td class="flex-1"> <div class="label">原领用单号</div> <div class="title">{{ formData.order_no }}</div> </td> <td class="flex-1"> <div class="label">原领用人</div> <div class="title">{{ formData.receive_name }}</div> </td> </tr> <tr class="flex"> <td class="flex-1"> <div class="label">入库库房</div> <div class="title"> {{ formData.in_repertory_name }} </div> </td> <td class="flex-1"> <div class="label">出库库房</div> <div class="title"> {{ formData.out_repertory_name }} </div> </td> <td class="flex-1"> <div class="label">备注</div> <div class="title"> {{ formData.remark }} </div> </td> </tr> </tbody> </table> </div> <div class="table-center-view" v-if="step == 2"> <!-- 表格 --> <div class="table-container"> <div class="table-wrapper"> <table border style="width: 100%; border-collapse: collapse" ref="drugTableRef" > <thead> <tr> <th>编码</th> <th>名称</th> <th>批次</th> <th>生产批号</th> <th>效期</th> <th>进价</th> <th>退回数量</th> <th>金额</th> </tr> </thead> <tbody> <template v-for="(row, index) in tableData" :key="index"> <!-- 子行 --> <template v-for="(rowItem, subIndex) in row?.selectedBatches" :key="subIndex + '-sub'" > <tr> <!-- 药品编号 --> <td width="80"> <span type="text">{{ row.commodity_no }}</span> </td> <!-- 药品名称 --> <td> <div type="text">{{ row.commodity_name }}</div> <div type="text" style="color: #999; font-size: 11px" > {{ row.size }} </div> </td> <td> <div type="text">{{ rowItem.serial_no }}</div> </td> <td> <div type="text">{{ rowItem.batch_no }}</div> </td> <td> <div type="text">{{ rowItem.expiry_date }}</div> </td> <!--进价 --> <td> <div type="text">{{ rowItem.pur_price }}</div> </td> <td> <div type="text"> <span class="text" v-if="Number(row.application_num) > 0" >{{ row.application_num }}{{ row.unit }}</span > <span class="text" v-if="Number(row.application_split_num) > 0" >{{ row.application_split_num }}{{ row.application_split_num }}</span > </div> </td> <td> <div type="text">{{ rowItem.pur_price }}</div> </td> <!-- <td width="170"> <div class="input-group"> <el-input v-model="rowItem.application_num" class="custom-input" @change=" handle_stock_num(rowItem, 'application_num'); updateTotalNumForRow(row); " > <template #append> <div class="unit"> {{ rowItem.unit || '' }} </div> </template> </el-input> <el-input v-model="rowItem.application_split_num" class="custom-input" @change=" handle_stock_num( rowItem, 'application_split_num' ); updateTotalNumForRow(row); " > <template #append> <div class="unit"> {{ rowItem.min_dose_unit || '' }} </div> </template> </el-input> </div> </td> --> </tr> </template> </template> </tbody> </table> </div> <div class="table-foot-view"> <div class="foot-view"> <span>品种</span> {{ tableData?.length || 0 }}<span>, 数量</span>{{ totalQuantity || 0 }} </div> </div> </div> <!-- 表格 --> </div> <footer class="footer-view" v-if="formData.out_repertory_id"> <div class="left-btns"> <!-- 草稿 可以删除 --> <el-button v-if="Number(formData.receive_status) == 0" type="primary" @click="deleteReceiveDraft" >删除</el-button > </div> <div class="right-btns"> <el-button v-if="Number(formData.receive_status) == 0" type="primary" @click="submitForm" >提交</el-button > <el-button v-if="Number(formData.receive_status) == 1" type="primary" @click="confirmCheckForm" >审核</el-button > <el-button v-if="step == 1" type="primary" @click="setStep(2)" :disabled="!isAllSelected" >下一步</el-button > <el-button v-if="step == 2" type="primary" @click="setStep(1)" >上一步</el-button > <el-button v-if="step == 2" type="primary" @click="backReceiveRecord" >提交</el-button > <el-button v-if="Number(formData.receive_status) == 0" type="primary" @click="saveDraft" >保存草稿</el-button > <el-button @click="closeForm">关闭</el-button> </div> </footer> </el-form> </div> </div> </div> </template> <script lang="ts" setup name="requisitionBack"> import { ref, watch, onMounted, computed, watchEffect } from 'vue'; import { useDeptController, useRepertoryController, useOutAndInStockController, useInnerCodeController, } from '/@/api'; import DrugSearch from '../drugSearch/index.vue'; import { DrugItem } from '../../type'; import { Utils, HsMessage, HsMessageBox } from 'hs-admin-ui'; import { showTextPopup } from '../textPopup/index.ts'; import { setLocalSystemParams } from 'hs-admin-ui/dist/types/utils'; const { getListAllRepertory } = useRepertoryController(); // 仓库(药房) const { getDeptUserTree } = useDeptController(); // 领用人 const { getCommodityRepertoryStock, postSaveReceiveDraft, postSaveReceiveRecord, postSaveReceiveRecordReturn, postAuditReceiveRecord, getDetailReceive, postRevokeReceiveRecord, postDeleteReceiveDraft, getGoodseatCommodityRepertory, } = useOutAndInStockController(); //查询商品在仓库里的库存信息 const { getValueBySet } = useInnerCodeController(); const props = defineProps({ close: { type: Function, }, typeText: { type: String, }, id: { type: String, }, data: { type: Object, }, }); const loading = ref(false); const step = ref(2); const repertory_list = ref([]) as any; //仓库列表 const deptData = ref([]) as any; //领用人 // 表格数据 // 表格数据 const tableData = ref<DrugItem[]>([]); const is_show = ref(true); const formData = ref({ id: '', order_no: '', store: '', //门店 create_time: '', //创建日期 receive: [], receive_status: '', //状态状态 0:草稿 1:待审核 2:已完成 3:审核驳回 4: 已撤回] receive_time: '', //领用时间 receive_id: '', //领用人ID receive_name: '', //领用人名称 receive_dept_id: '', //领用人部门ID in_repertory_id: '', //入库药房ID out_repertory_id: '', //出库药房ID in_repertory_name: '', out_repertory_name: '', remark: '', //备注: }); const FN = Utils.fixedNumAccuracy; const totalQuantity = computed(() => { return tableData.value?.reduce((sum, item) => { const value = parseFloat(item.total_num); return FN(Utils.add(sum, isNaN(value) ? 0 : value)); }, 0); }); function setStep(stepNum: number) { step.value = stepNum; if (stepNum == 1) { //下一步 } } const totalPurAmount = computed(() => { return ( tableData.value?.reduce((sum, item) => { const value = parseFloat(item.total_num); const price = parseFloat(item.pur_price); // 如果任意一个不是有效数字,则跳过该条目 if (isNaN(value) || isNaN(price)) { return FN(sum); } return FN(Utils.add(sum, value * price)); }, 0) ?? 0 ); // 如果 tableData 不存在,默认返回 0 }); const totalSaleAmount = computed(() => { return ( tableData.value?.reduce((sum, item) => { const value = parseFloat(item.total_num); const price = parseFloat(item.sale_price); // 如果任意一个不是有效数字,则跳过该条目 if (isNaN(value) || isNaN(price)) { return FN(sum); } return FN(Utils.add(sum, value * price)); }, 0) ?? 0 ); // 如果 tableData 不存在,默认返回 0 }); // 定义 refs const inRepertoryRef = ref(); const receiverRef = ref(); const outRepertoryRef = ref(); const drugTableRef = ref(); const focusSelectInput = async (refName) => { // await nextTick(); const selectComponent = refName?.value; if (selectComponent && selectComponent.handleOpen) { selectComponent.handleOpen(); // 推荐方式 } else { const selectEl = refName?.value?.$el; const selection = selectEl?.querySelector('.el-select__selection'); if (selection) { selection.click(); } } }; const focusSelectCascader = async (refName) => { // await nextTick(); const cascaderEl = document.querySelector('.el-cascader'); if (cascaderEl) { const input = cascaderEl.querySelector('.el-input__inner'); if (input) { input.click(); // 打开面板 } } }; // 滚动到指定元素 const scrollToElement = (element) => { if (element && element.scrollIntoView) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }; const focusTableRowInput = async (rowIndex = 0) => { // await nextTick(); const tableEl = drugTableRef.value; if (!tableEl) return; console.log('tableEl', tableEl); const rows = tableEl.querySelectorAll('tbody tr'); console.log('rows', rows); if (rowIndex >= 0 && rowIndex < rows.length) { const row = rows[rowIndex]; const input = row.querySelector('input'); console.log('input', input); if (input) { input.focus(); } } }; // 校验方法 const validateForm = () => { const errors = []; // 校验入库药房 if (!formData.value.in_repertory_id) { errors.push({ field: 'in_repertory_id', message: '请选择入库药房' }); } // 校验领用人 if (!formData.value.receive_id) { errors.push({ field: 'receive', message: '请选择领用人' }); } // 校验出库药房 if (!formData.value.out_repertory_id) { errors.push({ field: 'out_repertory_id', message: '请选择出库药房' }); } // 校验表格中的领用数量 const invalidIndex = tableData.value.findIndex( (row) => !row.application_num || Number(row.application_num) <= 0 ); if (invalidIndex >= 0) { errors.push({ field: 'table', message: `第 ${invalidIndex + 1} 行领用数量必须大于0`, }); // 聚焦 + 滚动 focusTableRowInput(invalidIndex); const rows = drugTableRef.value?.querySelectorAll('tbody tr'); if (rows && rows.length > invalidIndex) { scrollToElement(rows[invalidIndex]); } } else if (tableData.value?.length === 0) { errors.push({ field: 'table', message: '请输入药品名称或扫码添加', }); } if (errors.length > 0) { const firstError = errors[0]; HsMessage.error(firstError.message); switch (firstError.field) { case 'in_repertory_id': focusSelectInput(inRepertoryRef); scrollToElement(inRepertoryRef.value); break; case 'receive': focusSelectCascader(receiverRef); scrollToElement(receiverRef.value); break; case 'out_repertory_id': focusSelectInput(outRepertoryRef); scrollToElement(outRepertoryRef.value); break; case 'table': break; } return false; } return true; }; //更新总量 const updateTotalNumForRow = (row) => { if (row.selectedBatches && row.selectedBatches.length > 0) { // 退回数量 const totalApplicationNum = row.selectedBatches.reduce( (sum, batch) => sum + Number(batch.application_num || 0), 0 ); const totalApplicationSplitNum = row.selectedBatches.reduce( (sum, batch) => sum + Number(batch.application_split_num || 0), 0 ); row.application_num = totalApplicationNum; row.application_split_num = totalApplicationSplitNum; //可退数量 const totalReturnableNum = row.selectedBatches.reduce( (sum, batch) => sum + Number(batch.returnable_num || 0), 0 ); const totalReturnableSplitNum = row.selectedBatches.reduce( (sum, batch) => sum + Number(batch.returnable_split_num || 0), 0 ); row.returnable_num = totalReturnableNum; row.returnable_split_num = totalReturnableSplitNum; //领用数量 const totalCommodityNum = row.selectedBatches.reduce( (sum, batch) => sum + Number(batch.commodity_num || 0), 0 ); const totalCommoditySplitNum = row.selectedBatches.reduce( (sum, batch) => sum + Number(batch.commodity_split_num || 0), 0 ); row.commodity_num = totalCommodityNum; row.commodity_split_num = totalCommoditySplitNum; // 计算总数量 let memory_num_total = Number( FN( row.application_num * 1 + (row.application_split_num * 1) / row.min_package_num ) ).toFixed(2); row.total_num = FN(memory_num_total * 1); } else { row.application_num = ''; row.application_split_num = ''; // 如果没有子行,用主行输入的值 handle_stock_num(row, 'application_num'); } }; /** * 获取流水号 商品领用单单号 --LYD */ const getOrderNo = async () => { const res = await getValueBySet({ inner_code: 'LYD' }); formData.value.order_no = res.data; }; /*保存草稿 * @param {[]} id [领用单ID] 是否必填:否 * @param {[]} order_no [订单编号] 是否必填:否 getiner * @param {[]} receive_type [领用类型 1:领用 2:退回] 是否必填:否 * @param {[]} receive_status [状态 0:草稿 1:待审核 2:已完成 3:审核驳回 4: 已撤回] 是否必填:否 * @param {[]} receive_time [领用时间] 是否必填:否 * @param {[]} receive_id [领用人ID] 是否必填:否 * @param {[]} receive_name [领用人名称] 是否必填:否 * @param {[]} receive_dept_id [接收部门ID] 是否必填:否 * @param {[]} in_repertory_id [入库药房ID] 是否必填:否 * @param {[]} out_repertory_id [出库药房ID] 是否必填:否 * @param {[]} kind_num [商品种类数量] 是否必填:否 * @param {[]} total_num [总数量] 是否必填:否 * @param {[]} amount [金额] 是否必填:否 * @param {[]} amount_excluding_tax [税前金额] 是否必填:否 * @param {[]} remark [备注] 是否必填:否 * @param {[]} receiveCommodityList [领用商品集合[{id:领用商品ID commodity_id:商品ID application_num:申请商品整数量 application_split_num:申请商品拆零数量}]] 是否必填:是 * @return {[type]} [description] */ const saveDraft = () => { console.log('保存草稿', formData.value, tableData.value); // 调用校验方法 // if (!validateForm()) { // return; // } let params = { id: props.id || '', order_no: formData.value.order_no || '', receive_id: formData.value.receive_id || '', receive_name: formData.value.receive_name || '', receive_dept_id: formData.value.receive_dept_id || '', in_repertory_id: formData.value.in_repertory_id || '', out_repertory_id: formData.value.out_repertory_id || '', remark: formData.value.remark || '', kind_num: tableData.value?.length || 0, total_num: totalQuantity.value || 0, pur_amount: totalPurAmount.value, sale_amount: totalSaleAmount.value, receive_type: '1', // 领用类型 // receive_status: '0', // 草稿状态 } as any; const receiveCommodityList = tableData.value.map((item) => ({ id: item.id || '', commodity_id: item.commodity_id, application_num: item.application_num, application_split_num: item.application_split_num, // 新增:批次信息 commodityRelateList: item.selectedBatches || [], })); params['receiveCommodityList'] = receiveCommodityList; console.log('params', params); postSaveReceiveDraft(params).then((res) => { if (res.code == 1) { HsMessage.success('草稿保存成功'); props.close!({ type: 'confirm' }); } }); }; // try { // loading.value = true; // props.close!(_.cloneDeep(row)); // } finally { // loading.value = false; // } //提交领用单 postSaveReceiveRecord const submitForm = () => { console.log('提交表单', formData.value, tableData.value); // 调用校验方法 if (!validateForm()) { return; } HsMessageBox.confirm( `提交后将发送${formData.value.in_repertory_name}确认,确定提交吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', closeOnClickModal: false, } ) .then(async () => { loading.value = true; let params = { id: props.id || '', order_no: formData.value.order_no || '', receive_id: formData.value.receive_id || '', receive_name: formData.value.receive_name || '', receive_dept_id: formData.value.receive_dept_id || '', in_repertory_id: formData.value.in_repertory_id || '', out_repertory_id: formData.value.out_repertory_id || '', remark: formData.value.remark || '', kind_num: tableData.value?.length, total_num: totalQuantity.value, pur_amount: totalPurAmount.value, sale_amount: totalSaleAmount.value, receive_type: '1', // 领用类型 // receive_status: '1', // 待审核状态 } as any; const receiveCommodityList = tableData.value.map((item) => ({ id: item.id || '', commodity_id: item.commodity_id, application_num: item.application_num, application_split_num: item.application_split_num, receiveCommodityRelateList: (item.selectedBatches ?? []).map( (rowItem) => ({ out_goodseat_commodity_id: rowItem.id, commodity_total_num: FN( Utils.add( rowItem.application_num, Utils.div( rowItem.application_split_num, rowItem.min_package_num ) ) ), commodity_num: rowItem.application_num, commodity_split_num: rowItem.application_split_num, }) ), })); params['receiveCommodityList'] = receiveCommodityList; console.log('params', params); postSaveReceiveRecord(params) .then((res) => { loading.value = false; if (res.code == 1) { HsMessage.success('提交成功'); props.close!({ type: 'confirm' }); } }) .catch(() => { // 取消 loading.value = false; }); }) .catch(() => { // 取消 }); }; //确认审核 领用单 postSaveReceiveRecord /** * 接口分类: 仓储出入库操作接口 - OutAndInStockController * 接口名称: 领用单审核 - 接口地址: /his-web/pri/out_in_stock/audit_receive_record * 请求方式: post * @param {[]} id [领用单ID] 是否必填:是 * @param {[]} receive_status [是否同意,同意:2,不同意:3] 是否必填:是 * @param {[]} audit_comment [意见] 是否必填:否 * @param {[]} kind_num [商品种类数量] 是否必填:否 * @param {[]} total_num [总数量] 是否必填:否 * @param {[]} pur_amount [采购总金额] 是否必填:否 * @param {[]} sale_amount [销售总金额] 是否必填:否 * @param {[]} receiveCommodityList [领用商品集合[{id:领用商品IDcommodity_id:商品IDcommodity_num:商品整数量(实发)commodity_split_num:商品拆零数量(实发)receiveCommodityRelateList:领用商品与库存关系[{id:领用商品与库存关系id out_goodseat_commodity_id:出库库存idcommodity_total_num:商品总数量(实发)commodity_num:商品整数量(实发)commodity_split_num:商品拆零数量(实发)}]}]] 是否必填:是 * @return {[type]} [description] */ const confirmCheckForm = () => { console.log('审核表单', formData.value, tableData.value); // 调用校验方法 if (!validateForm()) { return; } showTextPopupClick(); // HsMessageBox.confirm( // `确认后将锁定库存并发送${formData.value.in_repertory_name}核对,确定提交吗?`, // '提示', // { // confirmButtonText: '确定', // cancelButtonText: '取消', // type: 'warning', // closeOnClickModal: false, // } // ) // .then(async () => { // }) // .catch(() => { // // 取消 // }); }; function showTextPopupClick() { showTextPopup().then((res: any) => { console.log('res', res); if (res.type === 'confirm') { handleConfirm(res.data, '2'); } else if (res.type === 'refuse') { handleConfirm(res.data, '3'); } }); } function handleConfirm(data: any, receive_status: string) { // if (data.titleText === '商品名称') { // goodsName.value.textarea // updateQueryData('qry_like_in_str', data); // } // tableInstance.value?.searchTo(); let audit_comment = data; loading.value = true; let params = { id: props.id || '', order_no: formData.value.order_no || '', receive_id: formData.value.receive_id || '', receive_name: formData.value.receive_name || '', receive_dept_id: formData.value.receive_dept_id || '', in_repertory_id: formData.value.in_repertory_id || '', out_repertory_id: formData.value.out_repertory_id || '', remark: formData.value.remark || '', kind_num: tableData.value?.length, total_num: totalQuantity.value, pur_amount: totalPurAmount.value, sale_amount: totalSaleAmount.value, receive_type: '1', // 领用类型 receive_status: receive_status || '', // 审核状态-通过 audit_comment: audit_comment || '', } as any; const receiveCommodityList = tableData.value.map((item) => ({ id: item.id || '', commodity_id: item.commodity_id, commodity_num: item.application_num, commodity_split_num: item.application_split_num, receiveCommodityRelateList: (item.selectedBatches ?? []).map((rowItem) => ({ // id: rowItem.id, out_goodseat_commodity_id: rowItem.id, commodity_total_num: FN( Utils.add( rowItem.application_num, Utils.div(rowItem.application_split_num, rowItem.min_package_num) ) ), commodity_num: rowItem.application_num, commodity_split_num: rowItem.application_split_num, })), })); params['receiveCommodityList'] = receiveCommodityList; console.log('params', params); postAuditReceiveRecord(params) .then((res) => { loading.value = false; if (res.code == 1) { if (receive_status === '2') { HsMessage.success('审核确认成功'); } else { HsMessage.success('审核驳回成功'); } props.close!({ type: 'confirm' }); } }) .catch(() => { // 取消 loading.value = false; }); } // 添加药品到主表格 const addDrugToTable = (drug: any) => { // tableData.value.push(drug); // 使用 id 作为唯一标识 const drugMap = new Map(tableData.value.map((item) => [item.id, item])); if (!drugMap.has(drug.id)) { tableData.value.push(drug); } else { HsMessage.warning('该药品已存在'); } // 校验表格中的领用数量 const invalidIndex = tableData.value?.length - 1; // 聚焦 + 滚动 focusTableRowInput(invalidIndex); }; /* *搜索之后选中商品 * @param {[]} commodity_id [商品ID] 是否必填:是 * @param {[]} repertory_id [仓库id] 是否必填:是 */ const handleAddDrug = async (drug: any) => { if (!drug.id) { return; } if (!formData.value.in_repertory_id) { HsMessage.warning('请选择入库库房!'); return; } if (!formData.value.out_repertory_id) { HsMessage.warning('请选择出库库房!'); return; } let params = { commodity_id: drug.id, repertory_id: formData.value.out_repertory_id, }; // let resultData: any = await getCommodityRepertoryStock(params); // console.log('resultData data', resultData.data); // drug.show_memory_num = '0' + drug.unit; // if (resultData.data) { // drug.show_memory_num = resultData.data.show_memory_num || '0'; // } let res1: any = await getGoodseatCommodityRepertory(params); console.log('resultData data', res1.data); let resultData = res1.data; resultData.forEach((item: any) => { item.selected = false; item.application_num = item.memory_num; item.application_split_num = item.split_memory_num; }); // 设置药品字段 drug.commodityRelateList = resultData || []; drug.selectedBatches = []; addDrugToTable(drug); }; // 新增行 function addRow() { tableData.value.push({}); } const popoverRefs = ref<Record<number, any>>({}); const popoverRefs2 = ref<Record<number, any>>({}); const popoverRefs3 = ref<Record<number, any>>({}); //批次选择 const tableRefs = ref<Record<number, any>>({}); const setPopoverRef = (el, index) => { popoverRefs.value[index] = el; }; const setPopoverRef2 = (el, index) => { popoverRefs2.value[index] = el; }; const setPopoverRef3 = (el, index) => { popoverRefs3.value[index] = el; }; const closePopover = (index) => { if (popoverRefs.value[index]) { popoverRefs.value[index].hide(); } }; const closePopover2 = (index) => { if (popoverRefs2.value[index]) { popoverRefs2.value[index].hide(); } }; const closePopover3 = (index) => { if (popoverRefs3.value[index]) { popoverRefs3.value[index].hide(); } }; watch( () => tableData.value, () => { console.log('表格数据更新,totalQuantity:', totalQuantity.value); }, { deep: true } ); const closeForm = () => { console.log('关闭表单'); props.close!({ type: 'close' }); }; function init() { getRepertoryList(); getDeptDataList(); console.log('props', props); if (!props.id) { getOrderNo(); } else { getDetailReceiveInfo(); } if (props.data) { formData.value.in_repertory_id = props.data?.in_repertory_id; formData.value.out_repertory_id = props.data?.out_repertory_id; formData.value.in_repertory_name = props.data?.in_repertory_name; formData.value.out_repertory_name = props.data?.out_repertory_name; } getUserInfoFn(); } let userInfo = {}; // 获取个人信息 async function getUserInfoFn() { userInfo = await Utils.systemStore.get('userInfo'); if (userInfo) { formData.value.store = userInfo.company_name; } } // 获取领用单详情 async function getDetailReceiveInfo() { if (!props.id) return; let res: any = await getDetailReceive({ id: props.id }); let resultData: any = res.data; if (resultData) { formData.value = resultData; resultData.commodityList.map((item: any) => { if (item.commodityRelateList) { // 赋值application_num和选中的批次和out_goodseat_commodity_id item.commodityRelateList.map((listItem: any) => { listItem.application_split_num = listItem.commodity_split_num; listItem.application_num = listItem.commodity_num; if (Number(resultData.receive_status) <= 1) { listItem.id = listItem.out_goodseat_commodity_id; } }); item.selectedBatches = item.commodityRelateList; } }); tableData.value = resultData.commodityList; formData.value.receive = [ resultData.receive_dept_id, resultData.receive_id, ]; if (userInfo) { formData.value.store = userInfo.company_name; } initSelection(); } // repertory_list.value = res1.data; } // 撤回领用单 function revokeReceiveRecord() { if (!props.id) return; HsMessageBox.confirm('确定撤回领用单吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', closeOnClickModal: false, }) .then(async () => { loading.value = true; postRevokeReceiveRecord({ id: props.id }).then((res) => { loading.value = false; if (res.code == 1) { HsMessage.success('撤回领用单成功'); props.close!({ type: 'confirm' }); } }); }) .catch(() => { // 取消 loading.value = false; }); } // 退回领用单 function backReceiveRecord() { if (!props.id) return; HsMessageBox.confirm('确定退回领用单吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', closeOnClickModal: false, }) .then(async () => { loading.value = true; let params = { receive_source_record_id: props.id || '', order_no: formData.value.order_no || '', remark: formData.value.remark || '', kind_num: tableData.value?.length, total_num: totalQuantity.value, pur_amount: totalPurAmount.value, sale_amount: totalSaleAmount.value, } as any; const receiveCommodityList = tableData.value.map((item) => ({ id: item.id || '', commodity_id: item.commodity_id, application_num: item.application_num, application_split_num: item.application_split_num, receiveCommodityRelateList: (item.selectedBatches ?? []).map( (rowItem) => ({ source_relate_id: rowItem.id, commodity_total_num: FN( Utils.add( rowItem.application_num, Utils.div( rowItem.application_split_num, rowItem.min_package_num ) ) ), commodity_num: rowItem.application_num, commodity_split_num: rowItem.application_split_num, }) ), })); params['receiveCommodityList'] = receiveCommodityList; console.log('params', params); postSaveReceiveRecordReturn(params) .then((res) => { loading.value = false; if (res.code == 1) { HsMessage.success('退回提交成功'); props.close!({ type: 'confirm' }); } }) .catch(() => { // 取消 loading.value = false; }); }) .catch(() => { // 取消 loading.value = false; }); } //删除领用单(草稿) function deleteReceiveDraft() { if (!props.id) return; HsMessageBox.confirm('确定删除领用单(草稿)吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', closeOnClickModal: false, }) .then(async () => { loading.value = true; postDeleteReceiveDraft({ id: props.id }).then((res) => { loading.value = false; if (res.code == 1) { HsMessage.success('删除领用单(草稿)成功'); props.close!({ type: 'confirm' }); } }); }) .catch(() => { // 取消 loading.value = false; }); } //获取仓库列表 async function getRepertoryList() { let res1 = await getListAllRepertory({ id_ne: '' }); repertory_list.value = res1.data; } //获取领用人列表 async function getDeptDataList() { let res1 = await getDeptUserTree({ id_ne: '' }); deptData.value = res1.data; console.log('deptData', deptData); } // computed 依赖 rawData.value const cascaderOptions = computed(() => { if (!Array.isArray(deptData.value)) return []; return deptData.value.map((dept) => ({ value: dept.code, label: dept.name, children: dept.children.map((user) => ({ value: user.user_id, label: user.user_name, })), })); }); const cascaderProps = { expandTrigger: 'hover', // 鼠标悬停展开下一级 }; const handleChange = (value) => { console.log('选中值:', value); // value 格式应为 [部门编码, 用户ID] if (Array.isArray(value) && value.length >= 2) { const deptCode = value[0]; // 接收部门ID const userId = value[1]; // 领用人ID // 查找用户信息 let userName = ''; // let deptName = ''; // 遍历 cascaderOptions 查找对应的部门和用户名称 for (const dept of cascaderOptions.value) { if (dept.value === deptCode) { // deptName = dept.label; // 接收部门名称 for (const user of dept.children) { if (user.value === userId) { userName = user.label; // 领用人名称 break; } } break; } } // 设置 formData 中的值 formData.value.receive_id = userId; formData.value.receive_name = userName; formData.value.receive_dept_id = deptCode; console.log('formData after handleChange:', formData.value); } else { console.warn('无效的 cascader 选择值:', value); } }; onMounted(() => { init(); }); const formatInput = (value) => { if (isNaN(Number(value))) return 0; return Math.max(0, Number(value)); }; function handle_stock_num(row: any, type: string) { row[type] = formatInput(row[type]); const splitnum = Number(row.min_package_num); if (type === 'application_split_num') { if (row.application_split_num >= splitnum) { const split_num = row.application_split_num % splitnum; const stock_num = Utils.div2(row.application_split_num, splitnum).toFixed( 0 ); row.application_split_num = split_num; row.application_num = Number(FN(Number(row.application_num) + stock_num)); } } if (Number(row.application_split_num) <= 0) { row.application_split_num = 0; } if (Number(row.application_num) <= 0) { row.application_num = 0; } //重新计算整数数量 if (Number(row.application_num) >= Number(row.memory_num)) { row.application_num = row.memory_num; } // 计算总数量 row.total_num = FN( Utils.add( row.application_num, Utils.div(row.application_split_num, row.min_package_num) ) ); } const setTableRef = (el, index) => { tableRefs.value[index] = el; }; //批次选择 const confirmBatchSelection = (index) => { const tableRef = tableRefs.value[index]; if (tableRef) { const selection = tableRef?.getSelectionRows(); const row = tableData.value[index]; row.selectedBatches = selection; updateTotalNumForRow(row); } closePopover3(index); }; const isAllSelected = ref(false); // 控制全选状态 // 初始化每行的 selected 字段 function initSelection() { tableData.value.forEach((row) => { row.selected = false; }); } // 全选/取消全选 function toggleAllSelection() { const isSelected = isAllSelected.value; tableData.value.forEach((row) => { row.selected = isSelected; }); } // 监听主行选中状态变化,更新全选按钮 watchEffect(() => { const allSelected = tableData.value.every((row) => row.selected); isAllSelected.value = allSelected; }); // 获取所有选中的主行 function getSelectedRows() { return tableData.value.filter((row) => row.selected); } function logSelected() { const selectedRows = getSelectedRows(); console.log('选中的主行数据:', selectedRows); } </script> <style scoped lang="scss"> @import './index.scss'; </style> totalQuantity 刚开始怎么赋值
07-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值