Browse Source

添加certificate数据模型和二维码相关功能

Xfy
Default User 1 week ago
parent
commit
1d2b225429
  1. 3
      app.json
  2. 343
      pages/collection/index.js
  3. 6
      pages/collection/index.json
  4. 111
      pages/collection/index.wxml
  5. 434
      pages/collection/index.wxss
  6. 4
      pages/index/index.wxml
  7. 7
      pages/qrcode/index.js
  8. 1
      pages/qrcode/index.wxml
  9. 375
      server-example/server-mysql.js
  10. 16
      utils/api.js

3
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": [
{

343
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, '<span class="highlight">$1</span>');
}
if (newQrCode.phoneNumber && newQrCode.phoneNumber.toLowerCase().includes(keyword)) {
const regex = new RegExp(`(${keyword})`, 'gi');
newQrCode.phoneNumber = newQrCode.phoneNumber.replace(regex, '<span class="highlight">$1</span>');
}
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
});
}
});

6
pages/collection/index.json

@ -0,0 +1,6 @@
{
"navigationBarTitleText": "二维码合集",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true
}

111
pages/collection/index.wxml

@ -0,0 +1,111 @@
<!--pages/collection/index.wxml-->
<view class="page-container">
<!-- 筛选和搜索栏 -->
<view class="filter-bar">
<view class="filter-tabs">
<view class="filter-tab {{activeFilter === 'all' ? 'active' : ''}}" data-filter="all" bindtap="switchFilter">
<text>全部</text>
</view>
<view class="filter-tab {{activeFilter === 'me' ? 'active' : ''}}" data-filter="me" bindtap="switchFilter">
<text>我的</text>
</view>
</view>
<view class="search-container">
<input
type="text"
placeholder="搜索电话号码/主体名称"
bindinput="onSearchInput"
value="{{searchKeyword}}"
class="search-input"
/>
</view>
<view wx:if="isAdmin && invitees.length > 0" class="filter-button" bindtap="openSidebar">
<text class="filter-icon">🔍</text>
<text class="filter-text">筛选</text>
</view>
</view>
<!-- 二维码列表 -->
<view class="qr-list">
<block wx:if="{{qrCodes.length === 0}}">
<view class="empty-state">
<image
src="https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=empty"
mode="aspectFit"
class="empty-image"
/>
<text class="empty-text">暂无二维码</text>
<text class="empty-subtext">请先生成邀请二维码</text>
</view>
</block>
<block wx:else>
<block wx:for="{{qrCodes}}" wx:key="sessionId" wx:for-index="index">
<!-- 二维码项 -->
<view class="qr-item" bindtap="toggleSection" data-index="{{index}}">
<view class="qr-item-header">
<view class="qr-item-info">
<rich-text nodes="{{item.company ? item.company : '未知'}}"></rich-text>
<view class="qr-item-details">
<rich-text nodes="{{item.phoneNumber ? item.phoneNumber : '未知'}}"></rich-text>
<text> / {{item.createdAt || '未知'}}</text>
</view>
</view>
<view class="qr-item-arrow">
<text>{{expandedIndex === index ? '▲' : '▼'}}</text>
</view>
</view>
<!-- 二维码详情 -->
<view class="qr-item-content" wx:if="{{expandedIndex === index}}">
<image
src="{{item.qrCodeUrl || 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + encodeURIComponent(item.url)}}"
mode="aspectFit"
class="qr-image"
bindload="onImageLoad"
binderror="onImageError"
/>
<view class="qr-info-center">
<view class="qr-info-item">
<text>{{item.inviter || '未知'}}</text>
</view>
<view class="qr-info-item">
<text>{{item.inviterProjectName || '无职位'}}</text>
</view>
</view>
</view>
</view>
</block>
</block>
</view>
<!-- 底部按钮 -->
<view class="bottom-section">
<button class="generate-button" bindtap="goToGenerate">
<text class="generate-button-text">生成邀请二维码</text>
</button>
</view>
<!-- 筛选侧边栏 -->
<view class="sidebar" wx:if="{{sidebarVisible}}">
<view class="sidebar-overlay" bindtap="closeSidebar"></view>
<view class="sidebar-content">
<view class="sidebar-header">
<text class="sidebar-title">筛选用户</text>
<view class="sidebar-close" bindtap="closeSidebar">
<text>✕</text>
</view>
</view>
<view class="sidebar-body">
<view
wx:for="{{invitees}}"
wx:key="inviter"
class="sidebar-item"
data-filter="invitee_{{index}}"
data-inviter="{{item.inviter}}"
bindtap="filterByInviter"
>
<text class="sidebar-item-text">{{item.inviter}}</text>
</view>
</view>
</view>
</view>
</view>

434
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;
}

4
pages/index/index.wxml

@ -215,8 +215,8 @@
<text class="function-btn-text">我们</text>
</view>
<view class="function-btn" bindtap="navigateToQRCode" wx:if="{{isInPersonnel}}">
<text class="function-btn-icon">📱</text>
<text class="function-btn-text">二维码</text>
<text class="function-btn-icon">📜</text>
<text class="function-btn-text">合格证</text>
</view>
</view>

7
pages/qrcode/index.js

@ -296,6 +296,13 @@ Page({
});
}
});
},
// 跳转到二维码合集页面
goToQrCollection: function () {
wx.redirectTo({
url: '/pages/collection/index?from=internal'
});
}
});

1
pages/qrcode/index.wxml

@ -52,6 +52,7 @@
<view class="action-buttons">
<button class="action-btn primary" bindtap="generateQRCode">生成邀请二维码</button>
<button wx:if="{{qrCodeUrl}}" class="action-btn primary" bindtap="shareQRCode">分享二维码</button>
<button class="action-btn primary" bindtap="goToQrCollection">二维码合集</button>
</view>
<view class="footer">

375
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 {

16
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);
});
});
}
};
Loading…
Cancel
Save