文章目录

  • Vue3 选择器 Canvas 增加页面性能
    • 基于Vue3 Composition API和Canvas实现的交互式选择器,支持PC端和移动端的拖动选择、多选取消选择功能
    • vue3组件封装
    • html代码

Vue3 选择器 Canvas 增加页面性能

基于Vue3 Composition API和Canvas实现的交互式选择器,支持PC端和移动端的拖动选择、多选取消选择功能

在这里插入图片描述

vue3组件封装

<script lang="ts" setup>
import { onMounted, reactive, watch } from 'vue';
import { CheckList } from '/@/types';
const props = defineProps({list: {type: Array as PropType<CheckList[]>,default: () => [],},
});
const emit = defineEmits(['changeValue']);// 正确类型定义
const canvas: Ref<HTMLCanvasElement | null> = ref(null);
const ctx: Ref<CanvasRenderingContext2D | null> = ref(null);
// 网格配置
const rows = 8;
const cols = 12;
const rowLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
const colLabels = Array.from({ length: 12 }, (_, i) => i + 1);
// 类型定义
type Position = {x: number;y: number;
};
// 状态管理
const isSelecting = ref(false);
const startPos = ref<Position>({ x: 0, y: 0 });
const endPos = ref<Position>({ x: 0, y: 0 });
const selectionMode = ref('add'); // 'add' 或 'remove'// 选项状态 (96个选项)
// const options = ref(
//   Array(rows * cols)
//     .fill()
//     .map((_, i) => ({
//       id: `${rowLabels[Math.floor(i / cols)]}${colLabels[i % cols]}`,
//       selected: false,
//     })),
// );
const options = ref([...props.list]);// 计算属性
const selectedItems = computed(() => options.value.filter((opt) => opt.selected).map((opt) => opt.id));const selectedCount = computed(() => options.value.filter((opt) => opt.selected).length);// 初始化Canvas
const initCanvas = () => {if (canvas.value == null) return;const canvasEl: HTMLCanvasElement = canvas.value;ctx.value = canvasEl.getContext('2d');// 设置Canvas尺寸canvasEl.width = canvasEl.clientWidth;canvasEl.height = canvasEl.clientHeight;drawGrid();
};// 绘制网格和选项
const drawGrid = () => {if (options.value.length == 0 || !canvas.value || !ctx.value) return;const canvasEl = canvas.value;ctx.value.clearRect(0, 0, canvasEl.width, canvasEl.height);// 计算每个选项的尺寸const cellWidth = canvasEl.width / cols;const cellHeight = canvasEl.height / rows;// 绘制网格和选项for (let row = 0; row < rows; row++) {for (let col = 0; col < cols; col++) {const x = col * cellWidth;const y = row * cellHeight;const index = row * cols + col;const isSelected = options.value[index].selected;// 绘制选项背景ctx.value.fillStyle = isSelected ? '#00a9bb' : '#ffffff';ctx.value.fillRect(x, y, cellWidth, cellHeight);// 绘制边框ctx.value.strokeStyle = isSelected ? '#eeeeee' : '#cccccc';ctx.value.lineWidth = isSelected ? 3 : 1;ctx.value.strokeRect(x, y, cellWidth, cellHeight);// 绘制选项文本ctx.value.fillStyle = isSelected ? '#fff' : '#000000';ctx.value.font = `bold ${cellHeight * 0.3}px Arial`;ctx.value.textAlign = 'center';ctx.value.textBaseline = 'middle';ctx.value.fillText(options.value[index].id, x + cellWidth / 2, y + cellHeight / 2);}}// 绘制行标签 (1-12)// ctx.value.fillStyle = '#f00';// ctx.value.font = `${12}px`;// for (let col = 0; col < cols; col++) {//   ctx.value.fillText(colLabels[col], (col + 0.5) * cellWidth, cellHeight * 0.2);// }// // 绘制列标签 (A-H)// for (let row = 0; row < rows; row++) {//   ctx.value.fillText(rowLabels[row].toString(), cellWidth * 0.1, (row + 0.5) * cellHeight);// }// 绘制选择框if (isSelecting.value) {const x = Math.min(startPos.value.x, endPos.value.x);const y = Math.min(startPos.value.y, endPos.value.y);const width = Math.abs(endPos.value.x - startPos.value.x);const height = Math.abs(endPos.value.y - startPos.value.y);ctx.value.fillStyle = selectionMode.value === 'add' ? 'rgba(100, 200, 255, 0.2)' : 'rgba(255, 100, 100, 0.2)';ctx.value.fillRect(x, y, width, height);ctx.value.strokeStyle = selectionMode.value === 'add' ? 'rgba(100, 200, 255, 0.8)' : 'rgba(255, 100, 100, 0.8)';ctx.value.lineWidth = 2;ctx.value.strokeRect(x, y, width, height);}
};// 获取Canvas坐标
const getCanvasPos = (event: MouseEvent | TouchEvent) => {if (canvas.value == null) return;const canvasEl: HTMLCanvasElement = canvas.value;const rect = canvasEl.getBoundingClientRect();let clientX: number, clientY: number;// if (event.type.includes('touch')) {if ('touches' in event) {clientX = event.touches[0].clientX;clientY = event.touches[0].clientY;} else {clientX = event.clientX;clientY = event.clientY;}return {x: clientX - rect.left,y: clientY - rect.top,};
};// 开始选择
const startSelection = (event: MouseEvent | TouchEvent) => {event.preventDefault();const pos: any = getCanvasPos(event);startPos.value = { ...pos };endPos.value = { ...pos };isSelecting.value = true;// 确定选择模式(添加或移除)if (canvas.value == null) return;const canvasEl: HTMLCanvasElement = canvas.value;const cellWidth = canvasEl.width / cols;const cellHeight = canvasEl.height / rows;const colIndex = Math.floor(pos.x / cellWidth);const rowIndex = Math.floor(pos.y / cellHeight);const index = rowIndex * cols + colIndex;if (index >= 0 && index < options.value.length) {// 如果点击的选项已选择,则设为移除模式,否则设为添加模式selectionMode.value = options.value[index].selected ? 'remove' : 'add';// 切换点击的选项状态options.value[index].selected = !options.value[index].selected;} else {selectionMode.value = 'add';}drawGrid();
};// 更新选择
const updateSelection = (event: MouseEvent | TouchEvent) => {if (!isSelecting.value) return;event.preventDefault();const pos: any = getCanvasPos(event);endPos.value = { ...pos };updateSelectedOptions();drawGrid();
};// 结束选择
const endSelection = (event: MouseEvent | TouchEvent) => {if (!isSelecting.value) return;event.preventDefault();isSelecting.value = false;drawGrid();
};// 更新被选中的选项
const updateSelectedOptions = () => {if (canvas.value == null) return;const canvasEl: HTMLCanvasElement = canvas.value;const cellWidth = canvasEl.width / cols;const cellHeight = canvasEl.height / rows;// 计算选择框覆盖的区域const minX = Math.min(startPos.value.x, endPos.value.x);const maxX = Math.max(startPos.value.x, endPos.value.x);const minY = Math.min(startPos.value.y, endPos.value.y);const maxY = Math.max(startPos.value.y, endPos.value.y);// 计算覆盖的网格范围const startCol = Math.max(0, Math.floor(minX / cellWidth));const endCol = Math.min(cols - 1, Math.floor(maxX / cellWidth));const startRow = Math.max(0, Math.floor(minY / cellHeight));const endRow = Math.min(rows - 1, Math.floor(maxY / cellHeight));// 更新选项状态for (let row = startRow; row <= endRow; row++) {for (let col = startCol; col <= endCol; col++) {const index = row * cols + col;if (selectionMode.value === 'add') {options.value[index].selected = true;} else {options.value[index].selected = false;}}}
};
const radioValue = ref('1');
const handleRadio = (e: any) => {if (e.target.value === '1') {selectAll();} else if (e.target.value === '2') {clearSelection();}
};
// 全选
const selectAll = () => {options.value.forEach((opt) => (opt.selected = true));drawGrid();
};// 清空选择
const clearSelection = () => {options.value.forEach((opt) => (opt.selected = false));drawGrid();
};// 生命周期钩子
onMounted(() => {initCanvas();window.addEventListener('resize', initCanvas);
});watch(options,(newVal) => {if (newVal.every((item) => item.selected)) {radioValue.value = '1';} else if (newVal.every((item) => !item.selected)) {radioValue.value = '2';} else {radioValue.value = '3';}emit('changeValue', newVal);},{deep: true,},
);
</script><template><div class="box"><canvasref="canvas"@mousedown="startSelection"@mousemove="updateSelection"@mouseup="endSelection"@mouseleave="endSelection"@touchstart="startSelection"@touchmove="updateSelection"@touchend="endSelection"></canvas><div class="mt-20 pl-26"><a-radio-group v-model:value="radioValue" @change="handleRadio" name="radioGroup"><a-radio value="1">全满</a-radio><a-radio value="2">全空</a-radio></a-radio-group><div mt-10>注:单击带蓝绿色表示有,单击显白色表示无</div></div></div>
</template><style lang="less" scoped>
canvas {width: 450px;height: 300px;background: rgba(10, 15, 30, 0.7);// border-radius: 10px;display: block;cursor: pointer;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
</style>

html代码


<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue3 Canvas 选择器组件</title><script src="https://unpkg.com/vue@3.2.47/dist/vue.global.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);min-height: 100vh;display: flex;justify-content: center;align-items: center;padding: 20px;color: #fff;}#app {max-width: 1200px;width: 100%;}.container {display: flex;flex-direction: column;gap: 30px;}header {text-align: center;padding: 30px 20px;background: rgba(255, 255, 255, 0.05);border-radius: 20px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);}h1 {font-size: 2.8rem;margin-bottom: 15px;background: linear-gradient(to right, #4facfe, #00f2fe);-webkit-background-clip: text;background-clip: text;color: transparent;}.subtitle {font-size: 1.2rem;opacity: 0.85;max-width: 800px;margin: 0 auto;line-height: 1.6;}.content {display: flex;gap: 30px;flex-wrap: wrap;}.canvas-container {flex: 1;min-width: 300px;background: rgba(255, 255, 255, 0.05);border-radius: 20px;padding: 25px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);}canvas {width: 100%;height: 600px;background: rgba(10, 15, 30, 0.8);border-radius: 15px;display: block;cursor: pointer;box-shadow: 0 5px 25px rgba(0, 0, 0, 0.5);}.instructions {font-size: 0.9rem;text-align: center;margin-top: 15px;opacity: 0.8;}.info-panel {width: 320px;background: rgba(255, 255, 255, 0.05);border-radius: 20px;padding: 30px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);}.info-panel h2 {font-size: 1.8rem;margin-bottom: 25px;color: #00f2fe;text-align: center;background: linear-gradient(to right, #4facfe, #00f2fe);-webkit-background-clip: text;background-clip: text;color: transparent;}.stats {display: flex;justify-content: space-between;margin-bottom: 30px;padding-bottom: 20px;border-bottom: 1px solid rgba(255, 255, 255, 0.1);}.stat {text-align: center;padding: 15px;background: rgba(0, 0, 0, 0.2);border-radius: 15px;flex: 1;margin: 0 10px;}.stat-value {font-size: 2.5rem;font-weight: bold;color: #4facfe;margin-bottom: 5px;}.stat-label {font-size: 0.95rem;opacity: 0.8;}.selected-items {max-height: 300px;overflow-y: auto;margin-top: 20px;}.selected-items h3 {margin-bottom: 20px;color: #00f2fe;text-align: center;font-size: 1.4rem;}.items-list {display: flex;flex-wrap: wrap;gap: 10px;justify-content: center;}.item {background: linear-gradient(to right, rgba(79, 172, 254, 0.2), rgba(0, 242, 254, 0.2));padding: 8px 15px;border-radius: 25px;font-size: 1rem;border: 1px solid rgba(79, 172, 254, 0.4);}.controls {display: flex;flex-wrap: wrap;gap: 15px;justify-content: center;margin-top: 30px;}button {background: linear-gradient(to right, #4facfe, #00f2fe);color: white;border: none;padding: 12px 30px;border-radius: 30px;font-size: 1rem;font-weight: 600;cursor: pointer;transition: all 0.3s ease;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);letter-spacing: 0.5px;min-width: 180px;}button:hover {transform: translateY(-3px);box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);}button:active {transform: translateY(1px);}.empty-message {text-align: center;opacity: 0.6;font-style: italic;margin: 25px 0;padding: 20px;background: rgba(0, 0, 0, 0.15);border-radius: 15px;}.mode-indicator {display: flex;justify-content: center;gap: 20px;margin-top: 15px;}.mode {display: flex;align-items: center;gap: 8px;padding: 8px 16px;border-radius: 20px;background: rgba(0, 0, 0, 0.2);}.mode-color {width: 20px;height: 20px;border-radius: 50%;}.add-color {background: rgba(100, 200, 255, 0.8);}.remove-color {background: rgba(255, 100, 100, 0.8);}.active-mode {background: rgba(79, 172, 254, 0.3);border: 1px solid rgba(79, 172, 254, 0.6);}footer {text-align: center;padding: 25px;opacity: 0.7;font-size: 0.95rem;background: rgba(255, 255, 255, 0.05);border-radius: 20px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);}@media (max-width: 768px) {.content {flex-direction: column;}.info-panel {width: 100%;}h1 {font-size: 2.2rem;}canvas {height: 500px;}.stat-value {font-size: 2rem;}}/* 滚动条样式 */.selected-items::-webkit-scrollbar {width: 8px;}.selected-items::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1);border-radius: 4px;}.selected-items::-webkit-scrollbar-thumb {background: linear-gradient(to bottom, #4facfe, #00f2fe);border-radius: 4px;}</style>
</head>
<body><div id="app"><div class="container"><header><h1>Vue3 Canvas 选择器组件</h1><p class="subtitle">基于Vue3 Composition API和Canvas实现的交互式选择器,支持PC端和移动端的拖动选择、多选取消选择功能</p></header><div class="content"><canvas-selector></canvas-selector></div><footer><p>Vue3 + Canvas 实现 | 支持PC端和移动端 | 拖动选择多个选项 | 点击切换选择模式</p></footer></div></div><script>const { createApp, ref, onMounted, computed, defineComponent } = Vue;const CanvasSelector = defineComponent({setup() {// 引用Canvas元素const canvas = ref(null);const ctx = ref(null);// 网格配置const rows = 12;const cols = 8;const colLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];const rowLabels = Array.from({ length: 12 }, (_, i) => i + 1);// 状态管理const isSelecting = ref(false);const startPos = ref({ x: 0, y: 0 });const endPos = ref({ x: 0, y: 0 });const selectionMode = ref('add'); // 'add' 或 'remove'// 选项状态 (96个选项)const options = ref(Array(rows * cols).fill().map((_, i) => ({id: `${colLabels[i % cols]}${rowLabels[Math.floor(i / cols)]}`,selected: false})));// 计算属性const selectedItems = computed(() => options.value.filter(opt => opt.selected).map(opt => opt.id));const selectedCount = computed(() => options.value.filter(opt => opt.selected).length);// 初始化Canvasconst initCanvas = () => {const canvasEl = canvas.value;ctx.value = canvasEl.getContext('2d');// 设置Canvas尺寸canvasEl.width = canvasEl.clientWidth;canvasEl.height = canvasEl.clientHeight;drawGrid();};// 绘制网格和选项const drawGrid = () => {const canvasEl = canvas.value;ctx.value.clearRect(0, 0, canvasEl.width, canvasEl.height);// 计算每个选项的尺寸const cellWidth = canvasEl.width / cols;const cellHeight = canvasEl.height / rows;// 绘制网格和选项for (let row = 0; row < rows; row++) {for (let col = 0; col < cols; col++) {const x = col * cellWidth;const y = row * cellHeight;const index = row * cols + col;const isSelected = options.value[index].selected;// 绘制选项背景ctx.value.fillStyle = isSelected ? 'rgba(79, 172, 254, 0.7)' : 'rgba(30, 35, 60, 0.8)';ctx.value.fillRect(x, y, cellWidth, cellHeight);// 绘制边框ctx.value.strokeStyle = isSelected ? 'rgba(0, 242, 254, 0.9)' : 'rgba(100, 150, 255, 0.3)';ctx.value.lineWidth = isSelected ? 3 : 1;ctx.value.strokeRect(x, y, cellWidth, cellHeight);// 绘制选项文本ctx.value.fillStyle = isSelected ? '#fff' : 'rgba(255, 255, 255, 0.7)';ctx.value.font = `bold ${cellHeight * 0.3}px Arial`;ctx.value.textAlign = 'center';ctx.value.textBaseline = 'middle';ctx.value.fillText(options.value[index].id, x + cellWidth / 2, y + cellHeight / 2);}}// 绘制列标签 (A-H)ctx.value.fillStyle = 'rgba(200, 220, 255, 0.9)';ctx.value.font = `bold ${cellHeight * 0.25}px Arial`;for (let col = 0; col < cols; col++) {ctx.value.fillText(colLabels[col],(col + 0.5) * cellWidth,cellHeight * 0.2);}// 绘制行标签 (1-12)for (let row = 0; row < rows; row++) {ctx.value.fillText(rowLabels[row].toString(),cellWidth * 0.2,(row + 0.5) * cellHeight);}// 绘制选择框if (isSelecting.value) {const x = Math.min(startPos.value.x, endPos.value.x);const y = Math.min(startPos.value.y, endPos.value.y);const width = Math.abs(endPos.value.x - startPos.value.x);const height = Math.abs(endPos.value.y - startPos.value.y);ctx.value.fillStyle = selectionMode.value === 'add' ? 'rgba(100, 200, 255, 0.2)' : 'rgba(255, 100, 100, 0.2)';ctx.value.fillRect(x, y, width, height);ctx.value.strokeStyle = selectionMode.value === 'add' ? 'rgba(100, 200, 255, 0.8)' : 'rgba(255, 100, 100, 0.8)';ctx.value.lineWidth = 2;ctx.value.setLineDash([5, 3]);ctx.value.strokeRect(x, y, width, height);ctx.value.setLineDash([]);}};// 获取Canvas坐标const getCanvasPos = (event) => {const canvasEl = canvas.value;const rect = canvasEl.getBoundingClientRect();let clientX, clientY;if (event.type.includes('touch')) {clientX = event.touches[0].clientX;clientY = event.touches[0].clientY;} else {clientX = event.clientX;clientY = event.clientY;}return {x: clientX - rect.left,y: clientY - rect.top};};// 开始选择const startSelection = (event) => {event.preventDefault();const pos = getCanvasPos(event);startPos.value = { ...pos };endPos.value = { ...pos };isSelecting.value = true;// 确定选择模式(添加或移除)const canvasEl = canvas.value;const cellWidth = canvasEl.width / cols;const cellHeight = canvasEl.height / rows;const colIndex = Math.floor(pos.x / cellWidth);const rowIndex = Math.floor(pos.y / cellHeight);const index = rowIndex * cols + colIndex;if (index >= 0 && index < options.value.length) {// 如果点击的选项已选择,则设为移除模式,否则设为添加模式selectionMode.value = options.value[index].selected ? 'remove' : 'add';// 切换点击的选项状态options.value[index].selected = !options.value[index].selected;} else {selectionMode.value = 'add';}drawGrid();};// 更新选择const updateSelection = (event) => {if (!isSelecting.value) return;event.preventDefault();const pos = getCanvasPos(event);endPos.value = { ...pos };updateSelectedOptions();drawGrid();};// 结束选择const endSelection = (event) => {if (!isSelecting.value) return;event.preventDefault();isSelecting.value = false;drawGrid();};// 更新被选中的选项const updateSelectedOptions = () => {const canvasEl = canvas.value;const cellWidth = canvasEl.width / cols;const cellHeight = canvasEl.height / rows;// 计算选择框覆盖的区域const minX = Math.min(startPos.value.x, endPos.value.x);const maxX = Math.max(startPos.value.x, endPos.value.x);const minY = Math.min(startPos.value.y, endPos.value.y);const maxY = Math.max(startPos.value.y, endPos.value.y);// 计算覆盖的网格范围const startCol = Math.max(0, Math.floor(minX / cellWidth));const endCol = Math.min(cols - 1, Math.floor(maxX / cellWidth));const startRow = Math.max(0, Math.floor(minY / cellHeight));const endRow = Math.min(rows - 1, Math.floor(maxY / cellHeight));// 更新选项状态for (let row = startRow; row <= endRow; row++) {for (let col = startCol; col <= endCol; col++) {const index = row * cols + col;if (selectionMode.value === 'add') {options.value[index].selected = true;} else {options.value[index].selected = false;}}}};// 全选const selectAll = () => {options.value.forEach(opt => opt.selected = true);drawGrid();};// 清空选择const clearSelection = () => {options.value.forEach(opt => opt.selected = false);drawGrid();};// 切换选择模式const toggleSelectionMode = () => {selectionMode.value = selectionMode.value === 'add' ? 'remove' : 'add';};// 生命周期钩子onMounted(() => {initCanvas();window.addEventListener('resize', initCanvas);});return {canvas,selectedItems,selectedCount,selectionMode,startSelection,updateSelection,endSelection,selectAll,clearSelection,toggleSelectionMode};},template: `<div class="canvas-container"><canvas ref="canvas" @mousedown="startSelection"@mousemove="updateSelection"@mouseup="endSelection"@mouseleave="endSelection"@touchstart="startSelection"@touchmove="updateSelection"@touchend="endSelection"></canvas><p class="instructions">PC端:点击并拖动鼠标进行选择 | 移动端:触摸并滑动进行选择</p><div class="mode-indicator"><div class="mode" :class="{ 'active-mode': selectionMode === 'add' }"><div class="mode-color add-color"></div><span>添加模式</span></div><div class="mode" :class="{ 'active-mode': selectionMode === 'remove' }"><div class="mode-color remove-color"></div><span>移除模式</span></div></div></div><div class="info-panel"><h2>选择信息面板</h2><div class="stats"><div class="stat"><div class="stat-value">{{ selectedCount }}</div><div class="stat-label">已选选项</div></div><div class="stat"><div class="stat-value">96</div><div class="stat-label">总选项</div></div></div><div class="selected-items"><h3>已选选项 ({{ selectedCount }})</h3><div v-if="selectedItems.length > 0" class="items-list"><div v-for="item in selectedItems" :key="item" class="item">{{ item }}</div></div><div v-else class="empty-message">暂无选择,请在左侧区域进行选择</div></div><div class="controls"><button @click="selectAll">全选</button><button @click="clearSelection">清空</button><button @click="toggleSelectionMode">{{ selectionMode === 'add' ? '切换到移除模式' : '切换到添加模式' }}</button></div></div>`});createApp({components: {CanvasSelector}}).mount('#app');</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/914123.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/914123.shtml
英文地址,请注明出处:http://en.pswp.cn/news/914123.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 实战:打造多文件批量重命名工具

引言在实际运维、测试、数据分析、开发流程中&#xff0c;我们经常会处理成百上千条命令操作&#xff0c;例如&#xff1a;各种脚本任务&#xff08;启动、备份、重启、日志查看&#xff09;数据处理流程&#xff08;爬取 → 清洗 → 统计 → 可视化&#xff09;配置自动化&…

设计模式笔记_结构型_代理模式

1. 代理模式介绍代理模式是一种结构型设计模式&#xff0c;它允许你提供一个代理对象来控制对另一个对象的访问。代理对象通常在客户端和目标对象之间起到中介作用&#xff0c;能够在不改变目标对象的前提下增加额外的功能操作&#xff0c;比如延迟初始化、访问控制、日志记录等…

C语言<数据结构-单链表>(收尾)

上篇博客我将基础的尾插、尾删、头插、头删逐一讲解了&#xff0c;这篇博客将对上篇博客进行收尾&#xff0c;讲一下指定位置操作增删以及查找这几个函数&#xff0c;其实大同小异&#xff1a;一.查找函数&#xff1a;查找函数其实就是一个简单的循环遍历&#xff0c;所以不加以…

十年架构心路:从单机到云原生的分布式系统演进史

十年架构心路&#xff1a;从单机到云原生的分布式系统演进史 这里写目录标题十年架构心路&#xff1a;从单机到云原生的分布式系统演进史一、技术生涯的起点&#xff1a;单体架构的黄金时代1.1 典型技术栈1.2 记忆深刻的故障二、分布式架构转型期2.1 服务化拆分实践2.2 分布式事…

使用docker搭建nginx

安装docker 和 docker compose验证docker版本配置docker目录配置代理&#xff0c;使docker能访问外网能否ping通最后直接拉入镜像即可docker pull nginx

Intel新CPU助攻:微软Copilot+将登陆台式电脑

微软的Copilot PC计划已经推出一年多&#xff0c;但目前仅支持平板电脑和笔记本电脑&#xff0c;以及少数迷你电脑。 随着Intel下一代桌面处理器——代号为“Arrow Lake Refresh”的推出&#xff0c;Copilot PC功能有望扩展到桌面计算机。 要支持Copilot PC的所有功能&#xff…

【Kubernetes】跨节点 Pod 网络不通排查案例

最近在部署一个集群环境的时候&#xff0c;发现集群中一个子节点与其他子节点不通&#xff0c;而 master 节点可与任何子节点互通&#xff0c;通过抓包排查后&#xff0c;发现是 Linux 路由决策导致的。因此&#xff0c;在此记录下来&#xff0c;希望对大家有所帮助。1、环境及…

【算法训练营Day11】二叉树part1

文章目录理论基础二叉树的递归遍历前序遍历中序遍历后序遍历总结二叉树的层序遍历基础层序遍历二叉树的右视图理论基础 二叉树在结构上的两个常用类型&#xff1a; 满二叉树完全二叉树 在功能应用上的比较常用的有&#xff1a; 二叉搜索树&#xff1a; 节点有权值、遵循”左…

Flutter 之 table_calendar 控件

1.库导入在pubspec.yaml文件中dev_dependencies:table_calendar: ^3.2.02. 代码编写TableCalendar(daysOfWeekHeight: 20,availableGestures: AvailableGestures.horizontalSwipe,firstDay: DateTime.now().subtract(const Duration(days: 365)),lastDay: DateTime.now(),cal…

【leetcode】1486. 数组异或操作

数组异或操作题目题解题目 1486. 数组异或操作 给你两个整数&#xff0c;n 和 start 。 数组 nums 定义为&#xff1a;nums[i] start 2*i&#xff08;下标从 0 开始&#xff09;且 n nums.length 。 请返回 nums 中所有元素按位异或&#xff08;XOR&#xff09;后得到的…

php7.4使用 new DateTime;报错 Class DateTime not found

php7.4使用 new DateTime;报错Uncaught Error: Class ‘app\home\c\DateTime’ not found 查了半天资料&#xff0c;最后找到了解决办法 DateTime 是 php 内置的类&#xff0c;不隶属于任何命名空间&#xff0c;如果你需要在命名空间中使用须有 \ 声明&#xff0c;解决办法就是…

Gartner《构建可扩展数据产品建设框架》心得

一、背景与价值 1.1 “数据产品”为什么忽然重要? 传统模式:业务提出需求 → IT 建数据集 → ETL 管道爆炸 → 维护成本指数级上升。 新范式:把“数据”包装成“产品”,以产品思维迭代演进,强调复用、自助、可扩展。 Gartner 观察到:大量组织把“报表”或“数据仓库”重…

CentOS/RHEL LVM 磁盘扩展完整教程

CentOS/RHEL LVM 磁盘扩展完整教程&#x1f4dd; 前言 在Linux系统管理中&#xff0c;磁盘空间不足是经常遇到的问题。特别是在生产环境中&#xff0c;当根分区空间告急时&#xff0c;我们需要通过添加新磁盘来扩展存储空间。本教程将详细介绍如何在CentOS/RHEL系统中使用LVM&a…

LVGL应用和部署(用lua做测试)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】嵌入式产品做好了&#xff0c;下面就是测试和量产了。以按键屏幕的开发模式为例&#xff0c;如果仅仅是简单的功能测试&#xff0c;那还比较好解决&…

phpstudy搭建pikachu

一.启动mysql和nginx服务二.修改靶场文件参数点击管理打开根目录&#xff0c;将下载好的靶场源文件解压到www目录下三.找到此文件用记事本打开四.修改配置文件五.打开浏览器,输入127.0.0.1/pikachu六.按照步骤初始化心得体会&#xff1a;如果mysql启动又立刻停止&#xff0c;大…

【Linux】GDB/CGDB 调试器学习笔记

GDB/CGDB 调试器学习笔记&#x1f680; 前言 GDB 是 GNU 项目下功能强大的命令行调试器&#xff0c;适用于 C/C 等多种语言。CGDB 则是在 GDB 之上构建的轻量级 curses 界面&#xff0c;适合喜欢终端操作且习惯 vi 风格的人。一、GDB 入门篇 1. 编译时带调试信息 gcc -g -O0 -W…

链接代理后无法访问网络

路由方向的问题 cmd 输入 route print 查看路由多了一个不是你网络的路由 我的嘎嘎好用直接那都通 route add -p 0.0.0.0 mask 0.0.0.0 0.0.0.0 参考这个 固定ip if是代理链路的 链路口又敏感词这个文章不合规两次评论区问我

day37 早停策略和模型权重的保存

DAY 37 我今天的笔记是用cpu训练的&#xff0c;请自行修改为gpu训练 仍然是循序渐进&#xff0c;先复习之前的代码 import torch import torch.nn as nn import torch.optim as optim from sklearn.datasets import load_iris from sklearn.model_selection import train_test_…

网络爬虫分类全解析

网络爬虫作为数据获取的重要工具,其分类方式多样,不同类型的爬虫在技术实现、应用场景和功能特性上存在显著差异。深入理解这些分类,有助于开发者根据实际需求选择合适的爬虫方案。本文将从技术特性、应用场景和架构设计三个维度,系统介绍网络爬虫的主要分类。 一、按技术…

ECR仓库CloudFormation模板完整指南

概述 本文档详细介绍了一个通用的Amazon ECR(Elastic Container Registry)仓库CloudFormation模板,该模板支持多业务组、参数化配置,并包含完整的安全策略、生命周期管理和监控功能。 模板特性 核心功能 ✅ 支持4个业务组:app、ai、mall、frontend✅ 灵活的服务名手动输…