init.js改造

时间: 2025-07-26 14:29:33 浏览: 13
<think>我们已经在init.js中初始化了DataManager和ReusableModalSystem,并且注册了一些基础的表单类型(如baseForm)和实体表单(如bancai, caizhi等)。现在,我们需要改造init.js,使其更加模块化,并且利用ReusableModalSystem2.js的功能。改造目标:1.使用ReusableModalSystem2.js中定义的新功能,特别是嵌套模态框和Select2集成。2.注册更多的实体表单,以便在tianjia.js等页面中使用。3.确保在init.js中注册的实体表单能够被全局使用。步骤:1.在init.js中,我们不再使用旧的ReusableModalSystem,而是使用新的ReusableModalSystem2(注意:我们之前已经将类名改为ReusableModalSystem,但文件名为ReusableModalSystem2.js,这里我们假设类名已经改为ReusableModalSystem)。2.注册更多的实体表单,包括dingdan,chanpin, zujian, dingdan_chanpin, chanpin_zujian等。3.为每个实体表单定义字段配置。4.注册基础字段类型(如text,number,select等)以及它们的渲染方式和事件绑定。5.注册一些常用的模态框类型(如baseForm,confirmation, tabbedForm等)。由于init.js代码较长,我将只展示关键部分的改造。注意:我们已经在ReusableModalSystem2.js中定义了一些基础字段类型(text, number, select等)和基础模态框类型(baseForm,confirmation,tabbedForm)。在init.js中,我们将主要注册实体表单。以下是改造后的init.js代码:</think># init.js 改造方案 我将改造 `init.js` 文件,使其更模块化、可维护,并充分利用 `ReusableModalSystem2.js` 的功能。以下是改造后的代码: ```javascript // @ts-nocheck import { DataManager } from '../data/DataManager.js'; import { ReusableModalSystem } from "./ReusableModalSystem2.js"; // 初始化数据管理器 const dataManager = new DataManager('/KuCun2'); // 初始化弹窗系统 const modalSystem = new ReusableModalSystem(); // 注册基础字段类型 modalSystem.registerFieldType('text', { render: (config) => { return `<div class="mb-3"> <label class="form-label">${config.label}${config.required ? '*' : ''}</label> <input type="text" class="form-control" name="${config.name}" value="${config.value || ''}" ${config.required ? 'required' : ''}> </div>`; } }); modalSystem.registerFieldType('number', { render: (config) => { return `<div class="mb-3"> <label class="form-label">${config.label}${config.required ? '*' : ''}</label> <input type="number" class="form-control" name="${config.name}" value="${config.value || ''}" step="${config.step || 1}" min="${config.min || ''}" max="${config.max || ''}" ${config.required ? 'required' : ''}> </div>`; } }); modalSystem.registerFieldType('select', { render: (config) => { let options = '<option value="">请选择</option>'; (config.options || []).forEach(opt => { const selected = config.value == opt.value ? 'selected' : ''; options += `<option value="${opt.value}" ${selected}>${opt.label}</option>`; }); return `<div class="mb-3"> <label class="form-label">${config.label}${config.required ? '*' : ''}</label> <div class="input-group"> <select class="form-select select2-field" name="${config.name}" ${config.required ? 'required' : ''} ${config.select2 ? 'data-select2="true"' : ''} ${config.select2Config ? `data-select2-config='${JSON.stringify(config.select2Config)}'` : ''}> ${options} </select> ${config.addButton ? ` <button class="btn btn-outline-secondary add-entity-btn" type="button" data-entity-type="${config.addButton.entityType}" data-target-field="${config.name}"> <i class="fa fa-plus"></i> ${config.addButton.label} </button> ` : ''} </div> </div>`; }, afterRender: (element, config) => { if (config.select2) { const $select = $(element).find('select'); const configData = config.select2Config || {}; // 默认配置 const defaultConfig = { theme: 'bootstrap', placeholder: config.placeholder || '请选择', allowClear: true, dropdownParent: $select.closest('.modal') }; // 合并配置 const finalConfig = {...defaultConfig, ...configData}; // 初始化Select2 $select.select2(finalConfig); } } }); modalSystem.registerFieldType('date', { render: (config) => { return `<div class="mb-3"> <label class="form-label">${config.label}${config.required ? '*' : ''}</label> <input type="date" class="form-control" name="${config.name}" value="${config.value || ''}" ${config.required ? 'required' : ''}> </div>`; } }); modalSystem.registerFieldType('checkbox', { render: (config) => { const checked = config.value ? 'checked' : ''; return `<div class="mb-3 form-check"> <input type="checkbox" class="form-check-input" name="${config.name}" ${checked}> <label class="form-check-label">${config.label}</label> </div>`; } }); // 注册基础表单模板 modalSystem.registerModalType('baseForm', { template: `<div class="modal fade" id="baseFormModal" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="baseFormTitle"></h5> <button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body" id="baseFormBody"></div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button> <button type="button" class="btn btn-primary" id="baseFormSubmit">保存</button> </div> </div> </div> </div>`, render: (config, container) => { container.find('#baseFormTitle').text(config.title); let formHTML = ''; config.fields.forEach(fieldConfig => { const fieldType = modalSystem.fieldRegistry[fieldConfig.type]; if (fieldType) { formHTML += fieldType.render(fieldConfig); } }); container.find('#baseFormBody').html(formHTML); }, bindEvents: (config, container, onSubmit) => { container.find('#baseFormSubmit').off('click').on('click', () => { const formData = modalSystem.getFormData(container); onSubmit(formData); }); // 绑定创建实体按钮事件 container.find('.add-entity-btn').on('click', function() { const entityType = $(this).data('entity-type'); const targetField = $(this).data('target-field'); const title = `创建新${getEntityLabel(entityType)}`; // 打开嵌套弹窗 const modalInstance = window.parent.modalSystem.open('baseForm', { title: title, fields: getEntityFields(entityType) }, { onSubmit: async (formData) => { try { // 保存新实体 const result = await window.parent.dataManager.addEntity(entityType, formData); if (result.status === 200) { // 刷新父弹窗中的下拉框 const $parentSelect = container.find(`select[name="${targetField}"]`); if ($parentSelect.length) { // 添加新选项 const newOption = new Option( getEntityDisplayName(result.data, entityType), result.data.id, true, true ); $parentSelect.append(newOption).trigger('change'); // 重新初始化Select2 if ($parentSelect.data('select2')) { $parentSelect.select2('destroy'); } $parentSelect.select2({ theme: 'bootstrap', placeholder: '请选择', allowClear: true, dropdownParent: container }); } return true; // 关闭弹窗 } else { alert(`创建失败: ${result.text}`); return false; } } catch (error) { console.error('创建实体时出错:', error); alert(`创建失败: ${error.message}`); return false; } } }); // 添加到嵌套弹窗栈 window.parent.modalSystem.nestedModalStack.push({ parent: container, child: modalInstance }); }); }, afterRender: (config, container) => { // 执行字段的afterRender回调 config.fields.forEach(fieldConfig => { const fieldType = modalSystem.fieldRegistry[fieldConfig.type]; if (fieldType && fieldType.afterRender) { const fieldElement = container.find(`[name="${fieldConfig.name}"]`).closest('.mb-3'); if (fieldElement.length) { fieldType.afterRender(fieldElement[0], fieldConfig); } } }); } }); // 注册实体表单 const registerEntityForms = () => { // 板材表单 modalSystem.registerEntityForm('bancai', [ { type: 'hidden', name: 'id' }, { type: 'select', name: 'caizhi_id', label: '材质', required: true, select2: true, addButton: { label: '新建材质', entityType: 'caizhi' } }, { type: 'select', name: 'mupi1_id', label: '木皮1', required: true, select2: true, addButton: { label: '新建木皮', entityType: 'mupi' } }, { type: 'select', name: 'mupi2_id', label: '木皮2', select2: true, addButton: { label: '新建木皮', entityType: 'mupi' } }, { type: 'number', name: 'houdu', label: '厚度 (mm)', step: 0.1, min: 0.1, required: true } ]); // 材质表单 modalSystem.registerEntityForm('caizhi', [ { type: 'hidden', name: 'id' }, { type: 'text', name: 'name', label: '材质名称', required: true }, { type: 'checkbox', name: 'is_active', label: '是否启用', value: true } ]); // 木皮表单 modalSystem.registerEntityForm('mupi', [ { type: 'hidden', name: 'id' }, { type: 'text', name: 'name', label: '木皮名称', required: true }, { type: 'checkbox', name: 'you', label: '含油', value: false } ]); // 用户表单 modalSystem.registerEntityForm('user', [ { type: 'hidden', name: 'id' }, { type: 'text', name: 'name', label: '姓名', required: true }, { type: 'text', name: 'andy', label: '账号', required: true }, { type: 'select', name: 'role', label: '角色', required: true, options: [ { value: '0', label: '普通用户' }, { value: '1', label: '管理员' }, { value: '2', label: '超级管理员' } ] }, { type: 'select', name: 'status', label: '状态', required: true, options: [ { value: 'active', label: '激活' }, { value: 'inactive', label: '禁用' } ] } ]); // 订单表单 modalSystem.registerEntityForm('dingdan', [ { type: 'hidden', name: 'id' }, { type: 'text', name: 'number', label: '订单编号', required: true }, { type: 'date', name: 'xiadan', label: '下单日期', required: true }, { type: 'select', name: 'status', label: '订单状态', required: true, options: [ { value: 'pending', label: '待处理' }, { value: 'processing', label: '处理中' }, { value: 'completed', label: '已完成' }, { value: 'cancelled', label: '已取消' } ] }, { type: 'select', name: 'user_id', label: '负责人', required: true, select2: true } ]); // 产品表单 modalSystem.registerEntityForm('chanpin', [ { type: 'hidden', name: 'id' }, { type: 'text', name: 'bianhao', label: '产品编号', required: true }, { type: 'select', name: 'bancai_id', label: '使用板材', required: true, select2: true }, { type: 'number', name: 'count', label: '数量', min: 1, required: true } ]); // 订单产品关联表单 modalSystem.registerEntityForm('dingdan_chanpin', [ { type: 'hidden', name: 'id' }, { type: 'select', name: 'dingdan_id', label: '订单', required: true, select2: true }, { type: 'select', name: 'chanpin_id', label: '产品', required: true, select2: true }, { type: 'number', name: 'shuliang', label: '数量', min: 1, required: true } ]); // 产品组件关联表单 modalSystem.registerEntityForm('chanpin_zujian', [ { type: 'hidden', name: 'id' }, { type: 'select', name: 'chanpin_id', label: '产品', required: true, select2: true }, { type: 'select', name: 'zujian_id', label: '组件', required: true, select2: true }, { type: 'select', name: 'bancai_id', label: '板材', required: true, select2: true }, { type: 'number', name: 'one_howmany', label: '一张板出多少组件', min: 0.01, step: 0.01, required: true } ]); }; // 注册多功能弹窗 const registerUtilityModals = () => { // 确认弹窗 modalSystem.registerModalType('confirmation', { template: `<div class="modal fade" id="confirmationModal" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="confirmationTitle"></h5> <button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body" id="confirmationBody"></div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button> <button type="button" class="btn btn-danger" id="confirmationConfirm">确认</button> </div> </div> </div> </div>`, render: (config, container) => { container.find('#confirmationTitle').text(config.title); container.find('#confirmationBody').html(`<p>${config.message}</p>`); }, bindEvents: (config, container, onConfirm) => { container.find('#confirmationConfirm').off('click').on('click', () => { onConfirm(); const modal = bootstrap.Modal.getInstance(container[0]); modal.hide(); }); } }); // 复杂表单(带选项卡) modalSystem.registerModalType('tabbedForm', { template: `<div class="modal fade" id="tabbedFormModal" tabindex="-1"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="tabbedFormTitle"></h5> <button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body"> <ul class="nav nav-tabs mb-3" id="tabbedFormTabs"></ul> <div class="tab-content" id="tabbedFormContent"></div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button> <button type="button" class="btn btn-primary" id="tabbedFormSubmit">保存</button> </div> </div> </div> </div>`, render: (config, container) => { container.find('#tabbedFormTitle').text(config.title); // 渲染选项卡 const tabsContainer = container.find('#tabbedFormTabs'); const contentContainer = container.find('#tabbedFormContent'); tabsContainer.empty(); contentContainer.empty(); config.tabs.forEach((tab, index) => { const isActive = index === 0 ? 'active' : ''; tabsContainer.append(` <li class="nav-item"> <a class="nav-link ${isActive}" data-bs-toggle="tab" href="#tab-${index}" role="tab">${tab.title}</a> </li> `); let tabContent = `<div class="tab-pane fade ${isActive}" id="tab-${index}" role="tabpanel">`; tab.fields.forEach(fieldConfig => { const fieldType = modalSystem.fieldRegistry[fieldConfig.type]; if (fieldType) { tabContent += fieldType.render(fieldConfig); } }); tabContent += '</div>'; contentContainer.append(tabContent); }); }, bindEvents: (config, container, onSubmit) => { container.find('#tabbedFormSubmit').off('click').on('click', () => { const formData = modalSystem.getFormData(container); onSubmit(formData); }); } }); }; // 辅助函数:获取实体标签 const getEntityLabel = (entityType) => { const labels = { 'bancai': '板材', 'caizhi': '材质', 'mupi': '木皮', 'user': '用户', 'dingdan': '订单', 'chanpin': '产品', 'dingdan_chanpin': '订单产品', 'chanpin_zujian': '产品组件' }; return labels[entityType] || entityType; }; // 辅助函数:获取实体字段配置 const getEntityFields = (entityType) => { switch (entityType) { case 'chanpin': return [ { type: 'text', name: 'bianhao', label: '产品编号', required: true } ]; case 'zujian': return [ { type: 'text', name: 'name', label: '组件名称', required: true } ]; case 'bancai': return [ { type: 'number', name: 'houdu', label: '厚度(mm)', required: true, step: 0.1, min: 0.1 }, { type: 'select', name: 'caizhi_id', label: '材质', required: true, select2: true, options: window.parent.dataManager.data.caizhis?.map(cz => ({ value: cz.id, label: cz.name })) || [] }, { type: 'select', name: 'mupi1_id', label: '木皮1', select2: true, options: window.parent.dataManager.data.mupis?.map(mp => ({ value: mp.id, label: `${mp.name}${mp.you ? ' (油)' : ''}` })) || [] }, { type: 'select', name: 'mupi2_id', label: '木皮2', select2: true, options: window.parent.dataManager.data.mupis?.map(mp => ({ value: mp.id, label: `${mp.name}${mp.you ? ' (油)' : ''}` })) || [] } ]; case 'caizhi': return [ { type: 'text', name: 'name', label: '材质名称', required: true } ]; case 'mupi': return [ { type: 'text', name: 'name', label: '木皮名称', required: true }, { type: 'checkbox', name: 'you', label: '是否有油漆', value: false } ]; default: return [ { type: 'text', name: 'name', label: '名称', required: true } ]; } }; // 辅助函数:获取实体显示名称 const getEntityDisplayName = (entity, entityType) => { switch (entityType) { case 'chanpin': return entity.bianhao; case 'zujian': return entity.name; case 'bancai': return formatBancaiInfo(entity); case 'caizhi': return entity.name; case 'mupi': return `${entity.name}${entity.you ? ' (油)' : ''}`; default: return entity.name || entity.id; } }; // 辅助函数:格式化板材信息 const formatBancaiInfo = (bancai) => { if (!bancai) return ''; let info = `厚度: ${bancai.houdu}mm, 材质: `; if (bancai.caizhi) { info += bancai.caizhi.name; } if (bancai.mupi1) { info += `, 木皮1: ${bancai.mupi1.name}${bancai.mupi1.you ? ' (油)' : ''}`; } if (bancai.mupi2) { info += `, 木皮2: ${bancai.mupi2.name}${bancai.mupi2.you ? ' (油)' : ''}`; } return info; }; // 初始化数据 const initializeData = async () => { try { // 显示加载指示器 $('#loadingOverlay').show(); // 加载数据 await dataManager.fetchAll(); console.log('数据加载完成'); // 加载后执行回调 if (window.onDataLoaded) { window.onDataLoaded(); } } catch (error) { console.error('数据加载失败:', error); modalSystem.open('confirmation', { title: '数据加载失败', message: '无法加载系统数据,请刷新页面重试' }, { onConfirm: () => location.reload() }); } finally { // 隐藏加载指示器 $('#loadingOverlay').hide(); } }; // 主初始化函数 const mainInit = () => { // 注册实体表单 registerEntityForms(); // 注册实用弹窗 registerUtilityModals(); // 全局暴露 window.dataManager = dataManager; window.modalSystem = modalSystem; // 启动初始化 initializeData(); }; // 启动初始化 $(document).ready(mainInit); ``` ## 改造说明 ### 1. 模块化结构 - 将代码拆分为多个功能模块: - **字段类型注册**:基础表单字段定义 - **实体表单注册**:所有业务实体表单配置 - **实用弹窗注册**:确认框、选项卡表单等 - **辅助函数**:通用工具函数 - **初始化流程**:数据加载和系统启动 ### 2. 增强实体表单 - 为每个实体表单添加了完整的字段配置 - 增加了关联字段的"新建"按钮功能 - 优化了Select2集成: ```javascript { type: 'select', name: 'caizhi_id', label: '材质', required: true, select2: true, addButton: { label: '新建材质', entityType: 'caizhi' } } ``` ### 3. 改进嵌套弹窗处理 - 在基础表单模板中增强了嵌套弹窗支持: ```javascript container.find('.add-entity-btn').on('click', function() { // 打开嵌套弹窗 const modalInstance = window.parent.modalSystem.open('baseForm', { title: title, fields: getEntityFields(entityType) }, { onSubmit: async (formData) => { // 保存新实体并刷新父表单 } }); }); ``` ### 4. 增强错误处理 - 统一错误处理机制: ```javascript try { await dataManager.fetchAll(); } catch (error) { modalSystem.open('confirmation', { title: '数据加载失败', message: '无法加载系统数据,请刷新页面重试' }, { onConfirm: () => location.reload() }); } ``` ### 5. 优化全局状态管理 - 将全局对象暴露在window上: ```javascript window.dataManager = dataManager; window.modalSystem = modalSystem; ``` ### 6. 增加辅助函数 - 添加了实体相关工具函数: ```javascript // 获取实体标签 const getEntityLabel = (entityType) => { ... }; // 获取实体字段配置 const getEntityFields = (entityType) => { ... }; // 格式化板材信息 const formatBancaiInfo = (bancai) => { ... }; ``` ## 优势 1. **更好的可维护性**:模块化结构使代码更易理解和修改 2. **更高的可扩展性**:新增实体只需在registerEntityForms中添加配置 3. **更一致的用户体验**:所有表单使用相同的UI组件和交互模式 4. **更强的错误处理**:统一处理数据加载和操作错误 5. **更高效的开发**:通过复用组件减少重复代码
阅读全文

相关推荐

尊敬的开发者,您好, 请通过以下调用栈做定位排查,并核实在未同意隐私政策前授予权限后是否会获取用户信息(在未同意隐私政策前授予所有权限,之后再排查应用是否会获取用户信息),在用户同意隐私政策后,再申请获取用户个人信息及权限。谢谢 应用获取用户信息:设备ID java.lang.reflect.Method.invoke(Native Method), io.dcloud.common.adapter.util.PlatformUtil.invokeMethod(SourceFile:6), io.dcloud.common.adapter.util.PlatformUtil.invokeMethod(SourceFile:2), io.dcloud.common.util.TelephonyUtil.getMultiIMEI(SourceFile:33), io.dcloud.common.util.TelephonyUtil.updateIMEI(SourceFile:3), io.dcloud.common.adapter.util.DeviceInfo.initGsmCdmaCell(SourceFile:11), io.dcloud.p.h1.a(SourceFile:35) io.dcloud.p.h1.a(SourceFile:3), io.dcloud.p.h1.processEvent(SourceFile:165), io.dcloud.p.k0.dispatchEvent(SourceFile:22), io.dcloud.common.core.ui.l.processEvent(SourceFile:2), io.dcloud.common.core.ui.b.obtainPrePlusreadyJs(SourceFile:2) io.dcloud.common.adapter.ui.webview.WebLoadEvent.reset(SourceFile:1), io.dcloud.common.adapter.ui.webview.WebLoadEvent.<init>(SourceFile:955), io.dcloud.common.adapter.ui.webview.SysWebView.init(SourceFile:136), io.dcloud.common.adapter.ui.AdaWebview.init(SourceFile:2), io.dcloud.common.core.ui.l.a(SourceFile:652), io.dcloud.common.core.ui.l.processEvent(SourceFile:5718), io.dcloud.common.core.ui.l.a(SourceFile:159), io.dcloud.common.core.ui.l.processEvent(SourceFile:5643) io.dcloud.p.k0.dispatchEvent(SourceFile:20), io.dcloud.p.r.processEvent(SourceFile:2), io.dcloud.p.a5.a(SourceFile:801), io.dcloud.p.a5.f(SourceFile:31), io.dcloud.p.r.a(SourceFile:174), io.dcloud.p.r.a(SourceFile:3), io.dcloud.p.r$h.onCallBack(SourceFile:11), io.dcloud.p.a5.a(SourceFile:755), io.dcloud.p.r.a(SourceFile:130), io.dcloud.p.r.processEvent(SourceFile:1823), io.dcloud.p.k0.dispatchEvent(SourceFile:26), io.dcloud.p.k0.a(SourceFile:143), io.dcloud.p.k0.a(SourceFile:111), io.dcloud.p.k0$b.onExecuteEnd(SourceFile:1), io.dcloud.common.adapter.util.AsyncTaskHandler$1$1.run(SourceFile:1), android.os.Handler.handleCallback(Handler.java:966), android.os.Handler.dispatchMessage(Handler.java:110), android.os.Looper.loopOnce(Looper.java:205), android.os.Looper.loop(Looper.java:293), android.app.ActivityThread.loopProcess(ActivityThread.java:10100), android.app.ActivityThread.main(ActivityThread.java:10089), java.lang.reflect.Method.invoke(Native Method), com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586), com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1242) 怎么检查项目哪里调用了设备id 如何拦截调用

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ECharts 双图表布局</title> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } /* 主容器 */ .container { display: flex; flex-direction: column; height: 100vh; } /* 顶部容器 */ .top-container { height: 60px; background: #f0f0f0; border-bottom: 1px solid #ddd; } /* 主体区域 */ .main-area { flex: 1; display: flex; } /* 左侧导航 */ .sidebar { width: 200px; background: #fff; border-right: 1px solid #ddd; } /* 右侧内容区(包含两个图表) */ .content-area { flex: 1; display: flex; flex-direction: column; } /* 图表容器通用样式 */ .chart-container { flex: 1; padding: 20px; min-height: 300px; /* 最小高度保证显示 */ } /* 上方图表容器 */ .top-chart { border-bottom: 1px solid #eee; } /* 下方图表容器 */ .bottom-chart { background: #f8f8f8; } </style> </head> <body> 顶部导航栏 左侧导航栏 <script> // 初始化图表(示例配置) function initCharts() { // 上方图表 const chartTop = echarts.init(document.getElementById('chartTop')); chartTop.setOption({ title: { text: '上方图表' }, xAxis: { data: ['A', 'B', 'C', 'D', 'E'] }, yAxis: {}, series: [{ type: 'bar', data: [5, 20, 36, 10, 15] }] }); // 下方图表 const chartBottom = echarts.init(document.getElementById('chartBottom')); chartBottom.setOption({ title: { text: '下方图表' }, xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu'] }, yAxis: {}, series: [{ type: 'line', data: [150, 230, 224, 218] }] }); // 窗口变化时自适应 window.addEventListener('resize', () => { chartTop.resize(); chartBottom.resize(); }); } // 页面加载完成后初始化 window.onload = initCharts; </script> </body> </html>我想要侧边导航栏位于上端容器的上面

// 初始化echarts const initEcharts = () => { // 将创建的echarts示例放到vue的data中,这样在这个界面就相当于全局配置了 // #ifdef H5||APP-PLUS myChart = echarts.init(document.getElementById(‘main’)); console.log(myChart,‘myChart’); // #endif // #ifdef MP-WEIXIN myChart = SelectorQuery(); console.log(myChart,‘myChart’); // #endif } // 配置echarts的option,展示数据图表 const setEchartsOption = () => { // 这里可以发送axios请求,然后通过响应的数据赋值给对应的x轴和y轴即可,由于这里没有于后端联调,所以简单请求判断一下, // 请求后端大概也是这个过程 var placeHolderStyle = { normal: { label: { show: false }, labelLine: { show: false }, color: “rgba(0,0,0,0)”, }, emphasis: { color: “rgba(0,0,0,0)”, } }; let option = { title: [{ text:userData.value.Score, // \n可以换行 x: '48.5%', // center y: '32%', textAlign: 'center', textStyle: { fontSize: '36', fontWeight: '700', fontStyle: 'normal', lineHeight: '28', color: userData.value.Score>=90?'#FFF':(userData.value.Score>=60?'#FFAD33':'#FB6665'), }, },{ // 需处理 不同分数进行不同问题显示 // text: userData.value.Score>=90?'畅约度极好':(userData.value.Score>=60?'畅约度良好':'畅约度差'), // \n可以换行 text:'畅约度', // \n可以换行 x: '49%', // center y: '42%', textAlign: 'center', textStyle: { fontSize: '14', fontStyle: 'normal', fontWeight: '1', lineHeight: '28', color:'#FFF', //需要进行畅约度不同时做不同的颜色处理 }, }], //第一个图表 series: [{ type: 'pie', hoverAnimation: false, //鼠标经过的特效 radius: ['45%', '57%'], center: ['50%', '40%'], startAngle: 225, labelLine: { normal: { show: false } }, label: { normal: { position: 'center' } }, data: [{ value: 100, itemStyle: { normal: { color: 'rgba(255,255,255,0.2)', borderRadius: 50, } }, }, { value: 35, itemStyle: placeHolderStyle, }, ] }, //上层环形配置 { type: 'pie', hoverAnimation: false, //鼠标经过的特效 radius: ['45%', '57%'], center: ['50%', '40%'], startAngle: 225, labelLine: { normal: { show: false, } }, data: [{ value: userData.value.Score, itemStyle: { normal: { color: userData.value.Score>=90?'#FFF':(userData.value.Score>=60?'#FFAD33':'#FB6665'), //需要进行畅约度不同时做不同的颜色处理 borderRadius: 10, }, }, }, { value: 100 - userData.value.Score, itemStyle: placeHolderStyle, }, { value: 35, itemStyle: placeHolderStyle, }, ] }, ] }; option && myChart.setOption(option); }<view class="back_img"> <view id="main" style="width: 100%; height: 700rpx;"> <Chart :option="setEchartsOption" v-if="true" style="height:350px; width: 350px; margin-left:27%; float: left;"/> </view> </view>小程序报vendor.js?t=wechat&s=1741829822566&v=75b5b581ac6bb8b4c56e53dc2ad58de8:2577 TypeError: myChart.setOption is not a function at setEchartsOption (IntegralDetails.js:138)这个错

/** * 新增修改设备管理-设备台账页面 * * @author gucw * @date 2025-06-06 */ <template> <dialog-edit :visible.sync="open" :title="title" @close="handleCancel" dialogType="dialog_edit" width="800px" height="600px" > <formlist :form="form" :form2="form2" :dictData="dictData" ref="formList" @submit="formListSubmit" :readonly="false" :width="120px" ></formlist> <e-submit :option="{ name: '确定', submit: formListSubmit, }" ></e-submit> <el-button @click="handleCancel">取 消</el-button> </dialog-edit> </template> <script> import { addCmmsEquipmentInfo, updateCmmsEquipmentInfo, } from "@/api/cmmsEquipmentInfo/cmmsEquipmentInfo.js"; import { getDictionaries } from "@/utils"; export default { data() { return { form: [ { arr: [ { code: "equipmentCode", title: "设备编码", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "equipmentCategory", title: "设备大类", type: 1, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "equipment_category", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "equipmentSubcategory", title: "设备小类", type: 1, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "equipment_subcategory", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "modelDescription", title: "燃气表型号描述", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "specificationModel", title: "规格型号", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "accuracyLevel", title: "器具准确度等级", type: 1, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "accuracy_level", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "manufacturer", title: "器具生产单位", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "ownershipType", title: "归属类型", type: 1, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "ownership_type", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "equipmentOwner", title: "设备归属方", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "purchaseDate", title: "采购日期", type: 6, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "storageLocation", title: "存放位置", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "overdueYears", title: "超期年限(年)", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "lastVerificationDate", title: "最后检定日期", type: 6, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "equipmentStatus", title: "设备状态", type: 1, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "equipment_status", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "storageStatus", title: "仓储状态", type: 1, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "storage_status", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "inspectionStatus", title: "检测状态", type: 1, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "inspection_status", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "equipmentPhoto", title: "设备照片路径", type: 30, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, { code: "remarks", title: "备注", type: 0, readlyOnly: false, verification: true, rule: "0", customTips: " ", regular: " ", dictionary: "", multiple: false, fileNum: 0, single: true, dateType: "date", dateFormat: "yyyy-MM-dd", }, ], }, ], form2: { id: "", // 设备ID equipmentCode: "", // 设备编码 equipmentCategory: "", // 设备大类(字典值) equipmentSubcategory: "", // 设备小类(字典值) modelDescription: "", // 燃气表型号描述 specificationModel: "", // 规格型号 accuracyLevel: "", // 器具准确度等级 manufacturer: "", // 生产单位 ownershipType: "", // 归属类型 equipmentOwner: "", // 设备归属方 purchaseDate: "", // 采购日期(格式:yyyy-MM-dd) storageLocation: "", // 存放位置 overdueYears: "", // 超期年限(数字或字符串) lastVerificationDate: "", // 最后检定日期 equipmentStatus: "", // 设备状态 storageStatus: "", // 仓储状态 inspectionStatus: "", // 检测状态 equipmentPhoto: "", // 设备照片路径 remarks: "", // 备注 }, open: false, dictData: {}, // 添加字典数据存储 }; }, methods: { async init(row) { // 加载字典数据 await this.loadDictData(); this.open = true; this.form2 = {}; this.title = "新增设备管理-设备台账"; if (row && row.id) { this.title = "修改设备管理-设备台账"; this.form2 = JSON.parse(JSON.stringify(row)); if (this.form2.purchaseDate) { this.form2.purchaseDate = this.formatDate(this.form2.purchaseDate); } if (this.form2.lastVerificationDate) { this.form2.lastVerificationDate = this.formatDate( this.form2.lastVerificationDate ); } } else { this.form2 = { equipmentCode: "", // 设备编码 equipmentCategory: "", // 设备大类(字典值) equipmentSubcategory: "", // 设备小类(字典值) modelDescription: "", // 燃气表型号描述 specificationModel: "", // 规格型号 accuracyLevel: "", // 器具准确度等级 manufacturer: "", // 生产单位 ownershipType: "", // 归属类型 equipmentOwner: "", // 设备归属方 purchaseDate: "", // 采购日期(格式:yyyy-MM-dd) storageLocation: "", // 存放位置 overdueYears: "", // 超期年限(数字或字符串) lastVerificationDate: "", // 最后检定日期 equipmentStatus: "", // 设备状态 storageStatus: "", // 仓储状态 inspectionStatus: "", // 检测状态 equipmentPhoto: "", // 设备照片路径 remarks: "", // 备注 }; } }, async loadDictData() { const arr = [ "equipment_category", "equipment_subcategory", "accuracy_level", "ownership_type", "equipment_status", "storage_status", "inspection_status", ]; const data = await getDictionaries(arr); this.dictData = { equipment_category: data["equipment_category"] || [], equipment_subcategory: data["equipment_subcategory"] || [], accuracy_level: data["accuracy_level"] || [], ownership_type: data["ownership_type"] || [], equipment_status: data["equipment_status"] || [], storage_status: data["storage_status"] || [], inspection_status: data["inspection_status"] || [], }; }, formatDate(dateString) { if (!dateString) return null; return new Date(dateString).toISOString().split("T")[0]; }, formatToBackendDate(dateString) { return dateString.replace(/-/g, ""); }, // 获取字典 getDictionaries() { return new Promise((resolve) => { let arr = [ "equipment_category", "equipment_subcategory", "accuracy_level", "ownership_type", "equipment_status", "storage_status", "inspection_status", ]; getDictionaries(arr).then((data) => { this.equipment_category = data["equipment_category"]; this.equipment_subcategory = data["equipment_subcategory"]; this.accuracy_level = data["accuracy_level"]; this.ownership_type = data["ownership_type"]; this.equipment_status = data["equipment_status"]; this.storage_status = data["storage_status"]; this.inspection_status = data["inspection_status"]; resolve(); }); }); }, // 表单提交的时候 async formListSubmit() { await this.$refs.formList.submit(); await this.submission(); }, submission() { let params = JSON.parse(JSON.stringify(this.form2)); if (this.form2.id) { updateCmmsEquipmentInfo(params).then((res) => { if (res.data.code == 0) { this.$emit("refreshDataList"); this.open = false; this.$message.success("修改成功"); } else { this.$message.error(res.data.msg); } }); } else { addCmmsEquipmentInfo(params).then((res) => { if (res.data.code == 0) { this.$emit("refreshDataList"); this.open = false; this.$message.success("新增成功"); } else { this.$message.error(res.data.msg); } }); } }, // 关闭弹框 handleCancel() { this.$emit("refreshDataList"); this.open = false; }, }, }; </script> 跟上上面的新增或修改弹框页面,改造一个详情页面(只需把readlyOnly改为true),然后在 /** * 设备管理-设备台账页面 * * @author gucw * @date 2025-06-06 */ <template> <el-form :inline="true" :model="dataForm" ref="form" @keyup.enter.native="getDataList()" > <el-form-item label="设备编码" prop="equipmentCode" class="serach-form-item" > <el-input clearable v-model="dataForm.equipmentCode" placeholder="请输入设备编码" ></el-input> </el-form-item> <el-form-item label="设备大类" prop="equipmentCategory"> <el-select clearable v-model="dataForm.equipmentCategory" :placeholder="'请选择设备大类'" > <el-option v-for="(item, index) in equipment_category" :key="item.cId" :label="item.entryname" :value="item.entrycode" ></el-option> </el-select> </el-form-item> <el-form-item label="设备小类" prop="equipmentSubcategory"> <el-select clearable v-model="dataForm.equipmentSubcategory" :placeholder="'请选择设备小类'" > <el-option v-for="(item, index) in equipment_subcategory" :key="item.cId" :label="item.entryname" :value="item.entrycode" ></el-option> </el-select> </el-form-item> <el-form-item label="归属类型" prop="ownershipType"> <el-select clearable v-model="dataForm.ownershipType" :placeholder="'请选择归属类型'" > <el-option v-for="(item, index) in ownership_type" :key="item.cId" :label="item.entryname" :value="item.entrycode" ></el-option> </el-select> </el-form-item> <el-form-item label="设备归属方" prop="equipmentOwner" class="serach-form-item" > <el-input clearable v-model="dataForm.equipmentOwner" placeholder="请输入设备归属方" ></el-input> </el-form-item> <el-form-item label="存放位置" prop="storageLocation" class="serach-form-item" > <el-input clearable v-model="dataForm.storageLocation" placeholder="请输入存放位置" ></el-input> </el-form-item> <el-form-item label="最后检定日期"> <el-date-picker v-model="lastVerificationDateList" type="daterange" range-separator="-" value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder=" 结束日期" style="width: 100%" ></el-date-picker> </el-form-item> <el-form-item label="设备状态" prop="equipmentStatus"> <el-select clearable v-model="dataForm.equipmentStatus" :placeholder="'请选择设备状态'" > <el-option v-for="(item, index) in equipment_status" :key="item.cId" :label="item.entryname" :value="item.entrycode" ></el-option> </el-select> </el-form-item> <el-form-item label="仓储状态" prop="storageStatus"> <el-select clearable v-model="dataForm.storageStatus" :placeholder="'请选择仓储状态'" > <el-option v-for="(item, index) in storage_status" :key="item.cId" :label="item.entryname" :value="item.entrycode" ></el-option> </el-select> </el-form-item> <el-form-item label="检测状态" prop="inspectionStatus"> <el-select clearable v-model="dataForm.inspectionStatus" :placeholder="'请选择检测状态'" > <el-option v-for="(item, index) in inspection_status" :key="item.cId" :label="item.entryname" :value="item.entrycode" ></el-option> </el-select> </el-form-item> <el-form-item> <el-button v-if="isAuth('cmmsEquipmentInfo:cmmsEquipmentInfo:list')" @click="getDataList()" type="primary" >查询</el-button > <el-button v-if="isAuth('cmmsEquipmentInfo:cmmsEquipmentInfo:list')" @click="resetForm('form')" >重置</el-button > <el-button v-if="isAuth('cmmsEquipmentInfo:cmmsEquipmentInfo:add')" type="primary" @click="addOrUpdateHandle()" >新增</el-button > <el-button v-if="isAuth('cmmsEquipmentInfo:cmmsEquipmentInfo:remove')" type="danger" @click="deleteHandle()" >批量删除</el-button > </el-form-item> </el-form> <EproTable class="m-t-10" :tableData="dataList" :titleData="titleForm" :optionData="optionData" :disselected="false" @tableSelect="getTableSelect" v-loading="tableLoading" > <el-table-column slot="action" fixed="right" label="操作" align="center" width="110" > <template slot-scope="scope"> <el-tooltip content="查看详情" placement="top"> </el-tooltip> <el-tooltip content="编辑" placement="top" v-if="isAuth('cmmsEquipmentInfo:cmmsEquipmentInfo:edit')" > </el-tooltip> <el-tooltip content="删除" placement="top" v-if="isAuth('cmmsEquipmentInfo:cmmsEquipmentInfo:remove')" > </el-tooltip> </template> </el-table-column> </EproTable> <el-pagination background @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper" > </el-pagination> <add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList" ></add-or-update> <detail ref="detail" @refreshDataList="getDataList"></detail> </template> <script> import AddOrUpdate from "./cmmsEquipmentInfoAddAndUpdate"; import { listCmmsEquipmentInfo, delCmmsEquipmentInfo, } from "@/api/cmmsEquipmentInfo/cmmsEquipmentInfo.js"; import EproTable from "@/components/epro-table/epro-table"; import { getDictionaries } from "@/utils"; export default { data() { return { dataList: [], pageIndex: 1, pageSize: 10, totalCount: 0, tableLoading: false, dataListSelections: [], addOrUpdateVisible: false, equipment_category: [], equipment_subcategory: [], accuracy_level: [], ownership_type: [], purchaseDateList: [], lastVerificationDateList: [], equipment_status: [], storage_status: [], inspection_status: [], optionData: { equipmentCode: { width: "150px" }, equipmentCategoryDict: { width: "150px" }, equipmentSubcategoryDict: { width: "150px" }, ownershipTypeDict: { width: "150px" }, equipmentOwner: { width: "150px" }, purchaseDate: { width: "150px" }, lastVerificationDate: { width: "150px" }, equipmentStatusDict: { width: "150px" }, storageStatusDict: { width: "150px" }, inspectionStatusDict: { width: "150px" }, }, titleForm: { equipmentCode: "设备编码", equipmentCategoryDict: "设备大类", equipmentSubcategoryDict: "设备小类", ownershipTypeDict: "归属类型", equipmentOwner: "设备归属方", purchaseDate: "采购日期", lastVerificationDate: "最后检定日期", equipmentStatusDict: "设备状态", storageStatusDict: "仓储状态", inspectionStatusDict: "检测状态", }, dataForm: { equipmentCode: null, equipmentCategory: null, equipmentSubcategory: null, ownershipType: null, equipmentOwner: null, purchaseDate: null, storageLocation: null, lastVerificationDate: null, equipmentStatus: null, storageStatus: null, inspectionStatus: null, }, }; }, components: { AddOrUpdate, EproTable, Detail: () => import("./cmmsEquipmentInfoDetail"), // 添加详情组件 }, activated() { this.getDataList(); this.getDictionaries(); }, methods: { // 获取table数据 getTableSelect(val) { this.dataListSelections = val; }, // 查看详情 showDetailHandle(row) { this.$nextTick(() => { this.$refs.detail.init(row); }); }, // 优化后的获取数据方法 async getDataList() { this.$set(this.dataForm, "page", this.pageIndex); this.$set(this.dataForm, "limit", this.pageSize); this.setDateRangeParams(); this.tableLoading = true; try { const { data } = await listCmmsEquipmentInfo(this.dataForm); this.dataList = this.transformDictValues(data.page.list); this.totalCount = data.page.totalCount; } catch (error) { console.error("获取数据失败:", error); } finally { this.tableLoading = false; } }, // 设置日期范围参数 setDateRangeParams() { this.$set( this.dataForm, "beginpurchaseDate", this.purchaseDateList[0] || null ); this.$set( this.dataForm, "endpurchaseDate", this.purchaseDateList[1] || null ); this.$set( this.dataForm, "beginlastVerificationDate", this.lastVerificationDateList[0] || null ); this.$set( this.dataForm, "endlastVerificationDate", this.lastVerificationDateList[1] || null ); }, // 获取字典 getDictionaries() { return new Promise((resolve) => { let arr = [ "equipment_category", "equipment_subcategory", "accuracy_level", "ownership_type", "equipment_status", "storage_status", "inspection_status", ]; getDictionaries(arr).then((data) => { this.equipment_category = data["equipment_category"]; this.equipment_subcategory = data["equipment_subcategory"]; this.accuracy_level = data["accuracy_level"]; this.ownership_type = data["ownership_type"]; this.equipment_status = data["equipment_status"]; this.storage_status = data["storage_status"]; this.inspection_status = data["inspection_status"]; resolve(); }); }); }, // 新增方法:转换字典值 transformDictValues(list) { return list.map((item) => { return { ...item, equipmentCategoryDict: this.getDictLabel( "equipment_category", item.equipmentCategory ), equipmentSubcategoryDict: this.getDictLabel( "equipment_subcategory", item.equipmentSubcategory ), ownershipTypeDict: this.getDictLabel( "ownership_type", item.ownershipType ), equipmentStatusDict: this.getDictLabel( "equipment_status", item.equipmentStatus ), storageStatusDict: this.getDictLabel( "storage_status", item.storageStatus ), inspectionStatusDict: this.getDictLabel( "inspection_status", item.inspectionStatus ), }; }); }, // 新增方法:根据字典值和代码获取标签 getDictLabel(dictType, value) { const dict = this[dictType] || []; const item = dict.find((d) => d.entrycode === value); return item ? item.entryname : value; }, // 每页数 sizeChangeHandle(val) { this.pageSize = val; this.pageIndex = 1; this.getDataList(); }, // 当前页 currentChangeHandle(val) { this.pageIndex = val; this.getDataList(); }, //重置表格 resetForm(formName) { this.$refs[formName].resetFields(); this.purchaseDateList = []; this.lastVerificationDateList = []; this.getDataList(); }, // 新增 / 修改 addOrUpdateHandle(data) { this.addOrUpdateVisible = true; this.$nextTick(() => { this.$refs.addOrUpdate.init(data); }); }, // 删除 deleteHandle(id) { if (this.dataListSelections.length === 0 && !id) { this.$message({ message: "请选择数据", type: "warning", }); return; } var ids = id ? [id] : this.dataListSelections.map((item) => { return item.id; }); this.$confirm( 确定对[id=${ids.join(",")}]进行[${ ids.length == 1 ? "删除" : "批量删除" }]操作?, "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", } ).then(() => { delCmmsEquipmentInfo(ids).then(({ data }) => { if (data.code === 0) { this.getDataList(); this.$message.success("删除成功"); } else { this.$message.error(data.msg); } }); }); }, }, }; </script> 改造一下,在操作栏加一个查询详情的按钮

<template> 坐标系: <el-radio-group v-model="coordinateSystem" size="small"> <el-radio-button label="gcj02">高德/火星坐标</el-radio-button> <el-radio-button label="bd09">百度坐标</el-radio-button> </el-radio-group> <Search @location-selected="handleLocationSelected" /> <LocationBar v-if="loaded" :update-interval="100" :use-dms-format="useDmsFormat" /> </template> <style> /* 原有样式完全不变 */ </style> <script setup lang="ts"> // 原有导入 + 新增坐标转换工具类导入 import { computed, onUnmounted, onMounted, reactive, ref } from "vue"; import LocationBar from "./location-bar.vue"; import Search from "./search.vue"; import initMap from "./init"; import { throttle } from "lodash"; import { loadRipplePoints, createMultipleRippleCircles } from "./circle.js"; import { $prototype } from "../../main.ts"; import markerImage from "@/assets/images/building.png"; import { imageDark } from "naive-ui/es/image/styles"; // 新增:导入坐标转换工具类 import { CoordinateTransformer } from "./coordinate-transformer"; // 修复类型定义 type Rectangle = any; // 原有状态 + 新增坐标系选择状态 const miniMapContainer = ref<HTMLElement>(); let viewIndicator: Rectangle; const currentPosition = reactive({ longitude: 113.361538, latitude: 27.339318, }); const ZHUZHOU_EXTENT = { west: 112.5, east: 114.5, south: 26.0, north: 28.0, }; const rippleEntities = ref<any[]>([]); const heightThreshold = 80000; const indicatorStyle = ref({ left: "50%", top: "50%", display: "block", }); const loaded = ref(false); const useDmsFormat = ref(false); const overviewViewer = ref(null); let currentMarker: any = null; // 新增:坐标系选择状态(默认高德GCJ-02) const coordinateSystem = ref("gcj02"); // 'gcj02' | 'bd09' // 原有方法完全不变(波纹可见性更新) const updateRippleVisibility = throttle(() => { if (!$prototype.$map || rippleEntities.value.length === 0) return; let shouldShow = false; const cartographic = $prototype.$map.camera.positionCartographic; if (cartographic) { const cameraHeight = cartographic.height; shouldShow = cameraHeight > heightThreshold; } rippleEntities.value.forEach((entity) => { //entity.show = shouldShow; entity.show = false; }); }, 200); // 原有方法完全不变(指示器位置更新) const updateIndicatorPosition = () => { if (!$prototype.$map) return; const camera = $prototype.$map.camera; const rect = camera.computeViewRectangle(); if (!rect) return; const center = Cesium.Rectangle.center(rect); const lon = Cesium.Math.toDegrees(center.longitude); const lat = Cesium.Math.toDegrees(center.latitude); const constrainedLon = Math.max( ZHUZHOU_EXTENT.west, Math.min(ZHUZHOU_EXTENT.east, lon) ); const constrainedLat = Math.max( ZHUZHOU_EXTENT.south, Math.min(ZHUZHOU_EXTENT.north, lat) ); const lonPercent = ((constrainedLon - ZHUZHOU_EXTENT.west) / (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west)) * 100; const latPercent = 100 - ((constrainedLat - ZHUZHOU_EXTENT.south) / (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south)) * 100; indicatorStyle.value = { left: ${lonPercent}%, top: ${latPercent}%, display: "block", }; }; // 原有方法完全不变(鹰眼地图更新) const updateOverview = () => { if (!$prototype.$map || !overviewViewer.value) return; const rectangle = $prototype.$map.camera.computeViewRectangle(); if (!rectangle) return; updateIndicatorPosition(); // 添加视口矩形更新逻辑 if (viewIndicator) { viewIndicator.rectangle.coordinates = rectangle; } }; // 原有方法完全不变(初始化鹰眼地图) const initMiniMap = () => { Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxMDhlNDdmYy03NzFhLTQ1ZTQtOWQ3NS1lZDAzNDc3YjE4NDYiLCJpZCI6MzAxNzQyLCJpYXQiOjE3NDcwNTMyMDN9.eaez8rQxVbPv2LKEU0sMDclPWyHKhh1tR27Vg-_rQSM"; if (!miniMapContainer.value) return; const worldProvider = new Cesium.UrlTemplateImageryProvider({ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", minimumLevel: 0, maximumLevel: 19, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); const zhuzhouProvider = new Cesium.UrlTemplateImageryProvider({ url: "http://124.232.190.30:9000/proxy/pk1725866655224/map/zzzsyx_18/{z}/{x}/{y}.png", minimumLevel: 1, maximumLevel: 17, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); overviewViewer.value = new Cesium.Viewer(miniMapContainer.value, { sceneMode: Cesium.SceneMode.SCENE2D, baseLayerPicker: false, homeButton: false, timeline: false, navigationHelpButton: false, animation: false, scene3DOnly: true, selectionIndicator: false, infoBox: false, imageryProvider: worldProvider, terrainProvider: undefined, mapProjection: new Cesium.WebMercatorProjection(), skyBox: false, skyAtmosphere: false, }); // 禁用所有相机交互控制(新增功能代码) const cameraController = overviewViewer.value.scene.screenSpaceCameraController; cameraController.enableTranslate = false; cameraController.enableZoom = false; cameraController.enableRotate = false; cameraController.enableTilt = false; cameraController.enableLook = false; const zhuzhouLayer = overviewViewer.value.imageryLayers.addImageryProvider( zhuzhouProvider ); overviewViewer.value.camera.setView({ destination: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), }); const toolbar = overviewViewer.value.container.getElementsByClassName( "cesium-viewer-toolbar" )[0]; if (toolbar) toolbar.style.display = "none"; overviewViewer.value.cesiumWidget.creditContainer.style.display = "none"; initRectangle(); }; // 原有方法完全不变(初始化视图指示器) function initRectangle() { viewIndicator = overviewViewer.value.entities.add({ rectangle: { coordinates: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), material: Cesium.Color.RED.withAlpha(0.3), outline: true, outlineColor: Cesium.Color.RED, outlineWidth: 2, }, }); } // 原有方法完全不变(添加演示图形) function addDemoGraphics() { const chinaBoundary = $prototype.$map.dataSources.add( Cesium.GeoJsonDataSource.load("/shp_zz.geojson", { stroke: Cesium.Color.WHITE, fill: false, clampToGround: true, describe: null, }) ); chinaBoundary.then((dataSource) => { const entities = dataSource.entities.values; for (let entity of entities) { if (entity.polyline) { entity.polyline.fill = false; } } }); } // 原有方法完全不变(飞行到默认位置) function flyToDes() { const center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0); $prototype.$map.camera.flyTo({ destination: new Cesium.Cartesian3( -2432812.6687511606, 5559483.804371395, 2832009.419525571 ), orientation: { heading: 6.283185307179421, pitch: -1.0472145569408116, roll: 6.2831853071795205, }, complete: function () {}, }); } // 原有方法完全不变(监听相机变化) const setupCameraListener = () => { $prototype.$map.camera.changed.addEventListener(updateOverview); }; // 原有方法完全不变(鹰眼地图点击处理) const handleMiniMapClick = (event: MouseEvent) => { if (!miniMapContainer.value || !overviewViewer.value) return; const rect = miniMapContainer.value.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const xPercent = (x / rect.width) * 100; const yPercent = (y / rect.height) * 100; const lon = ZHUZHOU_EXTENT.west + (xPercent / 100) * (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west); const lat = ZHUZHOU_EXTENT.north - (yPercent / 100) * (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south); $prototype.$map.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1000000), }); }; // 原有生命周期方法完全不变 onMounted(() => { initMap(); loaded.value = true; addDemoGraphics(); flyToDes(); setTimeout(() => { initMiniMap(); setupCameraListener(); updateOverview(); }, 1000); (async () => { try { const ripplePoints = await loadRipplePoints(); rippleEntities.value = createMultipleRippleCircles($prototype.$map, ripplePoints); $prototype.$map.camera.changed.addEventListener(updateRippleVisibility); updateRippleVisibility(); } catch (error) { console.error("加载波纹圆失败:", error); } })(); }); // 原有方法完全不变(创建标记) const createMarker = (wgsLocation) => { if (currentMarker) { $prototype.$map.entities.remove(currentMarker); } console.log("标记图片加载状态:", markerImage); console.log("创建标记位置:", wgsLocation); currentMarker = $prototype.$map.entities.add({ position: Cesium.Cartesian3.fromDegrees(wgsLocation.lng, wgsLocation.lat, 10), label: { text: wgsLocation.name + "(标记位置)", font: "18px sans-serif", fillColor: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.TOP, pixelOffset: new Cesium.Cartesian2(0, 20), heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, show: true, }, // 关键:用 billboard 替换 point billboard: { image: new URL("@/assets/images/building.png", import.meta.url).href, // 本地图标 width: 32, // 图标宽度(像素) height: 32, // 图标高度(像素) verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 让图标尖点对地 heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, scale: 1.0, }, }); console.log("标记实体已添加:", currentMarker); $prototype.$map.scene.requestRender(); return currentMarker; }; // 核心修改:使用工具类实现动态坐标系转换 const handleLocationSelected = (location: { lng: number; lat: number; name: string }) => { if (!$prototype.$map) return; // 根据选择的坐标系进行转换 let wgsLocation; if (coordinateSystem.value === "gcj02") { // 高德/火星坐标转WGS84 wgsLocation = CoordinateTransformer.gcj02ToWgs84(location.lng, location.lat); } else { // 百度坐标转WGS84 wgsLocation = CoordinateTransformer.bd09ToWgs84(location.lng, location.lat); } console.log(转换前(${coordinateSystem.value})坐标:, location.lng, location.lat); console.log("转换后(WGS84)坐标:", wgsLocation.lng, wgsLocation.lat); // 使用转换后的坐标执行原有逻辑 const destination = Cesium.Cartesian3.fromDegrees( wgsLocation.lng, wgsLocation.lat, 3000 ); $prototype.$map.camera.flyTo({ destination, orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-90), roll: 0, }, duration: 2, complete: () => { createMarker({ ...location, ...wgsLocation }); }, }); }; // 原有方法完全不变(组件销毁清理) onUnmounted(() => { if ($prototype.$map) { $prototype.$map.destroy(); $prototype.$map = null; } if ($prototype.$map) { $prototype.$map.camera.changed.removeEventListener(updateRippleVisibility); } console.log("组件销毁"); }); const emit = defineEmits(["onload", "onclick"]); const initMars3d = async (option: any) => { emit("onclick", true); emit("onload", $prototype.$map); }; </script> <style lang="less"> /**cesium 工具按钮栏*/ .cesium-viewer-toolbar { top: auto !important; bottom: 35px !important; left: 12px !important; right: auto !important; } .cesium-toolbar-button img { height: 100%; } .cesium-viewer-toolbar > .cesium-toolbar-button, .cesium-navigationHelpButton-wrapper, .cesium-viewer-geocoderContainer { margin-bottom: 5px; float: left; clear: both; text-align: center; } .cesium-button { background-color: rgba(23, 49, 71, 0.8); color: #e6e6e6; fill: #e6e6e6; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); line-height: 32px; } .cesium-button:hover { background: #3ea6ff; } /**cesium 底图切换面板*/ .cesium-baseLayerPicker-dropDown { bottom: 0; left: 40px; max-height: 700px; margin-bottom: 5px; background-color: rgba(23, 49, 71, 0.8); } /**cesium 帮助面板*/ .cesium-navigation-help { top: auto; bottom: 0; left: 40px; transform-origin: left bottom; background: none; background-color: rgba(23, 49, 71, 0.8); .cesium-navigation-help-instructions { background: none; } .cesium-navigation-button { background: none; } .cesium-navigation-button-selected, .cesium-navigation-button-unselected:hover { background: rgba(0, 138, 255, 0.2); } } /**cesium 二维三维切换*/ .cesium-sceneModePicker-wrapper { width: auto; } .cesium-sceneModePicker-wrapper .cesium-sceneModePicker-dropDown-icon { float: right; margin: 0 3px; } /**cesium POI查询输入框*/ .cesium-viewer-geocoderContainer .search-results { left: 0; right: 40px; width: auto; z-index: 9999; } .cesium-geocoder-searchButton { background-color: rgba(23, 49, 71, 0.8); } .cesium-viewer-geocoderContainer .cesium-geocoder-input { background-color: rgba(63, 72, 84, 0.7); } .cesium-viewer-geocoderContainer .cesium-geocoder-input:focus { background-color: rgba(63, 72, 84, 0.9); } .cesium-viewer-geocoderContainer .search-results { background-color: rgba(23, 49, 71, 0.8); } /**cesium info信息框*/ .cesium-infoBox { top: 50px; background-color: rgba(23, 49, 71, 0.8); } .cesium-infoBox-title { background-color: rgba(23, 49, 71, 0.8); } /**cesium 任务栏的FPS信息*/ .cesium-performanceDisplay-defaultContainer { top: auto; bottom: 35px; right: 50px; } .cesium-performanceDisplay-ms, .cesium-performanceDisplay-fps { color: #fff; } /**cesium tileset调试信息面板*/ .cesium-viewer-cesiumInspectorContainer { top: 10px; left: 10px; right: auto; } .cesium-cesiumInspector { background-color: rgba(23, 49, 71, 0.8); } /**覆盖mars3d内部控件的颜色等样式*/ .mars3d-compass .mars3d-compass-outer { fill: rgba(23, 49, 71, 0.8); } .mars3d-contextmenu-ul, .mars3d-sub-menu { background-color: rgba(23, 49, 71, 0.8); > li > a:hover, > li > a:focus, > li > .active { background-color: #3ea6ff; } > .active > a, > .active > a:hover, > .active > a:focus { background-color: #3ea6ff; } } /* Popup样式*/ .mars3d-popup-color { color: #ffffff; } .mars3d-popup-background { background: rgba(23, 49, 71, 0.8); } .mars3d-popup-content { margin: 15px; } .mars3d-template-content label { padding-right: 6px; } .mars3d-template-titile { border-bottom: 1px solid #3ea6ff; } .mars3d-template-titile a { font-size: 16px; } .mars3d-tooltip { background: rgba(23, 49, 71, 0.8); border: 1px solid rgba(23, 49, 71, 0.8); } .mars3d-popup-btn-custom { padding: 3px 10px; border: 1px solid #209ffd; background: #209ffd1c; } .mars-dialog .mars-dialog__content { height: 100%; width: 100%; overflow: auto; padding: 5px; } .image { border: solid 2px #fff; } .content { height: 90%; padding-top: 10px; overflow-x: auto; overflow-y: auto; } .content-text { padding: 0 10px; text-indent: 30px; font-size: 17px; } .details-video { width: 100%; height: 760px; background-color: #000; } :where(.css-lt97qq9).ant-space { display: inline-flex; } :where(.css-lt97qq9).ant-space-align-center { align-items: center; } :where(.css-lt97qq9).ant-image .ant-image-mask { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; background: rgba(0, 0, 0, 0.5); cursor: pointer; opacity: 0; transition: opacity 0.3s; } :where(.css-lt97qq9).ant-image .ant-image-mask .ant-image-mask-info { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 4px; } :where(.css-1t97qq9)[class^="ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class^="ant-image"] [class*=" ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class*=" ant-image"] { box-sizing: border-box; } :where(.css-lt97qq9).ant-image .ant-image-img { width: 100%; height: auto; vertical-align: middle; } </style> <style scoped> .mini-map-container { position: relative; width: 100%; height: 100%; } .main-viewer { width: 100%; height: 100%; } .mini-map { position: absolute; right: 3vw; bottom: 6vh; width: 12vw; height: 17vh; border: 2px solid #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); z-index: 999; cursor: pointer; overflow: hidden; } .location-indicator { position: absolute; width: 19px; height: 19px; transform: translate(-50%, -50%); z-index: 100; /* border: 2px solid red; border-radius: 2px; */ } /* 原十字准星伪元素保持不变 */ .location-indicator::before, .location-indicator::after { content: ""; position: absolute; background-color: red; top: 50%; left: 50%; transform: translate(-50%, -50%); } .location-indicator::before { width: 14px; height: 2px; } .location-indicator::after { width: 2px; height: 14px; } .view-rectangle { position: absolute; border: 2px solid #00f2fe; z-index: 99; pointer-events: none; box-shadow: 0 0 20px rgba(0, 242, 254, 0.7); } /* 相机聚焦样式 - 四个角折角 */ .corner { position: absolute; width: 25px; height: 25px; z-index: 100; pointer-events: none; } .corner::before, .corner::after { content: ""; position: absolute; background: #00f2fe; box-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .corner-top-left { top: -2px; left: -2px; } .corner-top-left::before { top: 0; left: 0; width: 15px; height: 3px; } .corner-top-left::after { top: 0; left: 0; width: 3px; height: 15px; } .corner-top-right { top: -2px; right: -2px; } .corner-top-right::before { top: 0; right: 0; width: 15px; height: 3px; } .corner-top-right::after { top: 0; right: 0; width: 3px; height: 15px; } .corner-bottom-left { bottom: -2px; left: -2px; } .corner-bottom-left::before { bottom: 0; left: 0; width: 15px; height: 3px; } .corner-bottom-left::after { bottom: 0; left: 0; width: 3px; height: 15px; } .corner-bottom-right { bottom: -2px; right: -2px; } .corner-bottom-right::before { bottom: 0; right: 0; width: 15px; height: 3px; } .corner-bottom-right::after { bottom: 0; right: 0; width: 3px; height: 15px; } .camera-icon { position: absolute; top: 10px; right: 10px; color: #00f2fe; font-size: 24px; z-index: 100; text-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .focus-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 98; border: 2px solid rgba(0, 242, 254, 0.2); border-radius: 5px; box-shadow: inset 0 0 30px rgba(0, 242, 254, 0.1); } .pulse { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 10px; height: 10px; border-radius: 50%; background: rgba(0, 242, 254, 0.7); box-shadow: 0 0 0 0 rgba(0, 242, 254, 0.7); animation: pulse 2s infinite; } ::v-deep.cesium-viewer-toolbar { display: none; } </style> 检查live-map代码,其中为什么这个红色十字定位位置会不对,会呆在左上方,虽然移动后会变正确,但是刷新页面显示时就是有时候会不对,会影响观感,你阅读live-map.vue代码中鹰眼地图部分代码,尝试寻找原因修改,然后尽可能不更改其他任何无关代码,减少对代码的修改

<template> <echarts-one-component :obj="content" v-show="isShow" :ref="'child'+index1" v-if="content.type === '1'" /> <echarts-two-component v-if="content.type === '2'" :obj="content" v-show="isShow" :ref="'child'+index1" /> </template> <script> const echarts = require('echarts') import EchartsOneComponent from './echartsOneComponent' import EchartsTwoComponent from './echartsTwoComponent' import TableComponent from './tableComponent' import JSZip from 'jszip' import { downLoadPPT, fastApi } from '@/api/ai/aiDashboard' export default { components: { EchartsOneComponent, TableComponent, EchartsTwoComponent }, name: 'dashboardOneComponent', props: { obj: { type: Object } }, watch: { obj: { handler(newVal) { //只有当DONE后才渲染 if (newVal.finish === true) { const refObj = this.$refs this.isShow = true for (var i = 0; i < this.obj.content.length; i++) { if (refObj['child' + i]) { refObj['child' + i][0].init(this.obj.content[i]) } if (refObj['table' + i]) { refObj['table' + i][0].init(this.obj.content[i]) } } this.$emit('scrollDown') } }, immediate: true, deep: true } }, data() { return { isShow: false, isDownload:false } }, created() { }, mounted() { }, methods: { //生成PPT和ZIP暂时注释 async download() { if (this.isDownload){ return } this.isDownload = true const hide = this.$message.loading('正在下载,大概需要1-2分钟,请耐心等待', 0) try { const zip = new JSZip() const charts = [] // 所有图表实例 const analyse = [] // 所有分析数据 const refObj = this.$refs for (var i = 0; i < this.obj.content.length; i++) { if (this.obj.content[i].echatsName === '新能源') { this.obj.content[i].echatsName = '私家车新能源' } else if (this.obj.content[i].echatsName === '燃油车') { this.obj.content[i].echatsName = '私家车燃油车' } else if (this.obj.content[i].echatsName === '拓展') { this.obj.content[i].echatsName = '业务品质监控扩展(计划)' } if (refObj['child' + i]) { charts.push( { obj:refObj['child' + i][0].getEcharts(), name: refObj['child' + i][0].getEchartTitle() + '@' + this.obj.content[i].echatsName, type: 'echats' }) analyse.push( { question: JSON.stringify(this.obj.content[i]), name: refObj['child' + i][0].getEchartTitle(), direction: this.obj.content[i].type === '1' ? '{direction:若是对分值的分析,则主要分析1分和10分数据,以及1分和10分各个月份的变化。若是对违章次数的分析,则主要分析0违章和-99+空违章占比趋势以及各个月份的变化。}' : '{direction:分析LR和PLR之间的趋势,若是二者差异大,则指出二者差异大的区间,若是二者整体相似,则输出LR和PLR基本靠近,符合预期。}' }) }else{ const tableImage = refObj['table' + i][0].exportImage() charts.push({ obj:tableImage, name: refObj['table' + i][0].getTableTitle() + '@' + this.obj.content[i].echatsName, type: 'table' }) analyse.push({ question: JSON.stringify(this.obj.content[i]), name: refObj['table' + i][0].getTableTitle(), direction: '{direction:若是对高档位(高违章和高占比等)的分析,则不对北京机构进行分析输出,需要分析出占比明显偏高和偏少的数据,以及整体的变化趋势。若是对未开启占比进行分析,则要看一个时间趋势,机构之间没有相对性,看月份的变化,比如有没有新开的、新关的、逐月增加或减少的。}' }) } } const formData = new FormData() // 对接工作流 await fastApi(analyse).then(res => { if (res.state === 'success') { // 获取分析数据数组 const analyseData = res.data // 转换为json文件,交给后端进行解析,用于整合进ppt const rawJsonBlob = new Blob([JSON.stringify(analyseData)], { type: 'application/json' }) console.log(rawJsonBlob) formData.append('analysisData', rawJsonBlob, 'analysis_data.json') for (let i = 0; i < charts.length; i++) { if (charts[i].type === 'table') { zip.file(charts[i].name + '.png', charts[i].obj.split(',')[1], { base64: true }) } else { const imgData = charts[i].obj.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' }) //formData.append('file[]', imgData) zip.file(charts[i].name + '.png', imgData.split(',')[1], { base64: true }) } } zip.generateAsync({ type: 'blob' }).then(content => { formData.append('file', content, 'images.zip') downLoadPPT(formData) .then(res => { const blob = new Blob([res], { type: res.type }) const downloadElement = document.createElement('a') const href = window.URL.createObjectURL(blob) //创建下载的链接 downloadElement.href = href downloadElement.download = 'test.pptx' document.body.appendChild(downloadElement) downloadElement.click() //点击下载 document.body.removeChild(downloadElement) //下载完成移除元素 window.URL.revokeObjectURL(href) //释放blob对象 this.$message.success('下载成功') }).catch((error) => { }) }) } else { console.error('请求失败:', res.message) this.errorMessage = '分析请求失败,请重试' } }).catch(error => { console.error('网络错误:', error) this.errorMessage = '网络请求异常,请检查连接' }) // setTimeout(() => { // downLoadPPT(formData) // .then(res => { // const blob = new Blob([res], {type: res.type}) // const downloadElement = document.createElement('a') // const href = window.URL.createObjectURL(blob) //创建下载的链接 // downloadElement.href = href // downloadElement.download = 'test.pptx' // document.body.appendChild(downloadElement) // downloadElement.click() //点击下载 // document.body.removeChild(downloadElement) //下载完成移除元素 // window.URL.revokeObjectURL(href) //释放blob对象 // this.$message.success('下载成功') // }).catch((error) => { // }) // }, 15000) } catch (err) { this.$message.warn('下载PPT异常请稍后再试') } finally { this.isDownload = false this.$message.destroy(hide) } } /*download() { const zip = new JSZip() const charts = [] // 所有图表实例 const refObj = this.$refs for (var i = 0; i < this.obj.content.length; i++) { if (refObj['child' + i]) { charts.push(refObj['child' + i][0].getEcharts()) } } const pdf = new jsPDF('landscape', 'pt', 'a4') // 横向A4纸‌:ml-citation{ref="2,7" data="citationList"} const pageHeight = pdf.internal.pageSize.getHeight() let currentY = 0 // 当前绘制高度 for (let i = 0; i < charts.length; i++) { const imgData = charts[i].getDataURL({type: 'png', pixelRatio: 2,backgroundColor: '#fff'}) const imgProps = pdf.getImageProperties(imgData) const imgWidth = pdf.internal.pageSize.getWidth() const imgHeight = (imgProps.height * imgWidth) / imgProps.width // 分页判断 if (currentY + imgHeight > pageHeight) { pdf.addPage() currentY = 0 } pdf.addImage(imgData, 'PNG', 0, currentY, imgWidth, imgHeight) currentY += imgHeight + 20 //zip.file(chart${i + 1}.png, imgData.split(',')[1],{ base64: true }) } pdf.save('chart.pdf') //pdf.save('chart.pdf') /!*zip.generateAsync({type: 'blob'}).then(content => { saveAs(content, 'charts.zip') })*!/ }*/ } } </script> <style scoped> .chartsList{ display: flex; justify-content: center; text-align: center; margin-bottom: 25px; } </style> 如何使用虚拟滚动技术实现图表渲染

import type { DrawStyleSecondCategoryGridRef } from '@aiComponents/DrawStyleCategoryGrid/SecondGrid.tsx'; import type { UseDrawStyleCategoryConfigProps } from '@aiComponents/DrawStyleCategoryGrid/useDrawStyleCategoryConfig.ts'; import type { FeatureLeaferFrameLayoutCommonProps, FeatureLeaferFrameLayoutRef, } from '@aiComponents/FeatureLeaferFrameLayout/types.ts'; import type { AiToolGenerateModalRef } from '@aiComponents/GenerateModal'; import type { QueryTagItem } from '@aiComponents/QueryTagImagesSelector/types.ts'; import type { SemanticsSegmentImageControlRef, SemanticsSegmentImageType, } from '@aiComponents/SemanticsSegmentImage/types.ts'; import type { StyleConfigTextAreaRef } from '@aiComponents/StyleConfigTextArea'; import type { UploadCropperModalProps, UploadCropperModalRef, } from '@aiComponents/UploadCropperModal'; import type { AiVipEquityLimitModalRef } from '@aiComponents/VipEquityLimitModal'; import type { ToolEquityCountWrapRef } from '@components/ToolEquityCountWrap'; import type { RequestDecorationDesignParams } from '@services/comfyUI.ts'; import type { RequestJzxzDrawImageParams, RequestTextToImageGenerateParams, } from '@services/draw.ts'; import { aiAddEventTrack, combineSentenceBySplitSymbol, getRandomNum, getStyleDataIndexByRefs, } from '@utils/ai.ts'; import { userAuth } from '@utils/auth.ts'; import { computeContentObjectFit, delayFun } from '@utils/common.ts'; import { decodeImageUrl } from '@utils/string.ts'; import { microGetHuoshanUploadImageUrl } from '@utils/upload.ts'; import type { UploadImageResponse } from '@wander/base-utils'; import { fileToBase64 } from '@wander/base-utils'; import { base64ToBlob, blobToFile, getImageSizeInfo, isMobile } from '@wander/base-utils'; import { useMount } from 'ahooks'; import { message } from 'antd'; import type { AxiosRequestConfig } from 'axios'; import { t } from 'i18next'; import PubSub from 'pubsub-js'; import type { ReactNode } from 'react'; import { useEffect, useRef, useState } from 'react'; import { useLocation, useSearchParams } from 'react-router-dom'; import type { CustomUploadProps } from '@/components/CustomUpload'; import type { UploadBtnImageResponse } from '@/components/HuabanUploadBtn'; import type { ImageSelectorModalProps, ImageSelectorModalRef, } from '@/components/ImageSelectorModal'; import type { ThemeSegmentedProps } from '@/components/ThemeSegmented'; import type { UploadImageType } from '@/components/UploadImageCard'; import { imgToImgSizeConfig, JZXZ_DRAW_INIT_NEGATIVE_PROMPTS } from '@/config/aiTool'; import useGetConfigValue from '@/hooks/useGetConfigValue.ts'; import { DECORATION_DESIGN_CATEGORY_SELECT_CONFIG_KEY, DECORATION_DESIGN_CHARTLET_CATEGORY_KEY, DECORATION_DESIGN_COMFY_UI_SELECT_CATEGORY_CONFIG_KEY, decorationDesignSpeedModePrompts, decorationDesignToolKey, TRACK_DECORATION_DESIGN_TOOL_NAME, } from '@/pages/AiTools/DecorationDesign/config.tsx'; import { commonDrawControlNetModelMap } from '@/pages/AiTools/Draw/configs.ts'; import { useToolRouterStore } from '@/pages/AiTools/Router/store/createStore.ts'; import type { RequestDescribeRetouchingParams } from '@/services/aiTool'; import { requestDescribeRetouching } from '@/services/aiTool'; import { useStore } from '@/store/createStore.ts'; import type { AiToolProcessSuccessResponse, ControlNetEditorImageInfo } from '@/types/ai'; import type { CandidateItem, DrawSizeType, DrawStyleConfigItem, GeneratePriorityType, LoraItem, } from '@/types/draw.ts'; import type { ChartletCategoryItem, ChartletImageItem } from '@/types/image'; import { imageDataToBase64 } from '@/utils/image'; export type VersionValueType = 'STANDARD' | 'EXTREME_SPEED' | 'ADVANCED'; // 请求之间的间隔时长 const REQUEST_DELAY_DURATION = 2000; // 默认重试次数 const DEFAULT_RETRY_COUNT = 2; type VersionMobileValue = any; export type VersionOptionItem = { label: ReactNode; value: VersionMobileValue; disabled?: boolean; }; export type CategoriesItem = { key: string; // 风格名称 label: string; // 风格展示图片 thumbUrl: string; // 提示词 promptsList: CandidateItem[]; // 大模型名称 modelName: string; // 小模型 loraParamses?: LoraItem[]; }; export type DecorationDesignStyleItem = { alias: string; key: string; categories: CategoriesItem[]; }; type PromptConfig = { key: string; list: CandidateItem[]; }; export const useAiDecorationDesign = () => { const location = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); const addResultImage = useToolRouterStore((state) => state.addResultImage); // 产品类型 const productType = useStore((state) => state.productInfo.productType); // 不限制权益 const noLimitFrequency = userAuth(['ORGANIZATION_VIP']); // 描述词获取 const styleConfigTextAreaRef = useRef<StyleConfigTextAreaRef>(null); // 底图图库 const imageSelectorModalRef = useRef<ImageSelectorModalRef>(null); // 生成弹窗 const generateModalRef = useRef<AiToolGenerateModalRef<RequestJzxzDrawImageParams>>(null); const drawStyleCategoryRef = useRef<DrawStyleSecondCategoryGridRef>(null); const toolEquityCountWrapRef = useRef<ToolEquityCountWrapRef>(null); // 画板Ref const featureLeaferFrameLayoutRef = useRef<FeatureLeaferFrameLayoutRef>(null); const semanticsSegmentImageControlRef = useRef<SemanticsSegmentImageControlRef>(null); // 刷新次数 const refreshToolEquityUserUseInfo = useStore((state) => state.refreshToolEquityUserUseInfo); // 创作模式优先级 const [generatePriorityType, setGeneratePriorityType] = useState<GeneratePriorityType>('CONTROLNET'); // 会员权益弹窗 const DecorationDesignipLimitModalRef = useRef<AiVipEquityLimitModalRef>(null); // 相似度 const [weight, setWeight] = useState(1); // 最终生成图尺寸大小 const [sizeType, setSizeType] = useState<DrawSizeType>('LARGE'); // 极速版-生成失败的信息 const [errorMsg, setErrorMsg] = useState(''); // 创作弹框-极速版 const generateSpeedModalRef = useRef<AiToolGenerateModalRef>(null); // comfyUI 创作弹框-高级版 const comfyUiGenerateModalRef = useRef<AiToolGenerateModalRef<RequestDecorationDesignParams>>(null); // 风格获取-高级版 const drawComfyUiStyleCategoryRef = useRef<DrawStyleSecondCategoryGridRef>(null); // 风格列表 const [comfyUiStyleList, setComfyUiStyleList] = useState<DecorationDesignStyleItem[]>([]); // 高级版-风格参考图信息 const [styleReferComfyImageInfo, setStyleReferComfyImageInfo] = useState<UploadImageResponse | null>(null); // 风格参考图图库 const comfySourceImageSelectorModalRef = useRef<ImageSelectorModalRef>(null); // comfyUI-XL版本参考图风格符合度 const [comfyStyleReferWeight, setComfyStyleReferWeight] = useState(0.8); // 上传裁剪弹窗 const uploadCropperModalRef = useRef<UploadCropperModalRef>(null); // 裁剪弹窗open const [cropperModalOpen, setCropperModalOpen] = useState(false); // 选中风格索引 const [currentStyleIndex, setCurrentStyleIndex] = useState(0); // 高级版-一级类目状态获取 const [activeAlias, setActiveAlias] = useState<string>(comfyUiStyleList[0]?.alias || ''); // 高级版-获取当前激活的二级类目 const activeCategories = comfyUiStyleList.find((item) => item.alias === activeAlias)?.categories || []; // 版本切换状态 const [versionValue, setVersionValue] = useState<VersionValueType>(() => { return userAuth(['USER', 'COMMON_VIP', 'TOURIST']) ? 'STANDARD' : 'EXTREME_SPEED'; }); // 标准版-该工具支持选择的类目组 const categoryRefsGroup: string[][] = useGetConfigValue({ productKeys: { JZXZ: DECORATION_DESIGN_CATEGORY_SELECT_CONFIG_KEY, JZDS: 'jzds_decoration_design_style_categories', }, }) || []; // 贴图类目列表 const chartletCategoryList = useGetConfigValue<ChartletCategoryItem[]>({ key: DECORATION_DESIGN_CHARTLET_CATEGORY_KEY }) || []; // 线稿库类目列表 const queryTagList = useGetConfigValue<QueryTagItem[]>({ key: 'jzxz_decoration_design_base_Image_query_tag_config', }) || []; // 高级版-该工具支持选择的类目组 const comfyUiCategoryRefsGroup: CategoriesItem[] = ( useGetConfigValue({ productKeys: { JZXZ: DECORATION_DESIGN_COMFY_UI_SELECT_CATEGORY_CONFIG_KEY, }, }) || [] ).filter( (category: CategoriesItem) => category.key === 'indoorHomeDecoration' || category.key === 'indoorWorkWear', ); // 高级版-描述词 const comfyUiPrompt: PromptConfig[] = ( useGetConfigValue({ productKeys: { JZXZ: 'jzxz_advance_draw_descriptor_config', }, }) || [] ).filter((p: PromptConfig) => p.key === 'indoorHomeDecoration' || p.key === 'indoorWorkWear'); console.log('comfyUiPrompt', comfyUiPrompt); // 版本切换-mobile const handleVersionMobileChange = (value: VersionValueType) => { if (userAuth(['TOURIST'])) { PubSub.publish('onLogin', true); return; } if (value !== 'STANDARD' && userAuth(['USER', 'COMMON_VIP'])) { console.log('value', value); DecorationDesignipLimitModalRef.current?.setLimitType('FOREVER_VIP'); return; } // 否则允许切换 setVersionValue(value); // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; } if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, true, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, true); } } }; // 版本切换-pc const handleVersionPcChange: ThemeSegmentedProps['onChange'] = (item) => { const value = item.value; if (userAuth(['TOURIST'])) { PubSub.publish('onLogin', true); return; } if (value !== 'STANDARD' && userAuth(['USER', 'COMMON_VIP'])) { DecorationDesignipLimitModalRef.current?.setLimitType('FOREVER_VIP'); return; } // 否则允许切换 setVersionValue(value); // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; } if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, true, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, true); } } }; // 高级版-风格类目描述词对应方法 function mapPromptsToCategories(categoryGroups: any, prompts: any) { return categoryGroups.map((categoryGroup: any) => { const promptConfig = prompts.find((prompt: any) => prompt.key === categoryGroup.key); if (!promptConfig) { return null; } return { alias: categoryGroup.alias, key: categoryGroup.key, categories: categoryGroup.categories.map((cat: any) => ({ key: cat.key, label: cat.alias, thumbUrl: cat.coverUrl, promptsList: promptConfig.list.map((item: any) => ({ placeholder: item.placeholder || '', keyword: item.keyword || '', })), modelName: cat.modelName, lora: cat.loraParamses?.[0], })), }; }); } // 高级版-设计领域与风格切换 const onComfyUiStyleIndexChange = (index: number) => { setCurrentStyleIndex(index); const currentItem = activeCategories[index]; const isUniversalCategory = activeCategories[0]; // 随机描述词 let randomPrompts: CandidateItem[] = []; if (currentItem.promptsList?.length) { const randomIndex = Math.floor(Math.random() * currentItem.promptsList.length); randomPrompts = [currentItem.promptsList[randomIndex]]; } // 更新候选配置 styleConfigTextAreaRef.current?.updateCandidateConfig(randomPrompts, true); if (isUniversalCategory) { styleConfigTextAreaRef.current?.updateCandidateConfig(currentItem.promptsList, true); } }; // 高级版-风格类目名称切换 const handleStyleAliasChange = (item: DecorationDesignStyleItem) => { // 切换一级类目时重置二级类目索引为0 setCurrentStyleIndex(0); setActiveAlias(item.alias); // 获取当前激活的二级类目列表 const currentCategories = comfyUiStyleList.find((style) => style.alias === item.alias)?.categories || []; // 确保存在有效的二级类目 if (currentCategories.length === 0) return; // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (versionValue === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } } // 合并所有子类的描述词 const allPrompts = currentCategories.flatMap((cat) => cat.promptsList); if (allPrompts.length === 0) return; // 随机选择一个描述词 const randomIndex = Math.floor(Math.random() * allPrompts.length); const randomPrompt = allPrompts[randomIndex]; // 更新到输入框 styleConfigTextAreaRef.current?.updateCandidateConfig([randomPrompt], true); onComfyUiStyleIndexChange(0); }; // 获取语意分割组件和局部绘制组件内部参考图信息并转为ControlNet所需参数 const getControlsControlNetArgs = ({ localDrawImage, semanticsSegmentImage, }: { localDrawImage: ControlNetEditorImageInfo | null; semanticsSegmentImage: SemanticsSegmentImageType | null; }) => { const controlNetArgs: Pick< RequestTextToImageGenerateParams, 'localDrawImage' | 'semanticsSegmentImage' > = {}; if (localDrawImage?.enabled) { controlNetArgs.localDrawImage = { ...localDrawImage, ...commonDrawControlNetModelMap.inpaint, enable: true, // 开始迭代步数 guidanceStart: 0, guidanceEnd: 1, // 结束迭代步数 weight: 1, }; } if (semanticsSegmentImage?.enabled) { controlNetArgs.semanticsSegmentImage = { ...semanticsSegmentImage, ...commonDrawControlNetModelMap.semanticsSegment, enable: true, // 开始迭代步数 guidanceStart: 0, guidanceEnd: 1, // 结束迭代步数 weight: 1, }; } return controlNetArgs; }; // 获取画板图片 const getFrameImage = async (sizeInfo: Pick<UploadImageResponse, 'width' | 'height'>) => { // 导出画布方法 const exportFrame = featureLeaferFrameLayoutRef.current?.exportFrame; if (!exportFrame) { return Promise.reject(); } // 导出内容 const result = await exportFrame('canvas', { size: { width: sizeInfo.width, height: sizeInfo.height, }, }); const leaferCanvas = result.data; const context = leaferCanvas.context; // 获取画板图片 const imageData = context.getImageData(0, 0, leaferCanvas.width, leaferCanvas.height); const base64 = imageDataToBase64(imageData); const file = blobToFile(base64ToBlob(base64)); const imageInfo = await microGetHuoshanUploadImageUrl(file); featureLeaferFrameLayoutRef.current?.setCompareImage(imageInfo); return imageInfo; }; // comfyUI-有风格参考图 const generateComfyStyleReferenceTask = async (uploadImage: UploadImageResponse) => { comfyUiGenerateModalRef.current?.setModalOpen(true); let imageInfo: UploadImageResponse = uploadImage; const prompt = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 当前尺寸的阈值 const thresholdNum = imgToImgSizeConfig[sizeType]; // 尺寸信息 const sizeParams = computeContentObjectFit({ containerWidth: thresholdNum, containerHeight: thresholdNum, contentWidth: uploadImage.width, contentHeight: uploadImage.height, type: 'contain', }); // 风格参考图获取 const baseImage = styleReferComfyImageInfo?.largeUrl; try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } comfyUiGenerateModalRef.current?.generateTask({ imageUrl: imageInfo.largeUrl, seed: getRandomNum(), ...sizeParams, // 描述词 prompt, // 底图符合度 baseImageWeight: weight, // 参考图片链接 styleReferImageUrl: baseImage, // 风格参考度-权重 styleReferWeight: comfyStyleReferWeight, }); } catch (e) { comfyUiGenerateModalRef.current?.setRequestStatus('GENERATE_ERROR'); } }; // comfyUI-无风格参考图 const generateComfyTask = async (uploadImage: UploadImageResponse) => { comfyUiGenerateModalRef.current?.setModalOpen(true); let imageInfo: UploadImageResponse = uploadImage; const prompt = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 当前尺寸的阈值 const thresholdNum = imgToImgSizeConfig[sizeType]; // 尺寸信息 const sizeParams = computeContentObjectFit({ containerWidth: thresholdNum, containerHeight: thresholdNum, contentWidth: uploadImage.width, contentHeight: uploadImage.height, type: 'contain', }); try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } comfyUiGenerateModalRef.current?.generateTask({ imageUrl: imageInfo.largeUrl, seed: getRandomNum(), ...sizeParams, // 描述词 prompt, // 底图符合度 baseImageWeight: weight, }); } catch (e) { comfyUiGenerateModalRef.current?.setRequestStatus('GENERATE_ERROR'); } }; // 标准版-开始创作 const onSdStart: FeatureLeaferFrameLayoutCommonProps['onStart'] = async ({ uploadImage, localDrawImage, semanticsSegmentImage, }) => { if (!noLimitFrequency && !toolEquityCountWrapRef.current?.verifyUseStatus()) { return; } if (!uploadImage) { message.warning(t('AiTools-LocalDrawImage-yES4')); return; } let imageInfo: UploadImageResponse = uploadImage; generateModalRef.current?.setModalOpen(true); try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } // 提取当前类目下下的参数 const { modelName = '', loraParamses = [] } = drawStyleCategoryRef.current?.getCategoryParams() || {}; let prompt = styleConfigTextAreaRef.current?.prompts.keyword || ''; // 启用了色块 获取色块描述 if (semanticsSegmentImage?.enabled) { // 去重描述词 const paintKeywords = [ ...new Set(semanticsSegmentImage.colorBlockList.map((item) => item.englishAlias)), ]; prompt = combineSentenceBySplitSymbol([...paintKeywords, prompt]); } // 参考图 const styleImage = styleReferComfyImageInfo; generateModalRef.current?.generateTask({ // 获取语意分割、局部重绘相关controlnet参数 ...getControlsControlNetArgs({ localDrawImage, semanticsSegmentImage }), prompt, modelName, loraParamses, negativePrompt: JZXZ_DRAW_INIT_NEGATIVE_PROMPTS, baseImage: { width: imageInfo.width, height: imageInfo.height, url: imageInfo.largeUrl || imageInfo.thumbUrl, // 开始迭代步数 guidanceStart: 0, // 结束迭代步数 guidanceEnd: 1, // 相似度 module: 'scribble_xdog', weight, model: 'control_v11p_sd15_scribble', }, ...(styleImage ? { styleReferImage: { guidanceEnd: 1, guidanceStart: 0, height: styleImage.height as number, model: 'ip-adapter_sd15_plus [32cd8f7f]', module: 'ip-adapter_clip_sd15', url: styleImage.largeUrl, weight: comfyStyleReferWeight, width: styleImage.width as number, }, } : {}), sizeType, generateMode: 'REPAINT', generatePriorityType, generateImageType: 'DECORATION_DESIGN', }); } catch (error) { message.error(t('AiTools-DecorationDesign-Operate-hook-trpaP')); } }; // comfy-高级版-开始创作 const onComfyStart: FeatureLeaferFrameLayoutCommonProps['onStart'] = async (params) => { if (!params?.uploadImage) { message.warn(t('AiTools-DetailEnhance-Operate-hook-PzPPn')); return; } if (styleReferComfyImageInfo) { await generateComfyStyleReferenceTask(params.uploadImage); } else { await generateComfyTask(params.uploadImage); } }; // 极速版 const sendRequest = async ( // 请求参数 params: RequestDescribeRetouchingParams, // 重试次数 retryCount: number, // 请求头配置 用于减次数 config?: AxiosRequestConfig, ): Promise<UploadImageType> => { // 没有重试次数了 直接抛出异常 if (retryCount <= 0) { throw { errorCode: 'MAX_RETRY_TIMES', errorMsg: '生成图片异常' }; } const response = await requestDescribeRetouching(params, config).catch((e) => { console.error('请求失败:', e); return null; }); // 本次生成没有结果 重试 if (!response) { await delayFun(REQUEST_DELAY_DURATION); return sendRequest(params, retryCount - 1, config); } // 拿到了结果 正常返回 if (response.imageList[0]) { return response.imageList[0]; } // 拿到了结果但是没有图片返回且有文案提示 if (!response.imageList.length && response.text) { setErrorMsg(response.text.replace(/\*/g, '')); // 特殊错误,不重试 throw { errorCode: 'NO_IMAGE', errorMsg: response.text }; } // 其他情况重试 await delayFun(REQUEST_DELAY_DURATION); return sendRequest(params, retryCount - 1, config); }; const batchRequest = async (params: RequestDescribeRetouchingParams) => { const result: UploadImageType[] = []; const data = await sendRequest(params, DEFAULT_RETRY_COUNT); result.push(data); return result; }; const onSpeedModeGenerateSuccess = (list: UploadImageType[]) => { const result = list[0].largeUrl; featureLeaferFrameLayoutRef.current?.setResult(result); // 添加进快捷使用 addResultImage({ ...list[0], url: list[0].largeUrl }); aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, 'createSuccess'); refreshToolEquityUserUseInfo(decorationDesignToolKey); }; // 开始创作-极速版 const onSpeedModeStart: FeatureLeaferFrameLayoutCommonProps['onStart'] = async ({ uploadImage, }) => { if (!uploadImage) { message.warning({ content: '请先上传图片', key: 'emptyImage' }); return; } setErrorMsg(''); generateSpeedModalRef.current?.setModalOpen(true); generateSpeedModalRef.current?.setRequestStatus('EXECUTING'); // 底图 const uploadImageInfo = uploadImage.largeUrl; // 参考图 const baseImage = styleReferComfyImageInfo?.largeUrl; const imageUrlList: string[] = []; imageUrlList.push(uploadImageInfo); if (baseImage) { imageUrlList.push(baseImage); } // 描述词 const prompts = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 拼接描述词-有参考图 const baseImageDescription = 图一参考图二,图片要求按${uploadImage.width}:${uploadImage.height}比例生成; const newPrompts = ${prompts},${baseImageDescription}; // 拼接描述词-没有参考图 const noBaseImageDescription = 图片要求按${uploadImage.width}:${uploadImage.height}比例生成; const promptsText = ${prompts},${noBaseImageDescription}; // 根据baseImage是否存在,设置description const description = baseImage ? newPrompts : promptsText; try { // 发起请求 const response = await batchRequest({ description: combineSentenceBySplitSymbol([description]), imageUrls: imageUrlList, }); onSpeedModeGenerateSuccess(response); generateSpeedModalRef.current?.setRequestStatus('PENDING'); generateSpeedModalRef.current?.setModalOpen(false); } catch (e) { aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, 'createError'); generateSpeedModalRef.current?.setRequestStatus('POLLING_ERROR'); } }; // 生成结果回调 const onGenerateSuccess = async (response: AiToolProcessSuccessResponse) => { try { const result = response.imageList[0]; // 刷新次数 refreshToolEquityUserUseInfo(decorationDesignToolKey); // 设置显示 featureLeaferFrameLayoutRef.current?.setResult(result); const sizeInfo = await getImageSizeInfo(result); // 添加生成记录 addResultImage({ url: result, ...sizeInfo, }); } catch (e) { console.error(e); } }; // 画板插入贴图 const onChartletItemClick = (imageInfo: ChartletImageItem) => { featureLeaferFrameLayoutRef.current?.insertPicture(imageInfo); }; // 画板插入本地上传图片 const onChartletUploadSuccess: CustomUploadProps['onUploadSuccess'] = async ({ url }) => { try { const size = await getImageSizeInfo(url); featureLeaferFrameLayoutRef.current?.insertPicture({ url, width: size.width, height: size.height, }); } catch (err) { console.error(err); message.error(t('AiTools-ImageEditorModal-hooks-mdQhK')); } }; // 创作优先级改变 const onGeneratePriorityTypeChange = (type: GeneratePriorityType) => { setGeneratePriorityType(type); setWeight(type === 'CONTROLNET' ? 1 : 1.2); }; // 标准版-类目改变回调 const onSelectRefsChange: UseDrawStyleCategoryConfigProps['onSelectRefsChange'] = ( refs, categories, ) => { if (versionValue !== 'STANDARD') { return; } drawStyleCategoryRef.current?.setSelectRefs(refs); const ids = getStyleDataIndexByRefs('id', categories, refs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids); } }; // 标准版 const onCategoryInit = (styleConfigItems: DrawStyleConfigItem[]) => { // 一级类目ref const firstCategoryRefs = categoryRefsGroup.map((item) => item[0]); // 过滤出相关的类目信息 const filterStyleConfigItems = styleConfigItems.filter((item) => firstCategoryRefs.includes(item.ref), ); drawStyleCategoryRef.current?.setStyleConfigList(filterStyleConfigItems); // 请求描述词 onSelectRefsChange(categoryRefsGroup[0], filterStyleConfigItems); }; // 当点击继续处理时 const onContinue = async (result: string) => { try { const sizeInfo = await getImageSizeInfo(result); featureLeaferFrameLayoutRef.current?.onUploadSuccess([ { ...sizeInfo, largeUrl: result, thumbUrl: result }, ]); } catch (e) { message.error(t('AiTools-DecorationDesign-Operate-hook-6ipKH')); } }; const onSelectorModalFinish: ImageSelectorModalProps['onFinish'] = async (item) => { featureLeaferFrameLayoutRef.current?.onUploadSuccess([item]); if (item.originFile) { aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, isMobile() ? 'mobileUpload' : 'pcUpload'); } }; // 风格底图选择弹窗回调 const onDecorationDesignReferImageModalFinish: ImageSelectorModalProps['onFinish'] = async ({ largeUrl, thumbUrl, width, height, originFile, }) => { setStyleReferComfyImageInfo({ width, height, largeUrl, thumbUrl, }); let base64; // 原图有originFile直接转base64 if (originFile) { try { base64 = await fileToBase64(originFile); } catch (e) { console.error(e); } } // base64转换成功 打开裁剪弹框 失败则默认为原图 setCropperModalOpen(true); uploadCropperModalRef.current?.setCropperImageUrl(base64 || largeUrl); }; // 风格底图裁剪完成图片弹框回调 const onStyleReferImageCropperSuccess: UploadCropperModalProps['onCropperSuccess'] = async ( obj, ) => { setStyleReferComfyImageInfo(obj); setCropperModalOpen(false); }; const onStyleReferImageCropperError = () => { setCropperModalOpen(false); message.error(t('AiTools-TransferSmudge-Operate-hook-MiGY')); }; // 开始创作版本判断 const generateTask: FeatureLeaferFrameLayoutCommonProps['onStart'] = (params) => { if (userAuth(['TOURIST'])) { PubSub.publish('onLogin', true); return; } if (!noLimitFrequency && !toolEquityCountWrapRef.current?.verifyUseStatus()) { return; } if (versionValue === 'STANDARD') { onSdStart(params); return; } if (versionValue === 'EXTREME_SPEED') { onSpeedModeStart(params); return; } if (versionValue === 'ADVANCED') { onComfyStart(params); } }; useMount(() => { // 获取极速版描述词 styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); // 获取高级版风格类目 const comfyUiStyle = mapPromptsToCategories(comfyUiCategoryRefsGroup, comfyUiPrompt); setComfyUiStyleList(comfyUiStyle); if (comfyUiStyle.length > 0) { setActiveAlias(comfyUiStyle[0].alias); setCurrentStyleIndex(0); } const image: UploadBtnImageResponse = location.state?.image; if (image) { featureLeaferFrameLayoutRef.current?.onUploadSuccess([image]); return; } // 地址栏参数传递 const url = decodeImageUrl(searchParams.get('url') || ''); const width = searchParams.get('width'); const height = searchParams.get('height'); if (url && width && height) { featureLeaferFrameLayoutRef.current?.onUploadSuccess([ { largeUrl: url, thumbUrl: url, width: Number(width), height: Number(height), }, ]); setSearchParams({}); } }); // 室内装修初始设置选中室内色块 useEffect(() => { semanticsSegmentImageControlRef.current?.setSelectCategoryIndex(1); }, []); return { noLimitFrequency, styleConfigTextAreaRef, imageSelectorModalRef, generateModalRef, featureLeaferFrameLayoutRef, drawStyleCategoryRef, toolEquityCountWrapRef, semanticsSegmentImageControlRef, generatePriorityType, categoryRefsGroup, weight, setWeight, sizeType, setSizeType, weightSliderMaxSize: generatePriorityType === 'PROMPTS' ? 2 : 1.3, chartletCategoryList, queryTagList, onChartletItemClick, onCategoryInit, onChartletUploadSuccess, onGenerateSuccess, onContinue, onGeneratePriorityTypeChange, onSelectRefsChange, onSelectorModalFinish, versionValue, handleVersionPcChange, productType, comfyUiStyleList, setActiveAlias, activeAlias, activeCategories, onComfyUiStyleIndexChange, currentStyleIndex, comfyUiGenerateModalRef, drawComfyUiStyleCategoryRef, styleReferComfyImageInfo, setStyleReferComfyImageInfo, comfySourceImageSelectorModalRef, comfyStyleReferWeight, setComfyStyleReferWeight, generateTask, onDecorationDesignReferImageModalFinish, uploadCropperModalRef, cropperModalOpen, setCropperModalOpen, onStyleReferImageCropperSuccess, onStyleReferImageCropperError, generateSpeedModalRef, errorMsg, DecorationDesignipLimitModalRef, handleVersionMobileChange, handleStyleAliasChange, }; }; import ToolBenefitOperator from '@aiComponents/BenefitOperator'; import AiToolBlockItem from '@aiComponents/BlockItem'; import { AiToolImageCoverCard } from '@aiComponents/CardItems'; import ChartletUploadBtn from '@aiComponents/ChartletUploadBtn'; import DrawStyleSecondCategoryGrid from '@aiComponents/DrawStyleCategoryGrid/SecondGrid.tsx'; import FeatureLeaferFrameLayoutPc from '@aiComponents/FeatureLeaferFrameLayout/pc'; import ImageSticker from '@aiComponents/ImageSticker'; import { colorBlockCategoryList } from '@aiComponents/SemanticsSegmentEditor/config.tsx'; import DrawStyleConfigTextArea from '@aiComponents/StyleConfigTextArea'; import UploadCropperModal from '@aiComponents/UploadCropperModal'; import AiVipEquityLimitModal from '@aiComponents/VipEquityLimitModal'; import ProductAuth from '@components/ProductAuth'; import ThemeInputNumber from '@components/ThemeInputNumber'; import ThemeSegmented from '@components/ThemeSegmented'; import ThemeSlider from '@components/ThemeSlider'; import ToolEquityCountWrap from '@components/ToolEquityCountWrap'; import UploadImageBox from '@components/UploadImageCard/UploadImageBox.tsx'; import { requestDecorationDesign } from '@services/comfyUI.ts'; import type { RequestJzxzDrawImageParams } from '@services/draw.ts'; import { requestJzxzDrawImage } from '@services/draw.ts'; import { userAuth } from '@utils/auth.ts'; import classNames from 'classnames/bind'; import type { CSSProperties } from 'react'; import { type FC } from 'react'; import { useTranslation } from 'react-i18next'; import ImageSelectorModal from '@/components/ImageSelectorModal/index.tsx'; import ThemeButton from '@/components/ThemeButton'; import { enlargeDownloadOptions } from '@/config/aiTool.ts'; import { generatePriorityList } from '@/config/aiTool.ts'; import { advanceDrawSizeTypeList } from '@/pages/AiTools/AdvanceDraw/config.ts'; import DrawSizeControls from '@/pages/AiTools/components/DrawSizeControls'; import AiToolGenerateModal from '@/pages/AiTools/components/GenerateModal'; import AiToolGridList from '@/pages/AiTools/components/GridList/index.tsx'; import UploadImagePreviewCard from '@/pages/AiTools/components/PreviewCard/index.tsx'; import type { GeneratePriorityType } from '@/types/draw.ts'; import type { DrawSizeType } from '@/types/draw.ts'; import { DECORATION_DESIGN_TOOL_NAME_KEY, decorationBrushTypeList, decorationDesignSpeedupKey, decorationDesignToolKey, decorationDesignVideoUrl, decorationOperationTypeList, TRACK_DECORATION_DESIGN_TOOL_NAME, } from '../../config'; import { useAiDecorationDesign } from '../hook.ts'; import styles from './index.module.less'; const cx = classNames.bind(styles); // 室内设计 const DecorationDesignPc: FC = () => { const { t } = useTranslation(); const { noLimitFrequency, styleConfigTextAreaRef, imageSelectorModalRef, generateModalRef, featureLeaferFrameLayoutRef, drawStyleCategoryRef, toolEquityCountWrapRef, semanticsSegmentImageControlRef, generatePriorityType, categoryRefsGroup, weight, setWeight, sizeType, setSizeType, weightSliderMaxSize, chartletCategoryList, queryTagList, onChartletItemClick, onCategoryInit, onChartletUploadSuccess, onGenerateSuccess, onContinue, onGeneratePriorityTypeChange, onSelectRefsChange, onSelectorModalFinish, versionValue, handleVersionPcChange, productType, comfyUiStyleList, activeAlias, activeCategories, onComfyUiStyleIndexChange, currentStyleIndex, comfyUiGenerateModalRef, styleReferComfyImageInfo, setStyleReferComfyImageInfo, comfySourceImageSelectorModalRef, comfyStyleReferWeight, setComfyStyleReferWeight, onDecorationDesignReferImageModalFinish, uploadCropperModalRef, cropperModalOpen, setCropperModalOpen, onStyleReferImageCropperSuccess, onStyleReferImageCropperError, generateTask, generateSpeedModalRef, errorMsg, DecorationDesignipLimitModalRef, handleStyleAliasChange, } = useAiDecorationDesign(); const contentBottomRender = ( <> <DrawStyleConfigTextArea ref={styleConfigTextAreaRef} textareaConfig={{ placeholder: t('AiTools-DecorationDesign-Operate-pc-PNiN'), }} blockItem={{ title: t('AiTools-Redraw-Operate-pc-3mzP'), size: 'large', iconType: null, }} /> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-3JRD')} size="large" hidden={versionValue !== 'STANDARD'} > <FeatureLeaferFrameLayoutPc.LocalDraw toolKey={decorationDesignToolKey} controlNetFeatureCard={{ style: { '--main-title-font-weight': 500 } as CSSProperties, }} /> <FeatureLeaferFrameLayoutPc.SemanticsSegment toolKey={decorationDesignToolKey} ref={semanticsSegmentImageControlRef} editorConfig={{ colorBlockCategoryList, }} controlNetFeatureCard={{ style: { '--main-title-font-weight': 500, marginBottom: 0, } as CSSProperties, }} /> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-twQz')} size="large" hidden={versionValue === 'EXTREME_SPEED'} > {!!categoryRefsGroup.length && ( <DrawStyleSecondCategoryGrid getScrollContainer={() => document.getElementById('rightOperatorContent')} scrollOffsetTop={200} defaultSelectRefs={categoryRefsGroup[0]} ref={drawStyleCategoryRef} onInit={onCategoryInit} hidden={versionValue !== 'STANDARD'} onSelectRefsChange={onSelectRefsChange} /> )} {comfyUiStyleList.map((item) => ( { handleStyleAliasChange(item); }} > {item.alias} ))} <AiToolGridList hidden={versionValue !== 'ADVANCED'} list={activeCategories || []} columns={4} renderItem={(item, index) => ( <AiToolImageCoverCard key={item.key} label={item.label} coverUrl={item.thumbUrl} active={currentStyleIndex === index} onClick={() => { onComfyUiStyleIndexChange(index); }} /> )} /> </AiToolBlockItem> {/* 风格参考图 */} <AiToolBlockItem title={t('AiTools-Draw-ControlNet-wn4Hz')} size="large"> {/* 未上传前展示上传按钮 */} <UploadImageBox imageStyle={{ width: 75 }} description="" customUploadBtn={() => ( { comfySourceImageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }} > {t('AiTools-ControlNet-UploadBtn-5chr')} )} /> <UploadImagePreviewCard url={styleReferComfyImageInfo?.largeUrl} actions={[ { iconType: 'micro-icon-edit', onClick: () => { comfySourceImageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }, }, { iconType: 'micro-icon-trash-can', onClick: () => { setStyleReferComfyImageInfo(null); }, }, ]} /> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-MultiAnglesDraw-Operate-StyleControlNetImageCard-Card-CbB7')} size="large" hidden={!styleReferComfyImageInfo} extra={ <ThemeInputNumber value={comfyStyleReferWeight} min={0} step={0.1} max={1} className={cx('input_number')} onChange={(value) => { if (value) { setComfyStyleReferWeight(value); } }} /> } > <ThemeSlider className={cx('slider')} value={comfyStyleReferWeight} min={0} step={0.1} max={1} onChange={(value) => setComfyStyleReferWeight(value)} /> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-chDk')} size="large" hidden={versionValue === 'EXTREME_SPEED'} extra={ <ChartletUploadBtn onUploadSuccess={onChartletUploadSuccess}> {t('AiTools-ImageSelectorModal-b6NR')} </ChartletUploadBtn> } > {!!chartletCategoryList.length && ( <ImageSticker onItemClick={onChartletItemClick} cateList={chartletCategoryList} /> )} </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-C7bC')} size="large" hidden={versionValue === 'EXTREME_SPEED'} extra={ versionValue === 'STANDARD' && ( <ThemeSegmented size="small" value={generatePriorityType} options={generatePriorityList.map((item) => ({ label: t(item.labelKey), value: item.value, }))} onChange={(item) => onGeneratePriorityTypeChange(item.value as GeneratePriorityType)} /> ) } > <ThemeSlider className={cx('slider')} value={weight} dots={false} onChange={setWeight} step={0.1} min={0} max={weightSliderMaxSize} /> </AiToolBlockItem> <AiToolBlockItem hidden={versionValue === 'EXTREME_SPEED'} title={t('AiTools-DetailEnhance-Operate-pc-DwnF')} size="large" > <DrawSizeControls value={sizeType} list={advanceDrawSizeTypeList} onChange={(item: DrawSizeType) => { setSizeType(item); }} /> </AiToolBlockItem> </> ); const contentTopRender = ( <> <AiToolBlockItem title={t('AiTools-InspirationDraw-Operate-pc-babel')} size="large"> <ThemeSegmented size="large" className={cx('version_grid')} options={[ { label: ( {t('AiTools-PlanColorizer-Operate-pc-Fess')} {t('Pro-vip-prefix')} ), value: 'ADVANCED', }, { label: ( {t('AiTools-PlanColorizer-Operate-pc-Jsonb')} {t('Pro-vip-prefix')} ), value: 'EXTREME_SPEED', }, { label: t('AiTools-PlanColorizer-Operate-pc-bess'), value: 'STANDARD', }, ]} value={versionValue} onChange={handleVersionPcChange} /> </AiToolBlockItem> </> ); return ( <> <FeatureLeaferFrameLayoutPc ref={featureLeaferFrameLayoutRef} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} onStart={generateTask} download={{ enlargeDownloadOptions, }} rightHeader={{ header: { title: t(DECORATION_DESIGN_TOOL_NAME_KEY), videoUrl: decorationDesignVideoUrl, }, }} content={{ top: contentTopRender, bottom: contentBottomRender, }} imageAddLayer="frame" uploadCard={{ customUploadBtn: (className) => ( { imageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }} > {t('HuabanUploadBtn-rWrt')} ), }} customUploadBtn={ <ThemeButton size="large" ghost className={cx('custom_upload_btn')} onClick={() => { imageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }} > {t('AiTools-LeaferFrameLayout-pc-bHEM')} </ThemeButton> } editTool={ versionValue !== 'EXTREME_SPEED' && { title: t('AiTools-ImageEditorModal-pc-NP8N'), operationTypeList: decorationOperationTypeList, brushTypeList: decorationBrushTypeList, } } modifyTool={ versionValue !== 'EXTREME_SPEED' && { title: t('AiTools-ImageEditorModal-pc-Q4Zr'), enableSmearRepair: true, } } onContinue={onContinue} leaferFrame={{ defaultBrushType: 'LINE', defaultBrushSize: 3, defaultBrushColorRgb: [0, 0, 0], opacityConfig: { paintingWritGroup: 1, }, }} footerExtra={ <ToolEquityCountWrap ref={toolEquityCountWrapRef} toolKey={decorationDesignToolKey} showToolName={t(DECORATION_DESIGN_TOOL_NAME_KEY)} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} disabled={noLimitFrequency} > <ToolBenefitOperator style={{ marginTop: 6 }} hidden={noLimitFrequency || userAuth(['TOURIST'])} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} showToolEquityInfo={() => { toolEquityCountWrapRef.current?.showToolEquityInfo(); }} showToolCountUsageIntro={() => { toolEquityCountWrapRef.current?.showToolCountUsageIntro(); }} /> </ToolEquityCountWrap> } /> {/* 旧房改造进度- 标准版 */} <AiToolGenerateModal<RequestJzxzDrawImageParams> customerRef={generateModalRef} toolKey={decorationDesignToolKey} speedupKey={decorationDesignSpeedupKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} pollingConfig={{ processingType: 'DECORATION_DESIGN', imageDetectMode: 'NONE', }} request={requestJzxzDrawImage} onGenerateSuccess={onGenerateSuccess} /> {/* 旧房改造进度-高级版 */} <AiToolGenerateModal customerRef={comfyUiGenerateModalRef} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} speedupKey={decorationDesignSpeedupKey} pollingConfig={{ processingType: 'COMFYUI_COMPOSE', imageDetectMode: 'NONE', }} request={requestDecorationDesign} onGenerateSuccess={onGenerateSuccess} renderStatusSection={{ EXECUTING: { title: '室内装修改造中...', }, }} /> {/* 极速模式 */} <AiToolGenerateModal customerRef={generateSpeedModalRef} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} customerRequest={() => {}} renderStatusSection={{ EXECUTING: { title: '室内装修改造中...', }, POLLING_ERROR: { title: t('AiTools-GenerateModal-YdY7'), extra: errorMsg && {errorMsg}, }, }} /> {/* 底图图库 */} <ImageSelectorModal ref={imageSelectorModalRef} queryTagImages={{ modalTitle: t('AiTools-DecorationDesign-Operate-pc-xkdY'), introduce: t('AiTools-DecorationDesign-Operate-pc-f4eJ'), categoryList: queryTagList, iconClassName: cx('image_selector_cover'), }} onFinish={onSelectorModalFinish} /> {/* 上传风格参考图图库 */} <ImageSelectorModal ref={comfySourceImageSelectorModalRef} queryTagImages={{ introduce: t('AiTools-DecorationDesign-Operate-pc-xkdY'), modalTitle: t('AiTools-DecorationDesign-Operate-pc-f4eJ'), categoryList: queryTagList, iconClassName: cx('image_selector_cover'), }} onFinish={onDecorationDesignReferImageModalFinish} /> {/* 裁剪弹窗 */} <UploadCropperModal ref={uploadCropperModalRef} open={cropperModalOpen} modalTitle={ <> {t('AiTools-ImageEditorModal-pc-EKx2')} {t('AiTools-TransferSmudge-Operate-pc-dZW2')} </> } maskClosable onCropperSuccess={onStyleReferImageCropperSuccess} onCropperError={onStyleReferImageCropperError} onCancel={() => { setCropperModalOpen(false); }} /> {/* 会员权益限制弹窗 */} <AiVipEquityLimitModal ref={DecorationDesignipLimitModalRef} /> </> ); }; export default DecorationDesignPc; 修改 // 高级版-风格类目名称切换 const handleStyleAliasChange = (item: DecorationDesignStyleItem) => { // 切换一级类目时重置二级类目索引为0 setCurrentStyleIndex(0); setActiveAlias(item.alias); // 获取当前激活的二级类目列表 const currentCategories = comfyUiStyleList.find((style) => style.alias === item.alias)?.categories || []; // 确保存在有效的二级类目 if (currentCategories.length === 0) return; // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (versionValue === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } } // 合并所有子类的描述词 const allPrompts = currentCategories.flatMap((cat) => cat.promptsList); if (allPrompts.length === 0) return; // 随机选择一个描述词 const randomIndex = Math.floor(Math.random() * allPrompts.length); const randomPrompt = allPrompts[randomIndex]; // 更新到输入框 styleConfigTextAreaRef.current?.updateCandidateConfig([randomPrompt], true); onComfyUiStyleIndexChange(0); };实现,点击切换 {comfyUiStyleList.map((item) => ( { handleStyleAliasChange(item); }} > {item.alias} ))} 的时候随机对应key值下的描述词,同时,点击随机显示的也是对应key值下的描述词

<body> <input type="text" id="new-todo" placeholder="Add new todo" /> <button onclick="addTodo()">添加</button> Show: <button onclick="showTodos('all')">All</button> <button onclick="showTodos('active')">Active</button> <button onclick="showTodos('completed')">删除</button> <script> // 加载本地存储的数据 function loadTodos() { let todos = localStorage.getItem('todos'); return todos ? JSON.parse(todos) : []; } // 保存数据到本地存储 function saveTodos(todos) { localStorage.setItem('todos', JSON.stringify(todos)); } // 添加新任务 function addTodo() { let text = document.getElementById('new-todo').value.trim(); if (!text) return; let todos = loadTodos(); todos.push({ text, completed: false }); saveTodos(todos); renderTodos(todos); document.getElementById('new-todo').value = ''; } // 渲染任务列表 function renderTodos(todos) { let listElement = document.getElementById('todo-list'); listElement.innerHTML = ''; todos.forEach((todo, index) => { let li = document.createElement('li'); li.textContent = todo.text; if (todo.completed) li.classList.add('completed'); li.addEventListener('click', () => toggleTodo(index)); listElement.appendChild(li); }); } // 切换任务完成状态 function toggleTodo(index) { let todos = loadTodos(); todos[index].completed = !todos[index].completed; saveTodos(todos); renderTodos(todos); } // 显示不同类型的任务 function showTodos(filter) { let todos = loadTodos(); let filteredTodos; switch (filter) { case 'active': filteredTodos = todos.filter(todo => !todo.completed); break; case 'completed': filteredTodos = todos.filter(todo => todo.completed); break; default: filteredTodos = todos; } renderTodos(filteredTodos); } // 初始化时渲染任务列表 document.addEventListener('DOMContentLoaded', () => { let todos = loadTodos(); renderTodos(todos); }); </script> </body> 按照以下格式写 <script> var form, todo, undo,todoLen,undoLen; var list = []; init(); function init() { form = document.querySelector("form"); todo = document.querySelector("#todo"); undo = document.querySelector("#undo"); todoLen=document.querySelector(".todo-len") undoLen=document.querySelector(".undo-len") form.addEventListener("submit", submitHandler); todo.addEventListener("change", changeHandler); undo.addEventListener("change", changeHandler); todo.addEventListener("click", clickHandler); undo.addEventListener("click", clickHandler); } function submitHandler(e) { e.preventDefault(); const item = form.firstElementChild.value; // " a aa ".trim() 去掉字符串前后空格 if (item.trim().length === 0) return; list.push({id:Date.now(),message:item.trim(),todo:true}) render(); form.firstElementChild.value="" } function changeHandler(e) { if(e.target.nodeName!=="INPUT") return; list.find(item=>item.id==e.target.id).todo=!e.target.checked render(); } function clickHandler(e) { if(e.target.className!=="remove") return; const id=e.target.getAttribute("data"); list.splice(list.findIndex(item=>item.id==id),1); render(); } function render(){ const todoList=list.filter(item=>item.todo); todoLen.innerHTML=todoList.length; todo.innerHTML=todoList.map(item=>{ return <input type='checkbox' id="${item.id}"/>${item.message}<button data="${item.id}" class='remove'>×</button> }).join("") const undoList=list.filter(item=>!item.todo) undoLen.innerHTML=undoList.length; undo.innerHTML=undoList.map(item=>{ return <input type='checkbox' id="${item.id}" checked/>${item.message}<button data="${item.id}" class='remove'>×</button> }).join("") } </script>

import type { DrawStyleSecondCategoryGridRef } from ‘@aiComponents/DrawStyleCategoryGrid/SecondGrid.tsx’; import type { UseDrawStyleCategoryConfigProps } from ‘@aiComponents/DrawStyleCategoryGrid/useDrawStyleCategoryConfig.ts’; import type { FeatureLeaferFrameLayoutCommonProps, FeatureLeaferFrameLayoutRef, } from ‘@aiComponents/FeatureLeaferFrameLayout/types.ts’; import type { AiToolGenerateModalRef } from ‘@aiComponents/GenerateModal’; import type { QueryTagItem } from ‘@aiComponents/QueryTagImagesSelector/types.ts’; import type { SemanticsSegmentImageControlRef, SemanticsSegmentImageType, } from ‘@aiComponents/SemanticsSegmentImage/types.ts’; import type { StyleConfigTextAreaRef } from ‘@aiComponents/StyleConfigTextArea’; import type { UploadCropperModalProps, UploadCropperModalRef, } from ‘@aiComponents/UploadCropperModal’; import type { AiVipEquityLimitModalRef } from ‘@aiComponents/VipEquityLimitModal’; import type { ToolEquityCountWrapRef } from ‘@components/ToolEquityCountWrap’; import type { RequestDecorationDesignParams } from ‘@services/comfyUI.ts’; import type { RequestJzxzDrawImageParams, RequestTextToImageGenerateParams, } from ‘@services/draw.ts’; import { aiAddEventTrack, combineSentenceBySplitSymbol, getRandomNum, getStyleDataIndexByRefs, } from ‘@utils/ai.ts’; import { userAuth } from ‘@utils/auth.ts’; import { computeContentObjectFit, delayFun } from ‘@utils/common.ts’; import { decodeImageUrl } from ‘@utils/string.ts’; import { microGetHuoshanUploadImageUrl } from ‘@utils/upload.ts’; import type { UploadImageResponse } from ‘@wander/base-utils’; import { fileToBase64 } from ‘@wander/base-utils’; import { base64ToBlob, blobToFile, getImageSizeInfo, isMobile } from ‘@wander/base-utils’; import { useMount } from ‘ahooks’; import { message } from ‘antd’; import type { AxiosRequestConfig } from ‘axios’; import { t } from ‘i18next’; import PubSub from ‘pubsub-js’; import type { ReactNode } from ‘react’; import { useEffect, useRef, useState } from ‘react’; import { useLocation, useSearchParams } from ‘react-router-dom’; import type { CustomUploadProps } from ‘@/components/CustomUpload’; import type { UploadBtnImageResponse } from ‘@/components/HuabanUploadBtn’; import type { ImageSelectorModalProps, ImageSelectorModalRef, } from ‘@/components/ImageSelectorModal’; import type { ThemeSegmentedProps } from ‘@/components/ThemeSegmented’; import type { UploadImageType } from ‘@/components/UploadImageCard’; import { imgToImgSizeConfig, JZXZ_DRAW_INIT_NEGATIVE_PROMPTS } from ‘@/config/aiTool’; import useGetConfigValue from ‘@/hooks/useGetConfigValue.ts’; import { DECORATION_DESIGN_CATEGORY_SELECT_CONFIG_KEY, DECORATION_DESIGN_CHARTLET_CATEGORY_KEY, DECORATION_DESIGN_COMFY_UI_SELECT_CATEGORY_CONFIG_KEY, decorationDesignSpeedModePrompts, decorationDesignToolKey, TRACK_DECORATION_DESIGN_TOOL_NAME, } from ‘@/pages/AiTools/DecorationDesign/config.tsx’; import { commonDrawControlNetModelMap } from ‘@/pages/AiTools/Draw/configs.ts’; import { useToolRouterStore } from ‘@/pages/AiTools/Router/store/createStore.ts’; import type { RequestDescribeRetouchingParams } from ‘@/services/aiTool’; import { requestDescribeRetouching } from ‘@/services/aiTool’; import { useStore } from ‘@/store/createStore.ts’; import type { AiToolProcessSuccessResponse, ControlNetEditorImageInfo } from ‘@/types/ai’; import type { CandidateItem, DrawSizeType, DrawStyleConfigItem, GeneratePriorityType, LoraItem, } from ‘@/types/draw.ts’; import type { ChartletCategoryItem, ChartletImageItem } from ‘@/types/image’; import { imageDataToBase64 } from ‘@/utils/image’; export type VersionValueType = ‘STANDARD’ | ‘EXTREME_SPEED’ | ‘ADVANCED’; // 请求之间的间隔时长 const REQUEST_DELAY_DURATION = 2000; // 默认重试次数 const DEFAULT_RETRY_COUNT = 2; type VersionMobileValue = any; export type VersionOptionItem = { label: ReactNode; value: VersionMobileValue; disabled?: boolean; }; export type CategoriesItem = { key: string; // 风格名称 label: string; // 风格展示图片 thumbUrl: string; // 提示词 promptsList: CandidateItem[]; // 大模型名称 modelName: string; // 小模型 loraParamses?: LoraItem[]; }; export type DecorationDesignStyleItem = { alias: string; key: string; categories: CategoriesItem[]; }; type PromptConfig = { key: string; list: CandidateItem[]; }; export const useAiDecorationDesign = () => { const location = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); const addResultImage = useToolRouterStore((state) => state.addResultImage); // 产品类型 const productType = useStore((state) => state.productInfo.productType); // 不限制权益 const noLimitFrequency = userAuth([‘ORGANIZATION_VIP’]); // 描述词获取 const styleConfigTextAreaRef = useRef<StyleConfigTextAreaRef>(null); // 底图图库 const imageSelectorModalRef = useRef<ImageSelectorModalRef>(null); // 生成弹窗 const generateModalRef = useRef<AiToolGenerateModalRef<RequestJzxzDrawImageParams>>(null); const drawStyleCategoryRef = useRef<DrawStyleSecondCategoryGridRef>(null); const toolEquityCountWrapRef = useRef<ToolEquityCountWrapRef>(null); // 画板Ref const featureLeaferFrameLayoutRef = useRef<FeatureLeaferFrameLayoutRef>(null); const semanticsSegmentImageControlRef = useRef<SemanticsSegmentImageControlRef>(null); // 刷新次数 const refreshToolEquityUserUseInfo = useStore((state) => state.refreshToolEquityUserUseInfo); // 创作模式优先级 const [generatePriorityType, setGeneratePriorityType] = useState<GeneratePriorityType>(‘CONTROLNET’); // 会员权益弹窗 const DecorationDesignipLimitModalRef = useRef<AiVipEquityLimitModalRef>(null); // 相似度 const [weight, setWeight] = useState(1); // 最终生成图尺寸大小 const [sizeType, setSizeType] = useState<DrawSizeType>(‘LARGE’); // 极速版-生成失败的信息 const [errorMsg, setErrorMsg] = useState(‘’); // 创作弹框-极速版 const generateSpeedModalRef = useRef<AiToolGenerateModalRef>(null); // comfyUI 创作弹框-高级版 const comfyUiGenerateModalRef = useRef<AiToolGenerateModalRef<RequestDecorationDesignParams>>(null); // 风格获取-高级版 const drawComfyUiStyleCategoryRef = useRef<DrawStyleSecondCategoryGridRef>(null); // 风格列表 const [comfyUiStyleList, setComfyUiStyleList] = useState<DecorationDesignStyleItem[]>([]); // 高级版-风格参考图信息 const [styleReferComfyImageInfo, setStyleReferComfyImageInfo] = useState<UploadImageResponse | null>(null); // 风格参考图图库 const comfySourceImageSelectorModalRef = useRef<ImageSelectorModalRef>(null); // comfyUI-XL版本参考图风格符合度 const [comfyStyleReferWeight, setComfyStyleReferWeight] = useState(0.8); // 上传裁剪弹窗 const uploadCropperModalRef = useRef<UploadCropperModalRef>(null); // 裁剪弹窗open const [cropperModalOpen, setCropperModalOpen] = useState(false); // 选中风格索引 const [currentStyleIndex, setCurrentStyleIndex] = useState(0); // 高级版-一级类目状态获取 const [activeAlias, setActiveAlias] = useState<string>(comfyUiStyleList[0]?.alias || ‘’); // 高级版-获取当前激活的二级类目 const activeCategories = comfyUiStyleList.find((item) => item.alias === activeAlias)?.categories || []; // 版本切换状态 const [versionValue, setVersionValue] = useState<VersionValueType>(() => { return userAuth([‘USER’, ‘COMMON_VIP’, ‘TOURIST’]) ? ‘STANDARD’ : ‘EXTREME_SPEED’; }); // 标准版-该工具支持选择的类目组 const categoryRefsGroup: string[][] = useGetConfigValue({ productKeys: { JZXZ: DECORATION_DESIGN_CATEGORY_SELECT_CONFIG_KEY, JZDS: ‘jzds_decoration_design_style_categories’, }, }) || []; // 贴图类目列表 const chartletCategoryList = useGetConfigValue<ChartletCategoryItem[]>({ key: DECORATION_DESIGN_CHARTLET_CATEGORY_KEY }) || []; // 线稿库类目列表 const queryTagList = useGetConfigValue<QueryTagItem[]>({ key: ‘jzxz_decoration_design_base_Image_query_tag_config’, }) || []; // 高级版-该工具支持选择的类目组 const comfyUiCategoryRefsGroup: CategoriesItem[] = ( useGetConfigValue({ productKeys: { JZXZ: DECORATION_DESIGN_COMFY_UI_SELECT_CATEGORY_CONFIG_KEY, }, }) || [] ).filter( (category: CategoriesItem) => category.key === ‘indoorHomeDecoration’ || category.key === ‘indoorWorkWear’, ); // 高级版-描述词 const comfyUiPrompt: PromptConfig[] = ( useGetConfigValue({ productKeys: { JZXZ: ‘jzxz_advance_draw_descriptor_config’, }, }) || [] ).filter((p: PromptConfig) => p.key === ‘indoorHomeDecoration’ || p.key === ‘indoorWorkWear’); // 版本切换-mobile const handleVersionMobileChange = (value: VersionValueType) => { if (userAuth([‘TOURIST’])) { PubSub.publish(‘onLogin’, true); return; } if (value !== ‘STANDARD’ && userAuth([‘USER’, ‘COMMON_VIP’])) { console.log(‘value’, value); DecorationDesignipLimitModalRef.current?.setLimitType(‘FOREVER_VIP’); return; } // 否则允许切换 setVersionValue(value); // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; } if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, true, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, true); } } }; // 版本切换-pc const handleVersionPcChange: ThemeSegmentedProps[‘onChange’] = (item) => { const value = item.value; if (userAuth(['TOURIST'])) { PubSub.publish('onLogin', true); return; } if (value !== 'STANDARD' && userAuth(['USER', 'COMMON_VIP'])) { DecorationDesignipLimitModalRef.current?.setLimitType('FOREVER_VIP'); return; } // 否则允许切换 setVersionValue(value); // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; } if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, true, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, true); } } }; // 高级版-风格类目描述词对应方法 function mapPromptsToCategories(categoryGroups: any, prompts: any) { return categoryGroups.map((categoryGroup: any) => { const promptConfig = prompts.find((prompt: any) => prompt.key === categoryGroup.key); if (!promptConfig) { return null; } return { alias: categoryGroup.alias, key: categoryGroup.key, categories: categoryGroup.categories.map((cat: any) => ({ key: cat.key, label: cat.alias, thumbUrl: cat.coverUrl, promptsList: promptConfig.list.map((item: any) => ({ placeholder: item.placeholder || '', keyword: item.keyword || '', })), modelName: cat.modelName, lora: cat.loraParamses?.[0], })), }; }); } // 高级版-设计领域与风格切换 const onComfyUiStyleIndexChange = (index: number) => { setCurrentStyleIndex(index); const currentItem = activeCategories[index]; const isUniversalCategory = activeCategories[0]; // 随机描述词 let randomPrompts: CandidateItem[] = []; if (currentItem.promptsList?.length) { const randomIndex = Math.floor(Math.random() * currentItem.promptsList.length); randomPrompts = [currentItem.promptsList[randomIndex]]; } // 更新候选配置 styleConfigTextAreaRef.current?.updateCandidateConfig(randomPrompts, true); if (isUniversalCategory) { styleConfigTextAreaRef.current?.updateCandidateConfig(currentItem.promptsList, true); } }; // 获取语意分割组件和局部绘制组件内部参考图信息并转为ControlNet所需参数 const getControlsControlNetArgs = ({ localDrawImage, semanticsSegmentImage, }: { localDrawImage: ControlNetEditorImageInfo | null; semanticsSegmentImage: SemanticsSegmentImageType | null; }) => { const controlNetArgs: Pick< RequestTextToImageGenerateParams, ‘localDrawImage’ | ‘semanticsSegmentImage’ > = {}; if (localDrawImage?.enabled) { controlNetArgs.localDrawImage = { ...localDrawImage, ...commonDrawControlNetModelMap.inpaint, enable: true, // 开始迭代步数 guidanceStart: 0, guidanceEnd: 1, // 结束迭代步数 weight: 1, }; } if (semanticsSegmentImage?.enabled) { controlNetArgs.semanticsSegmentImage = { ...semanticsSegmentImage, ...commonDrawControlNetModelMap.semanticsSegment, enable: true, // 开始迭代步数 guidanceStart: 0, guidanceEnd: 1, // 结束迭代步数 weight: 1, }; } return controlNetArgs; }; // 获取画板图片 const getFrameImage = async (sizeInfo: Pick<UploadImageResponse, ‘width’ | ‘height’>) => { // 导出画布方法 const exportFrame = featureLeaferFrameLayoutRef.current?.exportFrame; if (!exportFrame) { return Promise.reject(); } // 导出内容 const result = await exportFrame('canvas', { size: { width: sizeInfo.width, height: sizeInfo.height, }, }); const leaferCanvas = result.data; const context = leaferCanvas.context; // 获取画板图片 const imageData = context.getImageData(0, 0, leaferCanvas.width, leaferCanvas.height); const base64 = imageDataToBase64(imageData); const file = blobToFile(base64ToBlob(base64)); const imageInfo = await microGetHuoshanUploadImageUrl(file); featureLeaferFrameLayoutRef.current?.setCompareImage(imageInfo); return imageInfo; }; // comfyUI-有风格参考图 const generateComfyStyleReferenceTask = async (uploadImage: UploadImageResponse) => { comfyUiGenerateModalRef.current?.setModalOpen(true); let imageInfo: UploadImageResponse = uploadImage; const prompt = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 当前尺寸的阈值 const thresholdNum = imgToImgSizeConfig[sizeType]; // 尺寸信息 const sizeParams = computeContentObjectFit({ containerWidth: thresholdNum, containerHeight: thresholdNum, contentWidth: uploadImage.width, contentHeight: uploadImage.height, type: 'contain', }); // 风格参考图获取 const baseImage = styleReferComfyImageInfo?.largeUrl; try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } comfyUiGenerateModalRef.current?.generateTask({ imageUrl: imageInfo.largeUrl, seed: getRandomNum(), ...sizeParams, // 描述词 prompt, // 底图符合度 baseImageWeight: weight, // 参考图片链接 styleReferImageUrl: baseImage, // 风格参考度-权重 styleReferWeight: comfyStyleReferWeight, }); } catch (e) { comfyUiGenerateModalRef.current?.setRequestStatus('GENERATE_ERROR'); } }; // comfyUI-无风格参考图 const generateComfyTask = async (uploadImage: UploadImageResponse) => { comfyUiGenerateModalRef.current?.setModalOpen(true); let imageInfo: UploadImageResponse = uploadImage; const prompt = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 当前尺寸的阈值 const thresholdNum = imgToImgSizeConfig[sizeType]; // 尺寸信息 const sizeParams = computeContentObjectFit({ containerWidth: thresholdNum, containerHeight: thresholdNum, contentWidth: uploadImage.width, contentHeight: uploadImage.height, type: 'contain', }); try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } comfyUiGenerateModalRef.current?.generateTask({ imageUrl: imageInfo.largeUrl, seed: getRandomNum(), ...sizeParams, // 描述词 prompt, // 底图符合度 baseImageWeight: weight, }); } catch (e) { comfyUiGenerateModalRef.current?.setRequestStatus('GENERATE_ERROR'); } }; // 标准版-开始创作 const onSdStart: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = async ({ uploadImage, localDrawImage, semanticsSegmentImage, }) => { if (!noLimitFrequency && !toolEquityCountWrapRef.current?.verifyUseStatus()) { return; } if (!uploadImage) { message.warning(t(‘AiTools-LocalDrawImage-yES4’)); return; } let imageInfo: UploadImageResponse = uploadImage; generateModalRef.current?.setModalOpen(true); try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } // 提取当前类目下下的参数 const { modelName = '', loraParamses = [] } = drawStyleCategoryRef.current?.getCategoryParams() || {}; let prompt = styleConfigTextAreaRef.current?.prompts.keyword || ''; // 启用了色块 获取色块描述 if (semanticsSegmentImage?.enabled) { // 去重描述词 const paintKeywords = [ ...new Set(semanticsSegmentImage.colorBlockList.map((item) => item.englishAlias)), ]; prompt = combineSentenceBySplitSymbol([...paintKeywords, prompt]); } // 参考图 const styleImage = styleReferComfyImageInfo; generateModalRef.current?.generateTask({ // 获取语意分割、局部重绘相关controlnet参数 ...getControlsControlNetArgs({ localDrawImage, semanticsSegmentImage }), prompt, modelName, loraParamses, negativePrompt: JZXZ_DRAW_INIT_NEGATIVE_PROMPTS, baseImage: { width: imageInfo.width, height: imageInfo.height, url: imageInfo.largeUrl || imageInfo.thumbUrl, // 开始迭代步数 guidanceStart: 0, // 结束迭代步数 guidanceEnd: 1, // 相似度 module: 'scribble_xdog', weight, model: 'control_v11p_sd15_scribble', }, ...(styleImage ? { styleReferImage: { guidanceEnd: 1, guidanceStart: 0, height: styleImage.height as number, model: 'ip-adapter_sd15_plus [32cd8f7f]', module: 'ip-adapter_clip_sd15', url: styleImage.largeUrl, weight: comfyStyleReferWeight, width: styleImage.width as number, }, } : {}), sizeType, generateMode: 'REPAINT', generatePriorityType, generateImageType: 'DECORATION_DESIGN', }); } catch (error) { message.error(t('AiTools-DecorationDesign-Operate-hook-trpaP')); } }; // comfy-高级版-开始创作 const onComfyStart: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = async (params) => { if (!params?.uploadImage) { message.warn(t(‘AiTools-DetailEnhance-Operate-hook-PzPPn’)); return; } if (styleReferComfyImageInfo) { await generateComfyStyleReferenceTask(params.uploadImage); } else { await generateComfyTask(params.uploadImage); } }; // 极速版 const sendRequest = async ( // 请求参数 params: RequestDescribeRetouchingParams, // 重试次数 retryCount: number, // 请求头配置 用于减次数 config?: AxiosRequestConfig, ): Promise<UploadImageType> => { // 没有重试次数了 直接抛出异常 if (retryCount <= 0) { throw { errorCode: ‘MAX_RETRY_TIMES’, errorMsg: ‘生成图片异常’ }; } const response = await requestDescribeRetouching(params, config).catch((e) => { console.error('请求失败:', e); return null; }); // 本次生成没有结果 重试 if (!response) { await delayFun(REQUEST_DELAY_DURATION); return sendRequest(params, retryCount - 1, config); } // 拿到了结果 正常返回 if (response.imageList[0]) { return response.imageList[0]; } // 拿到了结果但是没有图片返回且有文案提示 if (!response.imageList.length && response.text) { setErrorMsg(response.text.replace(/\*/g, '')); // 特殊错误,不重试 throw { errorCode: 'NO_IMAGE', errorMsg: response.text }; } // 其他情况重试 await delayFun(REQUEST_DELAY_DURATION); return sendRequest(params, retryCount - 1, config); }; const batchRequest = async (params: RequestDescribeRetouchingParams) => { const result: UploadImageType[] = []; const data = await sendRequest(params, DEFAULT_RETRY_COUNT); result.push(data); return result; }; const onSpeedModeGenerateSuccess = (list: UploadImageType[]) => { const result = list[0].largeUrl; featureLeaferFrameLayoutRef.current?.setResult(result); // 添加进快捷使用 addResultImage({ ...list[0], url: list[0].largeUrl }); aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, 'createSuccess'); refreshToolEquityUserUseInfo(decorationDesignToolKey); }; // 开始创作-极速版 const onSpeedModeStart: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = async ({ uploadImage, }) => { if (!uploadImage) { message.warning({ content: ‘请先上传图片’, key: ‘emptyImage’ }); return; } setErrorMsg(‘’); generateSpeedModalRef.current?.setModalOpen(true); generateSpeedModalRef.current?.setRequestStatus(‘EXECUTING’); // 底图 const uploadImageInfo = uploadImage.largeUrl; // 参考图 const baseImage = styleReferComfyImageInfo?.largeUrl; const imageUrlList: string[] = []; imageUrlList.push(uploadImageInfo); if (baseImage) { imageUrlList.push(baseImage); } // 描述词 const prompts = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 拼接描述词-有参考图 const baseImageDescription = 图一参考图二,图片要求按${uploadImage.width}:${uploadImage.height}比例生成; const newPrompts = ${prompts},${baseImageDescription}; // 拼接描述词-没有参考图 const noBaseImageDescription = 图片要求按${uploadImage.width}:${uploadImage.height}比例生成; const promptsText = ${prompts},${noBaseImageDescription}; // 根据baseImage是否存在,设置description const description = baseImage ? newPrompts : promptsText; try { // 发起请求 const response = await batchRequest({ description: combineSentenceBySplitSymbol([description]), imageUrls: imageUrlList, }); onSpeedModeGenerateSuccess(response); generateSpeedModalRef.current?.setRequestStatus('PENDING'); generateSpeedModalRef.current?.setModalOpen(false); } catch (e) { aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, 'createError'); generateSpeedModalRef.current?.setRequestStatus('POLLING_ERROR'); } }; // 生成结果回调 const onGenerateSuccess = async (response: AiToolProcessSuccessResponse) => { try { const result = response.imageList[0]; // 刷新次数 refreshToolEquityUserUseInfo(decorationDesignToolKey); // 设置显示 featureLeaferFrameLayoutRef.current?.setResult(result); const sizeInfo = await getImageSizeInfo(result); // 添加生成记录 addResultImage({ url: result, …sizeInfo, }); } catch (e) { console.error(e); } }; // 画板插入贴图 const onChartletItemClick = (imageInfo: ChartletImageItem) => { featureLeaferFrameLayoutRef.current?.insertPicture(imageInfo); }; // 画板插入本地上传图片 const onChartletUploadSuccess: CustomUploadProps[‘onUploadSuccess’] = async ({ url }) => { try { const size = await getImageSizeInfo(url); featureLeaferFrameLayoutRef.current?.insertPicture({ url, width: size.width, height: size.height, }); } catch (err) { console.error(err); message.error(t(‘AiTools-ImageEditorModal-hooks-mdQhK’)); } }; // 创作优先级改变 const onGeneratePriorityTypeChange = (type: GeneratePriorityType) => { setGeneratePriorityType(type); setWeight(type === ‘CONTROLNET’ ? 1 : 1.2); }; // 标准版-类目改变回调 const onSelectRefsChange: UseDrawStyleCategoryConfigProps[‘onSelectRefsChange’] = ( refs, categories, ) => { if (versionValue !== ‘STANDARD’) { return; } drawStyleCategoryRef.current?.setSelectRefs(refs); const ids = getStyleDataIndexByRefs(‘id’, categories, refs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids); } }; // 标准版 const onCategoryInit = (styleConfigItems: DrawStyleConfigItem[]) => { // 一级类目ref const firstCategoryRefs = categoryRefsGroup.map((item) => item[0]); // 过滤出相关的类目信息 const filterStyleConfigItems = styleConfigItems.filter((item) => firstCategoryRefs.includes(item.ref), ); drawStyleCategoryRef.current?.setStyleConfigList(filterStyleConfigItems); // 请求描述词 onSelectRefsChange(categoryRefsGroup[0], filterStyleConfigItems); }; // 当点击继续处理时 const onContinue = async (result: string) => { try { const sizeInfo = await getImageSizeInfo(result); featureLeaferFrameLayoutRef.current?.onUploadSuccess([ { …sizeInfo, largeUrl: result, thumbUrl: result }, ]); } catch (e) { message.error(t(‘AiTools-DecorationDesign-Operate-hook-6ipKH’)); } }; const onSelectorModalFinish: ImageSelectorModalProps[‘onFinish’] = async (item) => { featureLeaferFrameLayoutRef.current?.onUploadSuccess([item]); if (item.originFile) { aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, isMobile() ? ‘mobileUpload’ : ‘pcUpload’); } }; // 风格底图选择弹窗回调 const onDecorationDesignReferImageModalFinish: ImageSelectorModalProps[‘onFinish’] = async ({ largeUrl, thumbUrl, width, height, originFile, }) => { setStyleReferComfyImageInfo({ width, height, largeUrl, thumbUrl, }); let base64; // 原图有originFile直接转base64 if (originFile) { try { base64 = await fileToBase64(originFile); } catch (e) { console.error(e); } } // base64转换成功 打开裁剪弹框 失败则默认为原图 setCropperModalOpen(true); uploadCropperModalRef.current?.setCropperImageUrl(base64 || largeUrl); }; // 风格底图裁剪完成图片弹框回调 const onStyleReferImageCropperSuccess: UploadCropperModalProps[‘onCropperSuccess’] = async ( obj, ) => { setStyleReferComfyImageInfo(obj); setCropperModalOpen(false); }; const onStyleReferImageCropperError = () => { setCropperModalOpen(false); message.error(t(‘AiTools-TransferSmudge-Operate-hook-MiGY’)); }; // 开始创作版本判断 const generateTask: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = (params) => { if (userAuth([‘TOURIST’])) { PubSub.publish(‘onLogin’, true); return; } if (!noLimitFrequency && !toolEquityCountWrapRef.current?.verifyUseStatus()) { return; } if (versionValue === 'STANDARD') { onSdStart(params); return; } if (versionValue === 'EXTREME_SPEED') { onSpeedModeStart(params); return; } if (versionValue === 'ADVANCED') { onComfyStart(params); } }; useMount(() => { // 获取极速版描述词 styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); // 获取高级版风格类目 const comfyUiStyle = mapPromptsToCategories(comfyUiCategoryRefsGroup, comfyUiPrompt); setComfyUiStyleList(comfyUiStyle); if (comfyUiStyle.length > 0) { setActiveAlias(comfyUiStyle[0].alias); setCurrentStyleIndex(0); } const image: UploadBtnImageResponse = location.state?.image; if (image) { featureLeaferFrameLayoutRef.current?.onUploadSuccess([image]); return; } // 地址栏参数传递 const url = decodeImageUrl(searchParams.get('url') || ''); const width = searchParams.get('width'); const height = searchParams.get('height'); if (url && width && height) { featureLeaferFrameLayoutRef.current?.onUploadSuccess([ { largeUrl: url, thumbUrl: url, width: Number(width), height: Number(height), }, ]); setSearchParams({}); } }); // 室内装修初始设置选中室内色块 useEffect(() => { semanticsSegmentImageControlRef.current?.setSelectCategoryIndex(1); }, []); return { noLimitFrequency, styleConfigTextAreaRef, imageSelectorModalRef, generateModalRef, featureLeaferFrameLayoutRef, drawStyleCategoryRef, toolEquityCountWrapRef, semanticsSegmentImageControlRef, generatePriorityType, categoryRefsGroup, weight, setWeight, sizeType, setSizeType, weightSliderMaxSize: generatePriorityType === ‘PROMPTS’ ? 2 : 1.3, chartletCategoryList, queryTagList, onChartletItemClick, onCategoryInit, onChartletUploadSuccess, onGenerateSuccess, onContinue, onGeneratePriorityTypeChange, onSelectRefsChange, onSelectorModalFinish, versionValue, handleVersionPcChange, productType, comfyUiStyleList, setActiveAlias, activeAlias, activeCategories, onComfyUiStyleIndexChange, currentStyleIndex, comfyUiGenerateModalRef, drawComfyUiStyleCategoryRef, styleReferComfyImageInfo, setStyleReferComfyImageInfo, comfySourceImageSelectorModalRef, comfyStyleReferWeight, setComfyStyleReferWeight, generateTask, onDecorationDesignReferImageModalFinish, uploadCropperModalRef, cropperModalOpen, setCropperModalOpen, onStyleReferImageCropperSuccess, onStyleReferImageCropperError, generateSpeedModalRef, errorMsg, DecorationDesignipLimitModalRef, handleVersionMobileChange, }; }; import ToolBenefitOperator from ‘@aiComponents/BenefitOperator’; import AiToolBlockItem from ‘@aiComponents/BlockItem’; import { AiToolImageCoverCard } from ‘@aiComponents/CardItems’; import ChartletUploadBtn from ‘@aiComponents/ChartletUploadBtn’; import DrawStyleSecondCategoryGrid from ‘@aiComponents/DrawStyleCategoryGrid/SecondGrid.tsx’; import FeatureLeaferFrameLayoutPc from ‘@aiComponents/FeatureLeaferFrameLayout/pc’; import ImageSticker from ‘@aiComponents/ImageSticker’; import { colorBlockCategoryList } from ‘@aiComponents/SemanticsSegmentEditor/config.tsx’; import DrawStyleConfigTextArea from ‘@aiComponents/StyleConfigTextArea’; import UploadCropperModal from ‘@aiComponents/UploadCropperModal’; import AiVipEquityLimitModal from ‘@aiComponents/VipEquityLimitModal’; import ProductAuth from ‘@components/ProductAuth’; import ThemeInputNumber from ‘@components/ThemeInputNumber’; import ThemeSegmented from ‘@components/ThemeSegmented’; import ThemeSlider from ‘@components/ThemeSlider’; import ToolEquityCountWrap from ‘@components/ToolEquityCountWrap’; import UploadImageBox from ‘@components/UploadImageCard/UploadImageBox.tsx’; import { requestDecorationDesign } from ‘@services/comfyUI.ts’; import type { RequestJzxzDrawImageParams } from ‘@services/draw.ts’; import { requestJzxzDrawImage } from ‘@services/draw.ts’; import { userAuth } from ‘@utils/auth.ts’; import classNames from ‘classnames/bind’; import type { CSSProperties } from ‘react’; import { type FC } from ‘react’; import { useTranslation } from ‘react-i18next’; import ImageSelectorModal from ‘@/components/ImageSelectorModal/index.tsx’; import ThemeButton from ‘@/components/ThemeButton’; import { enlargeDownloadOptions } from ‘@/config/aiTool.ts’; import { generatePriorityList } from ‘@/config/aiTool.ts’; import { advanceDrawSizeTypeList } from ‘@/pages/AiTools/AdvanceDraw/config.ts’; import DrawSizeControls from ‘@/pages/AiTools/components/DrawSizeControls’; import AiToolGenerateModal from ‘@/pages/AiTools/components/GenerateModal’; import AiToolGridList from ‘@/pages/AiTools/components/GridList/index.tsx’; import UploadImagePreviewCard from ‘@/pages/AiTools/components/PreviewCard/index.tsx’; import type { GeneratePriorityType } from ‘@/types/draw.ts’; import type { DrawSizeType } from ‘@/types/draw.ts’; import { DECORATION_DESIGN_TOOL_NAME_KEY, decorationBrushTypeList, decorationDesignSpeedupKey, decorationDesignToolKey, decorationDesignVideoUrl, decorationOperationTypeList, TRACK_DECORATION_DESIGN_TOOL_NAME, } from ‘…/…/config’; import { useAiDecorationDesign } from ‘…/hook.ts’; import styles from ‘./index.module.less’; const cx = classNames.bind(styles); // 室内设计 const DecorationDesignPc: FC = () => { const { t } = useTranslation(); const { noLimitFrequency, styleConfigTextAreaRef, imageSelectorModalRef, generateModalRef, featureLeaferFrameLayoutRef, drawStyleCategoryRef, toolEquityCountWrapRef, semanticsSegmentImageControlRef, generatePriorityType, categoryRefsGroup, weight, setWeight, sizeType, setSizeType, weightSliderMaxSize, chartletCategoryList, queryTagList, onChartletItemClick, onCategoryInit, onChartletUploadSuccess, onGenerateSuccess, onContinue, onGeneratePriorityTypeChange, onSelectRefsChange, onSelectorModalFinish, versionValue, handleVersionPcChange, productType, comfyUiStyleList, setActiveAlias, activeAlias, activeCategories, onComfyUiStyleIndexChange, currentStyleIndex, comfyUiGenerateModalRef, styleReferComfyImageInfo, setStyleReferComfyImageInfo, comfySourceImageSelectorModalRef, comfyStyleReferWeight, setComfyStyleReferWeight, onDecorationDesignReferImageModalFinish, uploadCropperModalRef, cropperModalOpen, setCropperModalOpen, onStyleReferImageCropperSuccess, onStyleReferImageCropperError, generateTask, generateSpeedModalRef, errorMsg, DecorationDesignipLimitModalRef, } = useAiDecorationDesign(); const contentBottomRender = ( <> <DrawStyleConfigTextArea ref={styleConfigTextAreaRef} textareaConfig={{ placeholder: t(‘AiTools-DecorationDesign-Operate-pc-PNiN’), }} blockItem={{ title: t(‘AiTools-Redraw-Operate-pc-3mzP’), size: ‘large’, iconType: null, }} /> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-3JRD')} size="large" hidden={versionValue !== 'STANDARD'} > <FeatureLeaferFrameLayoutPc.LocalDraw toolKey={decorationDesignToolKey} controlNetFeatureCard={{ style: { '--main-title-font-weight': 500 } as CSSProperties, }} /> <FeatureLeaferFrameLayoutPc.SemanticsSegment toolKey={decorationDesignToolKey} ref={semanticsSegmentImageControlRef} editorConfig={{ colorBlockCategoryList, }} controlNetFeatureCard={{ style: { '--main-title-font-weight': 500, marginBottom: 0, } as CSSProperties, }} /> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-twQz')} size="large" hidden={versionValue === 'EXTREME_SPEED'} > {!!categoryRefsGroup.length && ( <DrawStyleSecondCategoryGrid getScrollContainer={() => document.getElementById('rightOperatorContent')} scrollOffsetTop={200} defaultSelectRefs={categoryRefsGroup[0]} ref={drawStyleCategoryRef} onInit={onCategoryInit} hidden={versionValue !== 'STANDARD'} onSelectRefsChange={onSelectRefsChange} /> )} {comfyUiStyleList.map((item) => ( { setActiveAlias(item.alias); }} > {item.alias} ))} <AiToolGridList hidden={versionValue !== 'ADVANCED'} list={activeCategories || []} columns={4} renderItem={(item, index) => ( <AiToolImageCoverCard key={item.key} label={item.label} coverUrl={item.thumbUrl} active={currentStyleIndex === index} onClick={() => { onComfyUiStyleIndexChange(index); }} /> )} /> </AiToolBlockItem> {/* 风格参考图 */} <AiToolBlockItem title={t('AiTools-Draw-ControlNet-wn4Hz')} size="large"> {/* 未上传前展示上传按钮 */} <UploadImageBox imageStyle={{ width: 75 }} description="" customUploadBtn={() => ( { comfySourceImageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }} > {t('AiTools-ControlNet-UploadBtn-5chr')} )} /> <UploadImagePreviewCard url={styleReferComfyImageInfo?.largeUrl} actions={[ { iconType: 'micro-icon-edit', onClick: () => { comfySourceImageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }, }, { iconType: 'micro-icon-trash-can', onClick: () => { setStyleReferComfyImageInfo(null); }, }, ]} /> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-MultiAnglesDraw-Operate-StyleControlNetImageCard-Card-CbB7')} size="large" hidden={!styleReferComfyImageInfo} extra={ <ThemeInputNumber value={comfyStyleReferWeight} min={0} step={0.1} max={1} className={cx('input_number')} onChange={(value) => { if (value) { setComfyStyleReferWeight(value); } }} /> } > <ThemeSlider className={cx('slider')} value={comfyStyleReferWeight} min={0} step={0.1} max={1} onChange={(value) => setComfyStyleReferWeight(value)} /> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-chDk')} size="large" hidden={versionValue === 'EXTREME_SPEED'} extra={ <ChartletUploadBtn onUploadSuccess={onChartletUploadSuccess}> {t('AiTools-ImageSelectorModal-b6NR')} </ChartletUploadBtn> } > {!!chartletCategoryList.length && ( <ImageSticker onItemClick={onChartletItemClick} cateList={chartletCategoryList} /> )} </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-C7bC')} size="large" hidden={versionValue === 'EXTREME_SPEED'} extra={ versionValue === 'STANDARD' && ( <ThemeSegmented size="small" value={generatePriorityType} options={generatePriorityList.map((item) => ({ label: t(item.labelKey), value: item.value, }))} onChange={(item) => onGeneratePriorityTypeChange(item.value as GeneratePriorityType)} /> ) } > <ThemeSlider className={cx('slider')} value={weight} dots={false} onChange={setWeight} step={0.1} min={0} max={weightSliderMaxSize} /> </AiToolBlockItem> <AiToolBlockItem hidden={versionValue === 'EXTREME_SPEED'} title={t('AiTools-DetailEnhance-Operate-pc-DwnF')} size="large" > <DrawSizeControls value={sizeType} list={advanceDrawSizeTypeList} onChange={(item: DrawSizeType) => { setSizeType(item); }} /> </AiToolBlockItem> </> ); const contentTopRender = ( <> <AiToolBlockItem title={t(‘AiTools-InspirationDraw-Operate-pc-babel’)} size=“large”> <ThemeSegmented size=“large” className={cx(‘version_grid’)} options={[ { label: ( {t(‘AiTools-PlanColorizer-Operate-pc-Fess’)} {t(‘Pro-vip-prefix’)} ), value: ‘ADVANCED’, }, { label: ( {t(‘AiTools-PlanColorizer-Operate-pc-Jsonb’)} {t(‘Pro-vip-prefix’)} ), value: ‘EXTREME_SPEED’, }, { label: t(‘AiTools-PlanColorizer-Operate-pc-bess’), value: ‘STANDARD’, }, ]} value={versionValue} onChange={handleVersionPcChange} /> </AiToolBlockItem> </> ); return ( <> <FeatureLeaferFrameLayoutPc ref={featureLeaferFrameLayoutRef} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} onStart={generateTask} download={{ enlargeDownloadOptions, }} rightHeader={{ header: { title: t(DECORATION_DESIGN_TOOL_NAME_KEY), videoUrl: decorationDesignVideoUrl, }, }} content={{ top: contentTopRender, bottom: contentBottomRender, }} imageAddLayer=“frame” uploadCard={{ customUploadBtn: (className) => ( { imageSelectorModalRef.current?.setModalStatus(‘SELECT_MODAL’); }} > {t(‘HuabanUploadBtn-rWrt’)} ), }} customUploadBtn={ <ThemeButton size=“large” ghost className={cx(‘custom_upload_btn’)} onClick={() => { imageSelectorModalRef.current?.setModalStatus(‘SELECT_MODAL’); }} > {t(‘AiTools-LeaferFrameLayout-pc-bHEM’)} </ThemeButton> } editTool={ versionValue !== ‘EXTREME_SPEED’ && { title: t(‘AiTools-ImageEditorModal-pc-NP8N’), operationTypeList: decorationOperationTypeList, brushTypeList: decorationBrushTypeList, } } modifyTool={ versionValue !== ‘EXTREME_SPEED’ && { title: t(‘AiTools-ImageEditorModal-pc-Q4Zr’), enableSmearRepair: true, } } onContinue={onContinue} leaferFrame={{ defaultBrushType: ‘LINE’, defaultBrushSize: 3, defaultBrushColorRgb: [0, 0, 0], opacityConfig: { paintingWritGroup: 1, }, }} footerExtra={ <ToolEquityCountWrap ref={toolEquityCountWrapRef} toolKey={decorationDesignToolKey} showToolName={t(DECORATION_DESIGN_TOOL_NAME_KEY)} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} disabled={noLimitFrequency} > <ToolBenefitOperator style={{ marginTop: 6 }} hidden={noLimitFrequency || userAuth([‘TOURIST’])} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} showToolEquityInfo={() => { toolEquityCountWrapRef.current?.showToolEquityInfo(); }} showToolCountUsageIntro={() => { toolEquityCountWrapRef.current?.showToolCountUsageIntro(); }} /> </ToolEquityCountWrap> } /> {/* 旧房改造进度- 标准版 */} <AiToolGenerateModal<RequestJzxzDrawImageParams> customerRef={generateModalRef} toolKey={decorationDesignToolKey} speedupKey={decorationDesignSpeedupKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} pollingConfig={{ processingType: 'DECORATION_DESIGN', imageDetectMode: 'NONE', }} request={requestJzxzDrawImage} onGenerateSuccess={onGenerateSuccess} /> {/* 旧房改造进度-高级版 */} <AiToolGenerateModal customerRef={comfyUiGenerateModalRef} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} speedupKey={decorationDesignSpeedupKey} pollingConfig={{ processingType: 'COMFYUI_COMPOSE', imageDetectMode: 'NONE', }} request={requestDecorationDesign} onGenerateSuccess={onGenerateSuccess} renderStatusSection={{ EXECUTING: { title: '室内装修改造中...', }, }} /> {/* 极速模式 */} <AiToolGenerateModal customerRef={generateSpeedModalRef} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} customerRequest={() => {}} renderStatusSection={{ EXECUTING: { title: '室内装修改造中...', }, POLLING_ERROR: { title: t('AiTools-GenerateModal-YdY7'), extra: errorMsg && {errorMsg}, }, }} /> {/* 底图图库 */} <ImageSelectorModal ref={imageSelectorModalRef} queryTagImages={{ modalTitle: t('AiTools-DecorationDesign-Operate-pc-xkdY'), introduce: t('AiTools-DecorationDesign-Operate-pc-f4eJ'), categoryList: queryTagList, iconClassName: cx('image_selector_cover'), }} onFinish={onSelectorModalFinish} /> {/* 上传风格参考图图库 */} <ImageSelectorModal ref={comfySourceImageSelectorModalRef} queryTagImages={{ introduce: t('AiTools-DecorationDesign-Operate-pc-xkdY'), modalTitle: t('AiTools-DecorationDesign-Operate-pc-f4eJ'), categoryList: queryTagList, iconClassName: cx('image_selector_cover'), }} onFinish={onDecorationDesignReferImageModalFinish} /> {/* 裁剪弹窗 */} <UploadCropperModal ref={uploadCropperModalRef} open={cropperModalOpen} modalTitle={ <> {t('AiTools-ImageEditorModal-pc-EKx2')} {t('AiTools-TransferSmudge-Operate-pc-dZW2')} </> } maskClosable onCropperSuccess={onStyleReferImageCropperSuccess} onCropperError={onStyleReferImageCropperError} onCancel={() => { setCropperModalOpen(false); }} /> {/* 会员权益限制弹窗 */} <AiVipEquityLimitModal ref={DecorationDesignipLimitModalRef} /> </> ); }; export default DecorationDesignPc; 怎么实现点击切换alias可以随机对应key值下activeCategories的内容,同时切换后点击随机,对应activeCategories依旧可以随机,并且保持自定义 if (promptType === ‘USER’ || promptType === ‘INNOVATIVE’) { if (value === ‘ADVANCED’) { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === ‘EXTREME_SPEED’) { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === ‘STANDARD’) { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; }不变

最新推荐

recommend-type

2014年直流电压电流采样仪生产方案:电路板、BOM单、STM单片机程序及应用 核心版

2014年设计的一款直流电压电流采样仪的整套产品生产方案。该产品已量产1000余套,适用于电力、电子、通信等领域。文中涵盖了硬件和软件两大部分的内容。硬件方面,包括电路板设计、BOM单、外围器件清单以及外壳设计;软件方面,则涉及STM单片机程序和配套的上位机电脑软件。该采样仪的最大测量范围为1000V/100A,具备高精度、高稳定性的特点,能记录并存储8组电压电流数据,并带有触发模式用于实时监测和故障诊断。 适合人群:从事电力、电子、通信领域的工程师和技术人员,尤其是对直流电压电流采样仪有需求的研发人员。 使用场景及目标:①帮助工程师和技术人员了解直流电压电流采样仪的整体设计方案;②提供详细的硬件和软件资料,便于实际生产和应用;③适用于需要高精度、高稳定性的电压电流测量场合。 其他说明:该产品已经成功量产并获得市场好评,文中提供的方案对于相关领域的项目开发具有重要参考价值。
recommend-type

Python程序TXLWizard生成TXL文件及转换工具介绍

### 知识点详细说明: #### 1. 图形旋转与TXL向导 图形旋转是图形学领域的一个基本操作,用于改变图形的方向。在本上下文中,TXL向导(TXLWizard)是由Esteban Marin编写的Python程序,它实现了特定的图形旋转功能,主要用于电子束光刻掩模的生成。光刻掩模是半导体制造过程中非常关键的一个环节,它确定了在硅片上沉积材料的精确位置。TXL向导通过生成特定格式的TXL文件来辅助这一过程。 #### 2. TXL文件格式与用途 TXL文件格式是一种基于文本的文件格式,它设计得易于使用,并且可以通过各种脚本语言如Python和Matlab生成。这种格式通常用于电子束光刻中,因为它的文本形式使得它可以通过编程快速创建复杂的掩模设计。TXL文件格式支持引用对象和复制对象数组(如SREF和AREF),这些特性可以用于优化电子束光刻设备的性能。 #### 3. TXLWizard的特性与优势 - **结构化的Python脚本:** TXLWizard 使用结构良好的脚本来创建遮罩,这有助于开发者创建清晰、易于维护的代码。 - **灵活的Python脚本:** 作为Python程序,TXLWizard 可以利用Python语言的灵活性和强大的库集合来编写复杂的掩模生成逻辑。 - **可读性和可重用性:** 生成的掩码代码易于阅读,开发者可以轻松地重用和修改以适应不同的需求。 - **自动标签生成:** TXLWizard 还包括自动为图形对象生成标签的功能,这在管理复杂图形时非常有用。 #### 4. TXL转换器的功能 - **查看.TXL文件:** TXL转换器(TXLConverter)允许用户将TXL文件转换成HTML或SVG格式,这样用户就可以使用任何现代浏览器或矢量图形应用程序来查看文件。 - **缩放和平移:** 转换后的文件支持缩放和平移功能,这使得用户在图形界面中更容易查看细节和整体结构。 - **快速转换:** TXL转换器还提供快速的文件转换功能,以实现有效的蒙版开发工作流程。 #### 5. 应用场景与技术参考 TXLWizard的应用场景主要集中在电子束光刻技术中,特别是用于设计和制作半导体器件时所需的掩模。TXLWizard作为一个向导,不仅提供了生成TXL文件的基础框架,还提供了一种方式来优化掩模设计,提高光刻过程的效率和精度。对于需要进行光刻掩模设计的工程师和研究人员来说,TXLWizard提供了一种有效的方法来实现他们的设计目标。 #### 6. 系统开源特性 标签“系统开源”表明TXLWizard遵循开放源代码的原则,这意味着源代码对所有人开放,允许用户自由地查看、修改和分发软件。开源项目通常拥有活跃的社区,社区成员可以合作改进软件,添加新功能,或帮助解决遇到的问题。这种开放性促进了技术创新,并允许用户根据自己的需求定制软件。 #### 7. 压缩包子文件的文件名称列表 文件名称列表中的“txlwizard-master”可能指的是TXLWizard项目的主版本库或主分支。这个名称表明了这是项目源代码的中心点,其他开发者会从这个主分支拉取代码进行合作开发或部署。以“-master”结尾通常是版本控制系统中表示主要开发线路的常见约定,例如Git中的master(现在更常被称为main)分支。 通过这些知识点的详细解释,我们可以看到TXLWizard不仅是一个用于生成TXL文件的工具,它还整合了一系列的功能,使得电子束光刻掩模的设计工作更为高效和直观。同时,作为一个开源项目,它能够借助社区的力量不断进步,为用户带来更多的便利和创新。
recommend-type

【创新图生成:扣子平台的技术前沿与创新思维】:引领图像生成技术的新潮流

# 1. 扣子平台的图像生成技术概述 在当今数字化时代,图像生成技术正成为推动技术创新和社会变革的关键力量。扣子平台正是这一技术革新的代表。本章将对扣子平台的图像生成技术进行一个全面概述,探究它如何将理论技术转化为实际应用,并带来显著的社会影响。 扣子平台运用了先进的图像生成技术,将现实世界和数字空间无缝融合,打造了沉浸式的视觉体验。其技术的核心是通过高度复杂的算法模型,快速生成高质
recommend-type

海康威视机器视觉工程师考核

<think>好的,我现在需要帮助用户了解海康威视机器视觉工程师的考核内容和要求。根据用户提供的引用资料,特别是引用[1]和[2],里面提到了考核素材包分为初级和中级,涵盖理论、算法、应用案例等。首先,我要整理这些信息,确保结构清晰,符合用户要求的格式。 接下来,我需要确认素材包的具体内容,比如初级和中级的不同点。引用[2]提到初级包含基础理论、算法实现和实际案例,中级则增加复杂算法和项目分析。这部分需要分点说明,方便用户理解层次。 另外,用户可能想知道如何准备考核,比如下载素材、学习顺序、模拟考核等,引用[2]中有使用说明和注意事项,这部分也要涵盖进去。同时要注意提醒用户考核窗口已关闭,
recommend-type

Linux环境下Docker Hub公共容器映像检测工具集

在给出的知识点中,我们需要详细解释有关Docker Hub、公共容器映像、容器编排器以及如何与这些工具交互的详细信息。同时,我们会涵盖Linux系统下的相关操作和工具使用,以及如何在ECS和Kubernetes等容器编排工具中运用这些检测工具。 ### Docker Hub 和公共容器映像 Docker Hub是Docker公司提供的一项服务,它允许用户存储、管理以及分享Docker镜像。Docker镜像可以视为应用程序或服务的“快照”,包含了运行特定软件所需的所有必要文件和配置。公共容器映像指的是那些被标记为公开可见的Docker镜像,任何用户都可以拉取并使用这些镜像。 ### 静态和动态标识工具 静态和动态标识工具在Docker Hub上用于识别和分析公共容器映像。静态标识通常指的是在不运行镜像的情况下分析镜像的元数据和内容,例如检查Dockerfile中的指令、环境变量、端口映射等。动态标识则需要在容器运行时对容器的行为和性能进行监控和分析,如资源使用率、网络通信等。 ### 容器编排器与Docker映像 容器编排器是用于自动化容器部署、管理和扩展的工具。在Docker环境中,容器编排器能够自动化地启动、停止以及管理容器的生命周期。常见的容器编排器包括ECS和Kubernetes。 - **ECS (Elastic Container Service)**:是由亚马逊提供的容器编排服务,支持Docker容器,并提供了一种简单的方式来运行、停止以及管理容器化应用程序。 - **Kubernetes**:是一个开源平台,用于自动化容器化应用程序的部署、扩展和操作。它已经成为容器编排领域的事实标准。 ### 如何使用静态和动态标识工具 要使用这些静态和动态标识工具,首先需要获取并安装它们。从给定信息中了解到,可以通过克隆仓库或下载压缩包并解压到本地系统中。之后,根据需要针对不同的容器编排环境(如Dockerfile、ECS、Kubernetes)编写配置,以集成和使用这些检测工具。 ### Dockerfile中的工具使用 在Dockerfile中使用工具意味着将检测工具的指令嵌入到构建过程中。这可能包括安装检测工具的命令、运行容器扫描的步骤,以及将扫描结果集成到镜像构建流程中,确保只有通过安全和合规检查的容器镜像才能被构建和部署。 ### ECS与Kubernetes中的工具集成 在ECS或Kubernetes环境中,工具的集成可能涉及到创建特定的配置文件、定义服务和部署策略,以及编写脚本或控制器来自动执行检测任务。这样可以在容器编排的过程中实现实时监控,确保容器编排器只使用符合预期的、安全的容器镜像。 ### Linux系统下的操作 在Linux系统下操作这些工具,用户可能需要具备一定的系统管理和配置能力。这包括使用Linux命令行工具、管理文件系统权限、配置网络以及安装和配置软件包等。 ### 总结 综上所述,Docker Hub上的静态和动态标识工具提供了一种方法来检测和分析公共容器映像,确保这些镜像的安全性和可靠性。这些工具在Linux开发环境中尤为重要,因为它们帮助开发人员和运维人员确保他们的容器映像满足安全要求。通过在Dockerfile、ECS和Kubernetes中正确使用这些工具,可以提高应用程序的安全性,减少由于使用不安全的容器镜像带来的风险。此外,掌握Linux系统下的操作技能,可以更好地管理和维护这些工具,确保它们能够有效地发挥作用。
recommend-type

【扣子平台图像艺术探究:理论与实践的完美结合】:深入学习图像生成的艺术

# 1. 图像艺术的理论基础 艺术领域的每一个流派和技巧都有其理论基础。在图像艺术中,理论基础不仅是对艺术表现形式的认知,也是掌握艺术创作内在逻辑的关键。深入理解图像艺术的理论基础,能够帮助艺术家们在创作过程中更加明确地表达自己的艺术意图,以及更好地与观众沟通。 图像艺术的理论
recommend-type

增广路定理的证明

<think>嗯,用户想查找增广路定理的证明,关于图论和网络流算法的。我得先理解增广路定理是什么,然后找到相关的证明方法。根据之前的引用,尤其是引用5提到最大流最小割定理,里面有三个等价条件,其中第二个是残余网络中没有增广路径时,流就是最大流。这可能和增广路定理相关,也就是当残余网络中没有增广路时,当前流就是最大流,这可能就是增广路定理的内容。 首先,我需要明确增广路定理的陈述。根据引用5,增广路定理可能指的是:一个流是最大流当且仅当残余网络中不存在增广路径。这个定理的证明需要用到最大流最小割定理,也就是第三个条件,即最大流的流量等于最小割的容量。 证明的步骤可能需要分为两个方向:必要性(
recommend-type

Pulse:基于SwiftUI的Apple平台高效日志记录与网络监控

从给定文件信息中,我们可以提取出以下IT知识点进行详细阐述: **Pulse概览:** Pulse是一个专门针对Apple平台(如iOS、iPadOS、macOS等)的功能强大的日志记录系统。其设计目的是为了简化开发者在这些平台上调试网络请求和应用日志的过程。Pulse的核心特色是它使用SwiftUI来构建,这有助于开发者利用现代Swift语言的声明式UI优势来快速开发和维护。 **SwiftUI框架:** SwiftUI是一种声明式框架,由苹果公司推出,用于构建用户界面。与传统的UIKit相比,SwiftUI使用更加简洁的代码来描述界面和界面元素,它允许开发者以声明的方式定义视图和界面布局。SwiftUI支持跨平台,这意味着同一套代码可以在不同的Apple设备上运行,大大提高了开发效率和复用性。Pulse选择使用SwiftUI构建,显示了其对现代化、高效率开发的支持。 **Network Inspector功能:** Pulse具备Network Inspector功能,这个功能使得开发者能够在开发iOS应用时,直接从应用内记录和检查网络请求和日志。这种内嵌式的网络诊断能力非常有助于快速定位网络请求中的问题,如不正确的URL、不返回预期响应等。与传统的需要外部工具来抓包和分析的方式相比,这样的内嵌式工具大大减少了调试的复杂性。 **日志记录和隐私保护:** Pulse强调日志是本地记录的,并保证不会离开设备。这种做法对隐私保护至关重要,尤其是考虑到当前数据保护法规如GDPR等的严格要求。因此,Pulse的设计在帮助开发者进行问题诊断的同时,也确保了用户数据的安全性。 **集成和框架支持:** Pulse不仅仅是一个工具,它更是一个框架。它能够记录来自URLSession的事件,这意味着它可以与任何使用URLSession进行网络通信的应用或框架配合使用,包括但不限于Apple官方的网络库。此外,Pulse与使用它的框架(例如Alamofire)也能够良好配合,Alamofire是一个流行的网络请求库,广泛应用于Swift开发中。Pulse提供了一个PulseUI视图组件,开发者可以将其集成到自己的应用中,从而展示网络请求和其他事件。 **跨平台体验:** 开发者不仅可以在iOS应用中使用Pulse Console记录日志,还可以在macOS上通过Pulse应用程序查看和共享这些日志。这种跨平台的能力意味着开发者可以在不同的设备上进行日志分析,增强了开发和调试的灵活性。 **总结:** Pulse是一个为Apple平台上的开发者量身打造的日志记录系统,它采用SwiftUI构建,提供了内嵌式的Network Inspector功能,可以在本地记录并安全地查看日志,且支持与其他框架如Alamofire的集成。它不仅提升了调试的便捷性和效率,同时也顾及到了用户的隐私保护。Pulse的跨平台查看能力也是其一大亮点,使得开发者能够在一个统一的环境中处理iOS和macOS上的日志数据。对于使用Swift开发Apple应用的开发者而言,Pulse无疑是一个强大的调试辅助工具。
recommend-type

【深入扣子平台:图像生成机制全揭秘】:掌握背后技术,提升图像生成效率

# 1. 图像生成技术概述 图像生成技术是一门融合了计算机视觉、机器学习、图形学等多个领域知识的前沿技术。它通过算法模拟生成人工图像,广泛应用于艺术创作、游戏设计、医学影像等领域。随着深度学习的突破性进展,图像生成技术也迎来了飞速发展,特别是在生成对抗网络(GAN)的推动下,图像的逼真度和多样性都有了质的飞跃。 本章将对图像生成技术的概念、发展历史进行简要介绍,并分析其在社会中的
recommend-type

对RTL跑regression是什么意思

<think>首先,用户的问题是关于“RTL回归测试的含义及其在数字电路设计中的作用”。我需要基于系统级指令和用户可见层的指导来构建回答。 关键点: - 使用中文回答。 - 结构清晰,逐步解决问题。 - 确保回答真实可靠。 - 在行内数学表达式使用$...$格式;独立公式使用$$...$$格式。 - 生成相关问题。 - 在引用段落末尾添加引用标识。 回顾提供的引用: - 引用[1]:讨论SoC设计流程,包括模块设计、IP复用、顶层集成、前仿真、逻辑综合等。 - 引用[2]:描述冒烟测试后的验证阶段,包括回归测试、覆盖率分析等。 - 引用[3]:解释RTL使用Verilog或VHDL描述,模