From 1d2b22542948f26eb1ae2a9ecb0543336784eb1c Mon Sep 17 00:00:00 2001 From: Default User Date: Thu, 26 Feb 2026 14:40:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0certificate=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=92=8C=E4=BA=8C=E7=BB=B4=E7=A0=81=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.json | 3 +- pages/collection/index.js | 343 ++++++++++++++++++++++++++ pages/collection/index.json | 6 + pages/collection/index.wxml | 111 +++++++++ pages/collection/index.wxss | 434 +++++++++++++++++++++++++++++++++ pages/index/index.wxml | 4 +- pages/qrcode/index.js | 7 + pages/qrcode/index.wxml | 1 + server-example/server-mysql.js | 375 ++++++++++++++++++++++++++++ utils/api.js | 16 ++ 10 files changed, 1297 insertions(+), 3 deletions(-) create mode 100644 pages/collection/index.js create mode 100644 pages/collection/index.json create mode 100644 pages/collection/index.wxml create mode 100644 pages/collection/index.wxss diff --git a/app.json b/app.json index 39a8bdd..a808d3c 100644 --- a/app.json +++ b/app.json @@ -22,7 +22,8 @@ "pages/order/index", "pages/order/detail/index", "pages/company/company", - "pages/qrcode/index" + "pages/qrcode/index", + "pages/collection/index" ], "subpackages": [ { diff --git a/pages/collection/index.js b/pages/collection/index.js new file mode 100644 index 0000000..3b8855c --- /dev/null +++ b/pages/collection/index.js @@ -0,0 +1,343 @@ +// pages/collection/index.js +const API = require('../../utils/api.js'); + +Page({ + data: { + qrCodes: [], // 二维码数据 + invitees: [], // 邀请者列表 + isAdmin: false, // 是否为管理员 + activeFilter: 'all', // 当前激活的筛选条件 + sidebarVisible: false, // 侧边栏是否可见 + expandedIndex: -1, // 当前展开的二维码索引 + searchKeyword: '' // 搜索关键词 + }, + + onLoad: function (options) { + // 页面加载时的初始化逻辑 + console.log('二维码合集页面加载,options:', options); + + // 检查是否是通过内部按钮进入的 + if (!options.from || options.from !== 'internal') { + console.log('非内部进入,重定向到首页'); + wx.redirectTo({ + url: '/pages/index/index' + }); + return; + } + + // 加载二维码合集 + this.loadQrCollection(); + }, + + onShow: function () { + // 页面显示时重新加载数据 + this.loadQrCollection(); + }, + + // 加载用户信息 + loadUserInfo: function () { + return new Promise((resolve, reject) => { + // 从本地存储获取用户信息 + const userInfo = wx.getStorageSync('userInfo') || {}; + // 获取电话号码信息 + const phoneNumber = userInfo.phoneNumber || wx.getStorageSync('phoneNumber') || ''; + + // 如果有电话号码,查询personnel表获取详细信息 + if (phoneNumber) { + // 根据电话号码查询personnel表 + API.request('/api/personnel/get', 'POST', { phone: phoneNumber }).then(res => { + console.log('查询personnel表结果:', res); + if (res && res.success && res.data && res.data.length > 0) { + const personnelInfo = res.data[0]; + // 更新用户信息 + const updatedUserInfo = { + name: personnelInfo.name || userInfo.name || userInfo.userName || '微信用户', + phoneNumber: phoneNumber, + projectName: personnelInfo.projectName || '' + }; + // 存储到本地存储 + wx.setStorageSync('userInfo', updatedUserInfo); + resolve(updatedUserInfo); + } else { + // 如果查询失败,使用本地存储的用户信息 + const fallbackUserInfo = { + name: userInfo.name || userInfo.userName || '微信用户', + phoneNumber: phoneNumber, + projectName: userInfo.projectName || '' + }; + resolve(fallbackUserInfo); + } + }).catch(err => { + console.error('查询personnel表失败:', err); + // 如果查询失败,使用本地存储的用户信息 + const fallbackUserInfo = { + name: userInfo.name || userInfo.userName || '微信用户', + phoneNumber: phoneNumber, + projectName: userInfo.projectName || '' + }; + resolve(fallbackUserInfo); + }); + } else { + // 如果没有电话号码,使用本地存储的用户信息 + const fallbackUserInfo = { + name: userInfo.name || userInfo.userName || '微信用户', + phoneNumber: phoneNumber, + projectName: userInfo.projectName || '' + }; + resolve(fallbackUserInfo); + } + }); + }, + + // 将UTC时间转换为北京时间 + convertToBeijingTime: function (utcTime) { + if (!utcTime) return '未知'; + + // 创建Date对象 + const date = new Date(utcTime); + + // 转换为北京时间(UTC+8) + const beijingTime = new Date(date.getTime() + 8 * 60 * 60 * 1000); + + // 格式化时间 + const year = beijingTime.getFullYear(); + const month = String(beijingTime.getMonth() + 1).padStart(2, '0'); + const day = String(beijingTime.getDate()).padStart(2, '0'); + const hours = String(beijingTime.getHours()).padStart(2, '0'); + const minutes = String(beijingTime.getMinutes()).padStart(2, '0'); + const seconds = String(beijingTime.getSeconds()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + }, + + // 处理二维码数据,转换时间为北京时间 + processQrCodes: function (qrCodes) { + return qrCodes.map(qrCode => { + const newQrCode = { ...qrCode }; + // 转换创建时间 + if (newQrCode.createdAt) { + newQrCode.createdAt = this.convertToBeijingTime(newQrCode.createdAt); + } + // 转换签发日期 + if (newQrCode.issueDate) { + newQrCode.issueDate = this.convertToBeijingTime(newQrCode.issueDate); + } + return newQrCode; + }); + }, + + // 加载二维码合集 + loadQrCollection: function () { + this.loadUserInfo().then(user => { + try { + // 构建请求参数 + let params = {}; + if (user) { + params.userName = user.name; + params.phoneNumber = user.phoneNumber; + params.projectName = user.projectName; + } + + console.log('加载二维码合集,用户信息:', user); + + // 从服务器获取二维码合集 + API.getQrCollection(params).then(res => { + console.log('获取二维码合集结果:', res); + + if (res && res.success) { + // 动态生成筛选按钮 + if (res.isAdmin && res.invitees && res.invitees.length > 0) { + this.setData({ + isAdmin: res.isAdmin, + invitees: res.invitees + }); + } + + if (res.qrCodes && res.qrCodes.length > 0) { + // 处理二维码数据,转换时间为北京时间 + const processedQrCodes = this.processQrCodes(res.qrCodes); + + // 渲染二维码合集 + this.setData({ + qrCodes: processedQrCodes + }); + } else { + // 显示空状态 + this.setData({ + qrCodes: [] + }); + } + } else { + // 显示错误状态 + wx.showToast({ + title: '加载失败,请稍后重试', + icon: 'none', + duration: 2000 + }); + } + }).catch(err => { + console.error('获取二维码合集失败:', err); + wx.showToast({ + title: '加载失败,请稍后重试', + icon: 'none', + duration: 2000 + }); + }); + } catch (error) { + console.error('获取二维码合集失败:', error); + wx.showToast({ + title: '加载失败,请稍后重试', + icon: 'none', + duration: 2000 + }); + } + }); + }, + + // 打开筛选侧边栏 + openSidebar: function () { + this.setData({ + sidebarVisible: true + }); + }, + + // 关闭筛选侧边栏 + closeSidebar: function () { + this.setData({ + sidebarVisible: false + }); + }, + + // 切换展开/收起状态 + toggleSection: function (e) { + const index = e.currentTarget.dataset.index; + this.setData({ + expandedIndex: this.data.expandedIndex === index ? -1 : index + }); + }, + + // 切换筛选条件 + switchFilter: function (e) { + const filter = e.currentTarget.dataset.filter; + this.setData({ + activeFilter: filter + }); + this.filterQrCodes(filter, null, this.data.searchKeyword); + }, + + // 按邀请者筛选 + filterByInviter: function (e) { + const filter = e.currentTarget.dataset.filter; + const inviterName = e.currentTarget.dataset.inviter; + this.setData({ + activeFilter: filter + }); + this.filterQrCodes(filter, inviterName, this.data.searchKeyword); + this.closeSidebar(); + }, + + // 搜索输入事件 + onSearchInput: function (e) { + const searchKeyword = e.detail.value; + this.setData({ + searchKeyword: searchKeyword + }); + this.filterQrCodes(this.data.activeFilter, null, searchKeyword); + }, + + // 筛选二维码 + filterQrCodes: function (filter, inviterName, searchKeyword = '') { + this.loadUserInfo().then(user => { + // 构建请求参数 + let params = {}; + if (user) { + params.userName = user.name; + params.phoneNumber = user.phoneNumber; + params.projectName = user.projectName; + } + + console.log('筛选二维码,用户信息:', user); + + // 重新加载并筛选二维码 + API.getQrCollection(params).then(res => { + if (res && res.success && res.qrCodes && res.qrCodes.length > 0) { + // 处理二维码数据,转换时间为北京时间 + let filteredQrCodes = this.processQrCodes(res.qrCodes); + + if (filter === 'me' && user) { + // 筛选当前用户自己的二维码 + filteredQrCodes = filteredQrCodes.filter(qrCode => + qrCode.inviter === user.name + ); + } else if (filter.startsWith('invitee_') && inviterName) { + // 筛选特定邀请者的二维码 + filteredQrCodes = filteredQrCodes.filter(qrCode => + qrCode.inviter === inviterName + ); + } + + // 搜索功能 + if (searchKeyword) { + const keyword = searchKeyword.toLowerCase(); + filteredQrCodes = filteredQrCodes.filter(qrCode => { + return ( + (qrCode.phoneNumber && qrCode.phoneNumber.toLowerCase().includes(keyword)) || + (qrCode.company && qrCode.company.toLowerCase().includes(keyword)) + ); + }); + + // 添加高亮效果 + filteredQrCodes = filteredQrCodes.map(qrCode => { + const newQrCode = { ...qrCode }; + if (newQrCode.company && newQrCode.company.toLowerCase().includes(keyword)) { + const regex = new RegExp(`(${keyword})`, 'gi'); + newQrCode.company = newQrCode.company.replace(regex, '$1'); + } + if (newQrCode.phoneNumber && newQrCode.phoneNumber.toLowerCase().includes(keyword)) { + const regex = new RegExp(`(${keyword})`, 'gi'); + newQrCode.phoneNumber = newQrCode.phoneNumber.replace(regex, '$1'); + } + return newQrCode; + }); + } + + this.setData({ + qrCodes: filteredQrCodes + }); + } + }).catch(error => { + console.error('筛选二维码失败:', error); + }); + }); + }, + + // 返回上一页 + goBack: function () { + wx.navigateBack({ + delta: 1 + }); + }, + + // 跳转到生成邀请二维码页面 + goToGenerate: function () { + wx.redirectTo({ + url: '/pages/qrcode/index?from=internal' + }); + }, + + // 图片加载成功事件 + onImageLoad: function () { + console.log('二维码图片加载成功'); + }, + + // 图片加载失败事件 + onImageError: function (e) { + console.error('二维码图片加载失败:', e); + wx.showToast({ + title: '二维码图片加载失败', + icon: 'none', + duration: 2000 + }); + } + +}); \ No newline at end of file diff --git a/pages/collection/index.json b/pages/collection/index.json new file mode 100644 index 0000000..0727ad9 --- /dev/null +++ b/pages/collection/index.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "二维码合集", + "navigationBarBackgroundColor": "#ffffff", + "navigationBarTextStyle": "black", + "enablePullDownRefresh": true +} \ No newline at end of file diff --git a/pages/collection/index.wxml b/pages/collection/index.wxml new file mode 100644 index 0000000..34508aa --- /dev/null +++ b/pages/collection/index.wxml @@ -0,0 +1,111 @@ + + + + + + + 全部 + + + 我的 + + + + + + + 🔍 + 筛选 + + + + + + + + + 暂无二维码 + 请先生成邀请二维码 + + + + + + + + + + + + / {{item.createdAt || '未知'}} + + + + {{expandedIndex === index ? '▲' : '▼'}} + + + + + + + + {{item.inviter || '未知'}} + + + {{item.inviterProjectName || '无职位'}} + + + + + + + + + + + + + + + + + + + 筛选用户 + + + + + + + {{item.inviter}} + + + + + \ No newline at end of file diff --git a/pages/collection/index.wxss b/pages/collection/index.wxss new file mode 100644 index 0000000..c501c07 --- /dev/null +++ b/pages/collection/index.wxss @@ -0,0 +1,434 @@ +/* pages/collection/index.wxss */ + +/* 页面容器 */ +.page-container { + width: 100%; + background-color: #f5f5f5; + min-height: 100vh; + box-sizing: border-box; +} + +/* 筛选和搜索栏 */ +.filter-bar { + display: flex; + align-items: center; + padding: 16rpx; + background-color: #f0f9f0; + border-bottom: 1rpx solid #d4edda; + box-sizing: border-box; + position: sticky; + top: 0; + z-index: 900; + flex-wrap: wrap; + gap: 12rpx; + min-height: 100rpx; + overflow-x: hidden; +} + +.filter-tabs { + display: flex; + gap: 8rpx; + flex-shrink: 0; +} + +.filter-tab { + padding: 6rpx 12rpx; + border-radius: 16rpx; + background-color: #f0f0f0; + font-size: 22rpx; + color: #666; + transition: all 0.3s; + white-space: nowrap; +} + +.filter-tab.active { + background-color: #4CAF50; + color: white; +} + +.search-container { + flex: 1; + min-width: 150rpx; + position: relative; + margin: 0 8rpx; + flex-basis: 0; + max-width: 400rpx; +} + +.search-input { + width: 100%; + flex: 1; + padding: 16rpx 20rpx; + border: 1rpx solid #e0e0e0; + border-radius: 12rpx; + font-size: 28rpx; + box-sizing: border-box; + background-color: #f9f9f9; + min-height: 70rpx; +} + +.search-input:focus { + border-color: #4CAF50; + background-color: #ffffff; + box-shadow: 0 0 0 2rpx rgba(76, 175, 80, 0.1); +} + +/* 高亮效果 */ +.highlight { + background-color: #FFF3CD; + color: #856404; + padding: 0 4rpx; + border-radius: 4rpx; + font-weight: 500; +} + +.filter-button { + display: flex; + align-items: center; + gap: 6rpx; + padding: 12rpx 16rpx; + background-color: #4CAF50; + color: white; + border-radius: 10rpx; + font-size: 24rpx; + flex-shrink: 0; + min-height: 60rpx; + white-space: nowrap; +} + +.filter-icon { + font-size: 24rpx; +} + +.filter-text { + font-size: 22rpx; +} + +/* 二维码列表 */ +.qr-list { + padding: 16rpx; + box-sizing: border-box; +} + +/* 空状态 */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 100rpx 0; + color: #999; + text-align: center; +} + +.empty-image { + width: 120rpx; + height: 120rpx; + margin-bottom: 24rpx; + opacity: 0.5; +} + +.empty-text { + font-size: 28rpx; + margin-bottom: 8rpx; + color: #666; +} + +.empty-subtext { + font-size: 24rpx; + color: #999; +} + +/* 二维码项 */ +.qr-item { + background-color: white; + border-radius: 12rpx; + margin-bottom: 16rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06); + overflow: hidden; +} + +.qr-item-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16rpx; + cursor: pointer; +} + +.qr-item-info { + flex: 1; + overflow: hidden; +} + +.qr-item-info rich-text { + display: block; + font-size: 28rpx; + font-weight: 500; + color: #333; + margin-bottom: 8rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.qr-item-details { + display: flex; + align-items: center; + font-size: 24rpx; + color: #666; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.qr-item-details rich-text { + display: inline; + font-size: 24rpx; + color: #666; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.qr-item-details text { + margin-left: 8rpx; + font-size: 24rpx; + color: #666; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.qr-item-arrow { + font-size: 24rpx; + color: #999; + margin-left: 16rpx; + flex-shrink: 0; +} + +/* 二维码详情 */ +.qr-item-content { + padding: 0 16rpx 16rpx; + border-top: 1rpx solid #f0f0f0; + animation: slideDown 0.3s ease; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10rpx); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.qr-image { + width: 200rpx; + height: 200rpx; + margin: 16rpx auto; + display: block; + border-radius: 8rpx; +} + +.qr-info-center { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 16rpx; +} + +.qr-info-item { + margin-bottom: 8rpx; + font-size: 24rpx; + color: #333; + text-align: center; + width: 100%; + max-width: 200rpx; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* 底部按钮 */ +.bottom-section { + padding: 20rpx 16rpx; + background-color: white; + border-top: 1rpx solid #e0e0e0; + margin-top: 20rpx; +} + +.generate-button { + width: 100%; + height: 80rpx; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 12rpx; + font-size: 28rpx; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; +} + +.generate-button-text { + color: white; + font-size: 28rpx; +} + +/* 筛选侧边栏 */ +.sidebar { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 2000; + display: flex; + flex-direction: column; +} + +.sidebar-overlay { + flex: 1; + background-color: rgba(0, 0, 0, 0.5); +} + +.sidebar-content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 70%; + max-width: 300rpx; + background-color: white; + box-shadow: -4rpx 0 10rpx rgba(0, 0, 0, 0.1); + animation: slideIn 0.3s ease; +} + +@keyframes slideIn { + from { + transform: translateX(100%); + } + to { + transform: translateX(0); + } +} + +.sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx; + border-bottom: 1rpx solid #e0e0e0; +} + +.sidebar-title { + font-size: 28rpx; + font-weight: 600; + color: #333; +} + +.sidebar-close { + font-size: 32rpx; + color: #999; + cursor: pointer; +} + +.sidebar-body { + padding: 20rpx; + max-height: calc(100% - 80rpx); + overflow-y: auto; +} + +.sidebar-item { + padding: 16rpx 0; + border-bottom: 1rpx solid #f0f0f0; + cursor: pointer; +} + +.sidebar-item:last-child { + border-bottom: none; +} + +.sidebar-item-text { + font-size: 24rpx; + color: #333; +} + +/* 响应式调整 */ +@media (max-width: 375px) { + .filter-bar { + flex-direction: column; + align-items: stretch; + } + + .filter-tabs { + justify-content: center; + } + + .search-container { + min-width: 100%; + margin: 8rpx 0; + } + + .filter-button { + align-self: center; + } +} + +/* 针对中等屏幕的调整 */ +@media (min-width: 376px) and (max-width: 480px) { + .filter-bar { + flex-wrap: nowrap; + } + + .filter-tabs { + flex-shrink: 0; + } + + .search-container { + flex: 1; + min-width: 150rpx; + max-width: 300rpx; + } + + .filter-button { + flex-shrink: 0; + } +} + +/* 针对大屏幕的调整 */ +@media (min-width: 481px) { + .filter-bar { + flex-wrap: nowrap; + } + + .search-container { + flex: 1; + min-width: 200rpx; + max-width: 400rpx; + } +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 4rpx; + height: 4rpx; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 2rpx; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 2rpx; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} \ No newline at end of file diff --git a/pages/index/index.wxml b/pages/index/index.wxml index c14ba81..d9ffe1e 100644 --- a/pages/index/index.wxml +++ b/pages/index/index.wxml @@ -215,8 +215,8 @@ 我们 - 📱 - 二维码 + 📜 + 合格证 diff --git a/pages/qrcode/index.js b/pages/qrcode/index.js index 2f7de38..c7f8042 100644 --- a/pages/qrcode/index.js +++ b/pages/qrcode/index.js @@ -296,6 +296,13 @@ Page({ }); } }); + }, + + // 跳转到二维码合集页面 + goToQrCollection: function () { + wx.redirectTo({ + url: '/pages/collection/index?from=internal' + }); } }); \ No newline at end of file diff --git a/pages/qrcode/index.wxml b/pages/qrcode/index.wxml index 92743b3..9ae1bbf 100644 --- a/pages/qrcode/index.wxml +++ b/pages/qrcode/index.wxml @@ -52,6 +52,7 @@ + diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js index 1b545a2..e8dfda2 100644 --- a/server-example/server-mysql.js +++ b/server-example/server-mysql.js @@ -1781,6 +1781,86 @@ Product.init({ timestamps: false }); +// 合格证模型 +class Certificate extends Model { } +Certificate.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + company: { + type: DataTypes.STRING(255), + allowNull: false, + comment: '公司名称' + }, + phoneNumber: { + type: DataTypes.STRING(20), + allowNull: false, + comment: '电话号码' + }, + productName: { + type: DataTypes.STRING(255), + allowNull: false, + comment: '产品名称' + }, + grossWeight: { + type: DataTypes.STRING(100), + allowNull: false, + comment: '毛重' + }, + commitBasis: { + type: DataTypes.TEXT, + allowNull: false, + comment: '承诺依据' + }, + origin: { + type: DataTypes.STRING(255), + allowNull: false, + comment: '产地' + }, + issueDate: { + type: DataTypes.DATE, + allowNull: false, + comment: '签发日期' + }, + signature: { + type: DataTypes.TEXT, + allowNull: true, + comment: '签名' + }, + sessionId: { + type: DataTypes.STRING(255), + allowNull: true, + comment: '会话ID' + }, + qrCodeUrl: { + type: DataTypes.TEXT, + allowNull: true, + comment: '二维码URL' + }, + inviter: { + type: DataTypes.STRING(255), + allowNull: true, + comment: '邀请者名称' + }, + inviter_phone: { + type: DataTypes.STRING(20), + allowNull: true, + comment: '邀请者电话' + }, + projectName: { + type: DataTypes.STRING(255), + allowNull: true, + comment: '邀请者职位' + } +}, { + sequelize, + modelName: 'Certificate', + tableName: 'certificate', + timestamps: false +}); + // 购物车模型 class CartItem extends Model { } CartItem.init({ @@ -2774,6 +2854,301 @@ app.post('/api/user/update-location', async (req, res) => { } }); +// 获取二维码合集接口 +app.get('/api/certificate/getQrCollection', async (req, res) => { + try { + // 解析URL参数,获取用户信息 + const { userName, projectName } = req.query; + + console.log('获取二维码合集请求,用户信息:', { userName, projectName }); + + // 判断用户角色:如果职位名称包含"管理员"或"Admin",则视为管理员 + const isAdmin = projectName && (projectName.includes('管理员') || projectName.includes('Admin')); + console.log('用户角色判断:', { isAdmin }); + + // 构建查询条件 + let whereCondition = { + qrCodeUrl: { + [Op.ne]: null + } + }; + + if (!isAdmin && userName) { + // 采购员只能查看自己的二维码 + whereCondition.inviter = userName; + } + + // 从数据库获取合格证信息 + const certificates = await Certificate.findAll({ + where: whereCondition, + attributes: [ + 'inviter', + 'inviter_phone', + 'projectName', + 'sessionId', + 'qrCodeUrl', + 'company', + 'phoneNumber', + 'issueDate' + ], + group: ['sessionId'], + order: [['issueDate', 'DESC']] + }); + + // 生成二维码URL列表 + const qrCodes = certificates.map(cert => { + // 生成包含会话ID的URL + const url = `http://8.137.125.67:3008/certificate.html?sessionId=${encodeURIComponent(cert.sessionId)}&inviter=${encodeURIComponent(cert.inviter)}&inviterPhone=${encodeURIComponent(cert.inviter_phone)}&inviterProjectName=${encodeURIComponent(cert.projectName)}`; + + return { + inviter: cert.inviter, + inviterPhone: cert.inviter_phone, + inviterProjectName: cert.projectName, + sessionId: cert.sessionId, + qrCodeUrl: cert.qrCodeUrl, + company: cert.company, + phoneNumber: cert.phoneNumber, + issueDate: cert.issueDate, + createdAt: cert.issueDate ? cert.issueDate.toISOString() : null, + url: url + }; + }); + + console.log('获取二维码合集成功,数量:', qrCodes.length); + + // 获取所有邀请者列表(仅管理员) + let invitees = []; + if (isAdmin) { + const inviteeList = await Certificate.findAll({ + where: { + inviter: { + [Op.ne]: null + }, + qrCodeUrl: { + [Op.ne]: null + } + }, + attributes: [ + 'inviter', + 'inviter_phone', + 'projectName' + ], + group: ['inviter', 'inviter_phone', 'projectName'] + }); + + invitees = inviteeList.map(item => ({ + inviter: item.inviter, + inviterPhone: item.inviter_phone, + inviterProjectName: item.projectName + })); + } + + res.json({ + success: true, + qrCodes: qrCodes, + isAdmin: isAdmin, + invitees: invitees + }); + } catch (error) { + console.error('获取二维码合集失败:', error); + res.status(500).json({ + success: false, + error: '获取二维码合集失败,请重试' + }); + } +}); + +// 获取最新的合格证信息接口 +app.get('/api/certificate/getLatestCertificate', async (req, res) => { + try { + // 解析URL参数,获取sessionId + const { sessionId } = req.query; + + console.log('获取合格证信息,sessionId:', sessionId); + + // 构建查询条件 + let whereCondition = {}; + if (sessionId) { + whereCondition.sessionId = sessionId; + } else { + whereCondition.sessionId = null; + } + + // 从数据库获取对应会话的最新合格证信息 + const certificate = await Certificate.findOne({ + where: whereCondition, + attributes: [ + 'company', + 'phoneNumber', + 'productName', + 'grossWeight', + 'commitBasis', + 'origin', + 'issueDate', + 'signature', + 'qrCodeUrl', + 'inviter', + 'inviter_phone', + 'projectName' + ], + order: [['id', 'DESC']] + }); + + console.log('从数据库获取的最新合格证信息:', certificate); + + // 格式化响应数据 + let formattedCertificate = null; + if (certificate) { + formattedCertificate = { + subjectName: certificate.company, + contact: certificate.phoneNumber, + productName: certificate.productName, + weight: certificate.grossWeight, + basis: certificate.commitBasis, + origin: certificate.origin, + date: certificate.issueDate ? certificate.issueDate.toISOString().split('T')[0] : null, + signature: certificate.signature, + qrCodeUrl: certificate.qrCodeUrl, + inviter: certificate.inviter, + inviterPhone: certificate.inviter_phone, + inviterProjectName: certificate.projectName + }; + } + + // 返回成功响应 + res.json({ success: true, certificate: formattedCertificate }); + } catch (error) { + console.error('获取合格证信息失败:', error); + res.status(500).json({ success: false, error: '获取失败,请重试' }); + } +}); + +// 提交合格证信息接口 +app.post('/api/certificate/submit', async (req, res) => { + try { + const { subjectName, contact, productName, weight, basis, origin, sessionId, inviter, inviterPhone, inviterProjectName, signature } = req.body; + + console.log('接收到的表单数据:', { + subjectName, + contact, + productName, + weight, + basis, + origin, + sessionId, + inviter, + inviterPhone, + inviterProjectName, + hasSignature: !!signature + }); + + // 验证输入 + if (!subjectName || !contact || !productName || !weight || !basis || !origin) { + return res.status(400).json({ + success: false, + error: '缺少必要的表单数据' + }); + } + + // 处理手写签名,上传到OSS + let signatureUrl = null; + if (signature) { + try { + // 从base64字符串中提取图片数据 + const base64Data = signature.replace(/^data:image\/png;base64,/, ''); + const buffer = Buffer.from(base64Data, 'base64'); + + // 生成唯一的文件名,包含合格证信息的标识 + const timestamp = Date.now(); + const filename = `certificate_signature_${timestamp}.png`; + + // 上传到OSS,指定文件夹为certificate/signatures + signatureUrl = await OssUploader.uploadBuffer(buffer, filename, 'certificate/signatures', 'image'); + console.log('手写签名上传到OSS成功:', signatureUrl); + } catch (error) { + console.error('上传手写签名到OSS失败:', error.message); + // 即使上传失败,也继续处理,将签名数据存储为base64 + signatureUrl = signature; + } + } + + // 生成查看页面的URL,包含会话ID + const viewUrl = `http://8.137.125.67:3008/view.html?sessionId=${encodeURIComponent(sessionId)}`; + + // 生成二维码并上传到OSS + let qrCodeUrl = null; + try { + // 使用Google Charts API生成二维码 + const qrCodeApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(viewUrl)}`; + + // 下载二维码图片 + const https = require('https'); + const qrCodeBuffer = await new Promise((resolve, reject) => { + https.get(qrCodeApiUrl, (response) => { + const chunks = []; + response.on('data', (chunk) => chunks.push(chunk)); + response.on('end', () => resolve(Buffer.concat(chunks))); + response.on('error', reject); + }).on('error', reject); + }); + + // 生成唯一的文件名 + const timestamp = Date.now(); + const qrCodeFilename = `certificate_qrcode_${timestamp}.png`; + + // 上传到OSS,指定文件夹为certificate/qrcodes + qrCodeUrl = await OssUploader.uploadBuffer(qrCodeBuffer, qrCodeFilename, 'certificate/qrcodes', 'image'); + console.log('二维码上传到OSS成功:', qrCodeUrl); + } catch (error) { + console.error('上传二维码到OSS失败:', error.message); + // 即使上传失败,也继续处理 + } + + // 使用当前时间作为开具日期 + const now = getCurrentTime(); + + // 创建合格证记录 + const newCertificate = await Certificate.create({ + company: subjectName, + phoneNumber: contact, + productName: productName, + grossWeight: weight, + commitBasis: basis, + origin: origin, + issueDate: now, + signature: signatureUrl, + sessionId: sessionId, + qrCodeUrl: qrCodeUrl, + inviter: inviter, + inviter_phone: inviterPhone, + projectName: inviterProjectName + }); + + console.log('数据插入数据库成功,ID:', newCertificate.id); + + // 返回成功响应,确保返回的signature是OSS URL,日期只显示到日 + const responseCertificate = { + subjectName: subjectName, + contact: contact, + productName: productName, + weight: weight, + basis: basis, + origin: origin, + date: now.toISOString().split('T')[0], + signature: signatureUrl, + qrCodeUrl: qrCodeUrl, + inviter: inviter, + inviterPhone: inviterPhone, + inviterProjectName: inviterProjectName + }; + + res.json({ success: true, certificate: responseCertificate }); + } catch (error) { + console.error('保存合格证信息失败:', error); + res.status(500).json({ success: false, error: '保存失败,请重试' }); + } +}); + // 上传用户信息 app.post('/api/user/upload', async (req, res) => { try { diff --git a/utils/api.js b/utils/api.js index 0175a2b..ac664d8 100644 --- a/utils/api.js +++ b/utils/api.js @@ -4692,5 +4692,21 @@ module.exports = { reject(error); }); }); + }, + + // 获取二维码合集 + getQrCollection: function (params = {}) { + console.log('API.getQrCollection - params:', params); + return new Promise((resolve, reject) => { + request('/api/certificate/getQrCollection', 'GET', params) + .then(response => { + console.log('获取二维码合集成功:', response); + resolve(response); + }) + .catch(error => { + console.error('获取二维码合集失败:', error); + reject(error); + }); + }); } }; \ No newline at end of file