vscode工作区实现机制

news2024/11/27 8:41:17

工作区是编辑器的重要部分,它承载着编辑器和本地文件的连接,对文件增、删、改、查。下面我会介绍vscode工作区的创建。同样我们知道vscode软件打开的时候没有默认工作区,这里我对它进行了改造,软件启动时指向默认工作区。

工作区目录创建

我们先看下vscode在本地电脑的数据存储目录。
在这里插入图片描述
所有的缓存数据都在这里。我们也可以把项目中需要缓存的数据创建到这里面。
那么如何获取和设置软件数据存储目录呢?electron提供了官方api

  • app.getPath()
  • app.setPath()

官方文档地址: https://www.electronjs.org/zh/docs/latest/api/app#appgetpathname

在这里插入图片描述
对应setPath方法
在这里插入图片描述
我们看看vscode是如何创建软件数据存储目录的
src/main.js

const userDataPath = getUserDataPath(args);
app.setPath('userData', userDataPath);

跳进getUserDataPath方法

function factory(path, os, productName, cwd) {

		/**
		 * @param {NativeParsedArgs} cliArgs
		 *
		 * @returns {string}
		 */
		function getUserDataPath(cliArgs) {
			const userDataPath = doGetUserDataPath(cliArgs);
			const pathsToResolve = [userDataPath];
			if (!path.isAbsolute(userDataPath)) {
				pathsToResolve.unshift(cwd);
			}

			return path.resolve(...pathsToResolve);
		}

		/**
		 * @param {NativeParsedArgs} cliArgs
		 *
		 * @returns {string}
		 */
		function doGetUserDataPath(cliArgs) {

			// 1. Support portable mode
			const portablePath = process.env['VSCODE_PORTABLE'];
			if (portablePath) {
				return path.join(portablePath, 'user-data');
			}

			// 2. Support global VSCODE_APPDATA environment variable
			let appDataPath = process.env['VSCODE_APPDATA'];
			if (appDataPath) {
				return path.join(appDataPath, productName);
			}
			// 3. Support explicit --user-data-dir
			const cliPath = cliArgs['user-data-dir'];
			if (cliPath) {
				return cliPath;
			}

			// 4. Otherwise check per platform
			switch (process.platform) {
				case 'win32':
					appDataPath = process.env['APPDATA'];
					if (!appDataPath) {
						const userProfile = process.env['USERPROFILE'];
						if (typeof userProfile !== 'string') {
							throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable');
						}

						appDataPath = path.join(userProfile, 'AppData', 'Roaming');
					}
					break;
				case 'darwin':
					appDataPath = path.join(os.homedir(), 'Library', 'Application Support');
					break;
				case 'linux':
					appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
					break;
				default:
					throw new Error('Platform not supported');
			}

			return path.join(appDataPath, productName);
		}

		return {
			getUserDataPath
		};
	}

其中os.homedir()方法是nodejs的os模块的内置应用程序编程接口,用于获取当前用户的主目录路径

到此软件缓存数据总目录创建完成。

下面我们在总目录下面创建一个workspace-content目录作为工作区目录来存储文件。

src/vs/platform/environment/common/environment.ts
在vscode环境service里面添加工作区名称

export interface IEnvironmentService {
...
    workspaceContent: URI;
   ... 
}    

实现接口
src/vs/platform/environment/common/environmentService.ts

export abstract class AbstractNativeEnvironmentService implements INativeEnvironmentService {
...
    @memoize
    get workspaceContent(): URI { return URI.file(join(this.userDataPath, 'workspace-content')); }
...
}

默认工作区目录创建
src/vs/code/electron-main/main.ts

Promise.all<string | undefined>([
    environmentMainService.extensionsPath,
    environmentMainService.codeCachePath,
    environmentMainService.logsPath,
    environmentMainService.globalStorageHome.fsPath,
    environmentMainService.workspaceStorageHome.fsPath,
    environmentMainService.localHistoryHome.fsPath,
    environmentMainService.backupHome,

    environmentMainService.workspaceTemplate.fsPath,
].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)),

在这里通过调用mkdir方法创建了各个数据存储目录。

我们知道vscode软件打开的时候没有默认工作区,这里我对它进行了改造,软件启动时指向默认工作区。

src/vs/platform/windows/electron-main/windowsMainService.ts

const workspaceContentUri = this.environmentService.workspaceContent;
const workspacePath: IPathToOpen[] = [
    {
        workspace: {
            id: '1',
            uri: workspaceContentUri
        },
        type: 2, // 2 File is a directory.
        exists: true
    }
];
// Identify things to open from open config

// const pathsToOpen = this.getPathsToOpen(openConfig); //愿逻辑
const pathsToOpen = workspacePath;
...

这里把加载工作区入口写死了

文件信息创建

vscode是如何读取文件工作区的uri创建工作区列表的呢?我们接着看
src/vs/workbench/services/configuration/browser/configurationService.ts

private async toValidWorkspaceFolders(workspaceFolders: WorkspaceFolder[]): Promise<WorkspaceFolder[]> {
		const validWorkspaceFolders: WorkspaceFolder[] = [];
		for (const workspaceFolder of workspaceFolders) {
			try {
				const result = await this.fileService.stat(workspaceFolder.uri);
				if (!result.isDirectory) {
					continue;
				}
			} catch (e) {
				this.logService.warn(`Ignoring the error while validating workspace folder ${workspaceFolder.uri.toString()} - ${toErrorMessage(e)}`);
			}
			validWorkspaceFolders.push(workspaceFolder);
		}
		return validWorkspaceFolders;
	}

这里读取了工作区的文件信息,这里的stat是对node fs模块通过stat获取文件信息进行了二次封装的,我们可以根据需求修改里面的数据。我们看下定义文件数据的地方

src/vs/platform/files/common/fileService.ts

async stat(resource: URI): Promise<IFileStatWithPartialMetadata> {
		const provider = await this.withProvider(resource);

		const stat = await provider.stat(resource);

		return this.toFileStat(provider, resource, stat, undefined, true, () => false /* Do not resolve any children */);
	}

在往里跟

private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial<IStat>, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStat> {
		const { providerExtUri } = this.getExtUri(provider);
	
		let isLawDawnFolderFlag = await this.exists(providerExtUri.joinPath(resource, 'main.lawdawn.conf'));
		// 判断是否是draw画图文件夹
		let isDrawFolderFlag = await this.exists(providerExtUri.joinPath(resource, 'main.lawd'));
		// 判断是否是语音文件夹
		let isAudioFolderFlag = await this.exists(providerExtUri.joinPath(resource, 'main.audio'));
		// 判断是否是view画图文件夹
		let isViewFlag = await this.exists(providerExtUri.joinPath(resource, 'main.view'));
		// 判断是不是项目
		let isProject = await this.exists(providerExtUri.joinPath(resource, '.config'));
		// convert to file stat
		const fileStat: IFileStat = {
			resource,
			name: providerExtUri.basename(resource),
			isFile: (stat.type & FileType.File) !== 0,
			isDirectory: (stat.type & FileType.Directory) !== 0,
			isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0,
			mtime: stat.mtime,
			ctime: stat.ctime,
			size: stat.size,
			readonly: Boolean((stat.permissions ?? 0) & FilePermission.Readonly) || Boolean(provider.capabilities & FileSystemProviderCapabilities.Readonly),
			etag: etag({ mtime: stat.mtime, size: stat.size }),
			children: undefined,
			isLawDawnFolder: isLawDawnFolderFlag,
			isDrawFolder: isDrawFolderFlag,
			isViewFolder: isViewFlag,
			isAudioFolder: isAudioFolderFlag,
			isProject
		};
		// check to recurse for directories
		if (fileStat.isDirectory && recurse(fileStat, siblings)) {
			try {
				const entries = await provider.readdir(resource);
				// console.log('entries---', entries);
				const resolvedEntries = await Promises.settled(entries.map(async ([name, type]) => {
					try {
						const childResource = providerExtUri.joinPath(resource, name);
						// const childStat = resolveMetadata ? await provider.stat(childResource) : { type };
						/**
						 * @todo 获取子文件数据,愿逻辑由resolveMetadata开关控制
						 */
						const childStat = await provider.stat(childResource)
						return await this.toFileStat(provider, childResource, childStat, entries.length, resolveMetadata, recurse);
					} catch (error) {
						this.logService.trace(error);

						return null; // can happen e.g. due to permission errors
					}
				}));
				// make sure to get rid of null values that signal a failure to resolve a particular entry
				fileStat.children = coalesce(resolvedEntries);
			} catch (error) {
				this.logService.trace(error);

				fileStat.children = []; // gracefully handle errors, we may not have permissions to read
			}

			return fileStat;
		}

		return fileStat;
	}

可以看出这里生成文件信息
src/vs/workbench/contrib/files/common/explorerModel.ts
这个文件是Model层生成文件数据模型。后面用于渲染页面的。

static create(fileService: IFileService, configService: IConfigurationService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {

		let stat: ExplorerItem;
		if (raw.isLawDawnFolder) {
			stat = new ExplorerItem(raw.resource, fileService, configService, parent, false, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, false, raw.ctime);
		} else if (raw.isDrawFolder) {
			stat = new ExplorerItem(raw.resource, fileService, configService, parent, false, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, false, raw.ctime);
		} else if (raw.isAudioFolder) {
			stat = new ExplorerItem(raw.resource, fileService, configService, parent, false, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, false, raw.ctime);
		} else if (raw.isViewFolder) {
			stat = new ExplorerItem(raw.resource, fileService, configService, parent, false, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, false, raw.ctime);
		} else {
			stat = new ExplorerItem(raw.resource, fileService, configService, parent, raw.isDirectory, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, !raw.isFile && !raw.isDirectory, raw.ctime);
		}


		stat.isLawDawnFolder = (raw.isLawDawnFolder as boolean);
		// 判断是否是项目
		stat.isProject = (raw.isProject as boolean);

		stat.isDrawFolder = (raw.isDrawFolder as boolean);
		stat.isAudioFolder = (raw.isAudioFolder as boolean);
		stat.isViewFolder = (raw.isViewFolder as boolean);
		// Recursively add children if present
		if (stat.isDirectory && !stat.isLawDawnFolder && !stat.isDrawFolder && !stat.isViewFolder && !stat.isAudioFolder) {

			stat._isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => {
				return isEqualOrParent(r, stat.resource);
			}));

			if (raw.children) {
				for (let i = 0, len = raw.children.length; i < len; i++) {
					const child = ExplorerItem.create(fileService, configService, raw.children[i], stat, resolveTo);
					stat.addChild(child);
				}
			}
		}
		// 总 stat
		return stat;
	}

绘制文件列表

src/vs/workbench/contrib/files/browser/views/explorerView.ts

const promise = this.tree.setInput(input, viewState).then(async () => {
			
				...
		});

input是总工作区的文件信息
在这里插入图片描述

对文件排序功能

src/vs/workbench/contrib/files/common/files.ts
接口

export const enum SortOrder {
    Default = 'default',
    Mixed = 'mixed',
    FilesFirst = 'filesFirst',
    Type = 'type',
    ModifiedLower = 'modifiedLower',
    ModifiedUp = 'modifiedUp',
    NameLower = 'nameLower',
    NameUp = 'nameUp',
    createTimeLower = 'createTimeLower',
    createTimeUp = 'createTimeUp',
    FoldersNestsFiles = 'foldersNestsFiles',
}

settings文件里面的配置
src/vs/workbench/contrib/files/browser/files.contribution.ts

'explorer.sortOrder': {
    'type': 'string',
    'enum': [SortOrder.Default, SortOrder.Mixed, SortOrder.FilesFirst, SortOrder.Type, SortOrder.ModifiedLower, SortOrder.ModifiedUp, SortOrder.NameLower, SortOrder.NameUp, SortOrder.createTimeLower, SortOrder.createTimeUp, SortOrder.FoldersNestsFiles],
    'default': SortOrder.Default,
    'enumDescriptions': [
        nls.localize('sortOrder.default', 'Files and folders are sorted by their names. Folders are displayed before files.'),
        nls.localize('sortOrder.mixed', 'Files and folders are sorted by their names. Files are interwoven with folders.'),
        nls.localize('sortOrder.filesFirst', 'Files and folders are sorted by their names. Files are displayed before folders.'),
        nls.localize('sortOrder.type', 'Files and folders are grouped by extension type then sorted by their names. Folders are displayed before files.'),
        nls.localize('sortOrder.ModifiedLower', 'Files and folders are sorted by last modified date in descending order. Folders are displayed before  files.'),
        nls.localize('sortOrder.ModifiedUp', 'Files and folders are sorted by latest modified date in descending order. Folders are displayed before  files.'),
        nls.localize('sortOrder.nameLower', 'Files and folders are sorted by name'),
        nls.localize('sortOrder.nameUp', 'Files and folders are sorted by name'),
        nls.localize('sortOrder.createTimeLower', 'Files and folders are sorted by create time'),
        nls.localize('sortOrder.createTimeUp', 'Files and folders are sorted by create time'),
        nls.localize('sortOrder.foldersNestsFiles', 'Files and folders are sorted by their names. Folders are displayed before files. Files with nested children are displayed before other files.')
    ],
    'markdownDescription': nls.localize('sortOrder', "Controls the property-based sorting of files and folders in the explorer. When `#explorer.experimental.fileNesting.enabled#` is enabled, also controls sorting of nested files.")
},

实现

export class FileSorter implements ITreeSorter<ExplorerItem> {

	constructor(
		@IExplorerService private readonly explorerService: IExplorerService,
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
	) { }

	compare(statA: ExplorerItem, statB: ExplorerItem): number {
		// Do not sort roots
		if (statA.isRoot) {
			if (statB.isRoot) {
				const workspaceA = this.contextService.getWorkspaceFolder(statA.resource);
				const workspaceB = this.contextService.getWorkspaceFolder(statB.resource);
				return workspaceA && workspaceB ? (workspaceA.index - workspaceB.index) : -1;
			}

			return -1;
		}

		if (statB.isRoot) {
			return 1;
		}

		const sortOrder = this.explorerService.sortOrderConfiguration.sortOrder;
		const lexicographicOptions = this.explorerService.sortOrderConfiguration.lexicographicOptions;

		let compareFileNames;
		let compareFileExtensions;
		switch (lexicographicOptions) {
			case 'upper':
				compareFileNames = compareFileNamesUpper;
				compareFileExtensions = compareFileExtensionsUpper;
				break;
			case 'lower':
				compareFileNames = compareFileNamesLower;
				compareFileExtensions = compareFileExtensionsLower;
				break;
			case 'unicode':
				compareFileNames = compareFileNamesUnicode;
				compareFileExtensions = compareFileExtensionsUnicode;
				break;
			default:
				// 'default'
				compareFileNames = compareFileNamesDefault;
				compareFileExtensions = compareFileExtensionsDefault;
		}

		// Sort Directories
		switch (sortOrder) {
			case 'type':
				if (statA.isDirectory && !statB.isDirectory) {
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
					return 1;
				}

				if (statA.isDirectory && statB.isDirectory) {
					return compareFileNames(statA.name, statB.name);
				}

				break;

			case 'filesFirst':
				if (statA.isDirectory && !statB.isDirectory) {
					return 1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
					return -1;
				}

				break;

			case 'foldersNestsFiles':
				if (statA.isDirectory && !statB.isDirectory) {
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
					return 1;
				}

				if (statA.hasNests && !statB.hasNests) {
					return -1;
				}

				if (statB.hasNests && !statA.hasNests) {
					return 1;
				}

				break;

			case 'mixed':
				break; // not sorting when "mixed" is on

			default: /* 'default', 'modified' */
				if (statA.isDirectory && !statB.isDirectory) {
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
					return 1;
				}

				break;
		}

		// Sort Files
		switch (sortOrder) {
			case 'createTimeLower':
				return (statA.mcime && statB.mcime && statA.mcime < statB.mcime) ? -1 : 1;
			case 'createTimeUp':
				return (statA.mcime && statB.mcime && statA.mcime < statB.mcime) ? 1 : -1;
			case 'nameUp':
				return statB.name.localeCompare(statA.name);
			case 'nameLower':
				return statA.name.localeCompare(statB.name);
			case 'type':
				return compareFileExtensions(statA.name, statB.name);

			case 'modifiedLower':
				if (statA.mtime !== statB.mtime) {
					return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? 1 : -1;
				}

				return compareFileNames(statA.name, statB.name);

			case 'modifiedUp':
				if (statA.mtime !== statB.mtime) {
					return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? -1 : 1;
				}

				return compareFileNames(statA.name, statB.name);

			default: /* 'default', 'mixed', 'filesFirst' */
				return compareFileNames(statA.name, statB.name);
		}
	}
}

注册菜单调用

// 按创建日期排序

// 一级菜单
const explorerSortByCreateTimeSubMenu = new MenuId('sortOrderByCreateTime');
MenuRegistry.appendMenuItem(MenuId.ViewTitle, <ISubmenuItem>{
    submenu: explorerSortByCreateTimeSubMenu,
    title: nls.localize('sortOrderByCreateTime', "按创建日期排序"),
    when: ContextKeyExpr.equals('view', VIEW_ID),
    group: 'group',
    order: 3,
});


// 二级菜单
registerAction2(class extends Action2 {
    constructor() {
        super({
            id: 'sortOrder.create.time.lower',
            title: nls.localize('sortOrder.create.time.lower', "最新至最旧"),
            menu: {
                id: explorerSortByCreateTimeSubMenu,
                order: 1,
            }
        });
    }

    async run(accessor: ServicesAccessor): Promise<void> {
        const config = accessor.get(IConfigurationService);
        const paneCompositeService = accessor.get(IPaneCompositePartService);
        const explorerService = accessor.get(IExplorerService);

        config.updateValue('explorer.sortOrder', SortOrder.createTimeLower);
        await paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
        await explorerService.refresh();
    }
});

registerAction2(class extends Action2 {
    constructor() {
        super({
            id: 'sortOrder.create.time.up',
            title: nls.localize('sortOrder.create.time.up', "最旧至最新"),
            menu: {
                id: explorerSortByCreateTimeSubMenu,
                order: 2,
            }
        });
    }

    async run(accessor: ServicesAccessor): Promise<void> {
        const config = accessor.get(IConfigurationService);
        const paneCompositeService = accessor.get(IPaneCompositePartService);
        const explorerService = accessor.get(IExplorerService);

        config.updateValue('explorer.sortOrder', SortOrder.createTimeUp);
        await paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
        await explorerService.refresh();
    }
});

registerAction2(class extends Action2 {
    constructor() {
        super({
            id: 'sortOrder.create.time.default',
            title: nls.localize('sortOrder.create.time.default', "默认排序"),
            menu: {
                id: explorerSortByCreateTimeSubMenu,
                order: 3,
            }
        });
    }

    async run(accessor: ServicesAccessor): Promise<void> {
        const config = accessor.get(IConfigurationService);
        const paneCompositeService = accessor.get(IPaneCompositePartService);
        const explorerService = accessor.get(IExplorerService);

        config.updateValue('explorer.sortOrder', SortOrder.Default);
        await paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
        await explorerService.refresh();
    }
});

vscode网页版工作区创建机制

https://insiders.vscode.dev/
网页端使用的是window.showOpenFilePicker 浏览器API.
官方文档地址:https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API

src/vs/workbench/services/dialogs/browser/fileDialogService.ts

...
try {
	([fileHandle] = await window.showOpenFilePicker({ multiple: false }));
} catch (error) {
	return; // `showOpenFilePicker` will throw an error when the user cancels
}
...

其余流程一样

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

提高腾讯QQ(电脑版)语音通话、视频聊天和远程协助的稳定性的方法

一、背景说明 腾讯QQ默认使用的通讯协议是UDP。但是各大运营商将UDP的优先级设置为最低&#xff0c;导致UDP数据包经常被丢弃。导致腾讯QQ在语音通话、视频聊天和远程协助的时候&#xff0c;会出现连接不上、卡顿和断线&#xff0c;非常不稳定。我们可以手动将通讯协议调整为TC…

用Python实现自动化交易:从趋势到收益

在现代金融市场中&#xff0c;自动化交易已经成为越来越流行的一种方式。相比于传统的手工交易方式&#xff0c;自动化交易更加高效、精准、快速且免除了人为因素的影响。而Python作为一种高级编程语言&#xff0c;凭借其简单易学、灵活性强的优势逐渐成为自动化交易中最受欢迎…

Ubuntu下Go语言TCP广播服务器实现

最近在学习Go语言网络编程&#xff0c;突然想到很久以前的用C语言写过的TCP广播聊天程序&#xff0c;然后就用Go尝试写了一遍&#xff0c;在此做个记录。 目录 1.广播结构 2.实现效果 3.源码 4.Go语言学习参考网站 1.广播结构 2.实现效果 服务器&#xff1a; 客户端1&…

小程序 web-view h5页面背景音乐自动播放

/*** 年度账单-登录首页*/ import React,{useEffect} from react import swiper/swiper-bundle.min.css import styles from ./styles.less import bgm from ./bgm2.mp3 // 主体 const annualAccountLoginIndex (props) > {const goAnnualAccount ()>{const {location: …

Java Web Tomcat 23.7.5

Tomcat 1, Tomcat 1.1 简介 1.1.1 什么是Web服务器 Web服务器是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff0c;让Web开发更加便捷。主要功能是"提供网上信息浏览服务"…

[Python系列] Python虚拟环境Virtualenv

1. 什么是Virtualenv Python virtualenv 是一个用于创建和管理虚拟环境的工具。它可以帮助开发者在不同的项目中使用不同的 Python 版本和包&#xff0c;而不会相互干扰。使用 virtualenv&#xff0c;可以轻松地创建一个独立的 Python 环境&#xff0c;在其中安装所需的包和版本…

【网络】思科网络vlan配置+单臂路由

文章目录 前言一、vlan&#xff08;虚拟局域网&#xff09;二、配置vlan配置交换机0配置交换机1&#xff08;和交换机0相同&#xff09;配置计算机&#xff1a;测试联通性 三、单臂路由配置R0配置交换机1测试配置&#xff1a; 前言 VLAN&#xff08;Virtual Local Area Networ…

力扣 491. 递增子序列

题目来源&#xff1a;https://leetcode.cn/problems/non-decreasing-subsequences/description/ 回溯三部曲&#xff08;来源代码随想录&#xff09;&#xff1a; 递归函数参数&#xff1a;求子序列&#xff0c;很明显一个元素不能重复使用&#xff0c;所以需要startIndex&…

2023年 vue使用腾讯地图搜索、关键字输入提示、地点显示

先看结果 vue 在public文件下的index.html文件中引入&#xff1a; <script src"//map.qq.com/api/js?v2.exp&key你自己的key"></script><script src"https://map.qq.com/api/gljs?v1.exp&librariesservice&key你自己的key"&…

【保姆级教程】PyCharm通过SSH远程连接ModelArts

文章目录 一、创建Notebook二、配置SSH三、配置远程Python解释器四、成果展示 一、创建Notebook 首先&#xff0c;找到云资源下面的 ModelArts&#xff0c;然后点击并进入 ModelArts控制台。 在ModelArts控制台中&#xff0c;点击开发环境下的 Notebook 。然后点击创建&#x…

记录一次Nginx日志偶现499的排查

背景 查看到nginx日志在整点整分的时候频繁出现 499&#xff0c;因为配置了存活检查和就绪检查&#xff0c;担心业务会出现大面积重建导致现网故障&#xff0c;所以对出现499的原因进行排查&#xff0c;记录下排查思路&#xff0c;方便以后查看。 业务链路&#xff1a; 负载均…

B/S版手术麻醉系统源码,基于php、mysql和vue2开发

手术麻醉系统是一套以数字形式与医院信息系统&#xff08;如HIS、EMR、LIS、PACS等&#xff09;和医疗设备等软、硬件集成并获取围手术期相关信息的计算机系统&#xff0c;其核心是对围手术期患者信息自动采集、储存、分析并呈现。该系统通过整合围手术期中病人信息、人员信息、…

Packet Tracer – 研究 NAT 操作

Packet Tracer – 研究 NAT 操作 目标 第 1 部分&#xff1a;通过内联网研究 NAT 操作 第 2 部分&#xff1a;研究互联网中的 NAT 操作 第 3 部分&#xff1a;执行进一步研究 拓扑图 场景 帧通过网络时&#xff0c;MAC 地址可能更改。 当数据包由配置了 NAT 的设备转发时&…

转录组和蛋白组如何关联分析?先从绘制九象限图开始

转录组和蛋白组如何关联分析&#xff1f;先从绘制九象限图开始 五种常用蛋白质组学定量分析方法对比 - 知乎 (zhihu.com) 九象限图在多组学关联分析中非常重要&#xff0c;例如我们可以用九象限图展示“转录组蛋白组”、“转录组翻译组”等关联分析中不同基因的差异表达情况。…

DotNet VOL.Core框架学习使用笔记(二)(持续更新)

2023-7-5 生成代码的列表界面&#xff0c;在数据行里增加一个操作列 查看按钮&#xff0c;打开编辑框&#xff0c;然后让编辑框成为一个只读的查看界面。 页面对应的js文件中增加如下 this.columns.push 函数内容。 按钮的点击事件 重点代码 this.edit(row); 这就是框架里编…

构建工具——webpack、vite

文章目录 构建工具Webpack使用步骤配置文件&#xff08;webpack.config.js&#xff09;插件&#xff08;plugin&#xff09; ViteVite 也是前端的构建工具使用命令构建配置文件&#xff1a;vite.config.js 构建工具 当我们习惯了在 node 中编写代码的方式后&#xff0c;在回到…

page-break-after: always打印时强制分页

page-break-before 元素在指定元素前添加分页符。 <div stylepage-break-after: always;></div> <p stylepage-break-after: always;></p>https://www.runoob.com/cssref/pr-print-pagebb.html

小样本图像目标检测研究综述——张振伟论文阅读

小样本图像目标检测研究综述——张振伟&#xff08;计算机工程与应用 2022&#xff09; 论文阅读 目前&#xff0c;小样本图像目标检测方法多基于经典的俩阶段目标检测算法Faster R-CNN作为主干网络&#xff0c;当然也有将YOLO&#xff0c;SSD一阶段目标检测算法作为主干网络的…

Android Studio实现内容丰富的安卓公交线路查询平台

如需源码可以添加q-------3290510686&#xff0c;也有演示视频演示具体功能&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号084 1.开发环境 android stuido jdk1.8 eclipse mysql tomcat 开发语言&#xff1a;java 2.功能介绍 安卓端&#xff1a; …

zabbix基础3——邮箱告警

文章目录 一、环境说明二、第三方邮箱告警2.1 开启第三方邮箱SMTP服务2.2 配置用户媒介2.3 定义媒介类型2.4 配置告警方式和动作2.5 触发告警&#xff0c;测试效果 三、本地邮箱脚本3.1 服务端设置脚本3.2 设置用户媒介3.3 定义媒介类型3.4 配置告警方式动作3.4 触发告警&#…