活动介绍

{Factory: "北京工厂", ID: 2006, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "张三李四",…} 1 : {Factory: "北京工厂", ID: 2007, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "张三李四",…} 2 : {Factory: "北京工厂", ID: 2008, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 3 : {Factory: "北京工厂", ID: 2009, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 4 : {Factory: "北京工厂", ID: 2010, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 5 : {Factory: "北京工厂", ID: 2011, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 6 : {Factory: "北京工厂", ID: 2012, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 7 : {Factory: "北京工厂", ID: 2013, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 8 : {Factory: "北京工厂", ID: 2014, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 9 : {Factory: "北京工厂", ID: 2015, ShopName: "门店名称888", VisitTime: "2025-03-05 00:00:00", VisitPerson: "王二",…} 1 : [{ID: 2006, Name: "交流无刷", Value: "售后沟通1"}, {ID: 2007, Name: "机电", Value: "售后沟通2"},…] 0 : {ID: 2006, Name: "交流无刷", Value: "售后沟通1"} 1 : {ID: 2007, Name: "机电", Value: "售后沟通2"} 2 : {ID: 2008, Name: "交流", Value: "售后沟通123"} 3 : {ID: 2009, Name: "交流无刷", Value: "售后沟通123123"} 4 : {ID: 2010, Name: "交流", Value: "售后沟通123"} 5 : {ID: 2011, Name: "交流无刷", Value: "售后沟通123123"} 6 : {ID: 2012, Name: "交流", Value: "售后沟通123"} 7 : {ID: 2013, Name: "交流无刷", Value: "售后沟通123123"} 8 : {ID: 2014, Name: "交流", Value: "售后沟通123"} 9 : {ID: 2015, Name: "交流无刷", Value: "售后沟通123123"} 10 : {ID: 2006, Name: "交流无刷", Value: "售后沟通1"} 11 : {ID: 2007, Name: "机电", Value: "售后沟通2"} 12 : {ID: 2008, Name: "交流", Value: "售后沟通123"} 13 : {ID: 2009, Name: "交流无刷", Value: "售后沟通123123"} 14 : {ID: 2010, Name: "交流", Value: "售后沟通123"} 15 : {ID: 2011, Name: "交流无刷", Value: "售后沟通123123"} 16 : {ID: 2012, Name: "交流", Value: "售后沟通123"} 17 : {ID: 2013, Name: "交流无刷", Value: "售后沟通123123"} 18 : {ID: 2014, Name: "交流", Value: "售后沟通123"} 19 : {ID: 2015, Name: "交流无刷", Value: "售后沟通123123"} 接口返回结果是这样的,返回了两个数组,如何让两个数组ID列相等的数据进行嵌套,从而返回一个数组

时间: 2025-04-09 22:57:36 浏览: 24
<think>好的,我现在需要解决用户的问题:如何将接口返回的两个数组根据ID列进行嵌套,合并成一个数组。首先,我需要理解用户提供的数据结构。 用户给出的数据结构有两个数组,第一个数组包含多个对象,每个对象有Factory、ID、ShopName等字段,其中ID从2006到2015。第二个数组是包含多个子对象的数组,每个子对象有ID、Name、Value字段,ID也是从2006到2015,但看起来有重复,比如ID 2006出现了两次。 用户的目标是将这两个数组中ID相同的对象合并,嵌套到一个新的数组中。也就是说,对于每个ID,需要将第一个数组中的主对象和第二个数组中对应的子对象合并。 接下来,我需要考虑如何实现这个功能。首先,可能需要遍历第一个数组中的每个对象,然后根据它们的ID在第二个数组中查找匹配的对象。如果找到,就将第二个数组中的相关字段合并到第一个数组的对象中。 需要注意的是,第二个数组中的ID可能有重复的情况,比如ID 2006出现了两次。这时候需要决定如何处理重复项:是合并所有匹配项,还是只取第一个匹配项?用户的需求中没有明确说明这点,可能需要进一步确认。但根据常规处理方式,可能每个主数组对象对应多个子数组项,这时候可能需要将子数组项作为数组嵌套进去,或者根据具体情况处理。 假设用户希望将每个主对象与所有匹配的子对象合并,那么可能需要将子对象作为数组属性添加到主对象中。例如,主对象可能有一个新属性如details,包含所有匹配的子对象。 另外,考虑到两个数组的ID可能存在不匹配的情况,比如某些ID在其中一个数组中存在而在另一个中不存在,这时候需要决定是否保留这些不匹配的数据。根据用户的需求,可能只需要保留能匹配上的数据,或者保留所有主数组的数据,即使没有匹配的子项。 根据用户给出的示例数据,主数组中的ID是唯一的,从2006到2015,而子数组中的ID有重复,例如2006出现了两次。因此,合并时可能需要将主数组的每个对象与子数组中所有相同ID的对象合并,形成嵌套结构。 接下来,考虑具体的实现步骤。使用JavaScript的话,可以这样做: 1. 遍历主数组中的每个对象。 2. 对于每个主对象,获取其ID。 3. 在子数组中过滤出所有ID等于当前主对象ID的子对象。 4. 将过滤出的子对象数组作为新属性添加到主对象中。 5. 返回处理后的主数组,形成合并后的新数组。 这样,每个主对象都会包含一个子数组,里面是所有ID匹配的子对象。如果子数组中没有匹配的ID,则该子数组为空。 现在需要考虑如何处理重复的ID。例如,主数组中的ID 2006对应子数组中的两个对象,合并后主对象的details数组会有两个元素。这可能符合用户的需求,但需要确认。 用户可能希望将子对象中的多个项合并到主对象的不同属性中,或者以某种方式整合。但根据用户的问题描述,只是要求“嵌套”,因此将子对象作为数组嵌套进去可能是合适的。 最后,生成的结果数组将包含主数组的所有对象,每个对象新增了一个属性(例如details),其中包含对应的子数组项。如果用户希望将子对象的属性直接合并到主对象中,而不是嵌套,则需要不同的处理方式,比如展开子对象的属性。但根据问题描述,“嵌套”更可能是指将子对象作为子结构包含进去。 总结步骤: - 使用主数组的每个元素,查找子数组中所有相同ID的元素。 - 将找到的子元素合并到主元素中,可能作为数组属性。 - 返回合并后的主数组。 现在需要验证这个思路是否正确,是否符合用户的需求。例如,用户提供的示例数据中,主数组有多个ID,而子数组有重复的ID,合并后每个主对象会包含多个子对象,这可能正确。如果用户需要其他形式的合并,可能需要调整,但根据当前信息,这是合理的处理方式。</think>根据数据结构分析,我们可以通过以下步骤实现嵌套合并: $$数组合并算法步骤$$ 1. 创建哈希映射存储第二个数组的数据(时间复杂度$O(n)$) 2. 遍历第一个数组进行匹配(时间复杂度$O(n)$) 3. 合并数据形成嵌套结构(空间复杂度$O(n)$) 使用JavaScript实现的代码示例: ```javascript function mergeArrays(mainArr, subArr) { // 创建哈希映射 {ID: [对应子项]} const subMap = subArr.reduce((map, item) => { map[item.ID] = map[item.ID] || []; map[item.ID].push(item); return map; }, {}); // 合并主数组与子数组 return mainArr.map(mainItem => ({ ...mainItem, details: subMap[mainItem.ID] || [] })); } ``` $$数学表达式说明$$ 假设主数组元素数量为$n$,子数组元素数量为$m$,则: - 时间复杂度:$O(n + m)$ - 空间复杂度:$O(m)$ 实际应用示例: ```javascript // 主数组 const factories = [ {Factory: "北京工厂", ID: 2006, ShopName: "门店名称888"}, {Factory: "北京工厂", ID: 2007, ShopName: "门店名称888"} ]; // 子数组 const details = [ {ID: 2006, Name: "交流无刷", Value: "售后沟通1"}, {ID: 2006, Name: "机电", Value: "售后沟通2"}, {ID: 2007, Name: "交流", Value: "售后沟通123"} ]; console.log(mergeArrays(factories, details)); ``` 输出结果将会是: ```json [ { "Factory": "北京工厂", "ID": 2006, "ShopName": "门店名称888", "details": [ {"ID": 2006, "Name": "交流无刷", "Value": "售后沟通1"}, {"ID": 2006, "Name": "机电", "Value": "售后沟通2"} ] }, { "Factory": "北京工厂", "ID": 2007, "ShopName": "门店名称888", "details": [ {"ID": 2007, "Name": "交流", "Value": "售后沟通123"} ] } ] ``` 该解决方案特点: 1. 使用哈希映射提升查找效率 2. 保留原始数据结构不变性 3. 处理重复ID的情况(同一主记录对应多个子记录) 4. 自动过滤无效ID匹配(未匹配的ID返回空数组)
阅读全文

相关推荐

<el-dialog :visible.sync="setModelVisible"><el-form-item label="选择活动模板" required> <el-select v-model="ruleForm.subTemplateId" placeholder="请选择活动模板" style="width: 60%;" @change="ruleFormSubTemplateIdChange"> <el-option v-for="item in selectList" :key="item.id" :label="item.title" :value="item.id" /> </el-select> </el-form-item> <el-form-item :label="contentItem.subscriptionLabel" required> <el-date-picker v-if="contentItem.subscriptionKey.includes('time')||contentItem.subscriptionKey.includes('date')" v-model="contentItem.subscriptionValue" type="datetime" placeholder="选择日期时间" clearable /> <el-input-number v-else-if="contentItem.subscriptionKey.includes('number')" v-model="contentItem.subscriptionValue" :precision="2" :step="0.1" clearable /> <el-input v-model="contentItem.subscriptionValue" style="width: 60%;" maxlength="20" clearable /> 可自定义文字,最大20字 </el-form-item> </el-dialog>export default { data() { return {currentPopCardData: {}, currentPopCardDataForm: {}, shopInfo: { shopName: '', headPic: '', }, } }, methods: {ruleFormSubTemplateIdChange(id) { this.currentPopCardData = this.selectList.find(item => item.id === id) || {} this.currentPopCardDataForm.contentList = [] this.currentPopCardDataForm.title = this.currentPopCardData.title const contentList = [] if (this.currentPopCardData.contentList) { this.currentPopCardData.contentList.forEach(o => { contentList.push({ subscriptionLabel: o.subscriptionKey, subscriptionKey: o.subscriptionValue, subscriptionValue: null }) }) } this.$set(this.currentPopCardDataForm, 'contentList', contentList) },}弹窗打开以后,在如<el-input-number v-else-if="contentItem.subscriptionKey.includes('number')" v-model="contentItem.subscriptionValue" :precision="2" :step="0.1" clearable />的输入框内无法输入内容

<template> <el-input style="width: 220px;" placeholder="请输入用户信息" /> <el-input style="width: 220px;" placeholder="请输入旅行信息" /> <el-date-picker type="date" placeholder="请选择开始日期" /> <el-date-picker type="date" placeholder="请选择结束日期" /> <el-button type="primary" :icon="Search">搜索</el-button> <el-table ref="tableRef" row-key="orderStatus" :data="tableData" style="width: 100%"> <el-table-column prop="phone" label="用户信息" width="144" /> <el-table-column prop="trip" label="旅行行程" width="144" /> <el-table-column prop="orderstatus" label="订单状态" width="144" column-key="date" :filters="[ { text: '拒绝', value: '2' }, { text: '待处理', value: '1' }, { text: '已处理', value: '0' }, ]" :filter-method="filterHandler"> <template #default="scope"> {{ formatOrderStatus(scope.row) }} </template> </el-table-column> <el-table-column prop="refundstatus" label="退款状态" width="144"> <template #default="scope"> {{ formatRefundStatus(scope.row) }} </template> </el-table-column> <el-table-column prop="discount" label="折扣信息" width="144" :formatter="(row) => { const value = row.discount * 10; return ${value % 1 === 0 ? value.toFixed(0) : value.toFixed(1)}折; }" /> <el-table-column prop="price" label="订单价格" width="144"> <template #default="scope"> ¥{{ scope.row.price.toFixed(2) }} </template> </el-table-column> <el-table-column prop="transactionprice" label="实际成交价" width="144"> <template #default="scope"> ¥{{ scope.row.transactionprice.toFixed(2) }} </template> </el-table-column> <el-table-column prop="createtime" label="下单时间" width="144"> <template #default="scope"> {{ formatTime(scope.row.createtime) }} </template> </el-table-column> <el-table-column prop="shopname" label="门店" width="144" /> <el-table-column label="操作" width="144"> <template #default="scope"> <el-button type="primary" class="detail-btn" @click="handleDetail()">详情</el-button> </template> </el-table-column> <el-table-column label="退款操作" width="144"> <template #default="scope"> <el-button type="danger" class="refund-btn">退款</el-button> </template> </el-table-column> </el-table> 0" :total="pagination.total" :page-size="pagination.pageSize" :current-page="pagination.currentPage" @page-change="handlePageChange" @size-change="handleSizeChange" /> </template> <script setup lang='ts'> import { post } from '@/utils/request'; import { ref, onMounted } from 'vue'; import type { TableColumnCtx, TableInstance } from 'element-plus'; import { Search } from '@element-plus/icons-vue' import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; import dayjs from 'dayjs'; import Pagination from '@/components/Pagination.vue'; const pagination = ref({ total: 0, pageSize: 5, currentPage: 1 }); // 引入中文语言包(如果需要中文显示) import 'dayjs/locale/zh-cn'; dayjs.locale('zh-cn'); // 格式化时间函数 const formatTime = (createtime: string) => { if (!createtime) return '--'; return dayjs(createtime).format('YYYY-MM-DD HH:mm:ss'); } const router = useRouter() interface Order { phone: string; trip: string; orderstatus: number; // 保留原有的 status 属性 refundstatus: number; discount: string; price: number; transactionprice: number; createtime: string; shopname: string; [key: string]: any; // 显式声明索引签名 } interface PaginationParams { pageIndex: number; pageSize: number; [key: string]: any; // 允许其他参数 } interface ApiResponse { msg: string; code: number; dataset: { pageCount: number; orderInfo: Order[]; }; } const tableRef = ref(); const formatOrderStatus = (row: Order) => { const statusMap: Record<string, string> = { '0': '已处理', '1': '待处理', '2': '拒绝' }; return statusMap[String(row.orderstatus)] || '未知状态'; }; const formatRefundStatus = (row: Order) => { const Rstatus = String(row.refundstatus); const RstatusMap: Record<string, string> = { '0': '未退款', '1': '已退款' } return RstatusMap[Rstatus] || '未知状态'; } // 确保过滤方法正确处理类型 const filterHandler = ( value: string, row: Order, column: TableColumnCtx<Order> ) => { const property = column.property; return String(row[property]) === value; }; const tableData = ref<Order[]>([]) onMounted(() => { getOrder(); }) const getOrder = async () => { try { const params: PaginationParams = { pageIndex: pagination.value.currentPage, pageSize: pagination.value.pageSize, }; const res = await post<ApiResponse>('orders/getOrderInfo', params); if (res.code === 200) { // 确保数据结构正确 if (res.dataset && Array.isArray(res.dataset.orderInfo)) { tableData.value = res.dataset.orderInfo; // 设置总记录数 pagination.value.total = res.dataset.pageCount || 0; console.log('获取的数据:', res.dataset,pagination.value.total); } else { // 处理意外的数据结构 console.error('API 返回了意外的数据结构:', res); ElMessage.error('数据格式错误,请检查 API 响应'); } } else { ElMessage.error(res.msg || '获取数据失败'); } } catch (error) { console.log(error); } } // 处理页码变化 const handlePageChange = (page: number, pageSize: number) => { pagination.value.currentPage = page; getOrder(); }; // 处理每页显示数量变化 const handleSizeChange = (size: number) => { pagination.value.pageSize = size; pagination.value.currentPage = 1; // 重置到第一页 getOrder(); }; // 点击跳转详情 const handleDetail = () => { router.push('/orderDetail') }; </script> <style scoped lang='less'> .line { width: 50px; height: 2px; background-color: #333; } .main { width: 100%; height: 100%; } .top { display: flex; align-items: center; justify-content: right; gap: 15px; padding: 20px; background: linear-gradient(to right, #f8fafc, #f0f7ff); border-radius: 12px; margin-bottom: 25px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03); transition: all 0.3s ease; &:hover { box-shadow: 0 6px 16px rgba(0, 0, 0, 0.05); } } .detail { cursor: pointer; } .bottom { max-width: 1600px; margin: 0 auto; padding: 30px; background: #ffffff; border-radius: 16px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } // 表格样式优化 :deep(.el-table) { --el-table-border-color: #ebeef5; --el-table-header-bg-color: #f5f7fa; --el-table-row-hover-bg-color: #f5f7fa; th { background-color: #f5f7fa; color: #2c3e50; font-weight: 600; font-size: 15px; } } // 状态标签样式 .status-tag { display: inline-block; padding: 6px 12px; border-radius: 20px; font-size: 13px; font-weight: 600; text-align: center; min-width: 70px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } /* 订单状态样式 */ .status-0 { /* 已处理 */ background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; } .status-1 { /* 待处理 */ background-color: #fff8e1; color: #f57f17; border: 1px solid #ffecb3; } .status-2 { /* 拒绝 */ background-color: #ffebee; color: #c62828; border: 1px solid #ffcdd2; } .refund-0 { /* 未退款 */ background-color: #f5f5f5; color: #616161; border: 1px solid #e0e0e0; } .refund-1 { /* 已退款 */ background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; } // 时间高亮样式 .time-highlight { color: #1890ff; font-weight: 600; padding: 5px 10px; border-radius: 6px; background: rgba(24, 144, 255, 0.08); display: inline-block; font-family: 'Courier New', monospace; transition: all 0.3s ease; &:hover { background: rgba(24, 144, 255, 0.15); transform: translateX(2px); } } // 价格样式 .price-value { color: #f56c6c; font-weight: 600; } .transaction-value { color: #67c23a; font-weight: 600; } // 按钮样式 .detail-btn { border-radius: 8px; padding: 8px 15px; background-color: #3498db; border-color: #3498db; transition: all 0.3s ease; &:hover { background-color: #2980b9; transform: scale(1.05); } } .refund-btn { border-radius: 8px; padding: 8px 15px; transition: all 0.3s ease; &:not(:disabled):hover { transform: scale(1.05); box-shadow: 0 0 10px rgba(231, 76, 60, 0.3); } &:disabled { opacity: 0.7; cursor: not-allowed; } } </style><template> <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[5, 10, 20, 50]" :size="size" :background="true" :layout="'total, sizes, prev, pager, next, jumper'" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </template> <script setup lang="ts"> import { ref, watch } from 'vue'; import type { ComponentSize } from 'element-plus' const size = ref<ComponentSize>('default') const props = defineProps({ total: { type: Number, required: true, default: 0 }, pageSize: { type: Number, default: 10 }, currentPage: { type: Number, default: 1 } }); const emit = defineEmits(['page-change', 'size-change']); const currentPage = ref(props.currentPage); const pageSize = ref(props.pageSize); // 监听属性变化 watch( () => props.currentPage, (val) => { currentPage.value = val; } ); watch( () => props.pageSize, (val) => { pageSize.value = val; } ); // 处理每页显示数量变化 const handleSizeChange = (val: number) => { pageSize.value = val; emit('size-change', val); emit('page-change', currentPage.value, val); }; // 处理当前页变化 const handleCurrentChange = (val: number) => { currentPage.value = val; emit('page-change', val, pageSize.value); }; </script> <style scoped lang="less"> .pagination-container { margin-top: 20px; display: flex; justify-content: flex-end; padding: 10px 0; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); .el-pagination { padding: 0 15px; } } </style>分页功能有问题总页数无法正常显示

package main import ( "errors" "fmt" "image" "image/color" "log" "net/http" "os" "path/filepath" "runtime" "strconv" "strings" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "main.go/dataModel/CookieModel" "main.go/dataModel/ShopModel" "main.go/dataModel/SkuModel" "main.go/dataModel/UserModel" "main.go/res" "main.go/tuuz/database" ) // 新增分页状态结构体 type PaginationState struct { CurrentPage int PageSize int TotalPages int TotalProducts int Products []SkuModel.DataItem } // 全局状态 type AppState struct { Window fyne.Window CurrentUser UserModel.UserInfo Shops []ShopModel.Account ProductTabs *container.AppTabs StatusBar *widget.Label ShopListBinding binding.UntypedList LoginForm *widget.Form LeftPanel *fyne.Container // 改为存储整个左侧面板容器 FilterFilePath string FilterKeywords []string ShopListPanel *fyne.Container // 新增:存储店铺列表面板 FilterPanel *fyne.Container // 存储过滤面板引用 KeywordCount *widget.Label // 存储关键字计数标签 TabShopMap map[string]ShopModel.Account // 新增:存储标签页与店铺的映射 SplitContainer *container.Split // 新增:保存分割布局引用 TopPanel *fyne.Container // 新增:保存顶部面板引用 ContentPanel *fyne.Container // 新增:保存内容面板引用 NeedsRefresh bool // 新增:状态变更标志 LastRefreshTime time.Time // 新增:最后刷新时间 CurrentPage int // 当前页码 PageSize int // 每页显示数量 TotalProducts int // 商品总数 TotalPages int // 总页数 PaginationStates map[string]*PaginationState // [店铺名称]分页状态 } // 添加状态检查快捷键 func addStateDebugShortcut(window fyne.Window, appState *AppState) { window.Canvas().SetOnTypedKey(func(ev *fyne.KeyEvent) { if ev.Name == fyne.KeyF5 { // 手动刷新UI refreshLeftPanel(appState) appState.StatusBar.SetText("手动刷新UI") } else if ev.Name == fyne.KeyS { // 打印状态快照 fmt.Println("===== 应用状态快照 =====") fmt.Printf("当前用户: %s\n", appState.CurrentUser.LoginName) fmt.Printf("店铺数量: %d\n", len(appState.Shops)) fmt.Printf("最后刷新时间: %s\n", appState.LastRefreshTime.Format("15:04:05.000")) fmt.Printf("左侧面板: %T\n", appState.LeftPanel) fmt.Printf("分割布局: %T\n", appState.SplitContainer) fmt.Println("=======================") } }) } func main() { os.Setenv("PLAYWRIGHT_BROWSERS_PATH", "./browsers") database.Init() UserModel.UserInit() ShopModel.ShopInit() CookieModel.CreateCookieInfoTable() SkuModel.ProductInit() myApp := app.New() myWindow := myApp.NewWindow("店铺管理工具") myWindow.Resize(fyne.NewSize(1200, 800)) // 初始化应用状态 appState := &AppState{ FilterFilePath: getDefaultFilterPath(), TabShopMap: make(map[string]ShopModel.Account), // 初始化映射 LastRefreshTime: time.Now(), CurrentPage: 1, // 默认第一页 PageSize: 10, // 默认每页10条 PaginationStates: make(map[string]*PaginationState), // 初始化分页状态 } // 注册调试快捷键 addStateDebugShortcut(myWindow, appState) // 启动状态监听器 startStateListener(appState) // 尝试加载默认过滤文件 go loadFilterFile(appState) // 创建状态栏 appState.StatusBar = widget.NewLabel("就绪") statusBar := container.NewHBox(layout.NewSpacer(), appState.StatusBar) // 创建主布局 mainContent := createMainUI(myWindow, appState) // 设置整体布局 content := container.NewBorder( nil, // 顶部 statusBar, // 底部 nil, // 左侧 nil, // 右侧 mainContent, ) myWindow.SetContent(content) // 启动时尝试自动登录 go tryAutoLogin(appState) myWindow.ShowAndRun() } // 新增状态监听器 - 定期检查状态变化 func startStateListener(appState *AppState) { go func() { for { time.Sleep(100 * time.Millisecond) // 每100ms检查一次 if appState.NeedsRefresh { fyne.DoAndWait(func() { refreshLeftPanel(appState) appState.NeedsRefresh = false }) } } }() } // 获取默认过滤文件路径 func getDefaultFilterPath() string { if runtime.GOOS == "windows" { return filepath.Join("filter.txt") } return filepath.Join(os.Getenv("HOME"), "filter.txt") } // 修改 refreshAllProductTabs 函数 func refreshAllProductTabs(appState *AppState) { if appState.ProductTabs == nil || len(appState.ProductTabs.Items) == 0 { return } // 遍历所有标签页并刷新 for _, tab := range appState.ProductTabs.Items { // 通过标签页标题获取店铺 shop, exists := appState.TabShopMap[tab.Text] if !exists { continue } // 重新加载商品 go func(shop ShopModel.Account) { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("刷新 %s 商品失败: %s", shop.AccountName, err.Error())) }) return } fyne.DoAndWait(func() { // 更新标签页内容 tab.Content = container.NewMax(createProductList(products)) appState.ProductTabs.Refresh() appState.StatusBar.SetText(fmt.Sprintf("已刷新 %s 的商品", shop.AccountName)) }) }(shop) } } // 加载过滤文件 func loadFilterFile(appState *AppState) { if appState.FilterFilePath == "" { log.Printf("加载本地过滤文件失败: %s", appState.FilterFilePath) return } if _, err := os.Stat(appState.FilterFilePath); os.IsNotExist(err) { err := os.WriteFile(appState.FilterFilePath, []byte{}, 0644) if err != nil { log.Printf("创建过滤文件失败: %v", err) } return } content, err := os.ReadFile(appState.FilterFilePath) if err != nil { log.Printf("读取过滤文件失败: %v", err) return } lines := strings.Split(string(content), "\n") appState.FilterKeywords = []string{} for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed != "" { appState.FilterKeywords = append(appState.FilterKeywords, trimmed) } } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个过滤关键字", len(appState.FilterKeywords))) // 更新关键字数量标签 if appState.KeywordCount != nil { // 修正为 KeywordCount appState.KeywordCount.SetText(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) } // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }) } // 修改 createMainUI 函数 - 保存分割布局引用 func createMainUI(window fyne.Window, appState *AppState) fyne.CanvasObject { appState.Window = window // 创建整个左侧面板 leftPanel := createLeftPanel(window, appState) appState.LeftPanel = leftPanel // 右侧面板 appState.ProductTabs = container.NewAppTabs() appState.ProductTabs.SetTabLocation(container.TabLocationTop) rightPanel := container.NewBorder( widget.NewLabelWithStyle("商品信息", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), nil, nil, nil, container.NewMax(appState.ProductTabs), ) // 使用HSplit布局 - 保存引用 split := container.NewHSplit(leftPanel, rightPanel) split.SetOffset(0.25) appState.SplitContainer = split // 保存分割布局引用 return split } // 修改createFilterPanel函数 - 返回容器并保存引用 func createFilterPanel(appState *AppState) *fyne.Container { // 创建文件路径标签 pathLabel := widget.NewLabel("过滤文件: " + appState.FilterFilePath) pathLabel.Wrapping = fyne.TextWrapWord // 创建选择文件按钮 selectButton := widget.NewButton("选择过滤文件", func() { dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil { dialog.ShowError(err, appState.Window) return } if reader == nil { return // 用户取消 } // 更新文件路径 appState.FilterFilePath = reader.URI().Path() pathLabel.SetText("过滤文件: " + appState.FilterFilePath) // 加载过滤文件 go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() }, appState.Window) }) // 创建刷新按钮 refreshButton := widget.NewButton("刷新过滤", func() { if appState.FilterFilePath != "" { appState.StatusBar.SetText("刷新过滤关键字...") go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() } else { appState.StatusBar.SetText("请先选择过滤文件") } }) // 创建"增加商品"按钮 addProductsButton := widget.NewButton("增加商品", func() { if appState.ProductTabs.Selected() == nil { appState.StatusBar.SetText("请先选择一个店铺标签页") return } shopName := appState.ProductTabs.Selected().Text appState.StatusBar.SetText(fmt.Sprintf("为 %s 增加1000条商品...", shopName)) go func() { // 生成1000条模拟商品 newProducts := make([]SkuModel.DataItem, 1000) for i := 0; i < 1000; i++ { newProducts[i] = SkuModel.DataItem{ ProductID: fmt.Sprintf("ADD%04d", i+1), Name: fmt.Sprintf("%s - 新增商品%d", shopName, i+1), MarketPrice: (i + 1000) * 1000, // 从1000开始 DiscountPrice: (i + 1000) * 800, // 折扣价 } } fyne.DoAndWait(func() { // 获取该店铺的TabState tabState, exists := appState.PaginationStates[shopName] if !exists { // 如果不存在,则创建一个新的TabState tabState = &PaginationState{ PageSize: 10, CurrentPage: 1, } appState.PaginationStates[shopName] = tabState } // 添加到现有商品列表 tabState.Products = append(tabState.Products, newProducts...) // 刷新当前标签页 refreshCurrentProductTab(appState, shopName, tabState.Products) appState.StatusBar.SetText(fmt.Sprintf("已为 %s 增加1000条商品,总数: %d", shopName, len(tabState.Products))) }) }() }) // 修改按钮容器,添加新按钮 buttonContainer := container.NewHBox( selectButton, refreshButton, addProductsButton, // 新增按钮 ) // 创建关键字计数标签 - 保存引用 keywordCount := widget.NewLabel(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) keywordCount.TextStyle = fyne.TextStyle{Bold: true} appState.KeywordCount = keywordCount // 创建面板容器 panel := container.NewVBox( widget.NewSeparator(), widget.NewLabelWithStyle("商品过滤", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), pathLabel, keywordCount, buttonContainer, ) return panel } // 修改 createLoggedInPanel 函数 - 确保注销时直接刷新 func createLoggedInPanel(appState *AppState) fyne.CanvasObject { return container.NewVBox( widget.NewLabelWithStyle("登录状态", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewSeparator(), container.NewHBox( widget.NewLabel("用户:"), widget.NewLabel(appState.CurrentUser.LoginName), ), container.NewHBox( widget.NewLabel("店铺数量:"), widget.NewLabel(fmt.Sprintf("%d", len(appState.Shops))), ), widget.NewSeparator(), container.NewCenter( widget.NewButton("注销", func() { // 重置状态 appState.CurrentUser = UserModel.UserInfo{} appState.Shops = nil appState.ProductTabs.Items = nil appState.ProductTabs.Refresh() appState.TabShopMap = make(map[string]ShopModel.Account) // 直接调用刷新函数 refreshLeftPanel(appState) appState.StatusBar.SetText("已注销") }), ), ) } // 重构创建顶部面板函数 - 确保状态正确反映 func createTopPanel(appState *AppState) *fyne.Container { // 添加调试日志 fmt.Printf("创建顶部面板: 登录状态=%t, 用户名=%s\n", appState.CurrentUser.LoginName != "", appState.CurrentUser.LoginName) var content fyne.CanvasObject if appState.CurrentUser.LoginName != "" { content = createLoggedInPanel(appState) } else { content = createLoginForm(appState) } return container.NewMax(content) } // 重构 createContentPanel 函数 - 添加详细日志 func createContentPanel(appState *AppState) *fyne.Container { // 添加详细调试日志 fmt.Printf("创建内容面板: 登录状态=%t, 用户名=%s, 店铺数量=%d\n", appState.CurrentUser.LoginName != "", appState.CurrentUser.LoginName, len(appState.Shops)) if appState.CurrentUser.LoginName != "" { if len(appState.Shops) > 0 { return createShopListPanel(appState) } return container.NewCenter( widget.NewLabel("没有可用的店铺"), ) } return container.NewCenter( widget.NewLabel("请先登录查看店铺列表"), ) } // 重构刷新函数 - 确保完全重建UI func refreshLeftPanel(appState *AppState) { if appState.SplitContainer == nil { return } // 添加详细调试信息 fmt.Printf("刷新左侧面板 - 时间: %s, 用户: %s, 店铺数量: %d\n", time.Now().Format("15:04:05.000"), appState.CurrentUser.LoginName, len(appState.Shops)) // 创建新的左侧面板 newLeftPanel := createLeftPanel(appState.Window, appState) // 添加调试背景色(登录状态不同颜色不同) var debugColor color.Color if appState.CurrentUser.LoginName != "" { debugColor = color.NRGBA{R: 0, G: 100, B: 0, A: 30} // 登录状态绿色半透明 } else { debugColor = color.NRGBA{R: 100, G: 0, B: 0, A: 30} // 未登录状态红色半透明 } debugPanel := container.NewMax( canvas.NewRectangle(debugColor), newLeftPanel, ) // 替换分割布局中的左侧面板 appState.SplitContainer.Leading = debugPanel appState.LeftPanel = debugPanel // 刷新分割布局 appState.SplitContainer.Refresh() // 强制重绘整个窗口 appState.Window.Content().Refresh() appState.LastRefreshTime = time.Now() } // 重构 createLeftPanel 函数 - 确保使用正确的状态 func createLeftPanel(window fyne.Window, appState *AppState) *fyne.Container { // 创建顶部面板(用户状态/登录表单) topPanel := createTopPanel(appState) // 创建内容面板(店铺列表或提示) contentPanel := createContentPanel(appState) // 创建过滤面板 filterPanel := createFilterPanel(appState) // 使用Border布局 return container.NewBorder( topPanel, // 顶部 filterPanel, // 底部 nil, nil, // 左右 contentPanel, // 中间内容 ) } // 修改登录按钮回调 - 确保状态正确更新 func createLoginForm(appState *AppState) fyne.CanvasObject { usernameEntry := widget.NewEntry() passwordEntry := widget.NewPasswordEntry() usernameEntry.PlaceHolder = "输入邮箱地址" passwordEntry.PlaceHolder = "输入密码" // 登录按钮回调 loginButton := widget.NewButton("登录", func() { appState.StatusBar.SetText("登录中...") go func() { // 模拟网络延迟 time.Sleep(500 * time.Millisecond) // 获取店铺信息 shops := ShopModel.Api_select_struct(nil) fyne.DoAndWait(func() { if len(shops) == 0 { appState.StatusBar.SetText("获取店铺信息为空") return } // 更新应用状态 appState.Shops = shops appState.CurrentUser, _ = UserModel.Api_find_by_username(usernameEntry.Text) // 更新店铺列表绑定 updateShopListBinding(appState) // 新增:更新绑定数据 // 添加状态更新日志 fmt.Printf("登录成功 - 用户: %s, 店铺数量: %d\n", appState.CurrentUser.LoginName, len(appState.Shops)) if appState.CurrentUser.LoginName == "" { appState.CurrentUser.LoginName = "1" } appState.StatusBar.SetText(fmt.Sprintf("登录成功! 共 %d 个店铺", len(shops))) // 直接刷新UI refreshLeftPanel(appState) }) }() }) form := widget.NewForm( widget.NewFormItem("邮箱:", usernameEntry), widget.NewFormItem("密码:", passwordEntry), ) appState.LoginForm = form return container.NewVBox( widget.NewLabelWithStyle("登录面板", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), form, container.NewCenter(loginButton), ) } // 修改自动登录函数 - 添加详细日志 func tryAutoLogin(appState *AppState) { // 获取所有用户 users := UserModel.Api_select_struct(nil) if len(users) == 0 { fyne.DoAndWait(func() { appState.StatusBar.SetText("获取已经存在的账号为空") }) return } // 尝试使用第一个用户自动登录 user := users[0] fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("尝试自动登录: %s...", user.LoginName)) }) // 获取用户名输入框 if appState.LoginForm == nil || len(appState.LoginForm.Items) < 2 { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 登录表单尚未初始化") }) return } usernameItem := appState.LoginForm.Items[0] usernameEntry, ok := usernameItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 用户名控件类型错误") }) return } passwordItem := appState.LoginForm.Items[1] passwordEntry, ok := passwordItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 密码控件类型错误") }) return } // 触发登录 fyne.DoAndWait(func() { usernameEntry.SetText(user.LoginName) passwordEntry.SetText(user.LoginPass) appState.StatusBar.SetText("正在自动登录...") // 更新应用状态 appState.CurrentUser = user appState.Shops = ShopModel.Api_select_struct(nil) // 更新店铺列表绑定 updateShopListBinding(appState) // 新增 // 添加自动登录日志 fmt.Printf("自动登录成功 - 用户: %s, 店铺数量: %d\n", appState.CurrentUser.LoginName, len(appState.Shops)) // 直接刷新UI refreshLeftPanel(appState) }) } // 修改后的异步加载店铺头像函数 func loadShopAvatar(img *canvas.Image, url string) { if url == "" { // 使用默认头像 fyne.DoAndWait(func() { img.Resource = fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "account") img.Refresh() }) return } // 创建HTTP客户端(可设置超时) client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) if err != nil { log.Printf("加载头像失败: %v", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("头像请求失败: %s", resp.Status) return } // 解码图片 imgData, _, err := image.Decode(resp.Body) if err != nil { log.Printf("解码头像失败: %v", err) return } // 在主线程更新UI fyne.DoAndWait(func() { img.Image = imgData img.Refresh() }) } // 修改后的 createShopListPanel 函数 func createShopListPanel(appState *AppState) *fyne.Container { // 创建绑定数据 if appState.ShopListBinding == nil { appState.ShopListBinding = binding.NewUntypedList() } else { // 确保绑定数据是最新的 updateShopListBinding(appState) } // 创建列表控件 list := widget.NewListWithData( appState.ShopListBinding, func() fyne.CanvasObject { avatar := canvas.NewImageFromResource(nil) avatar.SetMinSize(fyne.NewSize(40, 40)) avatar.FillMode = canvas.ImageFillContain nameLabel := widget.NewLabel("") statusIcon := widget.NewIcon(nil) return container.NewHBox( avatar, container.NewVBox(nameLabel), layout.NewSpacer(), statusIcon, ) }, func(item binding.DataItem, obj fyne.CanvasObject) { hbox, ok := obj.(*fyne.Container) if !ok || len(hbox.Objects) < 4 { return } avatar, _ := hbox.Objects[0].(*canvas.Image) nameContainer, _ := hbox.Objects[1].(*fyne.Container) nameLabel, _ := nameContainer.Objects[0].(*widget.Label) statusIcon, _ := hbox.Objects[3].(*widget.Icon) val, err := item.(binding.Untyped).Get() if err != nil { return } shop, ok := val.(ShopModel.Account) if !ok { return } nameLabel.SetText(shop.AccountName) if shop.CanLogin { statusIcon.SetResource(res.ResShuffleSvg) } else { statusIcon.SetResource(fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "error")) } go loadShopAvatar(avatar, shop.AccountAvatar) }, ) list.OnSelected = func(id widget.ListItemID) { if id < 0 || id >= len(appState.Shops) { return } shop := appState.Shops[id] appState.StatusBar.SetText(fmt.Sprintf("加载 %s 的商品...", shop.AccountName)) go func() { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText("加载商品失败: " + err.Error()) }) return } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个商品", len(products))) addOrUpdateProductTab(appState, shop, products) }) }() } // 创建滚动容器 - 设置最小高度确保可滚动 scrollContainer := container.NewScroll(list) scrollContainer.SetMinSize(fyne.NewSize(280, 200)) // 最小高度200确保可滚动 // 使用Max容器确保填充空间 return container.NewMax( container.NewBorder( widget.NewLabel("店铺列表"), nil, nil, nil, scrollContainer, ), ) } // 更新店铺列表绑定数据 func updateShopListBinding(appState *AppState) { if appState.ShopListBinding == nil { appState.ShopListBinding = binding.NewUntypedList() } values := make([]interface{}, len(appState.Shops)) for i, shop := range appState.Shops { values[i] = shop } appState.ShopListBinding.Set(values) } // 应用商品过滤 func applyProductFilter(products []SkuModel.DataItem, keywords []string) []SkuModel.DataItem { if len(keywords) == 0 { return products // 没有关键字,返回所有商品 } filtered := []SkuModel.DataItem{} for _, product := range products { exclude := false for _, keyword := range keywords { if strings.Contains(strings.ToLower(product.Name), strings.ToLower(keyword)) { exclude = true break } } if !exclude { filtered = append(filtered, product) } } return filtered } // 修改 loadProductsForShop 函数,生成更多模拟数据 func loadProductsForShop(shop ShopModel.Account, appState *AppState) ([]SkuModel.DataItem, error) { // 模拟API调用获取商品数据 time.Sleep(500 * time.Millisecond) // 模拟网络延迟 // 生成100条模拟商品数据 products := make([]SkuModel.DataItem, 100) for i := 0; i < 100; i++ { products[i] = SkuModel.DataItem{ ProductID: fmt.Sprintf("SKU%04d", i+1), Name: fmt.Sprintf("%s - 商品%d", shop.AccountName, i+1), MarketPrice: i * 1000, DiscountPrice: i * 1000, } } // 应用过滤 filteredProducts := applyProductFilter(products, appState.FilterKeywords) return filteredProducts, nil } // 修改 addOrUpdateProductTab 函数,添加分页支持 func addOrUpdateProductTab(appState *AppState, shop ShopModel.Account, products []SkuModel.DataItem) { tabTitle := shop.AccountName // 获取或创建分页状态 pagination, exists := appState.PaginationStates[tabTitle] if !exists { // 初始化分页状态 pagination = &PaginationState{ PageSize: 10, CurrentPage: 1, TotalProducts: len(products), } appState.PaginationStates[tabTitle] = pagination } else { // 更新商品总数 pagination.TotalProducts = len(products) } // 计算总页数 pagination.TotalPages = (pagination.TotalProducts + pagination.PageSize - 1) / pagination.PageSize if pagination.TotalPages == 0 { pagination.TotalPages = 1 } // 获取当前页数据 currentPageProducts := getCurrentPageProducts(pagination, products) // 检查是否已存在该TAB for _, tab := range appState.ProductTabs.Items { if tab.Text == tabTitle { // 修改调用,传入店铺名称 tab.Content = createProductListWithPagination(appState, currentPageProducts, tabTitle, products) // 更新映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Refresh() return } } // 创建新TAB newTab := container.NewTabItem( tabTitle, createProductListWithPagination(appState, currentPageProducts, tabTitle, products), ) // 添加到映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Append(newTab) appState.ProductTabs.Select(newTab) } // 修改 getCurrentPageProducts 函数 func getCurrentPageProducts(pagination *PaginationState, products []SkuModel.DataItem) []SkuModel.DataItem { start := (pagination.CurrentPage - 1) * pagination.PageSize if start >= len(products) { start = 0 } end := start + pagination.PageSize if end > len(products) { end = len(products) } return products[start:end] } // 修改 createProductListWithPagination 函数 func createProductListWithPagination(appState *AppState, currentPageProducts []SkuModel.DataItem, shopName string, allProducts []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := createProductTable(currentPageProducts) // 创建分页控件 - 传入店铺名称 pagination := createPaginationControls(appState, shopName, allProducts) // 创建布局:表格在上,分页控件在下 return container.NewBorder(nil, pagination, nil, nil, table) } // 创建商品表格 func createProductTable(products []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := widget.NewTable( func() (int, int) { return len(products) + 1, 4 // 行数=商品数+表头,列数=4 }, func() fyne.CanvasObject { return widget.NewLabel("模板文本") }, func(id widget.TableCellID, cell fyne.CanvasObject) { label := cell.(*widget.Label) if id.Row == 0 { // 表头 switch id.Col { case 0: label.SetText("商品ID") case 1: label.SetText("商品名称") case 2: label.SetText("价格") case 3: label.SetText("库存") } label.TextStyle.Bold = true return } // 数据行 product := products[id.Row-1] switch id.Col { case 0: label.SetText(product.ProductID) case 1: label.SetText(product.Name) case 2: label.SetText(fmt.Sprintf("¥%.2f", float64(product.MarketPrice)/100)) case 3: label.SetText(fmt.Sprintf("%d", product.DiscountPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) // 创建滚动容器 scrollContainer := container.NewScroll(table) scrollContainer.SetMinSize(fyne.NewSize(600, 400)) return scrollContainer } // 修改 createPaginationControls 函数 func createPaginationControls(appState *AppState, shopName string, allProducts []SkuModel.DataItem) *fyne.Container { // 获取该店铺的分页状态 pagination, exists := appState.PaginationStates[shopName] if !exists { // 如果不存在,创建默认状态 pagination = &PaginationState{ PageSize: 10, CurrentPage: 1, TotalProducts: len(allProducts), TotalPages: (len(allProducts) + 9) / 10, } appState.PaginationStates[shopName] = pagination } // 使用闭包捕获当前店铺名称 refreshForShop := func() { refreshCurrentProductTab(appState, shopName, allProducts) } // 上一页按钮 prevBtn := widget.NewButton("上一页", func() { if pagination.CurrentPage > 1 { pagination.CurrentPage-- refreshForShop() } }) // 页码信息 pageInfo := widget.NewLabel(fmt.Sprintf("第 %d 页/共 %d 页", pagination.CurrentPage, pagination.TotalPages)) // 下一页按钮 nextBtn := widget.NewButton("下一页", func() { if pagination.CurrentPage < pagination.TotalPages { pagination.CurrentPage++ refreshForShop() } }) // 跳转输入框 jumpEntry := widget.NewEntry() jumpEntry.SetPlaceHolder("页码") jumpEntry.Validator = func(s string) error { _, err := strconv.Atoi(s) if err != nil { return errors.New("请输入数字") } return nil } jumpBtn := widget.NewButton("跳转", func() { page, err := strconv.Atoi(jumpEntry.Text) if err == nil && page >= 1 && page <= pagination.TotalPages { pagination.CurrentPage = page refreshForShop() } }) // 页面大小选择器 pageSizeSelect := widget.NewSelect([]string{"5", "10", "20", "50"}, nil) pageSizeSelect.SetSelected(fmt.Sprintf("%d", pagination.PageSize)) pageSizeSelect.OnChanged = func(value string) { size, _ := strconv.Atoi(value) pagination.PageSize = size pagination.CurrentPage = 1 // 重新计算总页数 pagination.TotalPages = (len(allProducts) + pagination.PageSize - 1) / pagination.PageSize if pagination.TotalPages == 0 { pagination.TotalPages = 1 } refreshForShop() } pageSizeLabel := widget.NewLabel("每页:") // 布局 return container.NewHBox( prevBtn, pageSizeLabel, pageSizeSelect, pageInfo, nextBtn, jumpEntry, jumpBtn, ) } // 修改 refreshCurrentProductTab 函数 func refreshCurrentProductTab(appState *AppState, shopName string, allProducts []SkuModel.DataItem) { // 获取当前选中的标签页 currentTab := appState.ProductTabs.Selected() if currentTab == nil { return } // 获取该店铺的分页状态 pagination, exists := appState.PaginationStates[shopName] if !exists { // 如果不存在,创建默认状态 pagination = &PaginationState{ PageSize: 10, CurrentPage: 1, TotalProducts: len(allProducts), } appState.PaginationStates[shopName] = pagination } // 更新商品总数 pagination.TotalProducts = len(allProducts) // 计算总页数 pagination.TotalPages = (pagination.TotalProducts + pagination.PageSize - 1) / pagination.PageSize if pagination.TotalPages == 0 { pagination.TotalPages = 1 } // 获取当前页数据 currentPageProducts := getCurrentPageProducts(pagination, allProducts) // 更新标签页内容 currentTab.Content = createProductListWithPagination(appState, currentPageProducts, shopName, allProducts) appState.ProductTabs.Refresh() } // 创建商品列表 - 修复表格填充问题 func createProductList(products []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := widget.NewTable( func() (int, int) { return len(products) + 1, 4 // 行数=商品数+表头,列数=4 }, func() fyne.CanvasObject { return widget.NewLabel("模板文本") }, func(id widget.TableCellID, cell fyne.CanvasObject) { label := cell.(*widget.Label) if id.Row == 0 { // 表头 switch id.Col { case 0: label.SetText("商品ID") case 1: label.SetText("商品名称") case 2: label.SetText("价格") case 3: label.SetText("库存") } label.TextStyle.Bold = true return } // 数据行 product := products[id.Row-1] switch id.Col { case 0: label.SetText(product.ProductID) case 1: label.SetText(product.Name) case 2: label.SetText(fmt.Sprintf("¥%.2f", float64(product.MarketPrice)/100)) case 3: label.SetText(fmt.Sprintf("%d", product.DiscountPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) // 创建滚动容器 scrollContainer := container.NewScroll(table) scrollContainer.SetMinSize(fyne.NewSize(600, 400)) // 返回可滚动的表格容器 return scrollContainer } 这个是修改后的代码,基本上能正常运行,接下来,我需要创建一个服务,调用 github.com/playwright-community/playwright-go 库,保持一个playwright实例一直在后台运行,同时playwright拦截Response,用于输出响应CODE。并且在状态栏的上面添加一个UI控件用于输入URL信息时和添加一个确认访问的按钮,点击访问按钮时,访问URL,状态栏输出playwright响应代码

package main import ( "errors" "fmt" "image" "image/color" "log" "net/http" "os" "path/filepath" "runtime" "strconv" "strings" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "github.com/playwright-community/playwright-go" "main.go/dataModel/CookieModel" "main.go/dataModel/ShopModel" "main.go/dataModel/SkuModel" "main.go/dataModel/UserModel" "main.go/res" "main.go/tuuz/database" ) // PlaywrightService 管理Playwright实例 type PlaywrightService struct { Browser playwright.Browser Context playwright.BrowserContext Page playwright.Page } // 新增分页状态结构体 type PaginationState struct { CurrentPage int PageSize int TotalPages int TotalProducts int Products []SkuModel.DataItem } // 全局状态 type AppState struct { Window fyne.Window CurrentUser UserModel.UserInfo Shops []ShopModel.Account ProductTabs *container.AppTabs StatusBar *widget.Label ShopListBinding binding.UntypedList LoginForm *widget.Form LeftPanel *fyne.Container FilterFilePath string FilterKeywords []string ShopListPanel *fyne.Container FilterPanel *fyne.Container KeywordCount *widget.Label TabShopMap map[string]ShopModel.Account SplitContainer *container.Split TopPanel *fyne.Container ContentPanel *fyne.Container NeedsRefresh bool LastRefreshTime time.Time PaginationStates map[string]*PaginationState Playwright *PlaywrightService // Playwright服务 UrlEntry *widget.Entry // URL输入框 } // 添加状态检查快捷键 func addStateDebugShortcut(window fyne.Window, appState *AppState) { window.Canvas().SetOnTypedKey(func(ev *fyne.KeyEvent) { if ev.Name == fyne.KeyF5 { refreshLeftPanel(appState) appState.StatusBar.SetText("手动刷新UI") } else if ev.Name == fyne.KeyS { fmt.Println("===== 应用状态快照 =====") fmt.Printf("当前用户: %s\n", appState.CurrentUser.LoginName) fmt.Printf("店铺数量: %d\n", len(appState.Shops)) fmt.Printf("最后刷新时间: %s\n", appState.LastRefreshTime.Format("15:04:05.000")) fmt.Println("=======================") } }) } // 初始化Playwright服务 func initPlaywrightService() (*PlaywrightService, error) { pw, err := playwright.Run() if err != nil { return nil, fmt.Errorf("启动Playwright失败: %w", err) } browser, err := pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{ Headless: playwright.Bool(false), }) if err != nil { return nil, fmt.Errorf("启动浏览器失败: %w", err) } context, err := browser.NewContext() if err != nil { return nil, fmt.Errorf("创建上下文失败: %w", err) } page, err := context.NewPage() if err != nil { return nil, fmt.Errorf("创建页面失败: %w", err) } return &PlaywrightService{ Browser: browser, Context: context, Page: page, }, nil } func main() { os.Setenv("PLAYWRIGHT_BROWSERS_PATH", "./browsers") database.Init() UserModel.UserInit() ShopModel.ShopInit() CookieModel.CreateCookieInfoTable() SkuModel.ProductInit() myApp := app.New() myWindow := myApp.NewWindow("店铺管理工具") myWindow.Resize(fyne.NewSize(1200, 800)) // 初始化Playwright服务 pwService, err := initPlaywrightService() if err != nil { log.Fatalf("初始化Playwright失败: %v", err) } defer func() { if err := pwService.Browser.Close(); err != nil { log.Printf("关闭浏览器失败: %v", err) } if err := playwright.Stop(); err != nil { log.Printf("停止Playwright失败: %v", err) } }() // 初始化应用状态 appState := &AppState{ FilterFilePath: getDefaultFilterPath(), TabShopMap: make(map[string]ShopModel.Account), LastRefreshTime: time.Now(), PaginationStates: make(map[string]*PaginationState), Playwright: pwService, // 注入Playwright服务 } // 注册调试快捷键 addStateDebugShortcut(myWindow, appState) // 启动状态监听器 startStateListener(appState) // 尝试加载默认过滤文件 go loadFilterFile(appState) // 创建状态栏 appState.StatusBar = widget.NewLabel("就绪") // 创建URL访问控件 appState.UrlEntry = widget.NewEntry() appState.UrlEntry.SetPlaceHolder("输入URL") visitButton := widget.NewButton("访问", func() { url := appState.UrlEntry.Text if url == "" { appState.StatusBar.SetText("请输入URL") return } appState.StatusBar.SetText(fmt.Sprintf("正在访问: %s...", url)) go func() { // 访问URL response, err := visitUrlWithPlaywright(appState, url) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("访问失败: %v", err)) }) return } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("访问完成! 状态码: %d", response.status)) }) }() }) // 创建底部控制栏 bottomControlBar := container.NewBorder( nil, nil, nil, visitButton, appState.UrlEntry, ) // 创建底部区域(状态栏 + URL控件) bottomArea := container.NewVBox( bottomControlBar, widget.NewSeparator(), container.NewHBox(layout.NewSpacer(), appState.StatusBar), ) // 创建主布局 mainContent := createMainUI(myWindow, appState) // 设置整体布局 content := container.NewBorder( nil, // 顶部 bottomArea, // 底部(包含URL控件和状态栏) nil, // 左侧 nil, // 右侧 mainContent, ) myWindow.SetContent(content) // 启动时尝试自动登录 go tryAutoLogin(appState) myWindow.ShowAndRun() } // 使用Playwright访问URL并拦截响应 func visitUrlWithPlaywright(appState *AppState, url string) (*playwright.Response, error) { // 设置响应拦截器 appState.Playwright.Page.OnResponse(func(response playwright.Response) { log.Printf("响应: %s - %d", response.URL(), response.Status()) }) // 导航到URL response, err := appState.Playwright.Page.Goto(url) if err != nil { return nil, fmt.Errorf("导航失败: %w", err) } // 等待页面加载完成 if err := appState.Playwright.Page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{ State: playwright.LoadStateNetworkidle, }); err != nil { return nil, fmt.Errorf("等待页面加载失败: %w", err) } return response, nil } // 新增状态监听器 - 定期检查状态变化 func startStateListener(appState *AppState) { go func() { for { time.Sleep(100 * time.Millisecond) // 每100ms检查一次 if appState.NeedsRefresh { fyne.DoAndWait(func() { refreshLeftPanel(appState) appState.NeedsRefresh = false }) } } }() } // 获取默认过滤文件路径 func getDefaultFilterPath() string { if runtime.GOOS == "windows" { return filepath.Join("filter.txt") } return filepath.Join(os.Getenv("HOME"), "filter.txt") } // 修改 refreshAllProductTabs 函数 func refreshAllProductTabs(appState *AppState) { if appState.ProductTabs == nil || len(appState.ProductTabs.Items) == 0 { return } // 遍历所有标签页并刷新 for _, tab := range appState.ProductTabs.Items { // 通过标签页标题获取店铺 shop, exists := appState.TabShopMap[tab.Text] if !exists { continue } // 重新加载商品 go func(shop ShopModel.Account) { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("刷新 %s 商品失败: %s", shop.AccountName, err.Error())) }) return } fyne.DoAndWait(func() { // 更新标签页内容 tab.Content = container.NewMax(createProductList(products)) appState.ProductTabs.Refresh() appState.StatusBar.SetText(fmt.Sprintf("已刷新 %s 的商品", shop.AccountName)) }) }(shop) } } // 加载过滤文件 func loadFilterFile(appState *AppState) { if appState.FilterFilePath == "" { log.Printf("加载本地过滤文件失败: %s", appState.FilterFilePath) return } if _, err := os.Stat(appState.FilterFilePath); os.IsNotExist(err) { err := os.WriteFile(appState.FilterFilePath, []byte{}, 0644) if err != nil { log.Printf("创建过滤文件失败: %v", err) } return } content, err := os.ReadFile(appState.FilterFilePath) if err != nil { log.Printf("读取过滤文件失败: %v", err) return } lines := strings.Split(string(content), "\n") appState.FilterKeywords = []string{} for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed != "" { appState.FilterKeywords = append(appState.FilterKeywords, trimmed) } } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个过滤关键字", len(appState.FilterKeywords))) // 更新关键字数量标签 if appState.KeywordCount != nil { // 修正为 KeywordCount appState.KeywordCount.SetText(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) } // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }) } // 修改 createMainUI 函数 - 保存分割布局引用 func createMainUI(window fyne.Window, appState *AppState) fyne.CanvasObject { appState.Window = window // 创建整个左侧面板 leftPanel := createLeftPanel(window, appState) appState.LeftPanel = leftPanel // 右侧面板 appState.ProductTabs = container.NewAppTabs() appState.ProductTabs.SetTabLocation(container.TabLocationTop) rightPanel := container.NewBorder( widget.NewLabelWithStyle("商品信息", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), nil, nil, nil, container.NewMax(appState.ProductTabs), ) // 使用HSplit布局 - 保存引用 split := container.NewHSplit(leftPanel, rightPanel) split.SetOffset(0.25) appState.SplitContainer = split // 保存分割布局引用 return split } // 修改createFilterPanel函数 - 返回容器并保存引用 func createFilterPanel(appState *AppState) *fyne.Container { // 创建文件路径标签 pathLabel := widget.NewLabel("过滤文件: " + appState.FilterFilePath) pathLabel.Wrapping = fyne.TextWrapWord // 创建选择文件按钮 selectButton := widget.NewButton("选择过滤文件", func() { dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil { dialog.ShowError(err, appState.Window) return } if reader == nil { return // 用户取消 } // 更新文件路径 appState.FilterFilePath = reader.URI().Path() pathLabel.SetText("过滤文件: " + appState.FilterFilePath) // 加载过滤文件 go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() }, appState.Window) }) // 创建刷新按钮 refreshButton := widget.NewButton("刷新过滤", func() { if appState.FilterFilePath != "" { appState.StatusBar.SetText("刷新过滤关键字...") go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() } else { appState.StatusBar.SetText("请先选择过滤文件") } }) // 创建"增加商品"按钮 addProductsButton := widget.NewButton("增加商品", func() { if appState.ProductTabs.Selected() == nil { appState.StatusBar.SetText("请先选择一个店铺标签页") return } shopName := appState.ProductTabs.Selected().Text appState.StatusBar.SetText(fmt.Sprintf("为 %s 增加1000条商品...", shopName)) go func() { // 生成1000条模拟商品 newProducts := make([]SkuModel.DataItem, 1000) for i := 0; i < 1000; i++ { newProducts[i] = SkuModel.DataItem{ ProductID: fmt.Sprintf("ADD%04d", i+1), Name: fmt.Sprintf("%s - 新增商品%d", shopName, i+1), MarketPrice: (i + 1000) * 1000, // 从1000开始 DiscountPrice: (i + 1000) * 800, // 折扣价 } } fyne.DoAndWait(func() { // 获取该店铺的TabState tabState, exists := appState.PaginationStates[shopName] if !exists { // 如果不存在,则创建一个新的TabState tabState = &PaginationState{ PageSize: 10, CurrentPage: 1, } appState.PaginationStates[shopName] = tabState } // 添加到现有商品列表 tabState.Products = append(tabState.Products, newProducts...) // 刷新当前标签页 refreshCurrentProductTab(appState, shopName, tabState.Products) appState.StatusBar.SetText(fmt.Sprintf("已为 %s 增加1000条商品,总数: %d", shopName, len(tabState.Products))) }) }() }) // 修改按钮容器,添加新按钮 buttonContainer := container.NewHBox( selectButton, refreshButton, addProductsButton, // 新增按钮 ) // 创建关键字计数标签 - 保存引用 keywordCount := widget.NewLabel(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) keywordCount.TextStyle = fyne.TextStyle{Bold: true} appState.KeywordCount = keywordCount // 创建面板容器 panel := container.NewVBox( widget.NewSeparator(), widget.NewLabelWithStyle("商品过滤", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), pathLabel, keywordCount, buttonContainer, ) return panel } // 修改 createLoggedInPanel 函数 - 确保注销时直接刷新 func createLoggedInPanel(appState *AppState) fyne.CanvasObject { return container.NewVBox( widget.NewLabelWithStyle("登录状态", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewSeparator(), container.NewHBox( widget.NewLabel("用户:"), widget.NewLabel(appState.CurrentUser.LoginName), ), container.NewHBox( widget.NewLabel("店铺数量:"), widget.NewLabel(fmt.Sprintf("%d", len(appState.Shops))), ), widget.NewSeparator(), container.NewCenter( widget.NewButton("注销", func() { // 重置状态 appState.CurrentUser = UserModel.UserInfo{} appState.Shops = nil appState.ProductTabs.Items = nil appState.ProductTabs.Refresh() appState.TabShopMap = make(map[string]ShopModel.Account) // 直接调用刷新函数 refreshLeftPanel(appState) appState.StatusBar.SetText("已注销") }), ), ) } // 重构创建顶部面板函数 - 确保状态正确反映 func createTopPanel(appState *AppState) *fyne.Container { // 添加调试日志 fmt.Printf("创建顶部面板: 登录状态=%t, 用户名=%s\n", appState.CurrentUser.LoginName != "", appState.CurrentUser.LoginName) var content fyne.CanvasObject if appState.CurrentUser.LoginName != "" { content = createLoggedInPanel(appState) } else { content = createLoginForm(appState) } return container.NewMax(content) } // 重构 createContentPanel 函数 - 添加详细日志 func createContentPanel(appState *AppState) *fyne.Container { // 添加详细调试日志 fmt.Printf("创建内容面板: 登录状态=%t, 用户名=%s, 店铺数量=%d\n", appState.CurrentUser.LoginName != "", appState.CurrentUser.LoginName, len(appState.Shops)) if appState.CurrentUser.LoginName != "" { if len(appState.Shops) > 0 { return createShopListPanel(appState) } return container.NewCenter( widget.NewLabel("没有可用的店铺"), ) } return container.NewCenter( widget.NewLabel("请先登录查看店铺列表"), ) } // 重构刷新函数 - 确保完全重建UI func refreshLeftPanel(appState *AppState) { if appState.SplitContainer == nil { return } // 添加详细调试信息 fmt.Printf("刷新左侧面板 - 时间: %s, 用户: %s, 店铺数量: %d\n", time.Now().Format("15:04:05.000"), appState.CurrentUser.LoginName, len(appState.Shops)) // 创建新的左侧面板 newLeftPanel := createLeftPanel(appState.Window, appState) // 添加调试背景色(登录状态不同颜色不同) var debugColor color.Color if appState.CurrentUser.LoginName != "" { debugColor = color.NRGBA{R: 0, G: 100, B: 0, A: 30} // 登录状态绿色半透明 } else { debugColor = color.NRGBA{R: 100, G: 0, B: 0, A: 30} // 未登录状态红色半透明 } debugPanel := container.NewMax( canvas.NewRectangle(debugColor), newLeftPanel, ) // 替换分割布局中的左侧面板 appState.SplitContainer.Leading = debugPanel appState.LeftPanel = debugPanel // 刷新分割布局 appState.SplitContainer.Refresh() // 强制重绘整个窗口 appState.Window.Content().Refresh() appState.LastRefreshTime = time.Now() } // 重构 createLeftPanel 函数 - 确保使用正确的状态 func createLeftPanel(window fyne.Window, appState *AppState) *fyne.Container { // 创建顶部面板(用户状态/登录表单) topPanel := createTopPanel(appState) // 创建内容面板(店铺列表或提示) contentPanel := createContentPanel(appState) // 创建过滤面板 filterPanel := createFilterPanel(appState) // 使用Border布局 return container.NewBorder( topPanel, // 顶部 filterPanel, // 底部 nil, nil, // 左右 contentPanel, // 中间内容 ) } // 修改登录按钮回调 - 确保状态正确更新 func createLoginForm(appState *AppState) fyne.CanvasObject { usernameEntry := widget.NewEntry() passwordEntry := widget.NewPasswordEntry() usernameEntry.PlaceHolder = "输入邮箱地址" passwordEntry.PlaceHolder = "输入密码" // 登录按钮回调 loginButton := widget.NewButton("登录", func() { appState.StatusBar.SetText("登录中...") go func() { // 模拟网络延迟 time.Sleep(500 * time.Millisecond) // 获取店铺信息 shops := ShopModel.Api_select_struct(nil) fyne.DoAndWait(func() { if len(shops) == 0 { appState.StatusBar.SetText("获取店铺信息为空") return } // 更新应用状态 appState.Shops = shops appState.CurrentUser, _ = UserModel.Api_find_by_username(usernameEntry.Text) // 更新店铺列表绑定 updateShopListBinding(appState) // 新增:更新绑定数据 // 添加状态更新日志 fmt.Printf("登录成功 - 用户: %s, 店铺数量: %d\n", appState.CurrentUser.LoginName, len(appState.Shops)) if appState.CurrentUser.LoginName == "" { appState.CurrentUser.LoginName = "1" } appState.StatusBar.SetText(fmt.Sprintf("登录成功! 共 %d 个店铺", len(shops))) // 直接刷新UI refreshLeftPanel(appState) }) }() }) form := widget.NewForm( widget.NewFormItem("邮箱:", usernameEntry), widget.NewFormItem("密码:", passwordEntry), ) appState.LoginForm = form return container.NewVBox( widget.NewLabelWithStyle("登录面板", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), form, container.NewCenter(loginButton), ) } // 修改自动登录函数 - 添加详细日志 func tryAutoLogin(appState *AppState) { // 获取所有用户 users := UserModel.Api_select_struct(nil) if len(users) == 0 { fyne.DoAndWait(func() { appState.StatusBar.SetText("获取已经存在的账号为空") }) return } // 尝试使用第一个用户自动登录 user := users[0] fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("尝试自动登录: %s...", user.LoginName)) }) // 获取用户名输入框 if appState.LoginForm == nil || len(appState.LoginForm.Items) < 2 { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 登录表单尚未初始化") }) return } usernameItem := appState.LoginForm.Items[0] usernameEntry, ok := usernameItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 用户名控件类型错误") }) return } passwordItem := appState.LoginForm.Items[1] passwordEntry, ok := passwordItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 密码控件类型错误") }) return } // 触发登录 fyne.DoAndWait(func() { usernameEntry.SetText(user.LoginName) passwordEntry.SetText(user.LoginPass) appState.StatusBar.SetText("正在自动登录...") // 更新应用状态 appState.CurrentUser = user appState.Shops = ShopModel.Api_select_struct(nil) // 更新店铺列表绑定 updateShopListBinding(appState) // 新增 // 添加自动登录日志 fmt.Printf("自动登录成功 - 用户: %s, 店铺数量: %d\n", appState.CurrentUser.LoginName, len(appState.Shops)) // 直接刷新UI refreshLeftPanel(appState) }) } // 修改后的异步加载店铺头像函数 func loadShopAvatar(img *canvas.Image, url string) { if url == "" { // 使用默认头像 fyne.DoAndWait(func() { img.Resource = fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "account") img.Refresh() }) return } // 创建HTTP客户端(可设置超时) client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) if err != nil { log.Printf("加载头像失败: %v", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("头像请求失败: %s", resp.Status) return } // 解码图片 imgData, _, err := image.Decode(resp.Body) if err != nil { log.Printf("解码头像失败: %v", err) return } // 在主线程更新UI fyne.DoAndWait(func() { img.Image = imgData img.Refresh() }) } // 修改后的 createShopListPanel 函数 func createShopListPanel(appState *AppState) *fyne.Container { // 创建绑定数据 if appState.ShopListBinding == nil { appState.ShopListBinding = binding.NewUntypedList() } else { // 确保绑定数据是最新的 updateShopListBinding(appState) } // 创建列表控件 list := widget.NewListWithData( appState.ShopListBinding, func() fyne.CanvasObject { avatar := canvas.NewImageFromResource(nil) avatar.SetMinSize(fyne.NewSize(40, 40)) avatar.FillMode = canvas.ImageFillContain nameLabel := widget.NewLabel("") statusIcon := widget.NewIcon(nil) return container.NewHBox( avatar, container.NewVBox(nameLabel), layout.NewSpacer(), statusIcon, ) }, func(item binding.DataItem, obj fyne.CanvasObject) { hbox, ok := obj.(*fyne.Container) if !ok || len(hbox.Objects) < 4 { return } avatar, _ := hbox.Objects[0].(*canvas.Image) nameContainer, _ := hbox.Objects[1].(*fyne.Container) nameLabel, _ := nameContainer.Objects[0].(*widget.Label) statusIcon, _ := hbox.Objects[3].(*widget.Icon) val, err := item.(binding.Untyped).Get() if err != nil { return } shop, ok := val.(ShopModel.Account) if !ok { return } nameLabel.SetText(shop.AccountName) if shop.CanLogin { statusIcon.SetResource(res.ResShuffleSvg) } else { statusIcon.SetResource(fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "error")) } go loadShopAvatar(avatar, shop.AccountAvatar) }, ) list.OnSelected = func(id widget.ListItemID) { if id < 0 || id >= len(appState.Shops) { return } shop := appState.Shops[id] appState.StatusBar.SetText(fmt.Sprintf("加载 %s 的商品...", shop.AccountName)) go func() { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText("加载商品失败: " + err.Error()) }) return } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个商品", len(products))) addOrUpdateProductTab(appState, shop, products) }) }() } // 创建滚动容器 - 设置最小高度确保可滚动 scrollContainer := container.NewScroll(list) scrollContainer.SetMinSize(fyne.NewSize(280, 200)) // 最小高度200确保可滚动 // 使用Max容器确保填充空间 return container.NewMax( container.NewBorder( widget.NewLabel("店铺列表"), nil, nil, nil, scrollContainer, ), ) } // 更新店铺列表绑定数据 func updateShopListBinding(appState *AppState) { if appState.ShopListBinding == nil { appState.ShopListBinding = binding.NewUntypedList() } values := make([]interface{}, len(appState.Shops)) for i, shop := range appState.Shops { values[i] = shop } appState.ShopListBinding.Set(values) } // 应用商品过滤 func applyProductFilter(products []SkuModel.DataItem, keywords []string) []SkuModel.DataItem { if len(keywords) == 0 { return products // 没有关键字,返回所有商品 } filtered := []SkuModel.DataItem{} for _, product := range products { exclude := false for _, keyword := range keywords { if strings.Contains(strings.ToLower(product.Name), strings.ToLower(keyword)) { exclude = true break } } if !exclude { filtered = append(filtered, product) } } return filtered } // 修改 loadProductsForShop 函数,生成更多模拟数据 func loadProductsForShop(shop ShopModel.Account, appState *AppState) ([]SkuModel.DataItem, error) { // 模拟API调用获取商品数据 time.Sleep(500 * time.Millisecond) // 模拟网络延迟 // 生成100条模拟商品数据 products := make([]SkuModel.DataItem, 100) for i := 0; i < 100; i++ { products[i] = SkuModel.DataItem{ ProductID: fmt.Sprintf("SKU%04d", i+1), Name: fmt.Sprintf("%s - 商品%d", shop.AccountName, i+1), MarketPrice: i * 1000, DiscountPrice: i * 1000, } } // 应用过滤 filteredProducts := applyProductFilter(products, appState.FilterKeywords) return filteredProducts, nil } // 修改 addOrUpdateProductTab 函数,添加分页支持 func addOrUpdateProductTab(appState *AppState, shop ShopModel.Account, products []SkuModel.DataItem) { tabTitle := shop.AccountName // 获取或创建分页状态 pagination, exists := appState.PaginationStates[tabTitle] if !exists { // 初始化分页状态 pagination = &PaginationState{ PageSize: 10, CurrentPage: 1, TotalProducts: len(products), } appState.PaginationStates[tabTitle] = pagination } else { // 更新商品总数 pagination.TotalProducts = len(products) } // 计算总页数 pagination.TotalPages = (pagination.TotalProducts + pagination.PageSize - 1) / pagination.PageSize if pagination.TotalPages == 0 { pagination.TotalPages = 1 } // 获取当前页数据 currentPageProducts := getCurrentPageProducts(pagination, products) // 检查是否已存在该TAB for _, tab := range appState.ProductTabs.Items { if tab.Text == tabTitle { // 修改调用,传入店铺名称 tab.Content = createProductListWithPagination(appState, currentPageProducts, tabTitle, products) // 更新映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Refresh() return } } // 创建新TAB newTab := container.NewTabItem( tabTitle, createProductListWithPagination(appState, currentPageProducts, tabTitle, products), ) // 添加到映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Append(newTab) appState.ProductTabs.Select(newTab) } // 修改 getCurrentPageProducts 函数 func getCurrentPageProducts(pagination *PaginationState, products []SkuModel.DataItem) []SkuModel.DataItem { start := (pagination.CurrentPage - 1) * pagination.PageSize if start >= len(products) { start = 0 } end := start + pagination.PageSize if end > len(products) { end = len(products) } return products[start:end] } // 修改 createProductListWithPagination 函数 func createProductListWithPagination(appState *AppState, currentPageProducts []SkuModel.DataItem, shopName string, allProducts []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := createProductTable(currentPageProducts) // 创建分页控件 - 传入店铺名称 pagination := createPaginationControls(appState, shopName, allProducts) // 创建布局:表格在上,分页控件在下 return container.NewBorder(nil, pagination, nil, nil, table) } // 创建商品表格 func createProductTable(products []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := widget.NewTable( func() (int, int) { return len(products) + 1, 4 // 行数=商品数+表头,列数=4 }, func() fyne.CanvasObject { return widget.NewLabel("模板文本") }, func(id widget.TableCellID, cell fyne.CanvasObject) { label := cell.(*widget.Label) if id.Row == 0 { // 表头 switch id.Col { case 0: label.SetText("商品ID") case 1: label.SetText("商品名称") case 2: label.SetText("价格") case 3: label.SetText("库存") } label.TextStyle.Bold = true return } // 数据行 product := products[id.Row-1] switch id.Col { case 0: label.SetText(product.ProductID) case 1: label.SetText(product.Name) case 2: label.SetText(fmt.Sprintf("¥%.2f", float64(product.MarketPrice)/100)) case 3: label.SetText(fmt.Sprintf("%d", product.DiscountPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) // 创建滚动容器 scrollContainer := container.NewScroll(table) scrollContainer.SetMinSize(fyne.NewSize(600, 400)) return scrollContainer } // 修改 createPaginationControls 函数 func createPaginationControls(appState *AppState, shopName string, allProducts []SkuModel.DataItem) *fyne.Container { // 获取该店铺的分页状态 pagination, exists := appState.PaginationStates[shopName] if !exists { // 如果不存在,创建默认状态 pagination = &PaginationState{ PageSize: 10, CurrentPage: 1, TotalProducts: len(allProducts), TotalPages: (len(allProducts) + 9) / 10, } appState.PaginationStates[shopName] = pagination } // 使用闭包捕获当前店铺名称 refreshForShop := func() { refreshCurrentProductTab(appState, shopName, allProducts) } // 上一页按钮 prevBtn := widget.NewButton("上一页", func() { if pagination.CurrentPage > 1 { pagination.CurrentPage-- refreshForShop() } }) // 页码信息 pageInfo := widget.NewLabel(fmt.Sprintf("第 %d 页/共 %d 页", pagination.CurrentPage, pagination.TotalPages)) // 下一页按钮 nextBtn := widget.NewButton("下一页", func() { if pagination.CurrentPage < pagination.TotalPages { pagination.CurrentPage++ refreshForShop() } }) // 跳转输入框 jumpEntry := widget.NewEntry() jumpEntry.SetPlaceHolder("页码") jumpEntry.Validator = func(s string) error { _, err := strconv.Atoi(s) if err != nil { return errors.New("请输入数字") } return nil } jumpBtn := widget.NewButton("跳转", func() { page, err := strconv.Atoi(jumpEntry.Text) if err == nil && page >= 1 && page <= pagination.TotalPages { pagination.CurrentPage = page refreshForShop() } }) // 页面大小选择器 pageSizeSelect := widget.NewSelect([]string{"5", "10", "20", "50"}, nil) pageSizeSelect.SetSelected(fmt.Sprintf("%d", pagination.PageSize)) pageSizeSelect.OnChanged = func(value string) { size, _ := strconv.Atoi(value) pagination.PageSize = size pagination.CurrentPage = 1 // 重新计算总页数 pagination.TotalPages = (len(allProducts) + pagination.PageSize - 1) / pagination.PageSize if pagination.TotalPages == 0 { pagination.TotalPages = 1 } refreshForShop() } pageSizeLabel := widget.NewLabel("每页:") // 布局 return container.NewHBox( prevBtn, pageSizeLabel, pageSizeSelect, pageInfo, nextBtn, jumpEntry, jumpBtn, ) } // 修改 refreshCurrentProductTab 函数 func refreshCurrentProductTab(appState *AppState, shopName string, allProducts []SkuModel.DataItem) { // 获取当前选中的标签页 currentTab := appState.ProductTabs.Selected() if currentTab == nil { return } // 获取该店铺的分页状态 pagination, exists := appState.PaginationStates[shopName] if !exists { // 如果不存在,创建默认状态 pagination = &PaginationState{ PageSize: 10, CurrentPage: 1, TotalProducts: len(allProducts), } appState.PaginationStates[shopName] = pagination } // 更新商品总数 pagination.TotalProducts = len(allProducts) // 计算总页数 pagination.TotalPages = (pagination.TotalProducts + pagination.PageSize - 1) / pagination.PageSize if pagination.TotalPages == 0 { pagination.TotalPages = 1 } // 获取当前页数据 currentPageProducts := getCurrentPageProducts(pagination, allProducts) // 更新标签页内容 currentTab.Content = createProductListWithPagination(appState, currentPageProducts, shopName, allProducts) appState.ProductTabs.Refresh() } // 创建商品列表 - 修复表格填充问题 func createProductList(products []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := widget.NewTable( func() (int, int) { return len(products) + 1, 4 // 行数=商品数+表头,列数=4 }, func() fyne.CanvasObject { return widget.NewLabel("模板文本") }, func(id widget.TableCellID, cell fyne.CanvasObject) { label := cell.(*widget.Label) if id.Row == 0 { // 表头 switch id.Col { case 0: label.SetText("商品ID") case 1: label.SetText("商品名称") case 2: label.SetText("价格") case 3: label.SetText("库存") } label.TextStyle.Bold = true return } // 数据行 product := products[id.Row-1] switch id.Col { case 0: label.SetText(product.ProductID) case 1: label.SetText(product.Name) case 2: label.SetText(fmt.Sprintf("¥%.2f", float64(product.MarketPrice)/100)) case 3: label.SetText(fmt.Sprintf("%d", product.DiscountPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) // 创建滚动容器 scrollContainer := container.NewScroll(table) scrollContainer.SetMinSize(fyne.NewSize(600, 400)) // 返回可滚动的表格容器 return scrollContainer } 这是修改后的代码,IDE提示3个编译错误,1:undefined: playwright.Stop,2:response.status undefined (type *playwright.Response is pointer to interface, not interface) 3:cannot use response (variable of interface type playwright.Response) as *playwright.Response value in return statement: playwright.Response does not implement *playwright.Response (type *playwright.Response is pointer to interface, not interface)

/ -- 在线客服聊天 --> 认证失败,重新设置聊天系统的用户密码 // 聊天窗口右侧默认展示最近订单,如果想要展示商品、订单、店铺则需要在当前页面中设置隐藏域,name必须为 chat_goods_id, // chat_order_id, chat_supp_id /* ------------------------------------------------------ */ function action_authfail () { $user_id = $_SESSION['user_id']; $sql = "select user_name, password, email from " . $GLOBALS['ecs']->table('users') . " where user_id = '$user_id'"; $row = $db->getRow($sql); $success = create_of_user($user_id, $row['password'], $row['user_name'], $row['email'], 10, - 1); if($success) { $result = array( 'error' => 1,'message' => '可能由于网络原因,发生错误!请点击 重试 ,重新连接...','content' => '' ); } else { $result = array( 'error' => 1,'message' => '由于网络原因,发生错误!请点击 重试 ,重新连接...','content' => '' ); } $result = json_encode($result); exit($result); } /** * 用户离线 */ function action_off_line() { // 用户超过5分钟未发言则视为自动离线,更新客服状态 } function is_telephone ($phone) { $chars = "/^13[0-9]{1}[0-9]{8}$|15[0-9]{1}[0-9]{8}$|18[0-9]{1}[0-9]{8}$/"; if(preg_match($chars, $phone)) { return true; } } /** * 获取db对象 * * @return unknown */ function get_database () { return $GLOBALS['db']; } /** * 获取smarty对象 * * @return unknown */ function get_smarty () { return $GLOBALS[smarty]; } /** * 获取ecs对象 * * @return unknown */ function get_ecs () { return $GLOBALS['ecs']; } /* * 获取商品所对应店铺的店铺基本信息 * @param int $suppid 店铺id * @param int $suppinfo 入驻商的信息 */ function get_dianpu_baseinfo ($suppid = 0) { if(intval($suppid) <= 0) { return; } $sql_supplier = "SELECT s.supplier_id,s.supplier_name,s.add_time,sr.rank_name FROM " . $GLOBALS['ecs']->table("supplier") . " as s left join " . $GLOBALS['ecs']->table("supplier_rank") . " as sr ON s.rank_id=sr.rank_id WHERE s.supplier_id=" . $suppid . " AND s.status=1"; $supp_info = $GLOBALS['db']->getRow($sql_supplier); $sql = "SELECT * FROM " . $GLOBALS['ecs']->table('supplier_shop_config') . " WHERE supplier_id = " . $suppid; $shopinfo = $GLOBALS['db']->getAll($sql); $config = array(); foreach($shopinfo as $value) { $config[$value['code']] = $value['value']; } $shop_info = array(); $shop_info['ghs_css_path'] = 'themes/' . $config['template'] . '/images/ghs/css/ghs_style.css'; // 入驻商所选模板样式路径 $shoplogo = empty($config['shop_logo']) ? 'themes/' . $config['template'] . '/images/dianpu.jpg' : $config['shop_logo']; $shop_info['shoplogo'] = $shoplogo; // 商家logo $shop_info['shopname'] = htmlspecialchars($config['shop_name']); // 店铺名称 $shop_info['suppid'] = $suppid; // 商家名称 $shop_info['suppliername'] = htmlspecialchars($supp_info['supplier_name']); // 商家名称 $shop_info['userrank'] = htmlspecialchars($supp_info['rank_name']); // 商家等级 $shop_info['region'] = get_province_city($config['shop_province'], $config['shop_city']); $shop_info['address'] = $config['shop_address']; $shop_info['serviceqq'] = $config['qq']; $shop_info['serviceww'] = $config['ww']; $shop_info['serviceemail'] = $config['service_email']; $shop_info['servicephone'] = $config['service_phone']; $shop_info['createtime'] = gmdate('Y-m-d', $config['add_time']); // 商家创建时间 $suppid = (intval($suppid) > 0) ? intval($suppid) : intval($_GET['suppId']); return $shop_info; } 构建多商户人工客服系统

package main import ( "errors" "fmt" "image" "image/color" "log" "net/http" "os" "path/filepath" "runtime" "strconv" "strings" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "main.go/dataModel/CookieModel" "main.go/dataModel/ShopModel" "main.go/dataModel/SkuModel" "main.go/dataModel/UserModel" "main.go/res" "main.go/tuuz/database" ) // 全局状态 type AppState struct { Window fyne.Window CurrentUser UserModel.UserInfo Shops []ShopModel.Account ProductTabs *container.AppTabs StatusBar *widget.Label ShopListBinding binding.UntypedList LoginForm *widget.Form LeftPanel *fyne.Container // 改为存储整个左侧面板容器 FilterFilePath string FilterKeywords []string ShopListPanel *fyne.Container // 新增:存储店铺列表面板 FilterPanel *fyne.Container // 存储过滤面板引用 KeywordCount *widget.Label // 存储关键字计数标签 TabShopMap map[string]ShopModel.Account // 新增:存储标签页与店铺的映射 SplitContainer *container.Split // 新增:保存分割布局引用 TopPanel *fyne.Container // 新增:保存顶部面板引用 ContentPanel *fyne.Container // 新增:保存内容面板引用 NeedsRefresh bool // 新增:状态变更标志 LastRefreshTime time.Time // 新增:最后刷新时间 CurrentPage int // 当前页码 PageSize int // 每页显示数量 TotalProducts int // 商品总数 TotalPages int // 总页数 AllProducts map[string][]SkuModel.DataItem // 新增:存储每个店铺的所有商品 [店铺名称]商品列表 } // 添加状态检查快捷键 func addStateDebugShortcut(window fyne.Window, appState *AppState) { window.Canvas().SetOnTypedKey(func(ev *fyne.KeyEvent) { if ev.Name == fyne.KeyF5 { // 手动刷新UI refreshLeftPanel(appState) appState.StatusBar.SetText("手动刷新UI") } else if ev.Name == fyne.KeyS { // 打印状态快照 fmt.Println("===== 应用状态快照 =====") fmt.Printf("当前用户: %s\n", appState.CurrentUser.LoginName) fmt.Printf("店铺数量: %d\n", len(appState.Shops)) fmt.Printf("最后刷新时间: %s\n", appState.LastRefreshTime.Format("15:04:05.000")) fmt.Printf("左侧面板: %T\n", appState.LeftPanel) fmt.Printf("分割布局: %T\n", appState.SplitContainer) fmt.Println("=======================") } }) } func main() { os.Setenv("PLAYWRIGHT_BROWSERS_PATH", "./browsers") database.Init() UserModel.UserInit() ShopModel.ShopInit() CookieModel.CreateCookieInfoTable() SkuModel.ProductInit() myApp := app.New() myWindow := myApp.NewWindow("店铺管理工具") myWindow.Resize(fyne.NewSize(1200, 800)) // 初始化应用状态 appState := &AppState{ FilterFilePath: getDefaultFilterPath(), TabShopMap: make(map[string]ShopModel.Account), // 初始化映射 LastRefreshTime: time.Now(), CurrentPage: 1, // 默认第一页 PageSize: 10, // 默认每页10条 AllProducts: make(map[string][]SkuModel.DataItem), // 初始化商品存储 } // 注册调试快捷键 addStateDebugShortcut(myWindow, appState) // 启动状态监听器 startStateListener(appState) // 尝试加载默认过滤文件 go loadFilterFile(appState) // 创建状态栏 appState.StatusBar = widget.NewLabel("就绪") statusBar := container.NewHBox(layout.NewSpacer(), appState.StatusBar) // 创建主布局 mainContent := createMainUI(myWindow, appState) // 设置整体布局 content := container.NewBorder( nil, // 顶部 statusBar, // 底部 nil, // 左侧 nil, // 右侧 mainContent, ) myWindow.SetContent(content) // 启动时尝试自动登录 go tryAutoLogin(appState) myWindow.ShowAndRun() } // 新增状态监听器 - 定期检查状态变化 func startStateListener(appState *AppState) { go func() { for { time.Sleep(100 * time.Millisecond) // 每100ms检查一次 if appState.NeedsRefresh { fyne.DoAndWait(func() { refreshLeftPanel(appState) appState.NeedsRefresh = false }) } } }() } // 获取默认过滤文件路径 func getDefaultFilterPath() string { if runtime.GOOS == "windows" { return filepath.Join(os.Getenv("USERPROFILE"), "Documents", "filter.txt") } return filepath.Join(os.Getenv("HOME"), "filter.txt") } // 修改 refreshAllProductTabs 函数 func refreshAllProductTabs(appState *AppState) { if appState.ProductTabs == nil || len(appState.ProductTabs.Items) == 0 { return } // 遍历所有标签页并刷新 for _, tab := range appState.ProductTabs.Items { // 通过标签页标题获取店铺 shop, exists := appState.TabShopMap[tab.Text] if !exists { continue } // 重新加载商品 go func(shop ShopModel.Account) { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("刷新 %s 商品失败: %s", shop.AccountName, err.Error())) }) return } fyne.DoAndWait(func() { // 更新标签页内容 tab.Content = container.NewMax(createProductList(products)) appState.ProductTabs.Refresh() appState.StatusBar.SetText(fmt.Sprintf("已刷新 %s 的商品", shop.AccountName)) }) }(shop) } } // 加载过滤文件 func loadFilterFile(appState *AppState) { if appState.FilterFilePath == "" { return } if _, err := os.Stat(appState.FilterFilePath); os.IsNotExist(err) { err := os.WriteFile(appState.FilterFilePath, []byte{}, 0644) if err != nil { log.Printf("创建过滤文件失败: %v", err) } return } content, err := os.ReadFile(appState.FilterFilePath) if err != nil { log.Printf("读取过滤文件失败: %v", err) return } lines := strings.Split(string(content), "\n") appState.FilterKeywords = []string{} for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed != "" { appState.FilterKeywords = append(appState.FilterKeywords, trimmed) } } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个过滤关键字", len(appState.FilterKeywords))) // 更新关键字数量标签 if appState.KeywordCount != nil { // 修正为 KeywordCount appState.KeywordCount.SetText(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) } // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }) } // 修改 createMainUI 函数 - 保存分割布局引用 func createMainUI(window fyne.Window, appState *AppState) fyne.CanvasObject { appState.Window = window // 创建整个左侧面板 leftPanel := createLeftPanel(window, appState) appState.LeftPanel = leftPanel // 右侧面板 appState.ProductTabs = container.NewAppTabs() appState.ProductTabs.SetTabLocation(container.TabLocationTop) rightPanel := container.NewBorder( widget.NewLabelWithStyle("商品信息", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), nil, nil, nil, container.NewMax(appState.ProductTabs), ) // 使用HSplit布局 - 保存引用 split := container.NewHSplit(leftPanel, rightPanel) split.SetOffset(0.25) appState.SplitContainer = split // 保存分割布局引用 return split } // 修改createFilterPanel函数 - 返回容器并保存引用 func createFilterPanel(appState *AppState) *fyne.Container { // 创建文件路径标签 pathLabel := widget.NewLabel("过滤文件: " + appState.FilterFilePath) pathLabel.Wrapping = fyne.TextWrapWord // 创建选择文件按钮 selectButton := widget.NewButton("选择过滤文件", func() { dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil { dialog.ShowError(err, appState.Window) return } if reader == nil { return // 用户取消 } // 更新文件路径 appState.FilterFilePath = reader.URI().Path() pathLabel.SetText("过滤文件: " + appState.FilterFilePath) // 加载过滤文件 go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() }, appState.Window) }) // 创建刷新按钮 refreshButton := widget.NewButton("刷新过滤", func() { if appState.FilterFilePath != "" { appState.StatusBar.SetText("刷新过滤关键字...") go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() } else { appState.StatusBar.SetText("请先选择过滤文件") } }) // 创建"增加商品"按钮 addProductsButton := widget.NewButton("增加商品", func() { if appState.ProductTabs.Selected() == nil { appState.StatusBar.SetText("请先选择一个店铺标签页") return } shopName := appState.ProductTabs.Selected().Text appState.StatusBar.SetText(fmt.Sprintf("为 %s 增加1000条商品...", shopName)) go func() { // 生成1000条模拟商品 newProducts := make([]SkuModel.DataItem, 1000) for i := 0; i < 1000; i++ { newProducts[i] = SkuModel.DataItem{ ProductID: fmt.Sprintf("ADD%04d", i+1), Name: fmt.Sprintf("%s - 新增商品%d", shopName, i+1), MarketPrice: (i + 1000) * 1000, // 从1000开始 DiscountPrice: (i + 1000) * 800, // 折扣价 } } fyne.DoAndWait(func() { // 添加到现有商品列表 if existing, ok := appState.AllProducts[shopName]; ok { appState.AllProducts[shopName] = append(existing, newProducts...) } else { appState.AllProducts[shopName] = newProducts } // 刷新当前标签页 refreshCurrentProductTab(appState, appState.AllProducts[shopName]) appState.StatusBar.SetText(fmt.Sprintf("已为 %s 增加1000条商品,总数: %d", shopName, len(appState.AllProducts[shopName]))) }) }() }) // 修改按钮容器,添加新按钮 buttonContainer := container.NewHBox( selectButton, refreshButton, addProductsButton, // 新增按钮 ) // 创建关键字计数标签 - 保存引用 keywordCount := widget.NewLabel(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) keywordCount.TextStyle = fyne.TextStyle{Bold: true} appState.KeywordCount = keywordCount // 创建面板容器 panel := container.NewVBox( widget.NewSeparator(), widget.NewLabelWithStyle("商品过滤", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), pathLabel, keywordCount, buttonContainer, ) return panel } // 修改 createLoggedInPanel 函数 - 确保注销时直接刷新 func createLoggedInPanel(appState *AppState) fyne.CanvasObject { return container.NewVBox( widget.NewLabelWithStyle("登录状态", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewSeparator(), container.NewHBox( widget.NewLabel("用户:"), widget.NewLabel(appState.CurrentUser.LoginName), ), container.NewHBox( widget.NewLabel("店铺数量:"), widget.NewLabel(fmt.Sprintf("%d", len(appState.Shops))), ), widget.NewSeparator(), container.NewCenter( widget.NewButton("注销", func() { // 重置状态 appState.CurrentUser = UserModel.UserInfo{} appState.Shops = nil appState.ProductTabs.Items = nil appState.ProductTabs.Refresh() appState.TabShopMap = make(map[string]ShopModel.Account) // 直接调用刷新函数 refreshLeftPanel(appState) appState.StatusBar.SetText("已注销") }), ), ) } // 重构创建顶部面板函数 - 确保状态正确反映 func createTopPanel(appState *AppState) *fyne.Container { // 添加调试日志 fmt.Printf("创建顶部面板: 登录状态=%t, 用户名=%s\n", appState.CurrentUser.LoginName != "", appState.CurrentUser.LoginName) var content fyne.CanvasObject if appState.CurrentUser.LoginName != "" { content = createLoggedInPanel(appState) } else { content = createLoginForm(appState) } return container.NewMax(content) } // 重构 createContentPanel 函数 - 添加详细日志 func createContentPanel(appState *AppState) *fyne.Container { // 添加详细调试日志 fmt.Printf("创建内容面板: 登录状态=%t, 用户名=%s, 店铺数量=%d\n", appState.CurrentUser.LoginName != "", appState.CurrentUser.LoginName, len(appState.Shops)) if appState.CurrentUser.LoginName != "" { if len(appState.Shops) > 0 { return createShopListPanel(appState) } return container.NewCenter( widget.NewLabel("没有可用的店铺"), ) } return container.NewCenter( widget.NewLabel("请先登录查看店铺列表"), ) } // 重构刷新函数 - 确保完全重建UI func refreshLeftPanel(appState *AppState) { if appState.SplitContainer == nil { return } // 添加详细调试信息 fmt.Printf("刷新左侧面板 - 时间: %s, 用户: %s, 店铺数量: %d\n", time.Now().Format("15:04:05.000"), appState.CurrentUser.LoginName, len(appState.Shops)) // 创建新的左侧面板 newLeftPanel := createLeftPanel(appState.Window, appState) // 添加调试背景色(登录状态不同颜色不同) var debugColor color.Color if appState.CurrentUser.LoginName != "" { debugColor = color.NRGBA{R: 0, G: 100, B: 0, A: 30} // 登录状态绿色半透明 } else { debugColor = color.NRGBA{R: 100, G: 0, B: 0, A: 30} // 未登录状态红色半透明 } debugPanel := container.NewMax( canvas.NewRectangle(debugColor), newLeftPanel, ) // 替换分割布局中的左侧面板 appState.SplitContainer.Leading = debugPanel appState.LeftPanel = debugPanel // 刷新分割布局 appState.SplitContainer.Refresh() // 强制重绘整个窗口 appState.Window.Content().Refresh() appState.LastRefreshTime = time.Now() } // 重构 createLeftPanel 函数 - 确保使用正确的状态 func createLeftPanel(window fyne.Window, appState *AppState) *fyne.Container { // 创建顶部面板(用户状态/登录表单) topPanel := createTopPanel(appState) // 创建内容面板(店铺列表或提示) contentPanel := createContentPanel(appState) // 创建过滤面板 filterPanel := createFilterPanel(appState) // 使用Border布局 return container.NewBorder( topPanel, // 顶部 filterPanel, // 底部 nil, nil, // 左右 contentPanel, // 中间内容 ) } // 修改登录按钮回调 - 确保状态正确更新 func createLoginForm(appState *AppState) fyne.CanvasObject { usernameEntry := widget.NewEntry() passwordEntry := widget.NewPasswordEntry() usernameEntry.PlaceHolder = "输入邮箱地址" passwordEntry.PlaceHolder = "输入密码" // 登录按钮回调 loginButton := widget.NewButton("登录", func() { appState.StatusBar.SetText("登录中...") go func() { // 模拟网络延迟 time.Sleep(500 * time.Millisecond) // 获取店铺信息 shops := ShopModel.Api_select_struct(nil) fyne.DoAndWait(func() { if len(shops) == 0 { appState.StatusBar.SetText("获取店铺信息为空") return } // 更新应用状态 appState.Shops = shops appState.CurrentUser, _ = UserModel.Api_find_by_username(usernameEntry.Text) // 添加状态更新日志 fmt.Printf("登录成功 - 用户: %s, 店铺数量: %d\n", appState.CurrentUser.LoginName, len(appState.Shops)) if appState.CurrentUser.LoginName == "" { appState.CurrentUser.LoginName = "1" } appState.StatusBar.SetText(fmt.Sprintf("登录成功! 共 %d 个店铺", len(shops))) // 直接刷新UI refreshLeftPanel(appState) }) }() }) form := widget.NewForm( widget.NewFormItem("邮箱:", usernameEntry), widget.NewFormItem("密码:", passwordEntry), ) appState.LoginForm = form return container.NewVBox( widget.NewLabelWithStyle("登录面板", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), form, container.NewCenter(loginButton), ) } // 修改自动登录函数 - 添加详细日志 func tryAutoLogin(appState *AppState) { // 获取所有用户 users := UserModel.Api_select_struct(nil) if len(users) == 0 { fyne.DoAndWait(func() { appState.StatusBar.SetText("获取已经存在的账号为空") }) return } // 尝试使用第一个用户自动登录 user := users[0] fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("尝试自动登录: %s...", user.LoginName)) }) // 获取用户名输入框 if appState.LoginForm == nil || len(appState.LoginForm.Items) < 2 { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 登录表单尚未初始化") }) return } usernameItem := appState.LoginForm.Items[0] usernameEntry, ok := usernameItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 用户名控件类型错误") }) return } passwordItem := appState.LoginForm.Items[1] passwordEntry, ok := passwordItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 密码控件类型错误") }) return } // 触发登录 fyne.DoAndWait(func() { usernameEntry.SetText(user.LoginName) passwordEntry.SetText(user.LoginPass) appState.StatusBar.SetText("正在自动登录...") // 更新应用状态 appState.CurrentUser = user appState.Shops = ShopModel.Api_select_struct(nil) // 添加自动登录日志 fmt.Printf("自动登录成功 - 用户: %s, 店铺数量: %d\n", appState.CurrentUser.LoginName, len(appState.Shops)) // 直接刷新UI refreshLeftPanel(appState) }) } // 修改后的异步加载店铺头像函数 func loadShopAvatar(img *canvas.Image, url string) { if url == "" { // 使用默认头像 fyne.DoAndWait(func() { img.Resource = fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "account") img.Refresh() }) return } // 创建HTTP客户端(可设置超时) client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) if err != nil { log.Printf("加载头像失败: %v", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("头像请求失败: %s", resp.Status) return } // 解码图片 imgData, _, err := image.Decode(resp.Body) if err != nil { log.Printf("解码头像失败: %v", err) return } // 在主线程更新UI fyne.DoAndWait(func() { img.Image = imgData img.Refresh() }) } // 修改后的 createShopListPanel 函数 func createShopListPanel(appState *AppState) *fyne.Container { // 创建绑定数据 if appState.ShopListBinding == nil { appState.ShopListBinding = binding.NewUntypedList() } else { // 确保绑定数据是最新的 updateShopListBinding(appState) } // 创建列表控件 list := widget.NewListWithData( appState.ShopListBinding, func() fyne.CanvasObject { avatar := canvas.NewImageFromResource(nil) avatar.SetMinSize(fyne.NewSize(40, 40)) avatar.FillMode = canvas.ImageFillContain nameLabel := widget.NewLabel("") statusIcon := widget.NewIcon(nil) return container.NewHBox( avatar, container.NewVBox(nameLabel), layout.NewSpacer(), statusIcon, ) }, func(item binding.DataItem, obj fyne.CanvasObject) { hbox, ok := obj.(*fyne.Container) if !ok || len(hbox.Objects) < 4 { return } avatar, _ := hbox.Objects[0].(*canvas.Image) nameContainer, _ := hbox.Objects[1].(*fyne.Container) nameLabel, _ := nameContainer.Objects[0].(*widget.Label) statusIcon, _ := hbox.Objects[3].(*widget.Icon) val, err := item.(binding.Untyped).Get() if err != nil { return } shop, ok := val.(ShopModel.Account) if !ok { return } nameLabel.SetText(shop.AccountName) if shop.CanLogin { statusIcon.SetResource(res.ResShuffleSvg) } else { statusIcon.SetResource(fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "error")) } go loadShopAvatar(avatar, shop.AccountAvatar) }, ) list.OnSelected = func(id widget.ListItemID) { if id < 0 || id >= len(appState.Shops) { return } shop := appState.Shops[id] appState.StatusBar.SetText(fmt.Sprintf("加载 %s 的商品...", shop.AccountName)) go func() { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText("加载商品失败: " + err.Error()) }) return } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个商品", len(products))) addOrUpdateProductTab(appState, shop, products) }) }() } // 创建滚动容器 - 设置最小高度确保可滚动 scrollContainer := container.NewScroll(list) scrollContainer.SetMinSize(fyne.NewSize(280, 200)) // 最小高度200确保可滚动 // 使用Max容器确保填充空间 return container.NewMax( container.NewBorder( widget.NewLabel("店铺列表"), nil, nil, nil, scrollContainer, ), ) } // 更新店铺列表绑定数据 func updateShopListBinding(appState *AppState) { if appState.ShopListBinding == nil { appState.ShopListBinding = binding.NewUntypedList() } values := make([]interface{}, len(appState.Shops)) for i, shop := range appState.Shops { values[i] = shop } appState.ShopListBinding.Set(values) } // 应用商品过滤 func applyProductFilter(products []SkuModel.DataItem, keywords []string) []SkuModel.DataItem { if len(keywords) == 0 { return products // 没有关键字,返回所有商品 } filtered := []SkuModel.DataItem{} for _, product := range products { exclude := false for _, keyword := range keywords { if strings.Contains(strings.ToLower(product.Name), strings.ToLower(keyword)) { exclude = true break } } if !exclude { filtered = append(filtered, product) } } return filtered } // 修改 loadProductsForShop 函数,生成更多模拟数据 func loadProductsForShop(shop ShopModel.Account, appState *AppState) ([]SkuModel.DataItem, error) { // 模拟API调用获取商品数据 time.Sleep(500 * time.Millisecond) // 模拟网络延迟 // 生成100条模拟商品数据 products := make([]SkuModel.DataItem, 100) for i := 0; i < 100; i++ { products[i] = SkuModel.DataItem{ ProductID: fmt.Sprintf("SKU%04d", i+1), Name: fmt.Sprintf("%s - 商品%d", shop.AccountName, i+1), MarketPrice: i * 1000, DiscountPrice: i * 1000, } } // 应用过滤 filteredProducts := applyProductFilter(products, appState.FilterKeywords) return filteredProducts, nil } // 修改 addOrUpdateProductTab 函数,添加分页支持 func addOrUpdateProductTab(appState *AppState, shop ShopModel.Account, products []SkuModel.DataItem) { tabTitle := shop.AccountName appState.AllProducts[tabTitle] = products // 更新分页信息 appState.TotalProducts = len(products) appState.TotalPages = (appState.TotalProducts + appState.PageSize - 1) / appState.PageSize if appState.TotalPages == 0 { appState.TotalPages = 1 } // 获取当前页数据 currentPageProducts := getCurrentPageProducts(appState, products) // 检查是否已存在该TAB for _, tab := range appState.ProductTabs.Items { if tab.Text == tabTitle { // 更新现有TAB tab.Content = createProductListWithPagination(appState, currentPageProducts, products) // 更新映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Refresh() return } } // 创建新TAB newTab := container.NewTabItem( tabTitle, createProductListWithPagination(appState, currentPageProducts, products), ) // 添加到映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Append(newTab) appState.ProductTabs.Select(newTab) } // 获取当前页的商品数据 func getCurrentPageProducts(appState *AppState, products []SkuModel.DataItem) []SkuModel.DataItem { start := (appState.CurrentPage - 1) * appState.PageSize if start >= len(products) { start = 0 } end := start + appState.PageSize if end > len(products) { end = len(products) } return products[start:end] } // 创建带分页的商品列表 func createProductListWithPagination(appState *AppState, currentPageProducts []SkuModel.DataItem, allProducts []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := createProductTable(currentPageProducts) // 创建分页控件 pagination := createPaginationControls(appState, allProducts) // 创建布局:表格在上,分页控件在下 return container.NewBorder(nil, pagination, nil, nil, table) } // 创建商品表格 func createProductTable(products []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := widget.NewTable( func() (int, int) { return len(products) + 1, 4 // 行数=商品数+表头,列数=4 }, func() fyne.CanvasObject { return widget.NewLabel("模板文本") }, func(id widget.TableCellID, cell fyne.CanvasObject) { label := cell.(*widget.Label) if id.Row == 0 { // 表头 switch id.Col { case 0: label.SetText("商品ID") case 1: label.SetText("商品名称") case 2: label.SetText("价格") case 3: label.SetText("库存") } label.TextStyle.Bold = true return } // 数据行 product := products[id.Row-1] switch id.Col { case 0: label.SetText(product.ProductID) case 1: label.SetText(product.Name) case 2: label.SetText(fmt.Sprintf("¥%.2f", float64(product.MarketPrice)/100)) case 3: label.SetText(fmt.Sprintf("%d", product.DiscountPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) // 创建滚动容器 scrollContainer := container.NewScroll(table) scrollContainer.SetMinSize(fyne.NewSize(600, 400)) return scrollContainer } // 创建分页控件 func createPaginationControls(appState *AppState, allProducts []SkuModel.DataItem) *fyne.Container { // 上一页按钮 prevBtn := widget.NewButton("上一页", func() { if appState.CurrentPage > 1 { appState.CurrentPage-- refreshCurrentProductTab(appState, allProducts) } }) // 页码信息 pageInfo := widget.NewLabel(fmt.Sprintf("第 %d 页/共 %d 页", appState.CurrentPage, appState.TotalPages)) // 下一页按钮 nextBtn := widget.NewButton("下一页", func() { if appState.CurrentPage < appState.TotalPages { appState.CurrentPage++ refreshCurrentProductTab(appState, allProducts) } }) // 跳转输入框 jumpEntry := widget.NewEntry() jumpEntry.SetPlaceHolder("页码") jumpEntry.Validator = func(s string) error { _, err := strconv.Atoi(s) if err != nil { return errors.New("请输入数字") } return nil } jumpBtn := widget.NewButton("跳转", func() { page, err := strconv.Atoi(jumpEntry.Text) if err == nil && page >= 1 && page <= appState.TotalPages { appState.CurrentPage = page refreshCurrentProductTab(appState, allProducts) } }) // 页面大小选择器 pageSizeSelect := widget.NewSelect([]string{"5", "10", "20", "50"}, nil) // 先不设置回调 pageSizeSelect.SetSelected(fmt.Sprintf("%d", appState.PageSize)) // 设置当前选中的值,此时没有回调,不会触发 // 设置回调 pageSizeSelect.OnChanged = func(value string) { size, _ := strconv.Atoi(value) appState.PageSize = size appState.CurrentPage = 1 // 重置到第一页 refreshCurrentProductTab(appState, allProducts) } pageSizeLabel := widget.NewLabel("每页:") // 布局 return container.NewHBox( prevBtn, pageSizeLabel, pageSizeSelect, pageInfo, nextBtn, jumpEntry, jumpBtn, ) } // 刷新当前标签页的商品列表 func refreshCurrentProductTab(appState *AppState, allProducts []SkuModel.DataItem) { // 获取当前选中的标签页 currentTab := appState.ProductTabs.Selected() if currentTab == nil { return } // 使用传入的商品数据而不是重新加载 appState.TotalProducts = len(allProducts) appState.TotalPages = (appState.TotalProducts + appState.PageSize - 1) / appState.PageSize if appState.TotalPages == 0 { appState.TotalPages = 1 } // 获取当前页数据 currentPageProducts := getCurrentPageProducts(appState, allProducts) // 更新标签页内容 currentTab.Content = createProductListWithPagination(appState, currentPageProducts, allProducts) appState.ProductTabs.Refresh() } // 创建商品列表 - 修复表格填充问题 func createProductList(products []SkuModel.DataItem) fyne.CanvasObject { // 创建表格 table := widget.NewTable( func() (int, int) { return len(products) + 1, 4 // 行数=商品数+表头,列数=4 }, func() fyne.CanvasObject { return widget.NewLabel("模板文本") }, func(id widget.TableCellID, cell fyne.CanvasObject) { label := cell.(*widget.Label) if id.Row == 0 { // 表头 switch id.Col { case 0: label.SetText("商品ID") case 1: label.SetText("商品名称") case 2: label.SetText("价格") case 3: label.SetText("库存") } label.TextStyle.Bold = true return } // 数据行 product := products[id.Row-1] switch id.Col { case 0: label.SetText(product.ProductID) case 1: label.SetText(product.Name) case 2: label.SetText(fmt.Sprintf("¥%.2f", float64(product.MarketPrice)/100)) case 3: label.SetText(fmt.Sprintf("%d", product.DiscountPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) // 创建滚动容器 scrollContainer := container.NewScroll(table) scrollContainer.SetMinSize(fyne.NewSize(600, 400)) // 返回可滚动的表格容器 return scrollContainer } 这是修改后的代码,有两个小BUG,点击登陆后“店铺列表”并未及时刷新,第一次列表信息不显示,需要第二次登陆才能正常显示。切换店铺信息时,fmt.Sprintf("第 %d 页/共 %d 页", appState.CurrentPage, appState.TotalPages),显示的任然是上一个店铺的appState.CurrentPage

大家在看

recommend-type

ELEC5208 Group project submissions.zip_furniturer4m_smart grid_悉

悉尼大学ELEC5208智能电网project的很多组的报告和code都在里面,供学习和参考
recommend-type

基于python单通道脑电信号的自动睡眠分期研究

【作品名称】:基于python单通道脑电信号的自动睡眠分期研究 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】:网络结构(具体可查看network.py文件): 网络整体结构类似于TinySleepNet,对RNN部分进行了修改,增加了双向RNN、GRU、Attention等网络结构,可根据参数进行调整选择。 定义了seq_len参数,可以更灵活地调整batch_size与seq_len。 数据集加载(具体可查看dataset.py文件) 直接继承自torch的Dataset,并定义了seq_len和shuffle_seed,方便调整输入,并复现实验。 训练(具体可查看train.py文件): 定义并使用了focal loss损失函数 在实验中有使用wandb,感觉用起来还挺方便的,非常便于实验记录追溯 测试(具体可查看test.py文件): 可以输出accuracy、mf1、recall_confusion_matrics、precision_confusion_matrics、f1
recommend-type

bid格式文件电子标书阅读器.zip

软件介绍: bid格式招投标文件阅读器,可以打开浏览、管理电子招标文件,如果打不开标书文件,请按下面步骤检查:1、请查看招标文件(.bid文件)是否下载完全,请用IE下载工具下载;2、查看IE浏览器版本,如果版本低于IE8,低于IE8版本的请升级为IE8浏览器。
recommend-type

机器翻译WMT14数据集

机器翻译WMT14数据集,ACL2014公布的share task,很多模型都在这上benchmark
recommend-type

高通QXDM使用手册.pdf

高通QXDM使用手册,介绍高通QXDM工具软件的使用,中文版的哦。

最新推荐

recommend-type

C#类库封装:简化SDK调用实现多功能集成,构建地磅无人值守系统

内容概要:本文介绍了利用C#类库封装多个硬件设备的SDK接口,实现一系列复杂功能的一键式调用。具体功能包括身份证信息读取、人证识别、车牌识别(支持臻识和海康摄像头)、LED显示屏文字输出、称重数据读取、二维码扫描以及语音播报。所有功能均被封装为简单的API,极大降低了开发者的工作量和技术门槛。文中详细展示了各个功能的具体实现方式及其应用场景,如身份证读取、人证核验、车牌识别等,并最终将这些功能整合到一起,形成了一套完整的地磅称重无人值守系统解决方案。 适合人群:具有一定C#编程经验的技术人员,尤其是需要快速集成多种硬件设备SDK的应用开发者。 使用场景及目标:适用于需要高效集成多种硬件设备SDK的项目,特别是那些涉及身份验证、车辆管理、物流仓储等领域的企业级应用。通过使用这些封装好的API,可以大大缩短开发周期,降低维护成本,提高系统的稳定性和易用性。 其他说明:虽然封装后的API极大地简化了开发流程,但对于一些特殊的业务需求,仍然可能需要深入研究底层SDK。此外,在实际部署过程中,还需考虑网络环境、硬件兼容性等因素的影响。
recommend-type

Teleport Pro教程:轻松复制网站内容

标题中提到的“复制别人网站的软件”指向的是一种能够下载整个网站或者网站的特定部分,然后在本地或者另一个服务器上重建该网站的技术或工具。这类软件通常被称作网站克隆工具或者网站镜像工具。 描述中提到了一个具体的教程网址,并提到了“天天给力信誉店”,这可能意味着有相关的教程或资源可以在这个网店中获取。但是这里并没有提供实际的教程内容,仅给出了网店的链接。需要注意的是,根据互联网法律法规,复制他人网站内容并用于自己的商业目的可能构成侵权,因此在此类工具的使用中需要谨慎,并确保遵守相关法律法规。 标签“复制 别人 网站 软件”明确指出了这个工具的主要功能,即复制他人网站的软件。 文件名称列表中列出了“Teleport Pro”,这是一款具体的网站下载工具。Teleport Pro是由Tennyson Maxwell公司开发的网站镜像工具,允许用户下载一个网站的本地副本,包括HTML页面、图片和其他资源文件。用户可以通过指定开始的URL,并设置各种选项来决定下载网站的哪些部分。该工具能够帮助开发者、设计师或内容分析人员在没有互联网连接的情况下对网站进行离线浏览和分析。 从知识点的角度来看,Teleport Pro作为一个网站克隆工具,具备以下功能和知识点: 1. 网站下载:Teleport Pro可以下载整个网站或特定网页。用户可以设定下载的深度,例如仅下载首页及其链接的页面,或者下载所有可访问的页面。 2. 断点续传:如果在下载过程中发生中断,Teleport Pro可以从中断的地方继续下载,无需重新开始。 3. 过滤器设置:用户可以根据特定的规则过滤下载内容,如排除某些文件类型或域名。 4. 网站结构分析:Teleport Pro可以分析网站的链接结构,并允许用户查看网站的结构图。 5. 自定义下载:用户可以自定义下载任务,例如仅下载图片、视频或其他特定类型的文件。 6. 多任务处理:Teleport Pro支持多线程下载,用户可以同时启动多个下载任务来提高效率。 7. 编辑和管理下载内容:Teleport Pro具备编辑网站镜像的能力,并可以查看、修改下载的文件。 8. 离线浏览:下载的网站可以在离线状态下浏览,这对于需要测试网站在不同环境下的表现的情况十分有用。 9. 备份功能:Teleport Pro可以用来备份网站,确保重要数据的安全。 在实际使用此类工具时,需要注意以下几点: - 著作权法:复制网站内容可能侵犯原作者的版权,因此在使用此类工具时,必须确保有合法权利去下载和使用目标网站的内容。 - 服务条款:许多网站的服务条款明确禁止未经授权的网站克隆。因此,在使用此类软件之前,应当仔细阅读并遵守目标网站的服务条款。 - 数据隐私:下载含有个人数据的网站可能触及隐私保护法律,特别是在欧洲通用数据保护条例(GDPR)等法规的环境下。 - 网络安全:随意下载网站可能导致恶意软件和病毒的传播,用户应当使用最新的反病毒软件,并在安全的环境中进行操作。 综上所述,虽然“复制别人网站的软件”在技术上是可行的,且可以用于多种正当目的,但在使用此类工具时,必须遵守法律法规和道德规范,不可侵犯他人的合法权益。
recommend-type

【跨平台开发者的必读】:解决Qt5Widgetsd.lib目标计算机类型冲突终极指南

# 1. 跨平台开发与Qt框架简介 跨平台开发已经成为现代软件开发中不可或缺的一部分,它使得开发者能够构建出能够在多种操作系统上运行的应用程序,从而触及更广泛的用户群体。在这其中,Qt框架作为一套成熟的跨平台应用程序和用户界面框架,扮演着关键角色。Qt不仅仅是一个跨平台的工具集,它的设计哲学还包含了模块化和可扩展性,使其能够适应各种规模和复杂度的项目。本章节我们将简要介绍
recommend-type

普通RNN结构和特点

<think>首先,用户的问题是:“我想了解普通RNN的结构及其特点请问RNN循环神经网络结构特点工作原理”。这涉及到RNN的结构、特点和工作原理。根据系统级指令:-所有行内数学表达式必须使用$...$格式。-独立公式必须使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识。用户可见层指令:-回答结构清晰,帮助用户逐步解决问题。-保证回答真实可靠。参考站内引用:-引用[1]:关于RNN的基本介绍,为什么需要RNN。-引用[2]:关于RNN的工作原理、结构图,以及与其他网络的比较。用户上一次的问题和我的回答:用户是第一次
recommend-type

探讨通用数据连接池的核心机制与应用

根据给定的信息,我们能够推断出讨论的主题是“通用数据连接池”,这是一个在软件开发和数据库管理中经常用到的重要概念。在这个主题下,我们可以详细阐述以下几个知识点: 1. **连接池的定义**: 连接池是一种用于管理数据库连接的技术,通过维护一定数量的数据库连接,使得连接的创建和销毁操作更加高效。开发者可以在应用程序启动时预先创建一定数量的连接,并将它们保存在一个池中,当需要数据库连接时,可以直接从池中获取,从而降低数据库连接的开销。 2. **通用数据连接池的概念**: 当提到“通用数据连接池”时,它意味着这种连接池不仅支持单一类型的数据库(如MySQL、Oracle等),而且能够适应多种不同数据库系统。设计一个通用的数据连接池通常需要抽象出一套通用的接口和协议,使得连接池可以兼容不同的数据库驱动和连接方式。 3. **连接池的优点**: - **提升性能**:由于数据库连接创建是一个耗时的操作,连接池能够减少应用程序建立新连接的时间,从而提高性能。 - **资源复用**:数据库连接是昂贵的资源,通过连接池,可以最大化现有连接的使用,避免了连接频繁创建和销毁导致的资源浪费。 - **控制并发连接数**:连接池可以限制对数据库的并发访问,防止过载,确保数据库系统的稳定运行。 4. **连接池的关键参数**: - **最大连接数**:池中能够创建的最大连接数。 - **最小空闲连接数**:池中保持的最小空闲连接数,以应对突发的连接请求。 - **连接超时时间**:连接在池中保持空闲的最大时间。 - **事务处理**:连接池需要能够管理不同事务的上下文,保证事务的正确执行。 5. **实现通用数据连接池的挑战**: 实现一个通用的连接池需要考虑到不同数据库的连接协议和操作差异。例如,不同的数据库可能有不同的SQL方言、认证机制、连接属性设置等。因此,通用连接池需要能够提供足够的灵活性,允许用户配置特定数据库的参数。 6. **数据连接池的应用场景**: - **Web应用**:在Web应用中,为了处理大量的用户请求,数据库连接池可以保证数据库连接的快速复用。 - **批处理应用**:在需要大量读写数据库的批处理作业中,连接池有助于提高整体作业的效率。 - **微服务架构**:在微服务架构中,每个服务可能都需要与数据库进行交互,通用连接池能够帮助简化服务的数据库连接管理。 7. **常见的通用数据连接池技术**: - **Apache DBCP**:Apache的一个Java数据库连接池库。 - **C3P0**:一个提供数据库连接池和控制工具的开源Java框架。 - **HikariCP**:目前性能最好的开源Java数据库连接池之一。 - **BoneCP**:一个高性能的开源Java数据库连接池。 - **Druid**:阿里巴巴开源的一个数据库连接池,提供了对性能监控的高级特性。 8. **连接池的管理与监控**: 为了保证连接池的稳定运行,开发者需要对连接池的状态进行监控,并对其进行适当的管理。监控指标可能包括当前活动的连接数、空闲的连接数、等待获取连接的请求队列长度等。一些连接池提供了监控工具或与监控系统集成的能力。 9. **连接池的配置和优化**: 连接池的性能与连接池的配置密切相关。需要根据实际的应用负载和数据库性能来调整连接池的参数。例如,在高并发的场景下,可能需要增加连接池中连接的数量。另外,适当的线程池策略也可以帮助连接池更好地服务于多线程环境。 10. **连接池的应用案例**: 一个典型的案例是电商平台在大型促销活动期间,用户访问量激增,此时通用数据连接池能够保证数据库操作的快速响应,减少因数据库连接问题导致的系统瓶颈。 总结来说,通用数据连接池是现代软件架构中的重要组件,它通过提供高效的数据库连接管理,增强了软件系统的性能和稳定性。了解和掌握连接池的原理及实践,对于任何涉及数据库交互的应用开发都至关重要。在实现和应用连接池时,需要关注其设计的通用性、配置的合理性以及管理的有效性,确保在不同的应用场景下都能发挥出最大的效能。
recommend-type

【LabVIEW网络通讯终极指南】:7个技巧提升UDP性能和安全性

# 摘要 本文系统介绍了LabVIEW在网络通讯中的应用,尤其是针对UDP协议的研究与优化。首先,阐述了UDP的原理、特点及其在LabVIEW中的基础应用。随后,本文深入探讨了通过调整数据包大小、实现并发通信及优化缓冲区管理等技巧来优化UDP性能的LabVIEW方法。接着,文章聚焦于提升UDP通信安全性,介绍了加密技术和认证授权机制在LabVIEW中的实现,以及防御网络攻击的策略。最后,通过具体案例展示了LabVIEW在实时数据采集和远程控制系统中的高级应用,并展望了LabVIEW与UDP通讯技术的未来发展趋势及新兴技术的影响。 # 关键字 LabVIEW;UDP网络通讯;性能优化;安全性;
recommend-type

简要介绍cnn卷积神经网络

<think>首先,用户的问题是:“我想了解卷积神经网络的基本概念和原理 请问CNN卷积神经网络简介及其工作原理”。所以,我需要基于提供的引用材料,构建一个清晰的回答。 引用材料总结: - [^1]: CNN的核心思想是局部感受野、权值共享和时间或空间亚采样,提供位移、尺度、形变不变性。三大特色:局部感知、权重共享和多卷积核。 - [^2]: CNN是一种前馈神经网络,由卷积层和池化层组成,特别在图像处理方面出色。与传统多层神经网络相比,CNN加入了卷积层和池化层,使特征学习更有效。 - [^3]: CNN与全连接神经网络的区别:至少有一个卷积层提取特征;神经元局部连接和权值共享,减少参数数
recommend-type

基于ASP的深度学习网站导航系统功能详解

从给定文件中我们可以提取以下IT知识点: ### 标题知识点 #### "ASP系统篇" - **ASP技术介绍**:ASP(Active Server Pages)是一种服务器端的脚本环境,用于创建动态交互式网页。ASP允许开发者将HTML网页与服务器端脚本结合,使用VBScript或JavaScript等语言编写代码,以实现网页内容的动态生成。 - **ASP技术特点**:ASP适用于小型到中型的项目开发,它可以与数据库紧密集成,如Microsoft的Access和SQL Server。ASP支持多种组件和COM(Component Object Model)对象,使得开发者能够实现复杂的业务逻辑。 #### "深度学习网址导航系统" - **深度学习概念**:深度学习是机器学习的一个分支,通过构建深层的神经网络来模拟人类大脑的工作方式,以实现对数据的高级抽象和学习。 - **系统功能与深度学习的关系**:该标题可能意味着系统在进行网站分类、搜索优化、内容审核等方面采用了深度学习技术,以提供更智能、自动化的服务。然而,根据描述内容,实际上系统并没有直接使用深度学习技术,而是提供了一个传统的网址导航服务,可能是命名上的噱头。 ### 描述知识点 #### "全后台化管理,操作简单" - **后台管理系统的功能**:后台管理系统允许网站管理员通过Web界面执行管理任务,如内容更新、用户管理等。它通常要求界面友好,操作简便,以适应不同技术水平的用户。 #### "栏目无限分类,自由添加,排序,设定是否前台显示" - **动态网站结构设计**:这意味着网站结构具有高度的灵活性,支持创建无限层级的分类,允许管理员自由地添加、排序和设置分类的显示属性。这种设计通常需要数据库支持动态生成内容。 #### "各大搜索和站内搜索随意切换" - **搜索引擎集成**:网站可能集成了外部搜索引擎(如Google、Bing)和内部搜索引擎功能,让用户能够方便地从不同来源获取信息。 #### "网站在线提交、审阅、编辑、删除" - **内容管理系统的功能**:该系统提供了一个内容管理平台,允许用户在线提交内容,由管理员进行审阅、编辑和删除操作。 #### "站点相关信息后台动态配置" - **动态配置机制**:网站允许管理员通过后台系统动态调整各种配置信息,如网站设置、参数调整等,从而实现快速的网站维护和更新。 #### "自助网站收录,后台审阅" - **网站收录和审核机制**:该系统提供了一套自助收录流程,允许其他网站提交申请,由管理员进行后台审核,决定是否收录。 #### "网站广告在线发布" - **广告管理功能**:网站允许管理员在线发布和管理网站广告位,以实现商业变现。 #### "自动生成静态页 ver2.4.5" - **动态与静态内容**:系统支持动态内容的生成,同时也提供了静态页面的生成机制,这可能有助于提高网站加载速度和搜索引擎优化。 #### "重写后台网址分类管理" - **系统优化与重构**:提到了后台网址分类管理功能的重写,这可能意味着系统进行了一次重要的更新,以修复前一个版本的错误,并提高性能。 ### 标签知识点 #### "ASP web 源代码 源码" - **ASP程序开发**:标签表明这是一个ASP语言编写的网站源代码,可能是一个开源项目,供开发者下载、研究或部署到自己的服务器上。 ### 压缩包子文件名称列表知识点 #### "深度学习(asp)网址导航程序" - **文件内容和类型**:文件列表中提到的“深度学习(asp)网址导航程序”表明这是一个ASP语言编写的网址导航系统程序,可能包含了系统安装和配置需要的所有源文件。 通过以上分析,我们可以得出这个ASP系统是一个传统的网址导航系统,以后台管理为核心功能,并没有实际运用到深度学习技术。系统的主要功能包括对网站内容、分类、搜索引擎、广告位、以及其他网站相关信息的管理。它可能还提供了一个平台,供用户提交网址,供管理员审核并收录到导航中。源代码可能以ASP语言编写,并在文件中包含了所有必要的程序文件。
recommend-type

【Oracle数据泵进阶技巧】:避免ORA-31634和ORA-31664错误的终极策略

# 1. Oracle数据泵技术概述 ## Oracle数据泵技术简介 Oracle数据泵(Data Pump)是一种用于高效地在Oracle数据库之间传输数据和元数据的工具。它从Oracle 10g版本开始引入,提供了快速且灵活的数据导入导出功能。数据泵技术优于旧版的`imp`和`exp`工具,因为它支持多线程,可以在导入和导出过程中显著提高性能。 ## 数据泵的核心优势 数据泵的核心优势在于它能并行处理数据,支持大对象(LOBs)和网络传输。它还允许用户自定义数据和对象的传输方式,以及可以控制传输过程中的各种细节,如过滤对象、调整数据缓冲区大小、并行度和网络数据包大小等。 ## 数据
recommend-type

多头注意力机制的时间复杂度

<think>首先,用户的问题是:“我想计算多头注意力机制的时间复杂度 请问多头注意力机制的时间复杂度计算方法”。我需要基于提供的引用和系统级指令来回答。 关键引用: - 引用[1]:提到Transformer模型的计算时间复杂度主要由自注意力机制的O(n²·d)决定。 - 引用[2]:详细解释了注意力机制的计算复杂度,包括QK转置的复杂度为O(N²d),内存需求为N² + Nd。 - 引用[3]:提到原始注意力机制的时间复杂度为O(n²d),并讨论了优化方法如稀疏注意力和线性注意力。 - 引用[4]:涉及多头注意力的未来趋势,但没有直接给出计算方法。 用户的问题是计算多头注意力机制的时间