作者归档:admin

base64图片复制

vue

$vue->method("copy_base64_data(data)","   
        location.origin.includes(`https://`) || Message.error(`图片复制功能不可用`);
        data = data.split(';base64,'); let type = data[0].split('data:')[1]; data = data[1]; 
        let bytes = atob(data), ab = new ArrayBuffer(bytes.length), ua = new Uint8Array(ab);
        [...Array(bytes.length)].forEach((v, i) => ua[i] = bytes.charCodeAt(i));
        let blob = new Blob([ab], { type }); 
        navigator.clipboard.write([new ClipboardItem({ [type]: blob })]);
");

PHP中返回的data值

'data:image/png;base64,'.base64_encode($content)

蓝牙 blueMixin.js

/**
 *  蓝牙相关操作
 * 
import { blueMixin } from 'mixin/blueMixin';

export default {
  mixins: [blueMixin], 
};

onLoad里面

this.openBluetooth(); 
this.device_no = uni.getStorageSync('uni_bluetooth_deviceId');  
 * 
 */
export const blueMixin = {
	data() {
		return {
			dataToSend: "",
			checkLette: '', // 蓝牙ID前缀
			//checkLette: '', // 蓝牙ID前缀
			devices: [], // 存储搜索到的设备
			receivedData: null, // 保存接收到的数据
			device_no: null, // 用localName
			deviceId: null, // 当前连接的设备ID
			serviceId: '', // 服务UUID
			characteristicId: '', // 特征UUID
			connectedDeviceName: '', //当前连接的设备
			characteristicIdWrite: '',
			connect_status: false, //连接状态
			loop_blue: '',
		};
	},
	onShow() {
		this.initBlue()
	},
	methods: {
		initBlue() {
			this.device_no = uni.getStorageSync('uni_bluetooth_deviceId');
		},
		sendData() {
			if (!this.dataToSend) {
				console.warn('没有要发送的数据');
				return;
			}
			console.log('sendData >> ', this.dataToSend);
			// 将字符串转换为 HEX
			let d = this.dataToSend
			d = JSON.stringify(this.dataToSend)
			const hexData = this.strToHex(d); // 转换为 HEX 
			console.log('hex', hexData);
			// 将 HEX 转换为 ArrayBuffer
			const buffer = this.hexToArrayBuffer(hexData); // 转换为 ArrayBuffer 
			console.log('ArrayBuffer', buffer);
			uni.writeBLECharacteristicValue({
				deviceId: this.deviceId,
				serviceId: this.serviceId,
				characteristicId: this.characteristicIdWrite,
				value: buffer,
				success: (res) => {
					uni.showToast({
						title: "发送成功,请在茶壶上操作",
						icon: "none"
					})
					console.log('发送数据成功', res);
				},
				fail: (err) => {
					uni.showToast({
						title: "连接异常",
						icon: "none"
					})
					console.error('发送数据失败', err);
				}
			});
		},
		openBluetooth() {
			uni.openBluetoothAdapter({
				success: (res) => {
					console.log('蓝牙适配器已打开', res);
					this.searchBluetooth();
					if (this.loop_blue) {
						clearInterval(this.loop_blue)
					}
					this.loop_blue = setInterval(() => {
						this.searchBluetooth()
						console.log("3 秒重连")
					}, 3000)
				},
				fail: (err) => {
					uni.showModal({
						title: '提示',
						content: '请打开蓝牙',
						showCancel: false,
						success: (res) => {
							if (res.confirm) {
								console.log('用户点击确定');
							}
						}
					})
					console.error('打开蓝牙适配器失败', err);
				}
			});
		},

		// 启动搜索设备
		searchBluetooth() {
			// 清空之前的设备列表
			this.devices = [];
			console.log('搜索蓝牙设备');
			uni.startBluetoothDevicesDiscovery({
				success: (res) => {
					console.log('开始搜索设备', res);
				},
				fail: (err) => {
					console.error('开始搜索设备失败', err);
				}
			});
			// 监听设备发现
			console.log("搜索设备No:" + this.device_no)
			uni.onBluetoothDeviceFound((devices) => {
				devices.devices.forEach(device => {
					// 根据前缀过滤设备
					if (device.name && (this.checkLette === '' || device.name.startsWith(this
							.checkLette))) {
						console.log('发现设备 deviceId' + device.deviceId)
						console.log('发现设备 localName ' + device.localName)
						console.log(device)
						this.deviceId = device.deviceId
						this.devices.push(device);
						if (this.device_no && this.device_no == device.localName) {
							this.connectToDevice(device);
						}
					}
				});
				// 去重处理
				this.devices = [...new Map(this.devices.map(item => [item.deviceId, item])).values()];
			});

			// 可选:设置一个定时器停止搜索
			setTimeout(() => {
				uni.stopBluetoothDevicesDiscovery({
					success: () => {
						console.log('停止搜索设备');
					},
					fail: (err) => {
						console.error('停止搜索设备失败', err);
					}
				});
			}, 10 * 1000); // 停止搜索
		},

		// 连接到蓝牙设备
		connectToDevice(device) {
			console.log('连接设备')
			console.log(device)
			uni.setStorageSync('uni_bluetooth_deviceId', device.localName);
			this.device_no = device.localName;
			this.connectedDeviceName = device.name;
			this.doConnectToDeviceId(device.deviceId);
		},
		// 
		doConnectToDeviceId(deviceId) {
			console.log("connect DeviceId", deviceId);
			var that = this
			//这里连接有时失败
			uni.createBLEConnection({
				deviceId: deviceId,
				success: (res) => {
					this.connect_status = true;
					if (this.loop_blue) {
						clearInterval(this.loop_blue)
					}
					console.log('成功连接到设备', deviceId);
					that.ajax(that.config.tea_bluetooth.device.bind, {
						device_no: this.device_no,
						data: {

						}
					}).then(res => {
						console.log('bind')
						console.log(res)
					})
					this.initBlue();
					this.getDeviceServices(); // 连接成功后获取服务 
				},
				fail: (err) => {
					console.error('连接设备失败', err);
					this.connect_status = false;
				}
			});
		},

		// 获取设备的服务
		getDeviceServices() {
			uni.getBLEDeviceServices({
				deviceId: this.deviceId,
				success: (res) => {
					console.log('获取到设备服务:', res.services);
					if (res.services.length > 0) {
						// 假设我们取第一个服务
						this.serviceId = res.services[0].uuid;
						this.getDeviceCharacteristics(); // 获取特征值
					}
				},
				fail: (err) => {
					console.error('获取设备服务失败', err);
				}
			});
		},

		// 获取服务中的特征值
		getDeviceCharacteristics() {
			uni.getBLEDeviceCharacteristics({
				deviceId: this.deviceId,
				serviceId: this.serviceId,
				success: (res) => {
					console.log(res);
					if (res.characteristics.length > 0) {
						res.characteristics.forEach(characteristic => {
							if (characteristic.properties.write) {
								this.characteristicIdWrite = characteristic.uuid;
							} else {
								this.characteristicId = characteristic.uuid;
							}
						});
						this.startListenData(); // 开始监听数据
					}
				},
				fail: (err) => {
					console.error('获取设备特征值失败', err);
				}
			});
		},
		writeLog(data) {
			let obj = JSON.parse(data)
			obj.device_no = this.device_no
			console.log('写日志,对象值')
			console.log(obj)
			this.ajax(this.config.tea_bluetooth.log, {
				data: obj
			}).then(res => {
				console.log('writeLog')
				console.log(res)
			})
		},
		// 开始监听蓝牙设备发来的数据
		startListenData() {
			console.log('startListenData')
			console.log("device no:" + this.device_no);
			console.log("deviceId:" + this.deviceId);
			console.log("serviceId:" + this.serviceId);
			console.log("characteristicId:" + this.characteristicId);
			uni.notifyBLECharacteristicValueChange({
				state: true,
				deviceId: this.deviceId,
				serviceId: this.serviceId,
				characteristicId: this.characteristicId,
				success: (res) => {
					console.log('已设置通知', res);
					// 监听特征值变化
					uni.onBLECharacteristicValueChange((characteristic) => {
						const {
							value
						} = characteristic;
						this.receivedData = this.ab2str(value); // 将 ArrayBuffer 转换为字符串
						console.log('接收到数据:', this.receivedData);
						//写入日志
						this.writeLog(this.receivedData);
					});
				},
				fail: (err) => {
					console.error('监听蓝牙数据失败', err);
				}
			});
		},
		// ArrayBuffer转16进度字符串示例
		ab2hex(buffer) {
			const hexArr = Array.prototype.map.call(
				new Uint8Array(buffer),
				function(bit) {
					return ('00' + bit.toString(16)).slice(-2)
				}
			)
			return hexArr.reverse().join(':')
		},
		// ArrayBuffer 转字符串的工具方法
		ab2str(buf) {
			return String.fromCharCode.apply(null, new Uint8Array(buf));
		},
		str2ab(str) {
			const buf = new ArrayBuffer(str.length);
			const bufView = new Uint8Array(buf);
			for (let i = 0; i < str.length; i++) {
				bufView[i] = str.charCodeAt(i);
			}
			return buf;
		},
		strToHex(str) {
			return str.split('').map(char => {
				const hex = char.charCodeAt(0).toString(16); // 获取字符的 Unicode 编码并转为 HEX
				return hex.padStart(2, '0'); // 确保每个字节为两位
			}).join('');
		},
		// 将 HEX 字符串转换为 ArrayBuffer
		hexToArrayBuffer(hex) {
			const len = hex.length;
			const buffer = new Uint8Array(len / 2);
			for (let i = 0; i < len; i += 2) {
				buffer[i / 2] = parseInt(hex.substr(i, 2), 16);
			}
			return buffer.buffer;
		},
	}
};

搜索蓝牙设备

<template>
	<view style="margin: 20px;">  
		<view class="device" v-for="(device, index) in devices">
			<view @click="connectToDevice(device)"
			 :class="device.deviceId == device_no?'actived':'' ">
				{{device.name}}  
			</view> 
		</view>
		<view class="padding">
			<view class="w-100">
				<cl-button @click="save" type="primary">чбошод</cl-button>
			</view>
		</view>
	</view>
</template>

<script>
	import {
		blueMixin
	} from '@/mixin/blueMixin';

	export default {
		mixins: [blueMixin],
		data() {
			return {
				 
			};
		},
		onLoad() {
			this.openBluetooth(); 
		},
		methods: {
			save() {
				this.back_data({
					step: 2
				})
			}
		}
	};
</script>

<style scoped>
	button {
		margin: 5px;
	}

	.device {
		margin-bottom: 20px;
		padding:10px;
		border: 1px solid #ccc;
		border-radius: 5px;
		background-color: #fff;
	}

	.space-between {
		display: flex;
		justify-content: space-between;
		align-items: center;
	}

	.actived {
		color: blue !important;
	}
</style>

ga统计

google analytics 访问统计

https://analytics.google.com

自定义事件

 gtag('event', 'click', {
  'event_category': 'button',
  'event_label': 'calc'
});

其中 event_label

如有问题请使用 https://gemini.google.com/ 提问。

追踪视频播放

gtag('event', 'video_start', {
  'event_category': 'video',
  'event_label': 'intro_video'
});

追踪表单提

gtag('event', 'submit', {
  'event_category': 'form',
  'event_label': 'contact_form'
});

加入购物车

gtag('event', 'add_to_cart', {
  'event_category': 'ecommerce',
  'event_label': 'product_name',
  'value': 19.99,
  'currency': 'USD'
});

点击支付事件

gtag('event', 'begin_checkout', {
  'event_category': 'ecommerce',
  'event_label': 'payment_button',
  'value': 19.99,
  'currency': 'USD'
});

支付成功

gtag('event', 'purchase', {
  'event_category': 'ecommerce',
  'event_label': 'order_12345',
  'value': 59.99,
  'currency': 'USD',
  'transaction_id': 'T12345'
});

redmine

依赖

Ruby、 Rails

npm install pm2@latest -g

修改gem、bundle源

gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
gem sources -l

确保只有 gems.ruby-china.com

bundle config mirror.https://rubygems.org https://gems.ruby-china.com

Redmine安装

Ngnix转发

location / {
    try_files /_not_exists_ @backend;
} 
location @backend {
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host            $http_host;
    proxy_set_header   X-Forwarded-Proto $scheme; 
    proxy_pass http://127.0.0.1:3000;
}

软链

ln -s /data/redmine-5.1/public  /www/wwwroot/yourdomain/

启动

pm2 start "bundle exec rails server -e production" --name redmine --cwd /data/redmine-5.1

保存

pm2 save
pm2 startup

修改状态颜色

public/javascripts/application.js

$(function(){
    $("#content table.issues tbody tr td").each(function(){
        let status_class = $(this).attr('class');
        if(status_class == 'status'){
            let html = $(this).html();
            if(html == '已解决'){
                $(this).html("<span style='color:green;'>已解决√</span>");
            }else if(html == '新问题'){
                $(this).html("<span style='color:red;'>新问题</span>");
            }else if(html == '处理中'){
                $(this).html("<span style='color:#00BBFF;'>处理中……</span>");
            }else if(html == '拒绝'){
                $(this).html("<span style='color:#BB5500;'>拒绝</span>");
            }else if(html == '关闭'){
                $(this).html("<span style='color:#444444;'>关闭</span>");
            }else if(html == '待反馈'){
                $(this).html("<span style='color:#EEEE00;'>待反馈</span>");
            } 
        }
    });
});