<template> <Com></Com> </template> <script setup lang="jsx"> const Com = () => { return ( <> 哈哈 </> ) } </script> 结合element plus 表单 写一个示例

时间: 2025-08-19 07:55:50 AIGC 浏览: 19
在 Vue 3 的 `.vue` 文件中使用 JSX 语法结合 Element Plus 实现表单组件时,需要确保项目已经正确配置了 JSX 支持,并且安装了 Element Plus。以下是完整的实现示例。 ### 配置 JSX 支持 确保项目中已经安装了 `@vitejs/plugin-vue-jsx` 插件,用于支持 JSX 语法。如果使用 Vite 构建工具,可以通过以下命令安装: ```bash npm install @vitejs/plugin-vue-jsx --save-dev ``` 然后在 `vite.config.js` 中启用 JSX 支持: ```javascript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' export default defineConfig({ plugins: [ vue(), vueJsx() ] }) ``` ### 创建表单组件 在 `.vue` 文件中,通过 JSX 语法结合 Element Plus 组件实现一个表单组件。以下是一个示例代码: ```vue <script setup> import { ref } from 'vue' import { ElForm, ElFormItem, ElInput, ElButton } from 'element-plus' </script> <template> <el-form :model="formData" label-width="120px" @submit.prevent="onSubmit"> <el-form-item label="姓名"> <el-input v-model="formData.name" /> </el-form-item> <el-form-item label="邮箱"> <el-input v-model="formData.email" /> </el-form-item> <el-form-item> <el-button type="primary" native-type="submit">提交</el-button> </el-form-item> </el-form> </template> <script> export default { data() { return { formData: { name: '', email: '' } } }, methods: { onSubmit() { console.log('提交表单数据:', this.formData) } } } </script> ``` 上述代码中,通过 `ElForm`、`ElFormItem`、`ElInput` 和 `ElButton` 等 Element Plus 组件构建了一个简单的表单界面,并通过 `v-model` 实现了数据双向绑定。表单提交时会触发 `onSubmit` 方法并输出当前数据到控制台。 ### 使用 JSX 语法 如果希望使用 JSX 语法直接编写表单内容,可以修改 `<template>` 部分为 JSX 写法: ```jsx <script setup> import { ref } from 'vue' import { ElForm, ElFormItem, ElInput, ElButton } from 'element-plus' </script> <template> <el-form :model="formData" label-width="120px" @submit.prevent="onSubmit"> <el-form-item label="姓名"> <el-input v-model="formData.name" /> </el-form-item> <el-form-item label="邮箱"> <el-input v-model="formData.email" /> </el-form-item> <el-form-item> <el-button type="primary" native-type="submit">提交</el-button> </el-form-item> </el-form> </template> <script> export default { data() { return { formData: { name: '', email: '' } } }, methods: { onSubmit() { console.log('提交表单数据:', this.formData) } } } </script> ``` ### 注意事项 - 在 Vue 3 中使用 JSX 时,需要确保项目构建工具支持 JSX 插件,例如 `@vitejs/plugin-vue-jsx`。 - Element Plus 的组件需要按需导入,或者在全局注册。 - 表单提交时,可以通过 `@submit.prevent` 阻止默认提交行为,并通过 `onSubmit` 方法处理数据。 ###
阅读全文

相关推荐

<script setup lang="ts"> import { defineComponent, ref } from 'vue' const ipcHandle = (): void => window.electron.ipcRenderer.send('ping') export default defineComponent({ setup() { const searchText = ref('') const results = ref<string[]>([]) const isSearched = ref(false) const handleSearch = () => { if (searchText.value) { // 模拟搜索结果 results.value = new Array(5).fill(searchText.value) isSearched.value = true } } return { searchText, results, isSearched, handleSearch } } }) </script> <template> <input v-model="searchText" type="text" /> <button @click="handleSearch">搜索</button> {{ item }} </template> <style scoped> .container { height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; transition: all 0.3s ease; } .search-box { display: flex; gap: 10px; transition: margin-top 0.3s ease; } .searched .search-box { margin-top: 20px; } .result-list { width: 300px; margin-top: 20px; padding: 0; list-style: none; } .result-list li { padding: 10px; border-bottom: 1px solid #eee; } @media (max-width: 480px) { .search-box { flex-direction: column; } } </style>9:54:59 AM [vite] (client) Pre-transform error: [@vue/compiler-sfc] <script setup> cannot contain ES module exports. If you are using a previous version of <script setup>, please consult the updated RFC at https://github.com/vuejs/rfcs/pull/227. /Users/apple/Desktop/electron-app/src/renderer/src/App.vue 1 | <script setup lang="ts"> 2 | import { defineComponent, ref } from 'vue' 3 | const ipcHandle = (): void => window.electron.ipcRenderer.send('ping') |

<template> <van-nav-bar title="我的购物车" @click-left="router.back()" @click-right="reload()" left-arrow style="background-color: #1E90FF; --van-nav-bar-icon-color:white; --van-nav-bar-arrow-size:30px; --van-nav-bar-title-text-color:white"> <template #right> <van-icon name="replay" color="white" size="30" /> </template> </van-nav-bar> <input type="checkbox" v-model="cbs" :value="item.id"> {{item.proName}} ¥:{{ item.price }}---鞋码:{{ item.size }}X{{ item.num }} <button @click="goUpdate(item)">修改</button> 暂无商品 <input v-model="cbAll" @click="selAll();" type="checkbox" name="" id=""> 全选 <button @click="del();">删除</button> 合计:¥:{{ total }}<button>立即结算</button> <van-overlay :show="upDialog"> 编辑商品 尺码: {{item}} 数量:<button>-</button><input type="text" v-model="unum" value="1"><button>+</button> <button @click="update()">确定</button> <button @click="upDialog=false">取消</button> </van-overlay> </template> <script setup> import {computed, onMounted, ref, watch} from "vue" import { useRouter } from "vue-router" import checkLogin from "../login"; import http from "../axios/http" import { showToast } from "vant"; let router=useRouter(); checkLogin(); //检查登入状态 //定义模型变量 let products=ref([]) let cbs=ref([]) //对应一组复选框 let cbAll=ref(false) let total=ref(0) //总价 let sizes=ref([]) //控制修改的尺码 let unum =ref(0) //控修改的数量 let size=ref(0) //控制用户选中尺码 //控制模态框 let upDialog=ref(false) //加载钩子 onMounted(()=>{ //调用接口获取购物车商品 http.get("cart/queryCart").then((data)=>{ console.log(data) products.value=data; }).catch((error)=>{ console.log(error) }) }); //转换图片路径 function changePath(path){ return path.replace("./img","/src/assets/img") } //实现删除 function del(){ let ary=cbs.value; //伪数组 proxy={0:值,1:值} console.log(ary) //console.log(Array.from(ary)) //将伪数组轮为数组 //console.log(ary.map((v,k)=>v)); //let ary1=ary.map((v,k)=>v); //将伪数转化为数组 let ary1=[]; ary.forEach(item=> ary1.push(item)) //调用接口实现删除 if(ary.length==0){ //alert("你没有选择删除的商品"); showToast("你没有选择删除的商品") return; } http.get("cart/deleteCart",{params:{ id:ary1 //传数组:[21,22] }}).then(data=>{ //console.log(data) if(data.status==200){ //重新查询购物车的商品信息展示 location.reload(); }else{ // alert("删除失败"); showToast("删除失败") } }).catch(error=>{ console.log(error) }) } function selAll(){ if(!cbAll.value){ //alert("全选") let ids=products.value.map(item=>item.id) cbs.value=ids; }else{ //alert("不全选") cbs.value=[] } } //监听 模型变量 watch(cbs,function(newv,olev){ //判断全选复选框的状态 if(cbs.value.length==products.value.length){ //让全选复选框选 cbAll.value=true; }else{ cbAll.value=false; } //计算总价 //循环复选框选中的值 let temp=0; cbs.value.forEach((item)=>{ //通过选中的id,找到商品 let pintem=products.value.find((p)=> p.id==item) temp = temp + pintem.num*pintem.price }) total.value=temp; }) //修改 function goUpdate(objItem){ //1.显示模态框 upDialog.value=true; //2.显示对应尺码和数量 //清空 sizes.value=[]; let ary=objItem.productSize.split("-"); for(let i=ary[0];i<=ary[1];i++){ sizes.value.push(i); } //3.还原数量,和对应的尺码 unum.value=objItem.num size.value=objItem.size } //修改 function update(){ alert("调用接口实现修改。。。关闭模态框"); upDialog.value=false; } function reload(){ location.reload() } </script> <style scoped> #maint { height: 100%;} #maint .top{ background-color: #1E90FF; height: 45px; display: flex; justify-content: space-between; align-items: center; position: fixed; width: 100%; top: 0px; } #maint .m{ width: 100%; height: calc(100% - 40px); /* margin-top: 45px; */ /* background-color: red; */ overflow-y: scroll; overflow-x: visible; /* text-align: center; */ padding: 10px; font-size: 14px; } .m .product{ display: flex; /* background-color: red; */ align-items:center; gap:15px; /*设置弹性盒子间距 */ margin-bottom: 15px; border-bottom: 1px solid grey; } .m img{ width: 66px; height: 66px;} .m .product .three{ align-self: stretch; /*高度自适应父盒子高,不设置由内容决定高度 */ flex: 1; display:flex; flex-direction: column; justify-content: space-between; align-items:flex-start; } #maint .foot{ height: 40px; position: fixed; bottom:60px; width: 100%; border: 1px solid grey; /* background-color: red; */ display: flex; justify-content: space-between; align-items:center; } .updateMark { position: absolute; left: 0; top: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); z-index: 10; } .updateMark .content{ background-color: white; border: 1px solid grey; border-radius: 20px; width:90%; margin-left:5%; height: 200px; margin-top:100px; } .cm{ border: 1px solid grey; padding: 5px 8px; margin:3px 6px; display: inline-block; } #active{ background-color: green;} .wrapper { display: flex; align-items: center; justify-content: center; height: 100%; } .block { width: 80%; height: 200px; border-radius: 20px; background-color: #fff; } </style>帮我把这个js写的购物车vue组件改成用ts写

<script setup lang="tsx"> import { computed, ref, watch } from "vue"; import { useNaiveForm } from "@/hooks/common/form"; import { $t } from "@/locales"; import { useAppStore } from "@/store/modules/app"; import { lockTypesRecord, moduleTypesRecord, logLevelTypeRecord, } from "@/constants/business"; defineOptions({ name: "InterlockDetailModal", }); const appStore = useAppStore(); export type OperateType = "detail"; const column = computed(() => (appStore.isMobile ? 1 : 3)); interface Props { /** the type of operation */ operateType: OperateType; /** the edit menu data or the parent menu data when adding a child menu */ /** all pages */ rowData: any | null; } const props = defineProps(); const visible = defineModel<boolean>("visible", { default: false, }); const { restoreValidation } = useNaiveForm(); type Model = Api.InterLock.Detail & { // 自定义字段 // query: NonNullable<Api.SystemManage.Menu["query"]>; // buttons: NonNullable<Api.SystemManage.Menu["buttons"]>; // layout: string; // page: string; // pathParam: string; }; // const model = ref(createDefaultModel()); // function createDefaultModel(): Model { // return { // interlockNo: "", // clientCode: "", // equip: "", // dept: "", // machineStatus: "", // equipStage: "", // outDate: null, // isWarranty: "", // isMoc: "", // requireNum: "", // ecnNum: "", // problemNum: "", // fmtOutDate: "", // customerName: "", // orderNo: "", // requireNo: "", // }; // } function handleInitModel() {} function closeDrawer() { visible.value = false; } watch(visible, () => { if (visible.value) { restoreValidation(); } }); </script> <template> <NModal v-model:show="visible" preset="card" class="w-1200px"> <template #header> <NSpace style="border-bottom: 1px solid var(--n-border-color)"> {{ rowData?.alarmName }}{{ $t("page.interlock.ledger.detail") }} </NSpace> </template> <NScrollbar class="h-full pr-20px"> <NCollapse :default-expanded-names="['basic', 'alarm']"> <NCollapseItem :title="$t('page.interlock.detail.basicTitle')" name="basic" > <NDescriptions label-placement="left" bordered size="small" :column="column" > <NDescriptionsItem :label="$t('page.interlock.ledger.alarmName')"> <NTag type="primary" :bordered="false">{{ rowData?.alarmName }}</NTag> </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.ledger.lockType')""> <NTag type={rowData?.lockType === "vinterlock" ? "error" : rowData?.lockType === "sinterlock" ? "success" : "default"} bordered={false} > {$t( lockTypesRecord[ rowData?.lockType as keyof typeof lockTypesRecord ] )} </NTag> </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.detail.alarmLevel')"> <NTag type="default" :bordered="false"> {{ $t( logLevelTypeRecord[rowData?.alarmLevel as Api.Common.LogLevelType ], ) }} </NTag> </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.ledger.product')"> {{ rowData?.product }} </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.detail.deptName')"> {{ rowData?.deptName }} </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.ledger.moduleName')"> {{ rowData?.moduleName }} </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.ledger.moduleType')"> <NTag type="default" :bordered="false"> {{ $t( moduleTypesRecord[rowData?.moduleType as Api.InterLock.moduleTypes ], ) }} </NTag> </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.ledger.trigger')"> {{ rowData?.trigger }} </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.ledger.triggerWay')"> {{ rowData?.triggerWay }} </NDescriptionsItem> </NDescriptions> </NCollapseItem> <NCollapseItem :title="$t('page.interlock.detail.alarmTitle')" name="alarm" > <NDescriptions label-placement="left" bordered size="small" :column="column" > <NDescriptionsItem v-if="rowData?.lockType === 'sinterlock'" :label="$t('page.interlock.detail.condition')" > {{ rowData?.condition }} </NDescriptionsItem> <NDescriptionsItem v-if="rowData?.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.channelNumber')" > {{ rowData?.channelNumber }} </NDescriptionsItem> <NDescriptionsItem v-if="rowData?.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.alarmDetail')" > {{ rowData?.alarmDetail }} </NDescriptionsItem> <NDescriptionsItem v-if="rowData?.lockType === 'vinterlock'" :label="$t('page.interlock.detail.alarmDesc')" > {{ rowData?.alarmDesc }} </NDescriptionsItem> <NDescriptionsItem v-if="rowData?.lockType === 'vinterlock'" :label="$t('page.interlock.detail.alarmAction')" > {{ rowData?.alarmAction }} </NDescriptionsItem> <NDescriptionsItem v-if="rowData?.lockType === 'vinterlock'" :label="$t('page.interlock.detail.alarmChannel')" > {{ rowData?.alarmChannel }} </NDescriptionsItem> <NDescriptionsItem v-if="rowData?.lockType === 'vinterlock'" :label="$t('page.interlock.detail.alarmActionDesc')" > {{ rowData?.alarmActionDesc }} </NDescriptionsItem> <NDescriptionsItem :label="$t('page.interlock.detail.desc')"> {{ rowData?.desc }} </NDescriptionsItem> </NDescriptions> </NCollapseItem> </NCollapse> </NScrollbar> <template #footer> <NSpace justify="end" :size="16" style="border-top: 1px solid var(--n-border-color)" > <NButton @click="closeDrawer" style="margin-top: 5px">{{ $t("common.cancel") }}</NButton> </NSpace> </template> </NModal> </template> <style scoped></style>

<template> <VAceEditor v-model:value="content" :lang="language" :theme="theme" :options="editorOptions" style="height: 300px; width: 100%" /> <component :is="Comp" /> </template> <script setup> import { ref, defineComponent, compile, watch } from "vue"; import { VAceEditor } from "vue3-ace-editor"; // 引入 VAceEditor 组件 import "ace-builds/src-noconflict/mode-vue"; // 引入 Vue 模式支持 import "ace-builds/src-noconflict/theme-github"; // 引入 GitHub 主题样式 // 定义编辑器的内容、语言模式、主题和配置选项 const content = ref(Hello, Vue Editor!); // 初始化代码内容 const language = ref("vue"); // 设置默认语言为 Vue const theme = ref("github"); // 设置默认主题为 GitHub 风格 // 编辑器的配置选项,如字体大小、是否显示打印边距等 const editorOptions = ref({ fontSize: "14px", // 设置字体大小 showPrintMargin: false, // 是否显示打印边距 enableLiveAutocompletion: true, // 启用实时自动补全功能 enableSnippets: true, // 启用代码段 highlightActiveLine: true, // 高亮行 highlightSelectedWord: true, // 高亮选中的字符 tabSize: 4, // tab缩进字符 }); // 动态编译用户输入的 Vue 代码 const Comp = ref(null); const updateComp = () => { console.log(compile,'compile') try { const result = compile(content.value); console.log(result, "result"); const { render, staticRenderFns } = result; Comp.value = defineComponent({ render, staticRenderFns, }); } catch (error) { console.error("编译 Vue 代码时出错:", error); } }; // 监听内容变化,更新组件 watch( content, () => { updateComp(); }, { immediate: true } ); </script> 修复这段代码

组件代码: <template> <el-input v-if="props.showSearch" class="left-tree-input" v-model="searchData" :placeholder="props.searchPlaceholder" :prefix-icon="Search" clearable @input="debouncedInput" /> <el-button v-if="props.showAddBtn" class="left-tree-btn" type="primary" :icon="Plus" @click="handleClick"> {{ props.addBtnText }} </el-button> <el-tree ref="treeRef" :data="filteredTreeData" :props="props.treeProps" :check-strictly="props.checkStrictly" :nodeKey="props.treeProps.nodeKey" :show-checkbox="props.showCheckbox" @check="handleCheck" @node-click="handleNodeClick" highlight-current :default-checked-keys="props.selectedNodes" :current-node-key="currentNodeKey" :default-expanded-keys="expandedList" @node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse" > <template #default="{ node, data }"> {{ node.label }} <slot name="label" :node="data"></slot> <el-dropdown v-if="props.operateList && props.operateList.length > 0" @command="e => handleCommand(node, e)"> <el-icon v-if="data.noShowIcon !== true"> <More /> </el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item v-for="(item, index) in props.operateList" :key="index" :command="item.type"> {{ item.name }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </template> </el-tree> </template> <script setup> import { ref, onMounted, watch, computed, getCurrentInstance } from 'vue'; import { Search, Plus } from '@element-plus/icons-vue'; import { useRouter } from 'vue-router'; import { debounce } from 'lodash'; import i18n from '@/locale/index'; defineOptions({ name: 'jl-left-tree' }); const { appContext } = getCurrentInstance(); const { $utils, $request, $message, $rules } = appContext.config.globalProperties; const emit = defineEmits(['checkCb', 'addCb', 'searchCb', 'treeOperateCb', 'clickTree']); const props = defineProps({ type: { type: String, default: 'primary' }, operateList: { type: Array, default: () => [] }, showSearch: { type: Boolean, default: false }, searchPlaceholder: { type: String, default: i18n.global.t('请输入') }, addBtnText: { type: String, default: i18n.global.t('新增') }, showAddBtn: { type: Boolean, default: false }, treeProps: { type: Object, default: () => { let obj = { label: 'label', children: 'children', nodeKey: 'id' }; return obj; } }, treeData: { type: Array, default: () => [] }, showCheckbox: { type: Boolean, default: false }, selectTreeData: { type: Object, default: () => {} }, // 默认展开 defaultExpanded: { type: Array, default: () => [] }, // 树label宽度 labelWidth: { type: [String, Number], default: 120 }, selectedNodes: { type: Array, default: () => [] }, checkStrictly: { type: Boolean, default: false }, isFilter: { type: Boolean, default: false } }); let currentNodeKey = ref(); // 选中节点 let treeRef = ref(); let searchData = ref(''); let originalTreeData = ref([]); // 保存原始树数据 // 创建深拷贝函数 const deepClone = (data) => { return JSON.parse(JSON.stringify(data)); }; let expandedList = ref([]); // 过滤树数据 const filteredTreeData = computed(() => { if (!props.isFilter || !searchData.value) { return originalTreeData.value; } const filter = searchData.value.toLowerCase(); const propsLabel = props.treeProps.label; const filterData = (data) => { return data.filter(item => { const label = item[propsLabel]?.toString().toLowerCase() || ''; // 检查当前节点是否匹配 const isMatch = label.includes(filter); // 递归过滤子节点 if (item.children && item.children.length > 0) { const filteredChildren = filterData(item.children); if (filteredChildren.length > 0) { // 如果有匹配的子节点,保留当前节点及其子节点 return { ...item, children: filteredChildren }; } } // 如果没有子节点或子节点不匹配,但当前节点匹配,则保留 return isMatch ? item : null; }).filter(Boolean); // 过滤掉null值 }; return filterData(deepClone(originalTreeData.value)); }); // 监听原始树数据变化 watch( () => props.treeData, (newValue) => { originalTreeData.value = deepClone(newValue); }, { immediate: true, deep: true } ); watch( () => props.selectTreeData, (newValue) => { if (newValue) { currentNodeKey.value = newValue[props.treeProps.nodeKey]; } }, { immediate: true, deep: true } ); /** * 树选中 */ const handleCheck = (data, e) => { emit('checkCb', { data, e }); }; /** * 新增按钮 */ const handleClick = () => { emit('addCb'); }; /** * 搜索 */ const handleInputChange = (e) => { if (props.isFilter) { // 前端过滤,无需额外处理 } else { emit('searchCb', e); } }; // 创建防抖函数 const debouncedInput = debounce(handleInputChange, 500); /** * 树更多菜单 */ const handleCommand = (item, type) => { emit('treeOperateCb', { item: item.data, type }); }; /** * 点击树节点 */ const handleNodeClick = (data, treeNode, treeNode1, treeNode2) => { emit('clickTree', data); }; /** * 展开树节点 */ const handleNodeExpand = (data, node, e) => { expandedList.value.push(data[props.treeProps.nodeKey]); }; /** * 收起树节点 */ const handleNodeCollapse = (data, node, e) => { let index = expandedList.value.findIndex(item => item === data[props.treeProps.nodeKey]); expandedList.value.splice(index, 1); }; onMounted(() => { expandedList.value = props.defaultExpanded; }); </script> <style scoped lang="scss"> .left-tree-wrap { padding: 12px; display: flex; flex-flow: column nowrap; height: 100%; .left-tree-input { margin-bottom: 12px; } .left-tree-btn { width: 100%; margin-bottom: 12px; } .tree-scroll { :deep(.el-tree-node__content) { padding-top: 20px !important; padding-bottom: 20px !important; padding-right: 8px; } overflow-y: auto; flex-shrink: 2; .tree-wrap { display: flex; justify-content: space-between; align-items: center; width: 100%; .text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 120px; } } .icon { border-radius: 4px; display: flex; justify-content: center; align-items: center; width: 20px; height: 20px; &:hover { background: #c8c9cc; } } } } </style> 页面使用: <jl-left-tree1 :treeData="treeData" showSearch :labelWidth="230" showCheckbox :isFilter="true" @checkCb="checkCb" :searchPlaceholder="t('请输入名称')" :treeProps="{ label: 'itemName', children: 'deviceList', nodeKey: 'id' }" @clickTree="clickTree" @searchCb="searchCb" :selectTreeData="selectTreeData" :selectedNodes="selectedNodes" ></jl-left-tree1> // 数选择 const checkCb = item => { console.log('item', item) selectedNodes.value = item.e.checkedKeys; isChange.value = true; }; /** * 树点击 */ const clickTree = data => { selectTreeData.value = data; }; // 树搜索 const searchCb = e => { devPointScreen(e, true); }; let selectTreeData = ref({}); // 选中树的数据 let selectedNodes = ref([]); // 被选中的节点数组 帮我综合分析并解决问题:当我在输入框输入文字利用isFilter=true进行过滤后,将搜索出来的节点进行勾选时会将搜索前已经勾选的节点取消勾选,同理,当我操作搜索结果节点取消勾选时也会将搜索前已勾选的取消勾选,解决此问题,输出为完整组件代码以及页面代码,保证我要的效果(当我搜索后勾选时就只操作当前操作的节点--对应搜索前的所有节点中,当我取消勾选时也只操作取消勾选的节点)

Error: Please import the top-level fullcalendar lib before attempting to import a plugin.下面是我的代码,并且使用的版本一致<template> <el-card body-style="padding:0 0 0 0; " class="normal-page"> 我的日程 <el-button type="primary" size="small" :icon="Plus" @click="addSchedule"> {{ $t('calendar.newschedule') }} </el-button> <el-row :gutter="14"> <el-col :span="7"> <calendar-bar ref="calendarBar" @handle-date="handleDate" /> </el-col> <el-col :span="17" class="line" v-loading="loading" element-loading-text="数据正在加载中"> <el-radio-group v-model="time" size="small" @change="selectChange"> <el-radio-button v-for="(itemn, indexn) in fromList" :key="indexn" :label="itemn.val" > {{ itemn.text }} </el-radio-button> </el-radio-group> <FullCalendar ref="calendar" id="calendar" style="height: calc(100vh - 150px)" :options="calendarOptions"> <template v-slot:slotLabelContent="arg"> </template> <template v-slot:dayHeaderContent="arg"> </template> <template v-slot:eventContent="arg"> {{ arg.event.title }} <template v-if="arg.event.extendedProps.show > -1"> </template> </template> </FullCalendar> </el-col> </el-row> </el-card> <add-todo ref="addTodo" @get-list="getList" :left-time="leftTime"></add-todo> <calendar-details ref="calendarDetails" @delete-fn="getList" @edit-fn="editFn" :date-info="dateInfo" ></calendar-details> <contract-dialog ref="contractDialog" :config="configContract" @is-ok="getList"></contract-dialog> <el-dialog title="添加跟进记录" class="record" v-model="dialogVisible" width="40%"> <record-upload :form-info="formInfo" @change="recordChange"></record-upload> </el-dialog> <edit-examine ref="editExamine" :parameter-data="parameterData" :ids="formInfo.data.id" @schedule-record="scheduleRecord" ></edit-examine> </template> <script> import FullCalendar from '@fullcalendar/vue3' // 核心库必须第一个导入 import dayGridPlugin from '@fullcalendar/daygrid' import timeGridPlugin from '@fullcalendar/timegrid' import interactionPlugin from '@fullcalendar/interaction' import listPlugin from '@fullcalendar/list' import { defineComponent, defineAsyncComponent, ref, reactive, onMounted, computed, toRefs, nextTick } from 'vue'; import { Plus } from '@element-plus/icons-vue'; //import { ElMessage } from 'element-plus'; import moment from 'moment'; // 正确顺序:先核心库再插件 // 样式导入(必须在插件之后) // import '@fullcalendar/common/main.css' // import '@fullcalendar/daygrid/main.css' // import '@fullcalendar/timegrid/main.css' // import '@fullcalendar/list/main.css' console.log(window.FullCalendar); // 检查全局对象 console.log(window.FullCalendarCore); // 如果你手动挂载了核心库 // 根据实际需要取消注释这些API导入 // import { // clientRemindDetailApi, // scheduleListApi, // scheduleStatusApi // } from '@/api/enterprise'; // import { configRuleApproveApi } from '@/api/config'; // import { toGetWeek, getColor } from '@/utils/format'; export default defineComponent({ name: 'WorkDealt', components: { CalendarBar: defineAsyncComponent(() => import('./components/calendarBar.vue')), AddTodo: defineAsyncComponent(() => import('./components/addTodo')), FullCalendar, CalendarDetails: defineAsyncComponent(() => import('./components/calendarDetails')) // ContractDialog: defineAsyncComponent(() => import('@/views/customer/contract/components/contractDialog')), // RecordUpload: defineAsyncComponent(() => import('@/views/customer/list/components/recordUpload')), // EditExamine: defineAsyncComponent(() => import('@/views/user/examine/components/editExamine')) }, setup() { const state = reactive({ loading: false, calendarApi: null, title: '', time: '1', cid: null, dialogVisible: false, configContract: {}, formInfo: { avatar: '', type: 'add', show: 1, data: {}, follow_id: 0 }, fromList: computed(() => [ {text: '日', val: '1'}, {text: '周', val: '2'}, {text: '月', val: '3'} ]), start_time: '', end_time: '', dateInfo: {}, type: [], period: 1, calendarOptions: { height: 'calc(100vh - 232px)', eventColor: '', initialDate: moment().format('YYYY-MM-DD HH:mm:ss'), plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin], handleWindowResize: true, displayEventTime: false, slotDuration: '00:60:00', scrollTime: '05:30:00', slotMinTime: '00:00:00', slotMaxTime: '24:00:00', headerToolbar: { left: '', center: 'prev title next', right: '' }, buttonText: { month: '月', week: '周', day: '天' }, allDaySlot: true, dayMaxEventRows: 6, allDayText: '', moreLinkContent: state => 还有${state.num}个日程, weekends: true, nowIndicator: true, weekNumbers: false, slotLabelFormat: { hour: '2-digit', minute: '2-digit', meridiem: false, hour12: false }, selectable: true, displayEventEnd: false, initialView: 'timeGridDay', dateClick: (e) => handleDateClick(e), customButtons: { next: { text: 'PREV', click: () => next() }, prev: { text: 'PREV', click: () => prev() } }, events: [], slotEventOverlap: false, eventOverlap: false, locale: 'zh-cn', weekNumberCalculation: 'ISO' }, detailedData: {}, parameterData: { contract_id: '', customer_id: '', invoice_id: '', bill_id: '' }, leftTime: '', buildData: [], item: {}, status: 0 }) const calendarBar = ref(null); const addTodo = ref(null); const calendarDetails = ref(null); const contractDialog = ref(null); const editExamine = ref(null); const userId = ref(null); // 日期更新函数 const updateCalendarDates = () => { if (!state.calendarApi) return; state.title = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD'); state.start_time = state.title + ' 00:00:00'; state.end_time = state.title + ' 23:59:59'; }; onMounted(() => { const userInfo = JSON.parse(localStorage.getItem('userInfo')); if (userInfo) { userId.value = userInfo.userId; state.formInfo.avatar = userInfo.avatar; } // 使用 nextTick 确保在 DOM 更新后执行 nextTick(() => { if (calendarBar.value && calendarBar.value.getApi) { state.calendarApi = calendarBar.value.getApi(); dayRender(); updateCalendarDates(); } }); }); // 原始方法开始(保持所有方法逻辑不变) // const handleEventClick = e => { calendarDetails.value.open(e.event.extendedProps); } const recordChange = () => { state.dialogVisible = false; getList(); } const getConfigApprove = async () => { // 实际项目中取消注释 // const result = await configRuleApproveApi(0); // state.buildData = result.data; } const putStatus = async (text, status, item) => { const {extendedProps} = item; state.item = extendedProps; const bill_id = extendedProps.bill_id; switch (extendedProps.cid_value) { case 3: case 4: const id = bill_id.bill_id !== 0 ? bill_id.bill_id : bill_id.remind_id; if (status == 3) { // 实际项目中取消注释 // clientRemindDetailApi(id).then((res) => { // res.data.id = bill_id.remind_id; // state.parameterData.customer_id = res.data.eid; // state.parameterData.contract_id = res.data.cid; // // const switchType = res.data.types === 0 ? 'contract_refund_switch' : 'contract_renew_switch'; // const data = {id: state.buildData[switchType]}; // editExamine.value.open(data, res.data.cid, switchType, item, status); // }) } break; case 2: if (extendedProps.finish === 3) return false; state.formInfo.follow_id = bill_id ? bill_id.follow_id : 0; state.formInfo.data.eid = extendedProps.link_id; state.dialogVisible = true; break; default: scheduleRecord(extendedProps, status); } } const scheduleRecord = async (data = state.item, status = state.status) => { const {start_time: start, end_time: end, itemId: id} = data; const info = {status, start, end}; // 实际项目中取消注释 // await scheduleStatusApi(id, info); await getList(); } const handleDateClick = e => { state.detailedData = { startDate: moment(e.date).format('YYYY-MM-DD'), startTime: moment(e.date).format('HH:mm:ss'), endDate: moment(e.date).format('YYYY-MM-DD'), time: moment(e.date).format('YYYY-MM-DD HH:mm:ss') } addTodo.value.open(state.detailedData); } const dayRender = () => { document.documentElement.style.setProperty(--fc-today-bg-color, '#fff'); let dayTime = document.querySelectorAll('.fc-timegrid-slot-label-cushion'); dayTime[0]?.classList.add('fcTime'); document.querySelectorAll('.fc-button-primary').forEach(li => li.setAttribute('title', '')); } const day = () => { state.period = 1; state.calendarApi.changeView('timeGridDay'); getList(); removeTitle(); dayRender(); } const month = () => { state.period = 3; state.calendarApi.changeView('dayGridMonth'); getList(); removeTitle(); } const week = () => { state.period = 2; state.calendarApi.changeView('timeGridWeek'); getList(); removeTitle(); dayRender(); } const prev = () => { state.calendarApi.prev(); updateCalendarDates(); getList(); if (state.period === 2) { const date = calendarBar.value.value; calendarBar.value.value = moment(date).subtract(7, 'd').format('YYYY-MM-DD'); } else { calendarBar.value.value = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD'); } } const next = () => { state.calendarApi.next(); updateCalendarDates(); getList(); if (state.period == 2) { let date = calendarBar.value.value; calendarBar.value.value = moment(date).add(7, 'd').format('YYYY-MM-DD'); } else { calendarBar.value.value = moment(state.calendarApi.view.currentStart).format('YYYY-MM-DD'); } } const getList = () => { // 实际项目中取消注释 // scheduleListApi().then(res => { // let newArr = [] // res.data.forEach(item => { // // ...处理逻辑... // }) // state.calendarOptions.events = newArr // }) calendarBar.value?.getList(); } const findItem = (arr, key, val) => { for (var i = 0; i < arr.length; i++) { if (arr[i].id === val || arr[i].id == userId.value) { return 2; } } return -1; } // const getWeek = date => toGetWeek(date); const editFn = (id, type, date) => { let data = { id, type, edit: true, date } addTodo.value.openBox(data); } const handleDate = (data, val) => { state.cid = data.type; state.leftTime = data.time; if (val) { state.calendarApi.gotoDate(data.time); } setTimeout(() => { state.calendarApi = calendarBar.value.getApi(); state.time = 1; day(); getList(); }, 300); } const selectChange = e => { state.time = e; if (state.time == 1) { day(); } else if (state.time == 2) { week(); } else { month(); } } const addSchedule = () => addTodo.value.open(); // 临时 getColorFn 实现 - 实际项目中应该使用真实实现 const getColorFn = (thisColor, thisOpacity) => { // 如果是透明背景 if (state.period === 3) { // RGBA 格式 const r = parseInt(thisColor.slice(1, 3), 16); const g = parseInt(thisColor.slice(3, 5), 16); const b = parseInt(thisColor.slice(5, 7), 16); return rgba(${r}, ${g}, ${b}, ${thisOpacity}); } return thisColor; } const removeTitle = () => { setTimeout(() => { document.querySelectorAll('.fc-daygrid-day-bottom a').forEach(li => li.title = ''); }, 300); } return { ...toRefs(state), Plus, calendarBar, addTodo, calendarDetails, contractDialog, editExamine, handleEventClick, recordChange, putStatus, scheduleRecord, handleDateClick, dayRender, day, month, week, prev, next, getList, findItem, // getWeek, editFn, handleDate, selectChange, addSchedule, getColorFn, removeTitle } } }) </script> <style lang="scss" scoped> .divBox { :deep(.fc-header-toolbar) { position: absolute; top: 0; left: 0; } .header { padding: 16px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #dcdfe6; .title { font-weight: 500; font-size: 18px; color: #303133; } } .day-header { display: flex; flex-direction: column; justify-content: center; height: 52px; font-family: PingFang SC-Regular, PingFang SC; line-height: 20px; .week { font-size: 13px; color: #909399; } .date { font-size: 18px; color: #606266; font-weight: 800; } } .ml30 { margin-left: -30px; } :deep(.fc-timegrid-slot-label-cushion) { color: #909399; font-size: 12px; } .line { border-left: 1px solid #f0f2f5; } .item { width: 100%; display: flex; font-size: 14px; font-family: PingFang SC-Regular, PingFang SC; justify-content: space-between; align-items: center; padding-right: 4px; border-radius: 13px; .img { flex-shrink: 0; width: 18px; height: 18px; border-radius: 50%; margin-right: 5px; object-fit: cover; } .yuan { width: 12px; height: 12px; border-radius: 50px; border: 1px solid #fff; } } .over-title { width: 98% !important; overflow: hidden !important; white-space: nowrap; text-overflow: ellipsis !important; } } </style>

<template> <RechargeComputingPowerComp v-if="Number(CurrentInterfaceIndex) == 1" @closed="CSClosed"> </RechargeComputingPowerComp> <UpgradeMembershipComp v-if="Number(CurrentInterfaceIndex) == 2" @closed="CSClosed" v-model:MembershipVisible="MembershipVisible"></UpgradeMembershipComp> <AuthorizedAccountComp v-if="Number(CurrentInterfaceIndex) == 3" @closed="CSClosed" @ToAuthorize="getAuthorizeIndex"></AuthorizedAccountComp> <SelectAccountComp v-if="Number(CurrentInterfaceIndex) == 4" @closed="CSClosed" @ToAnalysis="getAnalysisItem" :accountData="AccountList" v-model:CurrentAccount="CurrentAccount"> </SelectAccountComp> <AddProductComp v-if="Number(CurrentInterfaceIndex) == 5" @closed="CSClosed" @GetProdectUrl="GetProdectUrl" @fetchProductInfo="fetchProductInfo" v-model:CurrentProdectInfo="CurrentProdectInfo" :CurrentProdectUrl="CurrentProdectUrl"> </AddProductComp> <AddVideoComp v-if="Number(CurrentInterfaceIndex) == 6" @closed="CSClosed" v-model:CurrentBackgroundVideo="CurrentBackgroundVideo"> </AddVideoComp> <ChooseAIAnchorComp v-if="Number(CurrentInterfaceIndex) == 7" @closed="CSClosed" @AiAnchorRole="AiAnchorRole" v-model:CurrentAIAnchor="CurrentAIAnchor" v-model:VoiceCustVisible="VoiceCustVisible" :PublicAnthorList="PublicAnthorList" :PrivateAnthorList="PrivateAnthorList"> </ChooseAIAnchorComp> <SelectSoundCardComp v-if="Number(CurrentInterfaceIndex) == 8" @closed="CSClosed" v-model:CurrentSoundCard="CurrentSoundCard" :VirtualSoundList="VirtualSoundList"> </SelectSoundCardComp> <ActivateAIModelComp v-if="Number(CurrentInterfaceIndex) == 9" @closed="CSClosed" v-model:PreTempChunkArray="PreTempChunkArray" :ChunkLength-data="ChunkLength" :CurrentProdectUrl="CurrentProdectUrl" :CurrentProdectInfoStr="CurrentProdectInfoStr" :CurrentAIAnchor="CurrentAIAnchor" @ToResult="ToResult"> </ActivateAIModelComp> <AuthComp :AuthorizeIndex="Number(CurrentAuthIndex)" v-if="AuthoVisible" v-model:AuthoVisible="AuthoVisible"> </AuthComp> <AnalysisComp v-if="AnalysisVisible" v-model:AnalysisVisible="AnalysisVisible"></AnalysisComp> <MembershipComp v-if="MembershipVisible" v-model:MembershipVisible="MembershipVisible"></MembershipComp> <VoiceCustComp v-if="VoiceCustVisible" v-model:VoiceCustVisible="VoiceCustVisible" v-model:RecordVoiceVisible="RecordVoiceVisible" @SavePrivateVioce="SavePrivateVioce" v-model:RecordConfig="RecordConfig"></VoiceCustComp> <RecordVoiceComp v-if="RecordVoiceVisible" v-model:RecordVoiceVisible="RecordVoiceVisible" :RecordConfig="RecordConfig" @UsePrivateVioce="UsePrivateVioce"> </RecordVoiceComp> <LSHnav @RechargeComputingPower="RechargeComputingPower" @UpgradeMembership="UpgradeMembership"></LSHnav> <LSHselectbox @AuthorizedAccount="AuthorizedAccount" @SelectAccount="SelectAccount" @AddProduct="AddProduct" @AddVideo="AddVideo" @ChooseAIAnchor="ChooseAIAnchor" @SelectSoundCard="SelectSoundCard" :PlayStatus="PlayStatus" v-model:CurrentAccount="CurrentAccount" :CurrentProdectUrl="CurrentProdectUrl" :CurrentProdectInfo="CurrentProdectInfo" :CurrentBackgroundVideo="CurrentBackgroundVideo" :CurrentAIAnchor="CurrentAIAnchor" :CurrentSoundCard="CurrentSoundCard" v-model:AccountList="AccountList" :VirtualSoundList="VirtualSoundList"> </LSHselectbox> <LSHplaylist ref="PlayListRef" :EndResultArray="EndResultArray" :PlayStatus="PlayStatus" @ResultArrStatus="ResultArrStatus"> </LSHplaylist> <LSHinsertcontent ref="InsertRef" @InsertStatement="InsertStatement" :CurrentAIAnchor="CurrentAIAnchor" :PlayStatus="PlayStatus" @InsertText="InsertText"></LSHinsertcontent> <LSHoptionbtn :EndResultArray="ShowResultArray" v-model:PlayStatus="PlayStatus" @ResetPlay="ResetPlay" @NextAdioLoad="LoadOtherAdioFile" @ActivateAIModel="ActivateAIModel" :CurrentBackgroundVideo="CurrentBackgroundVideo"> </LSHoptionbtn> </template> <script> import RechargeComputingPowerComp from "./LiveStreamComp/RechargeComputingPower/index.vue"; import UpgradeMembershipComp from "./LiveStreamComp/UpgradeMembership/index.vue"; import AuthorizedAccountComp from "./LiveStreamComp/AuthorizedAccount/index.vue"; import SelectAccountComp from "./LiveStreamComp/SelectAccount/index.vue"; import AddProductComp from "./LiveStreamComp/AddProduct/index.vue"; import AddVideoComp from "./LiveStreamComp/AddVideo/index.vue"; import ChooseAIAnchorComp from "./LiveStreamComp/ChooseAIAnchor/index.vue"; import SelectSoundCardComp from "./LiveStreamComp/SelectSoundCard/index.vue"; import ActivateAIModelComp from "./LiveStreamComp/ActivateAIMode/index.vue"; // 授权界面 import AuthComp from "./LiveStreamComp/AuthorizedAccount/ChildComp/AuthComp.vue"; import AnalysisComp from "./LiveStreamComp/SelectAccount/ChildComp/AnalysisComp.vue"; import MembershipComp from "./LiveStreamComp/UpgradeMembership/ChildComp/MembershipComp.vue" import VoiceCustComp from "./LiveStreamComp/ChooseAIAnchor/ChildComp/VoiceCustComp.vue" import RecordVoiceComp from "./LiveStreamComp/ChooseAIAnchor/ChildComp/RecordVoiceComp.vue" // 布局组件 import LSHnav from "./LiveStreamComp/Z-Layout/LSHnav.vue"; import LSHselectbox from "./LiveStreamComp/Z-Layout/LSHselectbox.vue"; import LSHplaylist from "./LiveStreamComp/Z-Layout/LSHplaylist.vue"; import LSHinsertcontent from "./LiveStreamComp/Z-Layout/LSHinsertcontent.vue"; import LSHoptionbtn from "./LiveStreamComp/Z-Layout/LSHoptionbtn.vue"; // 测试用数据(需要临时生成音频替换) import { CSResultArr } from "./LiveStreamComp/utils/Z-CSResultArr.js" import { LiebianInfo } from "./LiveStreamComp/utils/Z-LiebianInfo.js" import { ProdectStr } from "./LiveStreamComp/utils/Z-prodectinfoStr.js" import { getInsertPosition, insertItemPlaying, encryptFilenameInPath, getFormattedTimestamp, ResultTemp, OrderEndArr, flattenLiveScripts, NewgenerateParagraphs, generateTemplateArray, startProgressAnimation, chunkArrayBySegments } from "./LiveStreamComp/utils/utilFn.js" import ClauseProcessor from "./LiveStreamComp/utils/ClauseProcessor.js" import reAI from "@/Method/reAI"; import { mapActions, mapState } from 'vuex'; // 信息弹窗 import { showMsg } from "./LiveStreamComp/Dialog.js" import { getUserInfo, getHuaShuMsg, getFissionTemplate, getaiaudio, cancelRequest, getSpeechContentApi } from "@/api/msg.js" // 调用接口方法 import { getPrivateVioce } from "./LiveStreamComp/utils/useApi.js" import { audioRequestManager } from "./LiveStreamComp/utils/audioRequestManager.js" export default { name: 'LiveStreamHelper', components: { RechargeComputingPowerComp, UpgradeMembershipComp, AuthorizedAccountComp, SelectAccountComp, AddProductComp, AddVideoComp, ChooseAIAnchorComp, SelectSoundCardComp, ActivateAIModelComp, AuthComp, AnalysisComp, MembershipComp, VoiceCustComp, RecordVoiceComp, LSHnav, LSHselectbox, LSHplaylist, LSHinsertcontent, LSHoptionbtn, }, data() { return { CSResultArr, LiebianInfo, ProdectStr, insertText: "", //插入话术内容 ModalOverlayVisible: false, //遮罩层状态 CurrentInterfaceIndex: null, //当前触发窗口索引 AuthoVisible: false, //授权界面状态 CurrentAuthIndex: 0, //授权选择索引 AnalysisVisible: false, //账号分析界面状态 MembershipVisible: false, //会员权限界面状态 VoiceCustVisible: false, //声音定制专属 RecordVoiceVisible: false, //声音录制 RecordConfig: {}, //声音录制的存储配置 AccountList: {}, //账号列表 CurrentAccount: {}, //当前账号 CurrentProdectUrl: "", //当前商品链接 CurrentProdectInfoStr: "", //当前商品信息字符串 CurrentProdectInfo: {}, //当前展示商品信息 CurrentBackgroundVideo: {}, //当前背景视频地址 CurrentAIAnchor: {}, //当前AI主播 CurrentSoundCard: {}, //当前声卡 VirtualSoundList: [], //虚拟声卡列表 PublicAnthorList: [], //获取的公共信息 PrivateAnthorList: [], //获取的定制信息 EndResultArray: [], //最终处理结果(当前展示的模板) ShowResultArray: [], //用于展示的数据 NextEndResultArray: [], //下套备用 ChunkLength: [3, 2, 2, 2, 1], //分段长度 HasSpeechLoading: false, //是否有话术模板正在生成中 MaxSpeechCount: 4, //话术生成最大套数 CurrentSpeechCount: 1, //当前生成的话术模板数量(初始1) PreTempChunkArray: [], //上套分段存储的音频模板 NextTempChunkArray: [], //下套分段存储的音频模板 NextSpeechTags: [], //下套的分类标签 NextTagArray: [], //下套分类标识 LastItem: null, //列表最后一项 ChunkLoadedIndex: new Set([0]), //已下载的音频模板索引(初始包含0) CurrentLoadingIndex: -1, //当前正在下载的模板索引(避免重复下载) PlayStatus: false, //开播状态 token: null, //token MoceTags: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P'], AdioConfig: { maxHuashu: 4, //最大生成话术套数 maxOneTemp: 10, //当套裂变数量 loadingHuashu: 1, //当前下载话术 loadingTemp: -1, //当前下载模板 loadedHushu: 1, //已下载话术 loadedTemp: [], //已下载模板 canUseTemp: new Set() } }; }, computed: { ...mapState(['SpeechTags']), }, methods: { ...mapActions(['setSpeechTagsData']), // 充值算力界面 RechargeComputingPower() { console.log("充值算力"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 1; }, //升级会员界面 UpgradeMembership() { console.log("升级会员"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 2; }, //授权账号界面 AuthorizedAccount() { console.log("授权账号"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 3; // 打开浏览器 window.electronAPI.broadcastMessage(showBrowser) }, //选择账号界面 SelectAccount() { console.log("选择账号"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 4; }, // 获取数据分析项 getAnalysisItem(item) { this.AnalysisVisible = true }, //添加商品界面 AddProduct() { console.log("添加商品"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 5; }, // 获取商品链接 GetProdectUrl(Url) { console.log("商品链接:" + Url); this.CurrentProdectUrl = Url; }, // 获取商品信息(调用接口) fetchProductInfo(ProdectStr) { console.log(ProdectStr); this.CurrentProdectInfoStr = ProdectStr }, //导入视频界面 AddVideo() { console.log("导入视频"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 6; }, //选择AI主播界面 ChooseAIAnchor() { console.log("选择AI主播"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 7; }, // 获取主播列表 AiAnchorRole(VioceInfo) { const { data, isPrivate } = VioceInfo if (isPrivate) { this.PrivateAnthorList = data } else { this.PublicAnthorList = data } }, //选择声卡界面 SelectSoundCard() { console.log("选择声卡"); this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 8; }, // 定制声音存储 SavePrivateVioce() { console.log("声音存储事件"); this.PrivateAnthorList = []; this.VoiceCustVisible = false; }, // 使用该项录音或上传录音 async UsePrivateVioce(val) { const { MdFileName, UseFlag } = val; const result = await getPrivateVioce(this) this.PrivateAnthorList = result; if (UseFlag) { this.CurrentAIAnchor = result.find(item => item.encryptFileName === MdFileName); this.RecordVoiceVisible = false; this.VoiceCustVisible = false; this.CurrentInterfaceIndex = -1; this.ModalOverlayVisible = false; } }, //插入话术 InsertStatement(textInfo) { const [targetitem, isPlaying] = this.$refs.PlayListRef.getCurrentItem(); console.log(targetitem); console.log(isPlaying); if (!targetitem) return; const { type: ItemType, uniqueId } = targetitem; const match = uniqueId.match(/(\d+)_(\d+)_(\d+)_(\d+)/); if (!match) return; // 匹配失败直接返回 // 公共逻辑:获取目标数组 const searchIndex = parseInt(match[2], 10) - 1; const TargetTempArray = this.EndResultArray[searchIndex]?.clauses; console.log(TargetTempArray); if (!TargetTempArray) return; // 公共逻辑:计算插入位置并执行插入 const index = TargetTempArray.findIndex(item => item.uniqueId === targetitem.uniqueId); console.log(index); const insertPos = isPlaying ? index + 1 : index; TargetTempArray.splice(insertPos, 0, { ...textInfo, HuashuIndex: targetitem.HuashuIndex, TempIndex: targetitem.TempIndex, uniqueId: ${uniqueId}_${textInfo.audioPath} }); console.log(TargetTempArray); this.$refs.InsertRef.processPaddingList(); }, // 测试用数据 InsertText() { }, //启动AI模型 ActivateAIModel() { // if (!this.CurrentProdectUrl) { // showMsg("请先选择商品") // return; // } // if (Object.keys(this.CurrentBackgroundVideo).length === 0) { // showMsg("请先导入视频") // return; // } if (Object.keys(this.CurrentAIAnchor).length === 0) { showMsg("请先选择AI主播") return; } // if (Object.keys(this.CurrentSoundCard).length === 0) { // showMsg("请先选择声卡") // return; // } // 播报文件1 window.electronAPI.broadcastMessage(setBackVideo1|${this.CurrentBackgroundVideo.url1 ? this.CurrentBackgroundVideo.url1 : null}) // 播报文件2 window.electronAPI.broadcastMessage(setBackVideo2|${this.CurrentBackgroundVideo.url2 ? this.CurrentBackgroundVideo.url2 : null}) this.ModalOverlayVisible = true; this.CurrentInterfaceIndex = 9; }, // 下载其他部分音频文件 LoadOtherAdioFile(loadIndex) { // // 该部分若已经下载过,退出 // if (this.ChunkLoadedIndex.has(loadIndex)) return; // // 将已下载的部分索引存储 // this.ChunkLoadedIndex.add(loadIndex); // // 模板存在,调用下载 // this.PreTempChunkArray.length > 0 && this.takeAdioFile(this.PreTempChunkArray[loadIndex], loadIndex) }, // 重置数据 ResetPlay() { console.log("重置触发"); this.EndResultArray = []; // this.AccountList = {}, //账号列表 this.CurrentAccount = {}, //当前账号 this.CurrentProdectUrl = "", //当前商品链接 this.CurrentProdectInfoStr = "", //当前商品信息字符串 this.CurrentProdectInfo = {}, //当前展示商品信息 this.CurrentBackgroundVideo = {}, //当前背景视频地址 this.CurrentAIAnchor = {}, //当前AI主播 this.CurrentSoundCard = {}, //当前声卡 this.VirtualSoundList = [], //虚拟声卡列表 this.PublicAnthorList = [], //获取的公共信息 this.PrivateAnthorList = [], //获取的定制信息 this.LastItem = null; this.CurrentSpeechCount = 1; this.ChunkLoadedIndex.clear(); this.ChunkLoadedIndex.add(0); this.NextEndResultArray = []; this.NextTempChunkArray = []; this.NextSpeechTags = []; this.PlayStatus = false; // console.log(this.EndResultArray); }, // 打开授权界面 getAuthorizeIndex(index) { this.AuthoVisible = true; this.CurrentAuthIndex = index; console.log("授权索引:" + index); }, // AI模型初始数据 ToResult(val) { console.log(val); this.EndResultArray = val; this.getUseTempResult(this.EndResultArray) }, // 获取可用数据 getUseTempResult() { this.EndResultArray.forEach((item, index) => { const canuse = item.areAllAudiosGenerated() if (canuse) { this.ShowResultArray.push(...item.clauses) // console.log(index); } }) console.log(this.ShowResultArray); }, // 关闭弹窗 CSClosed() { this.CurrentInterfaceIndex = null; this.ModalOverlayVisible = false; }, // 播放项状态更新 ResultArrStatus(val) { const { searchIndex, index, NewStatus } = val const targetItem = this.EndResultArray[searchIndex].clauses[index] targetItem.playStatus = NewStatus; switch (NewStatus) { // 收到播放项 case 1: // 更新播放队列 this.$refs.PlayListRef.updatePlayList(); //判断:若未达到指定上限,生成下套模板 if (this.AdioConfig.loadedHushu >= this.AdioConfig.maxHuashu) { break }; // 生成下套话术模板 this.MainProcess() break; // 收到完成播放项 case 2: // 更新播放队列 // this.$refs.PlayListRef.updatePlayList(); const NextTempIndex = targetItem.TempIndex const NextTemp = this.EndResultArray[NextTempIndex] // 下套存在 if (!!NextTemp) { // 判断下套音频是否已经生成 const hasAdio = NextTemp.areAllAudiosGenerated() if (hasAdio) { break } // 未生成 前往下载下套音频 this.takeAdioFile(NextTemp.clauses, NextTempIndex) } break; } }, // 获取账户列表 async GetUserInfo() { const Result = await window.electronAPI.getAllAccounts() this.AccountList = Result && Result.data || {} }, // 模板音频文件生成 takeAdioFile(TempArray, NextTempIndex) { // console.log(NextTempIndex); // console.log(this.AdioConfig.loadingTemp); // 若下套正在生成,退出 if (this.AdioConfig.loadingTemp != -1) { return }; this.AdioConfig.loadingTemp = NextTempIndex // console.log(TempArray); //唯一文件名生成 let templatefile = String(getFormattedTimestamp()); // 创建大文件夹 window.electronAPI .createFolderAsync([templatefile]) .then(async () => { // 创建一个数组来收集所有Promise const allPromises = []; TempArray.forEach((item) => { let templatefile = String(getFormattedTimestamp()); const { HuashuIndex, TempIndex, segmentNumber, clauseNumber, content, key } = item // 文件结构:时间戳/模板/段落/子句.mp3 const audioPath = ${templatefile}/Huashu${HuashuIndex}/template${TempIndex}/segment${segmentNumber}/clause${clauseNumber}.mp3; // 创建一个Promise并添加到数组中 const promise = audioRequestManager.addRequest({ tts_text: content, role_name: this.CurrentAIAnchor.isprivate ? this.CurrentAIAnchor.encryptFileName : ${this.CurrentAIAnchor.gender}-${this.CurrentAIAnchor.name}, speed: 1, is_private: this.CurrentAIAnchor.isprivate, cancelindex: key, }) .then(async (response) => { // 保存音频 await window.electronAPI.saveAudio({ filePath: audioPath, audioData: response.data, }); // 更新数据 audioPath && (item.audioPath = audioPath); response.data && (item.audioData = response.data); }) .catch((error) => { if (error.message == "请求被取消") { return; } this.$message?.error(处理 ${key} 时出错: ${error.message}); // 返回null表示该段落处理失败,但不影响其他段落 return null; }); allPromises.push(promise); }) Promise.all(allPromises).then(() => { this.AdioConfig.loadingTemp = -1; }); }) .catch((error) => { this.AdioConfig.loadingTemp = -1 if (error.message == "请求被取消") { return []; } return []; }); }, // 下套话术起步全流程 async MainProcess() { // 有话数正在生成,退出 if (this.HasSpeechLoading) return; this.HasSpeechLoading = true; let speechContent; //话术生成的数据 let fissionJsonStr; //裂变模板生成的数据 let adioTempArray; //音频生成的基础模板 // 话术生成 const joinStr = 逼单话术:通用话术\n主播性别:${this.CurrentAIAnchor?.gender || "女"}\n人群年龄:\n适用人群:\n性别:\n${this.CurrentProdectInfoStr}; const speechrequestKey = "/coze/generateScript" + "cancelindex1"; await getHuaShuMsg({ input: joinStr, cancelindex: 1 }, speechrequestKey) .then((response) => { // throw new Error('模拟的错误响应') speechContent = response.data.replace(/【.*?】/g, ""); // 匹配所有【】包裹的内容并提取 const bracketContent = response.data.match(/【(.*?)】/g) || []; // 移除每个元素中的【和】 this.NextTagArray = bracketContent.map(item => item.replace(/【|】/g, '')) }) .catch((error) => { this.HasSpeechLoading = false; cancelRequest(speechrequestKey); }); // 话术裂变 const postData = { additionalMessages: { contentType: "text", content: speechContent, }, userId: new Date().toISOString(), // 保证每次唯一性 cancelindex: 1, }; // 生成唯一 requestKey,和 request.js 生成方式保持一致 const FissionrequestKey = "/coze/conversation/chat" + "cancelindex1"; await getFissionTemplate(postData, FissionrequestKey) .then((response) => { let resContent; //流式数据指定内容 response.data.data.forEach(item => { if (item.type === 'answer') { resContent = reAI.CToJson(item.content) } }); // 分句 fissionJsonStr = JSON.stringify( NewgenerateParagraphs(resContent, this.AdioConfig.maxOneTemp) && NewgenerateParagraphs(resContent, this.AdioConfig.maxOneTemp).segments ); // 生成音频模板 adioTempArray = generateTemplateArray(fissionJsonStr, this.AdioConfig.maxOneTemp); // 下套模板起始索引 let TempStartIndex = this.EndResultArray.length; ++this.AdioConfig.loadingHuashu this.NextEndResultArray = adioTempArray.map(item => new ClauseProcessor(item, this.AdioConfig.loadingHuashu, ++TempStartIndex, this.NextTagArray)); console.log(this.NextEndResultArray); // 并入总模板 this.EndResultArray = this.EndResultArray.concat(this.NextEndResultArray) console.log(this.EndResultArray); // 话术模板可以生成 this.HasSpeechLoading = false; // 已下载模板数量加一 this.AdioConfig.loadedHushu++ }) .catch((error) => { this.HasSpeechLoading = false; cancelRequest(FissionrequestKey); }); }, //定时发言 async GetSpeechContent() { // 没有商品信息时退出 if (!this.CurrentProdectInfoStr) { return } // 统一生成请求体 const postData = { additionalMessages: { contentType: 'text', content: this.CurrentProdectInfoStr, }, userId: new Date().toISOString(), // 保证每次唯一性 cancelindex: 1, }; const requestKey = '/coze/conversation/chat3' + 'cancelindex1'; await getSpeechContentApi(postData, requestKey).then((response) => { response.data.data.forEach(item => { if (item.type === 'answer') { if (String(item.content) === '1') { return } // 播报返回 window.electronAPI.broadcastMessage(setSpeechContent|${item.content}) } }); }).catch(error => { this.$message.error('获取失败', error.message) }); }, }, watch: { // 监听当前账号,账号变更重置主播选择 CurrentAccount: { deep: true, // immediate: true, // 初始化时立即执行 handler(newValue, oldValue) { if (newValue && newValue.user_name !== oldValue?.user_name) { // 避免无意义的重复执行 // console.log('账户切换,重新加载数据', newValue); // 清空当前AI主播选择 this.CurrentAIAnchor = {} // 清空定制列表 this.PrivateAnthorList = [] } } }, // 监听显示列表长度 'ShowResultArray.length'(newLength, oldLength) { // console.log(EndResultArray 的长度从 ${oldLength} 变为 ${newLength}); /** * 1.第一套模板生成 || 第一套播放完成,下套执行赋值操作 * 0→n * 2.内容插入操作 * n++ */ if (newLength > oldLength) { // 获取列表最后一项,用于判断模板是否播放完毕 this.LastItem = this.ShowResultArray[this.ShowResultArray.length - 1]; console.table(this.LastItem) } /** * 3.播放完成的删除操作 * n-- */ } }, created() { // 页面初始化时从sessionStorage读取token this.token = sessionStorage.getItem('liveToken') || ''; // 获取账户列表信息 this.GetUserInfo() // 获取声卡播报 window.electronAPI.broadcastMessage(getDeviceList) // 监听播报返回数据 window.electronAPI.onWebSocketMessage((message) => { // console.log("这是AI直播助手播报监听"); const KeyWoed = message.split("|")[0] const Content = message.split("|")[1] const HasContent = !!Content[1] switch (HasContent) { // 带信息 case true: switch (KeyWoed) { // 声卡列表 case "deviceList": /*deviceList|{“nowDevice":1,"data":[{"id":1,"name":"xxxx","driver":"xxxx"}]}*/ const { nowDevice, data } = JSON.parse(Content) this.VirtualSoundList = data this.CurrentSoundCard = data.find(item => item.id === nowDevice); break; // 新增账号写入 case 'doyinuserinfo': // 登录的新增抖音账号信息 const NewDouyinUserInfo = JSON.parse(Content) !!NewDouyinUserInfo && this.GetUserInfo() break; } break; // 纯指令 case false: switch (KeyWoed) { // 定时发言 case "getSpeechContent": this.GetSpeechContent() break; } break; } }); } } </script> <style scoped lang="scss"> //@Mixin @include @mixin flex-layout($axle: row, $A: center, $J: center) { display: flex; flex-direction: $axle; align-items: $A; justify-content: $J; @content; } .modalOverlay { position: fixed; // 确保遮罩层固定在视口上 top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.3); z-index: 1000; @include flex-layout; /* 添加模糊效果 */ backdrop-filter: blur(0.1875rem); -webkit-backdrop-filter: blur(0.3125rem); /* 为了兼容Safari浏览器 */ } .ai-live-tream-helper { height: 100vh; width: 100vw; background: #0f172a; color: #e0e0e0; font-family: "Inter", sans-serif; margin: 0; padding: 1rem; box-sizing: border-box; @include flex-layout(column); img { vertical-align: middle; } .contentbox { margin: 0.625rem auto; width: clamp(48.4375rem, 75%, 100%); min-width: 48.4375rem; min-height: 0; flex: 1; display: flex; flex-direction: column; } } </style> 作为界面入口文件,代码显得太长太冗杂了,有优化的方法么?不使用状态库,需要注意数据的传递、数据绑定、以及通过v-model在子组件更新数据的情况

D:\financial-ui\financial-ui\src\views\setting\PermissionSetting\UserForm.vue 73 | prop: "avatar", 74 | cellRenderer: ({ row }) => ( 75 | <vxe-image src={row.avatar || userAvatar} class="w-[24px] h-[24px] rounded-full align-middle object-fill" show-preview={true}></vxe-image> | ^ 76 | ), 77 | width: 90 @ ./src/views/setting/PermissionSetting/UserForm.vue?vue&type=script&setup=true&lang=js 1:0-307 1:0-307 1:308-604 1:308-604 @ ./src/views/setting/PermissionSetting/UserForm.vue 2:0-70 3:0-65 3:0-65 8:49-55 @ ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./node_modules/windicss-webpack-plugin/dist/loaders/windicss-template.cjs!./src/views/setting/PermissionSetting/AddUser.vue?vue&type=script&setup=true&lang=js 11:0-70 94:19-27 179:16-24 @ ./src/views/setting/PermissionSetting/AddUser.vue?vue&type=script&setup=true&lang=js 1:0-306 1:0-306 1:307-602 1:307-602 @ ./src/views/setting/PermissionSetting/AddUser.vue 2:0-69 3:0-64 3:0-64 6:49-55 @ ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./node_modules/windicss-webpack-plugin/dist/loaders/windicss-template.cjs!./src/views/setting/PermissionSetting.vue?vue&type=script&lang=js 6:0-68 39:19-26 @ ./src/views/setting/PermissionSetting.vue?vue&type=script&lang=js 1:0-296 1:0-296 1:297-582 1:297-582 @ ./src/views/setting/PermissionSetting.vue 2:0-68 3:0-63 3:0-63 6:49-55 @ ./src/router.js 89:11-58 @ ./src/main.js 16:0-30 31:80-86

<script setup lang="tsx"> import { NButton, NPopconfirm, NTag } from "naive-ui"; import { fetchGetChangeLedgerList,fetchBatchDeleteChange,fetchDeleteChange,getDeptOptions,clearDeptCache } from "@/service/api"; import { $t } from "@/locales"; import { useAppStore } from "@/store/modules/app"; import { useTable, useTableOperate } from "@/hooks/common/table"; import { useAuth } from "@/hooks/business/auth"; import LedgerOperateDrawer from "./modules/ledger-operate-drawer.vue"; import LedgerSearch from "./modules/ledger-search.vue"; import LedgerUploadDrawer from "./modules/ledger-upload-drawer.vue"; import { useBoolean } from "@sa/hooks"; import { canUpgradeRecord } from "@/constants/business"; import { ref, onMounted } from "vue"; import {useDepartment} from "@/hooks/common/useDepartment" import { exportXlsx, SheetData } from "@/utils/export-excel"; // 使用封装的部门功能 const { deptLoading, deptOptions, } = useDepartment(); const appStore = useAppStore(); const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams, } = useTable({ apiFn: fetchGetChangeLedgerList, showTotal: true, apiParams: { current: 1, size: 10, // if you want to use the searchParams in Form, you need to define the following properties, and the value is null // the value can not be undefined, otherwise the property in Form will not be reactive source: null, pecoName: null, peco: null, softwareChange: null, projectno: null, dept: null, canUpgrade: null, softResponsiblePerson: null, elecResponsiblePerson: null, changeResponsiblePerson: null, customerDemandTime: null, softCompletionTime:null, hardCompletionTime: null, fmtCustomerDemandTime:null, fmtSoftCompletionTime:null, fmtHardCompletionTime: null, }, columns: () => [ { type: "selection", align: "center", width: 48, }, { key: "index", title: $t("common.index"), align: "center", width: 64, }, { key: "hwswcoId", title: $t("page.change.ledger.hwswcoId"), align: "center", minWidth: 50, }, { key: "source", title: $t("page.change.ledger.source"), align: "center", width: 50, }, { key: "pecoName", title: $t("page.change.ledger.pecoName"), align: "center", minWidth: 50, }, { key: "peco", title: $t("page.change.ledger.peco"), align: "center", minWidth: 50, }, { key: "softwareChange", title: $t("page.change.ledger.softwareChange"), align: "center", minWidth: 150, }, { key: "projectno", title: $t("page.change.ledger.projectno"), align: "center", minWidth: 50, }, { key: "dept", title: $t("page.change.ledger.dept"), align: "center", width: 130, render: (row) => { // 使用部门选项映射部门名称 if (row.dept && deptOptions.value.length > 0) { const dept = deptOptions.value.find(option => option.value === row.dept); if (dept) { return <NTag type="info">{dept.label}</NTag>; } } return null; }, }, { key: "softResponsiblePersonName", title: $t("page.change.ledger.softResponsiblePersonName"), align: "center", minWidth: 50, }, { key: "elecResponsiblePersonName", title: $t("page.change.ledger.elecResponsiblePersonName"), align: "center", minWidth: 50, }, // { // key: "changeResponsiblePerson", // title: $t("page.change.ledger.changeResponsiblePerson"), // align: "center", // minWidth: 50, // }, { key: "canUpgrade", title: $t("page.change.ledger.canUpgrade"), align: "center", minWidth: 50, render: (row) => { if (row.canUpgrade) { console.log( row.canUpgrade, canUpgradeRecord[ row.canUpgrade as Api.Change.canUpgrade ]) const label = $t( canUpgradeRecord[ row.canUpgrade as Api.Change.canUpgrade ] ); return <NTag type="default">{label}</NTag>; } return null; }, }, { key: "fmtCustomerDemandTime", title: $t("page.change.ledger.customerDemandTime"), align: "center", minWidth: 50, }, { key: "fmtSoftCompletionTime", title: $t("page.change.ledger.softCompletionTime"), align: "center", minWidth: 50, }, { key: "fmtHardCompletionTime", title: $t("page.change.ledger.hardCompletionTime"), align: "center", minWidth: 50, }, // { // key: "fmtPlanCompletionTime", // title: $t("page.change.ledger.planCompletionTime"), // align: "center", // minWidth: 50, // }, { key: "operate", title: $t("common.operate"), align: "center", width: 130, render: (row) => ( {hasAuth("B_ChangeLedger_Upgrade") ? ( <NButton type="primary" ghost size="small" onClick={() => window.open (http://192.168.100.63/pro/hwswco-upgrade-${row.hwswcoId}.html,"_blank")} > {$t("common.upgrade")} </NButton> ) : null} {hasAuth("B_ChangeLedger_Assign") ? ( <NButton type="primary" ghost size="small" onClick={() => window.open(http://192.168.100.63/pro/hwswco-view-${row.hwswcoId}.html,"_blank")} > {$t("common.assign")} </NButton> ) : null} ), }, ], });把点击跳转的网页实现嵌入

未启用CSS Modules的情况下在// 引入 path 模块 const path = require('path') // 判断当前环境是否是生产环境 const isProduction = process.env.NODE_ENV === 'production' // 样式单独分离到一个文件中 const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 官方文档:Vue Loader v15 现在需要配合一个 webpack 插件才能正确使用 const VueLoaderPlugin = require("vue-loader/lib/plugin"); // 引入静态资源复制插件 const CopyWebpackPlugin = require('copy-webpack-plugin') // nodejs核心模块,直接使用 const os = require("os"); // cpu核数 const threads = os.cpus().length; // 引入入口文件 const { entry } = require('./setting.js') const webpack = require("webpack") module.exports = { // 打包入口地址 entry: entry, // 模块resolve的规则 resolve: { //自动的扩展后缀,比如一个js文件,则引用时书写可不要写.js extensions: ['.js', '.json', '.css', '.less', '.vue','.jsx'], // 路径别名 alias: { '@': path.resolve(__dirname, '../src'), 'vue$': 'vue/dist/vue.esm.js', "@ht-form-create/element-ui": path.resolve(__dirname, "../src/libs/form-create/packages/element-ui/src"), "@ht-form-create/utils": path.resolve(__dirname, "../src/libs/form-create/packages/utils"), "@ht-form-create/designer": path.resolve(__dirname, "../src/libs/form-designer/src") }, fallback: { process: require.resolve("process/browser") } }, // context 是 webpack entry 的上下文,是入口文件所处的目录的绝对路径,默认情况下是当前根目录。 // 由于我们 webpack 配置文件放于 build 目录下,所以需要重新设置下 context ,使其指向根目录。 context: path.resolve(__dirname, '../'), // 构建目标 target: ['web', 'es5'], // 不同类型模块的处理规则 module: { rules: [ { oneOf: [ // 处理 css、less 文件 { test: /\.css$/i, use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', { loader: 'css-loader', options: { // 是否使用source-map sourceMap: !isProduction, esModule: false } }, { loader: 'postcss-loader', options: { // 是否使用source-map sourceMap: !isProduction } } ] }, { test: /\.less$/i, use: [ isProduction ? MiniCssExtractPlugin.loader : 'style-loader', { loader: 'css-loader', options: { // 是否使用source-map sourceMap: !isProduction, esModule: false } }, { loader: 'postcss-loader', options: { // 是否使用source-map sourceMap: !isProduction } }, 'less-loader' ] }, // 处理sass资源 { test: /\.s[ca]ss$/, use: [ 'style-loader', 'css-loader', 'sass-loader', ] }, // 解析 html 中的 src 路径 // { // test: /\.html$/, // use: 'html-loader' // }, // 对图片资源文件进行处理,webpack5已经废弃了url-loader,改为type { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, type: 'asset', exclude: [path.resolve(__dirname, 'src/assets/imgs')], generator: { filename: 'imgs/[name].[contenthash][ext]' } }, // 对字体资源文件进行处理,webpack5已经废弃了url-loader,改为type { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, type: 'asset', generator: { filename: 'fonts/[name].[contenthash][ext]' } }, // 对音频资源文件进行处理,webpack5已经废弃了url-loader,改为type { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, type: 'asset', exclude: [path.resolve(__dirname, 'src/assets/medias')], generator: { filename: 'medias/[name].[contenthash][ext]' } }, { test: /\.(m|j)s$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { cacheDirectory: true } }, { loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 }, } ] }, { test: /\.(js|jsx)$/, include: [path.resolve(__dirname, "../src"), path.resolve(__dirname, "../src/libs/form-create"), path.resolve(__dirname, "../src/libs/form-designer")], use: [ { loader: "babel-loader", options: { cacheDirectory: true, // 确保包含处理JSX的预设 presets: ["@babel/preset-env", "@vue/babel-preset-jsx"] } }, { loader: "thread-loader", options: { workers: threads } } ] } ] }, // 处理.vue文件 { test: /\.vue$/, use: 'vue-loader' } ] }, watchOptions: { ignored: /node_modules/, }, // 插件 plugins: [ new VueLoaderPlugin(), // 把public的一些静态文件复制到指定位置,排除 html 文件 new CopyWebpackPlugin({ patterns: [ { from: path.resolve(__dirname, '../public'), globOptions: { dot: true, gitignore: true } } ] }), new webpack.DefinePlugin({ "process.env": { NODE_ENV: JSON.stringify(process.env.NODE_ENV || "development") // 可根据需要添加其他环境变量 // API_URL: JSON.stringify('https://api.example.com') } }) ] } 怎么添加

最新推荐

recommend-type

Excel表格通用模板:出租屋水电费自动管理电子表格.xls

Excel表格通用模板:出租屋水电费自动管理电子表格.xls
recommend-type

SSRSSubscriptionManager工具:简化SSRS订阅的XML文件导入

### 知识点概述 #### 标题知识点 1. **SSRSSubscriptionManager**: 这是一个专门用于管理SQL Server Reporting Services (SSRS) 订阅的工具或脚本。它允许用户从一个集中的位置管理SSRS订阅。 2. **从XML文件导入SSRS订阅**: 描述了一个通过读取XML文件来配置SSRS订阅的过程。这可能是为了减少重复的手动设置和避免错误,提高管理效率。 #### 描述知识点 3. **快速部署多个SSRS订阅**: 该工具或脚本的一个主要功能是能够快速设置多个订阅,这比传统的SSRS在线向导更为高效。 4. **标准SSRS在线向导的局限性**: 描述了标准SSRS向导的不足之处,例如操作缓慢、单次只能设置一个订阅,以及易于出现人为错误。 5. **SSRS订阅管理器的优势**: 解释了为什么使用SSRS订阅管理器比标准向导更可靠。它允许使用预定义的XML文档进行设置,这些文档可以经过测试和验证以减少错误。 6. **受控文档**: 强调了使用SSRS订阅管理器的一个好处是能够控制订阅设置,使其更为可靠且易于管理。 7. **版本控制和订阅设置**: 讨论了SSRS报告可以进行版本控制,但是传统的订阅设置通常不包含在版本控制中,而SSRS订阅管理器提供了一种方式,可以对这些设置进行记录和控制。 #### 标签知识点 8. **C#**: 指示了实现SSRSSubscriptionManager可能使用的技术,C# 是一种面向对象的编程语言,通常用于开发.NET应用程序,包括SSRS订阅管理器。 #### 压缩包子文件名列表 9. **SSRSSubscriptionManager-master**: 表示这是一个开源项目或组件的主干文件夹。名称表明这是一个版本控制仓库中的主分支,可能包含了源代码、项目文件和其他资源文件。 ### 详细知识点 #### 关于SSRS - SQL Server Reporting Services (SSRS) 是一个服务器基础的报告平台,它能够通过Web界面、文件共享和电子邮件来交付报表内容。SSRS用户可以根据数据源生成数据驱动的报表,并设置订阅以便自动分发这些报表。 - SSRS订阅是一个功能,允许用户根据设定的计划或用户触发条件自动获取报表。订阅可以是快照订阅、数据驱动订阅或基于事件的订阅。 #### 关于SSRSSubscriptionManager - SSRSSubscriptionManager是一个工具,其设计意图是简化SSRS订阅的管理过程。它允许管理员在单个操作中部署大量订阅,相比于传统方法,它极大地节省了时间。 - 通过使用XML文件来定义订阅的设置,该工具提供了更高的准确性和一致性,因为XML文件可以被严格地测试和审核。 - 自动化和批量操作可以减少因手动设置造成的错误,并且提高了操作效率。这对于有大量报表和订阅需求的企业来说尤为重要。 - SSRSSubscriptionManager的出现也表明了开发人员对IT自动化、脚本化操作和管理工具的需求,这可以视为一种持续的向DevOps文化和实践的推进。 #### 关于C# - C# 是一种由微软开发的通用编程语言,它被广泛应用于开发Windows应用程序、服务器端Web应用程序以及移动和游戏开发。 - 在开发SSRSSubscriptionManager时,C# 语言的利用可能涉及到多种.NET框架中的类库,例如System.Xml用于解析和操作XML文件,System.Data用于数据库操作等。 - 使用C# 实现SSRS订阅管理器可以享受到.NET平台的诸多优势,比如类型安全、内存管理和跨平台兼容性。 #### 关于版本控制 - 版本控制是一种记录源代码文件更改历史的方法,它允许开发团队追踪和管理代码随时间的变化。常见的版本控制系统包括Git、Subversion等。 - 在SSRS订阅的上下文中,版本控制意味着可以追踪每个订阅设置的变更,从而保证订阅设置的一致性和可追溯性。 - SSRSSubscriptionManager通过使用XML文件,可以使得版本控制变得更加容易,因为XML文件可以被版本控制系统跟踪。 - 这种做法还确保了订阅设置文件的历史版本可以被审计,对企业的合规性和管理都有积极影响。 ### 结论 SSRSSubscriptionManager通过集成自动化、XML文件和版本控制,为SSRS订阅管理提供了更高效、可信赖和可管理的解决方案。使用C# 实现的这一工具能够极大提高IT专业人员在创建和维护SSRS订阅时的工作效率,并减少可能由手工操作引入的错误。通过强调自动化和可控制的文档处理,它也反映了IT行业的趋势,即追求效率、可靠性和版本管理。
recommend-type

图形缩放与平移实现全攻略:Delphi视图变换核心技术详解

# 摘要 本文系统探讨了图形缩放与平移技术的基本原理及其在实际开发中的应用,涵盖从数学基础到编程实现的全过程。文章首先介绍了图形变换的数学模型,包括坐标系统、矩
recommend-type

Unknown custom element: <CustomForm> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

在使用 Vue.js 时,如果遇到未知自定义组件 `<CustomForm>` 的错误提示,通常是由于组件注册过程中存在某些疏漏或错误。以下是常见的原因及对应的解决方案: ### 1. 组件未正确注册 确保 `<CustomForm>` 组件已经在使用它的父组件或全局中进行了注册。如果未注册,Vue 会提示该组件是未知的。 正确的注册方式如下: - **全局注册**(适用于所有组件都能访问的场景): ```javascript import CustomForm from '@/components/CustomForm.vue' Vue.component('CustomForm',
recommend-type

使用KnockoutJS开发的黑客新闻阅读器 hn-ko

在给定的文件信息中,我们可以提炼出以下IT相关知识点: ### 标题知识点 #### KnockoutJS - **KnockoutJS定义**:Knockout是一个轻量级的JavaScript库,它允许开发者利用声明式绑定方式创建富交互的Web应用程序。它特别擅长于实现UI的自动更新,当模型的数据发生变化时,视图会自动响应这些变化而更新,无需手动操作DOM。 - **KnockoutJS核心特性**: - **依赖项跟踪**:Knockout能够跟踪数据模型中的变化,当数据更新时自动更新相关联的UI元素。 - **声明式绑定**:开发者可以使用简单的数据绑定语法在HTML标记中直接指定数据与DOM元素之间的关系,这样可以使代码更加清晰和易于维护。 - **模板和自定义绑定**:Knockout提供了灵活的模板系统,可以创建可复用的UI组件,并通过自定义绑定来扩展其核心功能,以满足特定需求。 - **组件化**:Knockout支持创建独立的、可复用的视图模型组件,以构建复杂的用户界面。 ### 描述知识点 #### 入门和运行应用 - **Git克隆**:通过`git clone`命令可以从远程仓库克隆代码到本地环境,这是版本控制中常见的操作,有助于团队协作和代码共享。`https://github.com/crissdev/hn-ko.git`指向一个特定的GitHub仓库,其中包含着使用KnockoutJS编写的黑客新闻应用代码。 - **NPM(Node Package Manager)**:NPM是随Node.js一起安装的一个包管理工具,它用于安装和管理JavaScript项目依赖。`npm install`命令用于安装项目中的所有依赖项,这可能包括KnockoutJS库以及其他可能用到的库或框架。 - **启动应用**:`npm start`是启动脚本的命令,它通常在`package.json`文件的scripts部分定义,用以启动开发服务器或运行应用。 #### 麻省理工学院许可证 - **MIT许可证**:这是一种常见的开源许可证,允许用户在任何类型的项目中免费使用软件,无论是个人的还是商业的。在保留原作者版权声明的同时,用户可以根据自己的需要修改和分发代码。这是很多开源项目选择的许可证。 ### 标签知识点 #### JavaScript - **JavaScript作用**:JavaScript是一种高级的、解释执行的编程语言,它通常是运行在浏览器中的脚本语言,用于实现网页的动态效果和用户交互。JavaScript作为全栈开发的关键技术之一,也被广泛用于服务器端开发(Node.js)。 - **JavaScript特点**: - **事件驱动**:JavaScript可以响应用户的点击、输入等事件,并据此进行操作。 - **对象导向**:JavaScript支持面向对象编程,可以通过创建对象、继承、多态等特性来组织代码。 - **异步编程**:JavaScript支持异步编程模型,利用回调函数、Promises、async/await等技术,可以有效处理网络请求、用户输入等异步操作。 ### 压缩包子文件的文件名称列表知识点 - **hn-ko-master**:这表明压缩包中的文件是从名为`hn-ko`的GitHub仓库的`master`分支获取的。文件列表中的这个名称可以帮助开发者快速识别包含KnockoutJS项目的代码仓库版本。 ### 总结 以上知识点总结了文件信息中提及的关于KnockoutJS、Git、NPM、MIT许可证和JavaScript的核心概念和应用实践。KnockoutJS作为一个功能强大的前端库,特别适用于复杂用户界面的数据绑定和动态更新。而通过Git的使用可以方便地管理项目的版本,并与其他开发者协作。NPM则使得项目的依赖管理和模块化开发变得更加简单高效。MIT许可证为项目的使用者提供了法律上的许可,确保了软件使用的自由度。JavaScript作为一种多用途的编程语言,在前端开发中扮演了不可替代的角色。理解并运用这些知识点,将有助于进行现代Web应用的开发工作。
recommend-type

Delphi图层管理机制设计:打造高效绘图控件的架构之道

# 摘要 本文系统研究了Delphi图层管理机制的核心概念、理论基础与实现细节,重点分析了图层的数据模型、渲染流程及其交互机制。通过对图层容器设计、绘制性能优化与事件分发模型的深入探讨,提出了一个高效、可扩展的图层管理架构,并结合实际绘图控件开发,验证了该机制
recommend-type

激光slam14讲

激光SLAM(Simultaneous Localization and Mapping,同步定位与地图构建)是机器人领域中的关键技术之一,广泛应用于室内机器人、自动驾驶、无人机导航等领域。对于初学者来说,系统地学习相关理论和实践方法是入门的关键。以下是一些推荐的学习资料和学习路径,帮助你更好地掌握激光SLAM。 ### 推荐书籍与资料 1. **《视觉SLAM十四讲》**:虽然书名强调“视觉”,但其中的许多核心理论,如贝叶斯估计、卡尔曼滤波、因子图优化等,与激光SLAM有高度重合,是入门SLAM的必备读物。 2. **《概率机器人》**:这本书是SLAM领域的经典教材,深入讲解了粒子滤
recommend-type

星云Dapp加密游戏深度解析与实践指南

### 星云的Dapp加密游戏知识点梳理 #### 标题解读 标题“dapp-crypto-game:星云的Dapp加密游戏”中的“dapp”指的是“Decentralized Application”,即去中心化应用。而“crypto-game”则表示这是一款基于加密货币技术的游戏,它可能涉及到区块链技术、加密资产交易、智能合约等元素。而“星云”可能是游戏的名称或者主题背景,但没有更多的信息,我们无法得知它是否指一个特定的区块链项目。 #### 描述解读 描述中的“星云的Dapp加密游戏”是一个简短的说明,它指明了这是一个与星云相关主题的去中心化应用程序,并且是一款游戏。描述信息过于简洁,没有提供具体的游戏玩法、加密技术的应用细节等关键信息。 #### 标签解读 标签“JavaScript”说明该Dapp游戏的前端或后端开发可能使用了JavaScript语言。JavaScript是一种广泛应用于网页开发的脚本语言,它也是Node.js的基础,Node.js是一种运行在服务器端的JavaScript环境,使得JavaScript能够用于开发服务器端应用程序。在区块链和Dapp开发领域,JavaScript及其相关的开发工具库(如web3.js)是与以太坊等智能合约平台交互的重要技术。 #### 文件名称解读 文件名称“dapp-crypto-game-master”表明这是一个包含Dapp游戏源代码的压缩包,并且该压缩包内包含了一个“master”目录。这通常意味着它是一个版本控制系统(如Git)中的主分支或主版本的代码。开发者可能会使用这种命名习惯来区分不同的开发阶段,如开发版、测试版和稳定版。 #### 知识点详细说明 1. **区块链技术与加密游戏**:Dapp加密游戏通常建立在区块链技术之上,允许玩家拥有独一无二的游戏资产,这些资产可以是游戏内的货币、道具或者角色,它们以加密货币或代币的形式存在,并储存在区块链上。区块链提供的不可篡改性和透明性,使得游戏资产的安全性和真实性得以保障。 2. **智能合约**:智能合约是区块链上自动执行、控制或文档化相关事件和动作的计算机程序。在Dapp加密游戏中,智能合约可以用来定义游戏规则,自动结算比赛胜负,分发游戏奖励等。智能合约的编写通常涉及专门的编程语言,如Solidity。 3. **加密货币**:加密游戏可能会用到各种类型的加密货币,包括但不限于比特币、以太币、ERC20或ERC721代币。在区块链游戏中,玩家可能需要使用这些货币来购买游戏内资产、参与游戏或赚取收益。 4. **JavaScript在Dapp开发中的应用**:由于区块链技术在前端的应用需要与用户进行交云,JavaScript在Dapp的前端开发中扮演重要角色。web3.js等库让JavaScript能够与区块链进行通信,使得开发人员能够构建用户界面,与智能合约进行交互。 5. **去中心化应用(Dapp)的特性**:Dapp的一个核心特性是它们不是由单一实体控制的。用户可以在不受第三方干涉的情况下运行或访问Dapp。这样的开放性和去中心化给用户带来了自由度,但同时也带来了安全性和法律方面的新挑战。 6. **版本控制**:使用版本控制系统的“master”分支来组织代码是一种常见的实践,它保证了项目的主版本代码是最稳定的。在多人协作的项目中,“master”分支往往只允许合并已经过测试和审查的代码。 7. **开发环境搭建**:对于想要开发Dapp的开发者来说,他们需要搭建适当的开发环境,包括安装Node.js和相应的库,如web3.js,以与区块链进行交互,以及可能的开发工具和IDE(如Truffle, Remix, Visual Studio Code)。 8. **安全性考虑**:在开发Dapp加密游戏时,安全是非常重要的考量。游戏开发者需要对智能合约进行安全审计,防止漏洞被利用导致玩家资产被盗,以及确保游戏的公平性和稳定性。 #### 结语 以上就是对“dapp-crypto-game:星云的Dapp加密游戏”相关知识点的详细解读。概括来说,一个加密游戏Dapp的开发涉及到区块链技术、智能合约、加密货币以及前端开发技术等多个领域。随着区块链技术的不断成熟和普及,越来越多的开发者将参与到Dapp游戏的开发中来,为用户提供全新的去中心化游戏体验。
recommend-type

抗锯齿技术深度对比:Delphi绘图中图像质量提升实战方案

# 摘要 抗锯齿技术是提升计算机图形质量的关键手段,尤其在Delphi绘图引擎中对图像边缘的平滑处理具有重要意义。本文系统阐述了抗锯齿技术的理论基础,包括锯齿成因、技术分类及其在性能与画质之间的权衡。结合Delphi平台的图形支持体系,探讨了GDI+、Direct2D及第三方库在抗锯齿实现中的技术路径与关键API配置。文章进一步提出了在矢量图形绘制、图像变换及动态渲染中的实战优化技巧,并分析了抗锯齿带来
recommend-type

mano关节点顺序

在 MANO 模型中,关节点的顺序定义是基于手部解剖结构的层级排列,以支持对手部姿态的参数化控制。通常,MANO 模型包含 16 个主要的关节点(joints),其中包括一个根关节(root joint)和 15 个手指关节,这些关节按照一定的顺序排列,用于描述手部的全局位置和各手指的弯曲与伸展状态。 具体的关节点顺序如下: 1. 根关节(Wrist / Root) 2. 大拇指(Thumb): - Thumb 1 (thumb MCP) - Thumb 2 (thumb PIP) - Thumb 3 (thumb DIP) 3. 食指(Index finger):