当前位置: 首页 > news >正文

vue3 canvas 选择器 Canvas 增加页面性能

文章目录

  • 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.xdnf.cn/news/1108531.html

相关文章:

  • Kimi K2万亿参数开源模型原理介绍
  • 【论文阅读】HCCF:Hypergraph Contrastive Collaborative Filtering
  • 缓存三剑客解决方案
  • 【C语言】回调函数、转移表、qsort 使用与基于qsort改造冒泡排序
  • 利用docker部署前后端分离项目
  • 敏捷开发方法全景解析
  • SQL server之版本的初认知
  • C#枚举:从基础到高级的全方位解析
  • 《通信原理》学习笔记——第一章
  • 《Spring 中上下文传递的那些事儿》Part 11:上下文传递最佳实践总结与架构演进方向
  • 基于MCP的CI/CD流水线:自动化部署到云平台的实践
  • Vue Vue-route (5)
  • Adobe Illustrator关于图标创建的问题
  • 【跟我学运维】chkconfig jenkins on的含义
  • 初等行变换会改变矩阵的什么?不变改变矩阵的什么?求什么时需要初等行变换?求什么时不能初等行变换?
  • 回归(多项式回归)
  • 电网通俗解析术语2:一二次设备关联
  • 【PycharmPyqt designer桌面程序设计】
  • Effective Modern C++ 条款9:优先考虑别名声明而非typedef
  • Socket到底是什么(简单来说)
  • 【Elasticsearch】昂贵算法与廉价算法
  • 史上最全 MySQL 锁详解:从理论到实战,一篇搞定所有锁机制
  • 网络编程员工管理系统
  • 【数据分析】03 - Matplotlib
  • 【Elasticsearch 】search_throttled
  • 力扣-19. 删除链表的倒数第N个节点
  • Windows环境下解决Matplotlib中文字体显示问题的详细指南
  • Git入门教程
  • JVM与系统性能监控工具实战指南:从JVM到系统的全链路分析
  • 虚拟现实的镜廊:当技术成为存在之茧