<template>
<view class="content">
<view class="main-content">
<view class="text-area">
<text class="title">{{title}}</text>
</view>
<!-- 二维码组件 -->
<tki-qrcode v-if="qrCodeVisible" ref="qrcode" :val="qrContent" :size="200" />
<view class="operation-buttons">
<button @click="openSQL">打开数据库</button>
<button @click="createTable1">创建商品表</button>
<button @click="createTable2">创建账单表</button>
<button @click="selectTableData">查询表数据</button>
<button @click="showInsertDialog">新增商品数据</button>
<button @click="showCreateBillDialog">生成账单</button>
<button @click="sendToDevice">发送二维码</button>
</view>
<!-- 显示查询结果 -->
<view class="result-list" v-if="listData.length > 0">
<view class="result-item" v-for="(item, index) in listData" :key="index">
<text>{{item.id}}. {{item.name}}</text>
<text>售价: ¥{{ (item.price || 0).toFixed(2) }}</text>
<text>进价: ¥{{ (item.purchaseprice || 0).toFixed(2) }}</text>
<text>状态: {{item.status || '无'}}</text>
<text>条码: {{item.barcode || '无'}}</text>
<view class="item-actions">
<button @click="updateTableData(item)" size="mini">修改</button>
<button @click="confirmDelete(item.id)" size="mini" type="warn">删除</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { DB } from '@/db/database.js';
import tkiQrcode from '@/components/tki-qrcode/tki-qrcode.vue';
export default {
components: {
tkiQrcode
},
data() {
return {
title: '欢迎光临',
db: null,
listData: [],
isInserting: false,
inputProduct: {
name: '',
price: '',
purchaseprice: '',
status: '在售',
barcode: ''
},
statusList: ['在售', '缺货', '下架'],
qrCodeVisible: false,
qrContent: '',
currentBill: null
};
},
onLoad() {
this.openSQL().then(() => {
return DB.selectTableData("products").then(res => {
console.log('当前表数据:', res);
}).catch(err => {
console.warn('表可能不存在:', err);
this.showToast('表不存在,请先创建表');
});
});
},
methods: {
resetDatabase() {
DB.dropTable("products").then(() => {
this.showToast("表已删除");
this.createTable1();
});
},
openSQL() {
if (!DB.isOpen()) {
DB.openSqlite()
.then(() => {
console.log("数据库已打开");
this.showToast("数据库已打开");
})
.catch((error) => {
console.error("数据库开启失败", error);
this.showToast("数据库开启失败");
});
}
},
createTable1() {
if (DB.isOpen()) {
const sql = `
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"price" REAL DEFAULT 0,
"purchaseprice" REAL DEFAULT 0,
"status" TEXT DEFAULT '在售',
"barcode" TEXT
`;
DB.createTable("products", sql)
.then(() => {
console.log("创建表成功");
this.showToast("商品表创建成功");
})
.catch((error) => {
console.error("创建表失败详情:", error);
this.showToast(`创建表失败: ${error.message}`);
});
} else {
this.showToast("数据库未打开");
}
},
createTable2() {
if (DB.isOpen()) {
const billsSql = `
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"total" REAL DEFAULT 0,
"items" TEXT NOT NULL,
"timestamp" TEXT NOT NULL
`;
DB.createTable("bills", billsSql)
.then(() => {
console.log("账单表创建成功");
this.showToast("账单表创建成功");
})
.catch((error) => {
console.error("创建账单表失败详情:", error);
this.showToast(`创建账单表失败: ${error.message}`);
});
} else {
this.showToast("数据库未打开");
}
},
showCreateBillDialog() {
uni.showModal({
title: '生成账单',
content: '',
editable: true,
placeholderText: '格式:总金额,商品明细',
success: (res) => {
if (res.confirm && res.content) {
this.parseAndCreateBill(res.content).then(bill => {
this.currentBill = bill;
this.generateQRCode(bill);
});
}
}
});
},
generateQRCode(bill) {
this.qrContent = `账单号:${new Date(bill.timestamp).getTime()}\n金额:${bill.total.toFixed(2)}\n商品:${bill.items}`;
this.qrCodeVisible = true;
this.$nextTick(() => {
this.$refs.qrcode._makeCode();
uni.showModal({
title: '二维码已生成',
content: `金额: ${bill.total.toFixed(2)}`,
showCancel: false
});
});
},
parseAndCreateBill(inputStr) {
const parts = inputStr.split(',').map(part => part.trim());
if (parts.length < 2) {
this.showToast('请输入完整信息:总金额,商品明细');
return Promise.reject('输入不完整');
}
const bill = {
total: parseFloat(parts[0]),
items: parts[1],
timestamp: new Date().toISOString()
};
if (isNaN(bill.total)) {
this.showToast('请输入有效的总金额');
return Promise.reject('无效金额');
}
const values = [
bill.total,
`'${bill.items.replace(/'/g, "''")}'`,
`'${bill.timestamp}'`
].join(',');
const columns = "total,items,timestamp";
return DB.insertTableData("bills", values, columns)
.then(() => {
this.showToast("账单生成成功");
return bill;
})
.catch(err => {
console.error("账单生成失败:", err);
this.showToast(`账单生成失败: ${err.message || err}`);
throw err;
});
},
async sendToDevice() {
try {
// 检查二维码内容
if (!this.qrContent) {
uni.showToast({ title: '请先生成二维码', icon: 'none' });
return;
}
// 检查运行环境
if (typeof navigator === 'undefined' || !navigator.serial) {
// 非浏览器环境或浏览器不支持Web Serial API
if (uni.getSystemInfoSync().platform === 'android') {
// Android平台使用原生插件
await this.sendViaNativePlugin();
} else {
uni.showToast({
title: '当前环境不支持串口通信,请使用Chrome/Edge浏览器或Android APP',
icon: 'none',
duration: 3000
});
}
return;
}
// 请求串口访问权限
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600 }); // 波特率需与STM32一致
// 将二维码转换为适合OLED显示的格式
const qrCodeData = await this.prepareQrCodeForOled();
// 发送数据
const writer = port.writable.getWriter();
// 发送起始标志
await writer.write(new TextEncoder().encode('@QRCODE_START@\r\n'));
// 分块发送数据(避免缓冲区溢出)
const chunkSize = 64; // 根据STM32缓冲区大小调整
for (let i = 0; i < qrCodeData.length; i += chunkSize) {
const chunk = qrCodeData.slice(i, i + chunkSize);
await writer.write(new TextEncoder().encode(chunk));
await new Promise(resolve => setTimeout(resolve, 10)); // 添加小延迟
}
// 发送结束标志
await writer.write(new TextEncoder().encode('@QRCODE_END@\r\n'));
writer.releaseLock();
uni.showToast({ title: '二维码已发送到设备', icon: 'success' });
} catch (err) {
console.error('发送失败:', err);
uni.showToast({ title: `发送失败: ${err.message}`, icon: 'none' });
}
},
// 准备OLED格式的二维码数据
async prepareQrCodeForOled() {
// 获取二维码的像素数据(使用tki-qrcode组件)
const qrCodeCanvas = await this.$refs.qrcode._getQrcodeCanvas();
const ctx = qrCodeCanvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, qrCodeCanvas.width, qrCodeCanvas.height);
// 转换为单色位图格式(1位/像素)
const width = qrCodeCanvas.width;
const height = qrCodeCanvas.height;
const bytesPerRow = Math.ceil(width / 8);
const oledData = new Uint8Array(bytesPerRow * height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = (y * width + x) * 4;
const r = imageData.data[index];
const g = imageData.data[index + 1];
const b = imageData.data[index + 2];
// 简单亮度计算
const brightness = (r + g + b) / 3;
const isBlack = brightness < 128; // 阈值可调整
if (isBlack) {
const byteIndex = Math.floor(x / 8) + y * bytesPerRow;
const bitIndex = 7 - (x % 8);
oledData[byteIndex] |= (1 << bitIndex);
}
}
}
// 转换为16进制字符串便于传输
return Array.from(oledData).map(b => b.toString(16).padStart(2, '0')).join('');
},
insertTableData() {
if (DB.isOpen()) {
const testData = [
{ name: '矿泉水', price: 2.00, purchaseprice: 1.50, status: '在售', barcode: '1234567890123' },
{ name: '面包', price: 5.00, purchaseprice: 3.50, status: '在售', barcode: '9876543210987' }
];
testData.forEach(item => {
const sql = `'${item.name.replace(/'/g, "''")}', ${item.price}, ${item.purchaseprice}, '${item.status.replace(/'/g, "''")}', '${item.barcode.replace(/'/g, "''")}'`;
const condition = "'name', 'price', 'purchaseprice', 'status', 'barcode'";
DB.insertTableData("products", sql, condition)
.then(() => {
console.log("新增数据成功");
this.showToast("新增数据成功");
this.selectTableData();
})
.catch((error) => {
console.error("新增数据失败", error);
this.showToast("新增数据失败: " + error.message);
});
});
this.showToast("测试数据已添加");
} else {
this.showToast("数据库未打开");
}
},
showInsertDialog() {
uni.showModal({
title: '新增商品',
content: '',
editable: true,
placeholderText: '格式:名称,价格,进价,状态,条码',
success: (res) => {
if (res.confirm && res.content) {
this.parseAndInsert(res.content);
}
}
});
},
parseAndInsert(inputStr) {
// 分割输入字符串
const parts = inputStr.split(',').map(part => part.trim());
if (parts.length < 5) {
this.showToast('请输入完整信息:名称,价格,进价,状态,条码');
return;
}
// 构造商品对象
const product = {
name: parts[0],
price: parseFloat(parts[1]),
purchaseprice: parseFloat(parts[2]),
status: parts[3],
barcode: parts[4]
};
// 验证数据
if (!product.name) {
this.showToast('商品名称不能为空');
return;
}
if (isNaN(product.price)) {
this.showToast('请输入有效的价格');
return;
}
if (isNaN(product.purchaseprice)) {
this.showToast('请输入有效的进价');
return;
}
// 处理单引号转义
const escapeStr = str => str.replace(/'/g, "''");
// 构建SQL参数
const values = [
`'${escapeStr(product.name)}'`,
product.price,
product.purchaseprice,
`'${escapeStr(product.status)}'`,
product.barcode ? `'${escapeStr(product.barcode)}'` : 'NULL'
].join(',');
const columns = "name,price,purchaseprice,status,barcode";
// 插入数据
DB.insertTableData("products", values, columns)
.then(() => {
this.showToast("商品添加成功");
this.selectTableData(); // 刷新列表
})
.catch(err => {
console.error("添加失败:", err);
this.showToast(`添加失败: ${err.message || err}`);
});
},
selectTableData() {
if (DB.isOpen()) {
DB.selectTableData("products")
.then((res) => {
console.log("查询结果", res);
this.listData = res;
})
.catch((error) => {
console.error("查询失败", error);
this.showToast("查询失败");
});
} else {
this.showToast("数据库未打开");
}
},
updateTableData(item) {
// 第一步:输入新名称
uni.showModal({
title: '修改商品名称',
content: item.name,
editable: true,
placeholderText: '请输入新名称',
success: (nameRes) => {
if (nameRes.confirm && nameRes.content) {
// 第二步:输入新价格
uni.showModal({
title: '修改商品价格',
content: String(item.price),
editable: true,
placeholderText: '请输入新价格',
success: (priceRes) => {
if (priceRes.confirm && priceRes.content) {
// 第三步:输入新进价
uni.showModal({
title: '修改商品进价',
content: String(item.purchaseprice),
editable: true,
placeholderText: '请输入新进价',
success: (purchasepriceRes) => {
if (purchasepriceRes.confirm && purchasepriceRes.content) {
// 第四步:输入新状态
uni.showModal({
title: '修改商品状态',
content: item.status,
editable: true,
placeholderText: '请输入新状态',
success: (statusRes) => {
if (statusRes.confirm && statusRes.content) {
// 第五步:输入新条形码
uni.showModal({
title: '修改商品条形码',
content: item.barcode,
editable: true,
placeholderText: '请输入新条形码',
success: (barcodeRes) => {
if (barcodeRes.confirm && barcodeRes.content) {
const newName = nameRes.content;
const newPrice = parseFloat(priceRes.content) || item.price;
const newPurchaseprice = parseFloat(purchasepriceRes.content) || item.purchaseprice;
const newStatus = statusRes.content;
const newBarcode = barcodeRes.content;
const data = `
name = '${newName}',
price = ${newPrice},
purchaseprice = ${newPurchaseprice},
status = '${newStatus}',
barcode = '${newBarcode}'
`;
DB.updateTableData("products", data, "id", item.id)
.then(() => {
this.showToast("修改成功");
this.selectTableData();
})
.catch((error) => {
this.showToast("修改失败");
});
}
}
});
}
}
});
}
}
});
}
}
});
}
}
});
},
confirmDelete(id) {
uni.showModal({
title: '确认删除',
content: '确定要删除这个商品吗?',
success: (res) => {
if (res.confirm) {
this.deleteTableData(id);
}
}
});
},
deleteTableData(id) {
if (DB.isOpen()) {
DB.deleteTableData("products", "id", id)
.then(() => {
this.showToast("删除成功");
this.selectTableData();
})
.catch((error) => {
this.showToast("删除失败");
});
}
},
showToast(message) {
uni.showToast({
title: message,
icon: "none",
duration: 2000
});
}
}
};
</script>
<style lang="scss" scoped>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: 100rpx;
}
.main-content {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.text-area {
display: flex;
justify-content: center;
margin-bottom: 30rpx;
.title {
font-size: 80rpx;
color: #3c3c3e;
}
}
.operation-buttons {
margin-top: 100rpx;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10rpx;
width: 100%;
button {
background-color: #ffd778;
color: white;
border: none;
padding: 20rpx 30rpx;
border-radius: 80rpx;
font-size: 32rpx;
min-width: 200rpx;
width: 100%;
}
}
.result-list {
margin-top: 40rpx;
width: 90%;
border: 1rpx solid #eee;
padding: 20rpx;
border-radius: 8rpx;
.result-item {
padding: 15rpx 0;
border-bottom: 1rpx solid #f0f0f0;
font-size: 28rpx;
&:last-child {
border-bottom: none;
}
}
}
.item-actions {
display: flex;
justify-content: flex-end;
margin-top: 10rpx;
button {
margin-right: 10rpx;
padding: 0 20rpx;
font-size: 24rpx;
height: 50rpx;
line-height: 50rpx;
}
}
</style>