| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754 |
- <template>
- <el-tabs v-model="topsTab" type="card" class="top_tabs" :disabled="isSortMode">
- <el-tab-pane label="执行左脚程序" name="left">
- </el-tab-pane>
- <el-tab-pane label="执行右脚程序" name="right"></el-tab-pane>
- </el-tabs>
- <div class="two_tabs">
- <div class="item"
- :class="item.id === activeTab.id ? 'active' : ''"
- v-for="item in tabs" :key="item.id"
- @click="toggleTab(item)" v-log="{ describe: { action: '点击切换动作Tab', tabName: item.mode_name, tabId: item.id } }"
- :style="{ cursor: isSortMode ? 'not-allowed' : 'pointer', opacity: isSortMode ? 0.5 : 1 }"
- >{{item.mode_name}}</div>
- </div>
- <div class="form-table">
- <div v-if="isSortMode" class="sort-tip">
- <el-icon><Warning /></el-icon>
- <span>排序模式:请拖拽行进行排序,完成后点击"保存排序"</span>
- </div>
- <div class="btnBox">
- <div class="primary-btn" @click="addRow" v-log="{ describe: { action: '点击新增一行' } }" :class="{ disabled: isSortMode }" :style="{ opacity: isSortMode ? 0.5 : 1, cursor: isSortMode ? 'not-allowed' : 'pointer' }">新增一行</div>
- <div class="primary-btn" @click="resetConfig" v-log="{ describe: { action: '点击重新初始化', tab: topsTab } }" :class="{ disabled: isSortMode }" :style="{ opacity: isSortMode ? 0.5 : 1, cursor: isSortMode ? 'not-allowed' : 'pointer' }">重新初始化</div>
- <div class="primary-btn" @click="reName" v-log="{ describe: { action: '点击重命名配置' } }" :class="{ disabled: isSortMode }" :style="{ opacity: isSortMode ? 0.5 : 1, cursor: isSortMode ? 'not-allowed' : 'pointer' }">重命名配置</div>
- <div class="primary-btn" @click="toggleSortMode" v-log="{ describe: { action: isSortMode ? '点击保存排序' : '点击排序' } }">
- {{ isSortMode ? '保存排序' : '排序' }}
- </div>
- <div v-if="isSortMode" class="normal-btn" @click="cancelSort" v-log="{ describe: { action: '点击取消排序' } }">
- 取消排序
- </div>
- <el-radio-group style="margin-left: 10px" v-model="selectID" @click.native.stop="changeSelectId($event,activeTab.id)" :disabled="isSortMode">
- <el-radio :label="activeTab.id">切换成执行配置</el-radio>
- </el-radio-group>
- </div>
- <el-table
- max-height="700"
- :data="tableData"
- style="width: 100%"
- border
- row-key="id"
- :row-class-name="getRowClassName"
- ref="tableRef"
- >
- <el-table-column prop="sort" label="排序" width="80" v-if="isSortMode">
- <template #default="scope">
- <div class="sort-content">
- <span class="sort-number">{{ scope.row.sort }}</span>
- <div class="sort-handle">
- <el-icon><Rank /></el-icon>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column prop="action_name" label="步骤" >
- <template #default="scope">
- {{ scope.row.action_name }}
- <el-tag type="success" size="small" v-if="calibrationId === scope.row.id">校准位</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="take_picture" label="是否拍照" width="200px">
- <template #default="scope">
- <div v-if="!scope.row.is_system">
- {{ scope.row.take_picture ? '拍照' : '不拍照' }}
- </div>
- <span v-else></span>
- </template>
- </el-table-column>
- <el-table-column prop="value" label="操作" >
- <template #default="{row, $index}">
- <a class="mar-right-10 cursor-pointer" @click="editRow(row, $index)" v-log="{ describe: { action: '点击编辑步骤', id: row.id, action_name: row.action_name } }">编辑</a>
- <a class="cursor-pointer" v-if="!row.is_system" @click="deleteRow(row, $index)" v-log="{ describe: { action: '点击删除步骤', id: row.id, action_name: row.action_name } }">删除</a>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <EditDialog
- v-if="dialogVisible"
- v-model="dialogVisible"
- :id="editId"
- :addRowData="addRowData"
- @confirm="getList"
- />
- </template>
- <script setup lang="ts">
- import { ref, defineProps, defineEmits , watch,onMounted, reactive,onBeforeUnmount } from 'vue'
- import EditDialog from "./EditDialog.vue";
- import { ElMessage, ElMessageBox } from 'element-plus';
- import { Rank, Warning } from '@element-plus/icons-vue';
- import client from "@/stores/modules/client";
- import icpList from '@/utils/ipc';
- import tokenInfo from '@/stores/modules/token';
- const clientStore = client();
- import socket from "@/stores/modules/socket";
- const socketStore = socket(); // WebSocket状态管理实例
- const tokenInfoStore = tokenInfo();
- import { getTopTabs, getDeviceConfigs,setLeftRightConfig,restConfig,sortDeviceConfig,setTabName,delDviceConfig } from '@/apis/setting'
- // 表格数据和对话框状态
- const tableData = ref([]); // 配置表格数据
- const dialogVisible = ref(false); // 编辑对话框可见状态
- const editTitle = ref(''); // 编辑对话框标题
- const addRowData = ref({}); // 新增行
- const topsTab = ref('left'); // 顶部tab
- const activeTab = ref({}); // 当前激活的标签页
- const tabs = ref([]); // 所有标签页
- const editId = ref(0); // 当前编辑行的索引
- const selectID = ref(0) //当前默认的ID
- const isSortMode = ref(false); // 是否处于排序模式
- const originalTableData = ref([]); // 保存原始数据用于排序
- const tableRef = ref(null); // 表格引用
- let dragEventHandlers = null; // 拖拽事件处理器
- onBeforeUnmount(()=>{
- window.removeEventListener('beforeunload', handleBeforeUnload);
- // 清理排序模式
- if (isSortMode.value) {
- exitSortMode();
- }
- })
- onMounted(()=>{
- window.addEventListener('beforeunload', handleBeforeUnload);
- topsTab.value = 'left';
- getTopList()
- })
- const handleBeforeUnload = (e)=>{
- if(dialogVisible.value){
- // 没有token时,直接关闭实时预览,不显示提示
- dialogVisible.value = false;
- // 调用hideVideo逻辑
- hideVideo();
- return;
- // 检查是否有token
- const token = tokenInfoStore.getToken;
- if (!token || token.trim() === '') {
- // 没有token时,直接关闭实时预览,不显示提示
- dialogVisible.value = false;
- // 调用hideVideo逻辑
- hideVideo();
- return;
- }
- // 有token时,显示提示阻止关闭
- e.preventDefault();
- const message = '您已打开实时预览弹出框,请先取消或者保存后,关闭编辑弹出框,后再关闭此窗口';
- e.returnValue = message; // 标准方式
- ElMessage.error('您已打开实时预览弹出框,请先取消或者保存后,关闭编辑弹出框,后再关闭此窗口,')
- return message; // 兼容某些浏览器
- }
- }
- // 添加hideVideo函数
- function hideVideo(){
- clientStore.ipc.removeAllListeners(icpList.camera.PreviewHide);
- clientStore.ipc.send(icpList.camera.PreviewHide);
- clientStore.ipc.on(icpList.camera.PreviewHide, async () => {
- // 这里可以添加清理逻辑,比如清除定时器等
- })
- }
- /**
- * 监听topsTab变化,获取对应标签页的设备配置列表。
- */
- watch(() => topsTab.value, (newTab) => {
- if (!isSortMode.value) {
- getTopList();
- }
- });
- const getTopList = async ()=>{
- const result = await getTopTabs({
- type: topsTab.value == 'left' ? 0 :1
- })
- if(result.code == 0 && result.data){
- tabs.value = result.data.tabs
- tabs.value.some(item=>{
- if(item.id === result.data.select_configs[topsTab.value]){
- selectID.value = item.id
- activeTab.value = item
- return true;
- }
- })
- if(!selectID.value){
- selectID.value = tabs.value[0].id
- activeTab.value = tabs.value[0]
- }
- getList()
- }
- }
- //切换tab
- const toggleTab = (item) => {
- if (isSortMode.value) return; // 排序模式下禁用
- activeTab.value = item
- getList()
- };
- const calibrationId = ref(null) //校准位
- /**
- * 获取设备配置列表。
- */
- const getList = async () => {
- // 如果正在排序模式,先退出
- if (isSortMode.value) {
- exitSortMode();
- }
- let params = {
- tab_id: activeTab.value.id
- }
- const result = await getDeviceConfigs(params)
- if (result.code == 0) {
- tableData.value = result.data.list;
- const calibration = result.data.list.filter(item=>(item.action_name === '侧视'))
- if(calibration.length >= 1){
- calibrationId.value = calibration[0].id
- }else{
- calibrationId.value = tableData.value[0].id
- }
- }
- };
- const changeSelectId = async (e,id)=>{
- if (e.target.tagName === 'INPUT') return;
- if (isSortMode.value) return; // 排序模式下禁用
- if(id === selectID.value) return;
- let params = {
- type: topsTab.value,
- id,
- }
- const result = await setLeftRightConfig(params)
- if (result.code == 0) {
- selectID.value = id;
- }
- }
- /**
- * 编辑指定行的配置。
- * @param {Object} row - 当前行的数据
- * @param {number} index - 当前行的索引
- */
- const editRow = (row, index) => {
- if (isSortMode.value) return; // 排序模式下禁用
- addRowData.value = {}
- dialogVisible.value = true;
- editId.value = row.id
- };
- /**
- * 删除指定行的配置。
- * @param {Object} row - 当前行的数据
- * @param {number} index - 当前行的索引
- */
- const deleteRow = (row, index) => {
- if (isSortMode.value) return; // 排序模式下禁用
- ElMessageBox.confirm('确定删除该步骤吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- const result = await delDviceConfig({
- id: row.id
- })
- if (result.code == 0) {
- getList();
- ElMessage.success('删除成功');
- }
- });
- };
- /**
- * 重置设备配置。
- */
- const resetConfig = () => {
- if (isSortMode.value) return; // 排序模式下禁用
- console.log(activeTab.value);
- ElMessageBox.confirm(`确定初始化${activeTab.value.mode_name}吗?`, '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- const result = await restConfig({
- tab_id: activeTab.value.id
- })
- if (result.code == 0) {
- getList();
- ElMessage.success('重置成功');
- }
- });
- };
- const reName = ()=>{
- if (isSortMode.value) return; // 排序模式下禁用
- ElMessageBox.prompt('', '重命名配置', {
- confirmButtonText: '保存',
- cancelButtonText: '取消',
- inputValue: activeTab.value.mode_name,
- inputPlaceholder:'请输入配置名称',
- inputValidator: (value) => {
- if (value === '') {
- return '请输入配置名称';
- }
- return true;
- },
- })
- .then(async ({ value }) => {
- const result = await setTabName({
- id: activeTab.value.id,
- mode_name:value,
- })
- if (result.code == 0) {
- activeTab.value.mode_name = value
- tabs.value.some(item=>{
- if(item.id === activeTab.value.id){
- item.mode_name = activeTab.value.mode_name
- }
- })
- ElMessage.success('重命名成功');
- }
- })
- }
- /**
- * 新增一行配置。
- */
- const addRow = () => {
- if (isSortMode.value) return; // 排序模式下禁用
- editId.value = -1
- let length = Number(tableData.value.length)+1
- addRowData.value = {
- mode_type: topsTab.value === 'left' ? '执行左脚程序' : '执行右脚程序',
- tab_id: activeTab.value.id,
- action_name: '新增步骤'+length,
- take_picture: false,
- camera_height: 0,
- camera_angle: 0,
- turntable_position: 0,
- turntable_angle: 0,
- shoe_upturn: false,
- led_switch: false,
- number_focus: 0,
- pre_delay: 0,
- after_delay: 0,
- }
- dialogVisible.value = true;
- editTitle.value = '新增步骤';
- };
- /**
- * 切换排序模式
- */
- const toggleSortMode = () => {
- if (isSortMode.value) {
- // 保存排序
- saveSortOrder();
- } else {
- // 进入排序模式
- enterSortMode();
- }
- };
- /**
- * 取消排序
- */
- const cancelSort = () => {
- exitSortMode();
- ElMessage.info('已取消排序');
- };
- /**
- * 进入排序模式
- */
- const enterSortMode = () => {
- isSortMode.value = true;
- // 保存原始数据
- originalTableData.value = JSON.parse(JSON.stringify(tableData.value));
- // 为每行添加排序值
- tableData.value.forEach((item, index) => {
- item.sort = index + 1;
- });
- // 等待DOM更新后初始化Sortable
- setTimeout(() => {
- initSortable();
- }, 100);
- ElMessage.info('请拖拽行进行排序,完成后点击"保存排序"');
- };
- /**
- * 初始化拖拽排序
- */
- const initSortable = () => {
- if (!tableRef.value) return;
- const tbody = tableRef.value.$el.querySelector('.el-table__body-wrapper tbody');
- if (!tbody) return;
- // 使用原生拖拽API实现排序
- let draggedRow = null;
- let draggedIndex = -1;
- // 创建事件处理器
- dragEventHandlers = {
- handleDragStart: (e) => {
- if (!isSortMode.value) return;
- draggedRow = e.target.closest('tr');
- if (draggedRow) {
- e.dataTransfer.effectAllowed = 'move';
- draggedRow.style.opacity = '0.5';
- const rows = Array.from(tbody.querySelectorAll('tr'));
- draggedIndex = rows.indexOf(draggedRow);
- }
- },
- handleDragEnd: (e) => {
- if (draggedRow) {
- draggedRow.style.opacity = '';
- draggedRow = null;
- draggedIndex = -1;
- }
- },
- handleDragOver: (e) => {
- if (!isSortMode.value || !draggedRow) return;
- e.preventDefault();
- e.dataTransfer.dropEffect = 'move';
- },
- handleDrop: (e) => {
- if (!isSortMode.value || !draggedRow) return;
- e.preventDefault();
- const dropRow = e.target.closest('tr');
- if (!dropRow || dropRow === draggedRow) return;
- // 获取目标行的索引
- const rows = Array.from(tbody.querySelectorAll('tr'));
- const dropIndex = rows.indexOf(dropRow);
- if (draggedIndex !== -1 && dropIndex !== -1 && draggedIndex !== dropIndex) {
- // 重新排序数据
- const newData = [...tableData.value];
- const [draggedItem] = newData.splice(draggedIndex, 1);
- newData.splice(dropIndex, 0, draggedItem);
- // 更新排序值
- newData.forEach((item, index) => {
- item.sort = index + 1;
- });
- tableData.value = newData;
- }
- }
- };
- // 添加事件监听器
- tbody.addEventListener('dragstart', dragEventHandlers.handleDragStart);
- tbody.addEventListener('dragend', dragEventHandlers.handleDragEnd);
- tbody.addEventListener('dragover', dragEventHandlers.handleDragOver);
- tbody.addEventListener('drop', dragEventHandlers.handleDrop);
- // 为每行添加拖拽属性
- const rows = tbody.querySelectorAll('tr');
- rows.forEach(row => {
- row.draggable = isSortMode.value;
- });
- };
- /**
- * 保存排序
- */
- const saveSortOrder = async () => {
- // 准备排序数据
- const sortData = tableData.value.map((item, index) => ({
- id: item.id,
- action_index: index + 1
- }));
- console.log("sort_data",sortData)
- const result = await sortDeviceConfig({
- sorts: sortData
- })
- if (result.code == 0) {
- getList();
- ElMessage.success('排序成功');
- }
- };
- /**
- * 获取行类名
- */
- const getRowClassName = ({ row, rowIndex }) => {
- if (isSortMode.value) {
- return 'sortable-row';
- }
- return '';
- };
- /**
- * 退出排序模式
- */
- const exitSortMode = () => {
- isSortMode.value = false;
- // 恢复原始数据
- tableData.value = JSON.parse(JSON.stringify(originalTableData.value));
- // 移除拖拽事件监听器和属性
- if (tableRef.value && dragEventHandlers) {
- const tbody = tableRef.value.$el.querySelector('.el-table__body-wrapper tbody');
- if (tbody) {
- // 移除事件监听器
- tbody.removeEventListener('dragstart', dragEventHandlers.handleDragStart);
- tbody.removeEventListener('dragend', dragEventHandlers.handleDragEnd);
- tbody.removeEventListener('dragover', dragEventHandlers.handleDragOver);
- tbody.removeEventListener('drop', dragEventHandlers.handleDrop);
- // 移除拖拽属性
- const rows = tbody.querySelectorAll('tr');
- rows.forEach(row => {
- row.draggable = false;
- });
- }
- dragEventHandlers = null;
- }
- };
- </script>
- <style lang="scss" scoped>
- .top_tabs {
- height: 40px;
- overflow: hidden;
- border: 1px solid #c8c8c8;
- border-bottom: none;
- ::v-deep {
- .el-tabs__item {
- height: 40px;
- line-height: 40px;
- padding: 0 15px;
- font-size: 14px;
- color: #333;
- &.is-active {
- color: #2957FF;
- }
- }
- }
- }
- .two_tabs {
- width: 100%;
- height: 30px;
- background: #fff;
- border: 1px solid #c8c8c8;
- border-top: none;
- .item {
- float: left;
- padding: 0 15px;
- font-size: 14px;
- height: 30px;
- line-height: 30px;
- cursor: pointer;
- &.active {
- color: #2957FF;
- }
- }
- }
- .form-table{
- margin-top: 10px;
- .btnBox{
- display: flex;
- align-items: center;
- margin-bottom: 12px;
- }
- :deep(.el-table .el-table__header){
- padding: 0;
- height: 30px;
- .el-table__cell{
- background: #F1F4FF;
- }
- }
- :deep(.el-table .el-table__cell){
- padding: 0;
- text-align: center;
- }
- :deep(.el-table__row) {
- height: 30px;
- padding: 0;
- &:nth-child(even) {
- background: #F1F4FF;
- }
- &:nth-child(odd) {
- background: #FFFFFF;
- }
- }
- .primary-btn{
- width: 80px;
- height: 30px;
- background: linear-gradient( 135deg, #2FB0FF 0%, #B863FB 100%);
- border-radius: 4px;
- color: #fff;
- font-size: 14px;
- text-align: center;
- cursor: pointer;
- line-height: 30px;
- margin-right: 10px;
- }
- .normal-btn{
- width: 80px;
- height: 30px;
- background: #fff;
- border: 1px solid #CCCCCC;
- border-radius: 4px;
- font-size: 14px;
- text-align: center;
- line-height: 30px;
- cursor: pointer;
- }
- .cursor-pointer{
- cursor: pointer;
- }
- // 排序相关样式
- .sort-content {
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 100%;
- .sort-number {
- font-weight: bold;
- color: #2957FF;
- font-size: 14px;
- }
- .sort-handle {
- cursor: move;
- color: #909399;
- display: flex;
- align-items: center;
- justify-content: center;
- &:hover {
- color: #2957FF;
- }
- }
- }
- :deep(.sortable-row) {
- cursor: move;
- &:hover {
- background-color: #f5f7fa !important;
- }
- }
- :deep(.el-table__row.sortable-row) {
- transition: all 0.3s ease;
- }
- :deep(.el-table__body-wrapper tbody tr) {
- &.sortable-row {
- cursor: move;
- &:hover {
- background-color: #f5f7fa !important;
- }
- }
- }
- .disabled {
- pointer-events: none;
- }
- .sort-tip {
- background: #EAF3FF;
- border: 1px solid #CBE1FF;
- border-radius: 4px;
- padding: 8px 12px;
- margin-bottom: 12px;
- display: flex;
- align-items: center;
- gap: 8px;
- color: #2957FF;
- font-size: 14px;
- }
- }
- .editDialog{
- .el-dialog__body{
- padding: 0 !important;
- }
- .btn-row{
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 10px;
- }
- .primary-btn{
- width: 100px;
- height: 30px;
- background: linear-gradient( 135deg, #2FB0FF 0%, #B863FB 100%);
- border-radius: 4px;
- color: #fff;
- font-size: 14px;
- text-align: center;
- line-height: 30px;
- cursor: pointer;
- }
- .normal-btn{
- width: 100px;
- height: 30px;
- background: #fff;
- border: 1px solid #CCCCCC;
- border-radius: 4px;
- font-size: 14px;
- text-align: center;
- line-height: 30px;
- cursor: pointer;
- }
- }
- </style>
|