tauri2+typescript+vue+vite+leaflet等的简单联合使用(一)

项目目标

主要的目的是学习tauri。

流程

1、搭建项目

2、简单的在项目使用leaflet

3、打包

准备项目

环境准备

废话不多说,直接开始

需要有准备能运行Rust的环境和Node,对于Rust可以参考下面这位大佬的文章,Node不必细说。

Rust 和 Cargo 安装指南-CSDN博客https://blog.csdn.net/qq_44154915/article/details/139365116

建立项目

进入tauri2

官网https://tauri.app/start/

笔者将使用pnpm建立项目,项目名称为ttvvl

如下

使用VSCode或者WebStorm打开项目

安装依赖

pnpm install 

运行

pnpm tauri dev

然后就在compiling

这笔者感到疑惑,可能是第一次运行,搞了许久

运行结果

 没有问题。

看一下文件夹的属性

6个G,有点大。

如果以

pnpm run dev

点击按钮,会出现错误

观察上面的代码,我们可以发现,这个是invoke好像是个方法。第一个参数是个字符串greet

第二个参数是个对象,属性是name。

同时找到src-tauri/src/lib.rs中的代码,如下

fn 是Rust的关键字,相当与定义了一个函数,函数名叫greet,参数name

format!是个格式化字符串的函数

其他不是很懂,但我们可以把这段代码给deepseek,问一问

解答如下

从这里我们可以得到关键信息——被这个宏标记的函数能被前端调用

因为Rust函数的函数名叫greet,而invoke的第一个参数是字符串greet,我们可以很容易猜测

二者必然有关系,我们可以检验一下

修改函数名为greets,运行

发现报错了

我们可以看到下面还有一个函数

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {tauri::Builder::default().plugin(tauri_plugin_opener::init()).invoke_handler(tauri::generate_handler![greet]).run(tauri::generate_context!()).expect("error while running tauri application");
}

可以问一问deepseek

原来下面那个函数是入口函数,invoke_handler是注册器,因此,把中括号里面的greet改成greets

应该可以运行了,(每次都要编译,有点慢)。

如果点击按钮,下面应该不会出现字

事实确实如此。

那么推断很有可能是正确的,那么我们把字符串greet改成greets,就可以出现字了

事实确实如此,笔者明白了。

再次观察,Rust函数greets返回值是String,

而返回格式化后的字符串,对于App.vue中的TypeScript代码,打印出greetMsg.value的值

在运行后的的tauri中,可以打开开发者工具,打印结果如下。

既然如此,感觉明白了,invoke作为Rust与TypeScript交互的关键函数

安装leaflet有关依赖

pnpm install leaflet leaflet-geoman-free

因为使用typescipt,还需要安装类型定义包

 pnpm install --save @types/leaflet.pm @types/leaflet

显示地图

这也是很麻烦的事情。

安装vue-router

pnpm install vue-router

新建一些目录和文件,如下

在router目录的index.ts中

import {createRouter, createWebHashHistory, Router, RouteRecordRaw, RouterOptions} from "vue-router";
const routes:RouteRecordRaw[]=[{path:"/",redirect:"/map",children:[{path:"map",component:()=>import("../views/Map.vue")}]}
]
const options:RouterOptions={history:createWebHashHistory(),routes
}
const router:Router=createRouter(options)
export default router

暂时先这么写

main.ts的内容

import { createApp } from "vue";
import App from "@/App.vue";
import router from "@/router";
import * as L from "leaflet";
import "leaflet/dist/leaflet.css";
import "@geoman-io/leaflet-geoman-free";
import "@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css";
const app=createApp(App);
app.use(router);
app.config.globalProperties.$L = L;app.mount("#app");

将L作为Vue的全局属性,并且引入插件leaflet-geoman-free及相关样式

关于@符号,不多解释。

在constant/data.ts中

export const MAP_URL= "https://tile-a.openstreetmap.fr/hot/{z}/{x}/{y}.png";

 这个是地图瓦片的URL。通过它就能访问地图。

在utils/Map.ts中

import {MAP_URL} from "@/constant/data.ts";
import L from 'leaflet';
const GetMap = (control: any=null, //控件language:any = 'zh' //语言
): L.Map => {const options: L.MapOptions = {center: [30.6667, 104.0667], //中心点minZoom: 0,maxZoom: 30,zoom: 10,zoomControl: true,doubleClickZoom: true,attributionControl: true,dragging: true,boxZoom: true,scrollWheelZoom: true,zoomSnap: 0.5,};const map = L.map('map', options);L.tileLayer(MAP_URL, {attribution: '',}).addTo(map);map.pm.addControls(control); map.pm.setLang(language);return map;
};
export default GetMap;

如果不写 

import L from 'leaflet'

Vue: 'L' refers to a UMD global, but the current file is a module. Consider adding an import instead.

在views/Map.vue中 

<script setup lang="ts">
import GetMap from "@/utils/Map.ts";
import { onMounted } from 'vue';
let map: any = null
onMounted(() => {map = GetMap();map.on('click', (e: any) => {console.log(e);});
})
</script><template>
<div id="map"></div>
</template><style scoped>
#map {height: 100vh;width: 200vh;
}
</style>

运行,结果如下

地图定位到成都市。

显示控件

可以查看leaflet-geoman-free文档

Introduction | Documentation for Leaflet-Geomanhttps://geoman.io/docs/leaflet在constant/data.ts中

export const MAP_CONTROL={
}

可以写一些设置,但有默认选项,就不改了。

在views/Map.vue中导入MAP_CONTROL,作为GetMap的参数

map = GetMap(MAP_CONTROL);

,使用控件,运行结果如下

搞点小操作1——计算两点之间的距离

操作过程

点两个点,通过invoke函数发送经纬度到rust函数,返回结果

计算距离函数

使用Haversine 公式

haversine公式计算两经纬度点距离-CSDN博客https://blog.csdn.net/spatial_coder/article/details/116605509使用Rust实现,src-tauri/src/lib.rs的代码如下

const EARTH_RADIUS_KM: f64 = 6371.0; // 地球半径,单位:公里
#[tauri::command]
// 计算两个经纬度点之间的距离(单位:公里)
fn haversine_distance(start:[f64;2],end:[f64;2]) -> f64 {// 将经纬度从度数转换为弧度let lat1_rad = start[0].to_radians();let lon1_rad =  start[1].to_radians();let lat2_rad = end[0].to_radians();let lon2_rad = end[1].to_radians();// 经纬度差值let dlat = lat2_rad - lat1_rad;let dlon = lon2_rad - lon1_rad;// Haversine 公式let a = (dlat / 2.0).sin().powi(2) + lat1_rad.cos() * lat2_rad.cos() * (dlon / 2.0).sin().powi(2);let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());// 计算距离EARTH_RADIUS_KM * c
}#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {tauri::Builder::default().plugin(tauri_plugin_opener::init()).invoke_handler(tauri::generate_handler![haversine_distance]).run(tauri::generate_context!()).expect("error while running tauri application");
}

以公里作为单位

对两个点marker的思考

点了两个点marker,我们通过leaflet和有关插件的某些事件可以获得这两个点的属性,

我们可以修改这些属性。

那应该然后修改?

可以尝试使用tauri的多窗口。

marker位于父窗口,发送信号到子窗口,在子窗口进行修改,返回父窗口。

父子窗口以及发送信号

关于窗口的配置

src-tauri/capabilities/default.json

{"$schema": "../gen/schemas/desktop-schema.json","identifier": "default","description": "Capability for the main window","windows": ["main","marker-name"],"permissions": ["core:default","opener:default","core:window:allow-hide","core:window:allow-show"]
}

在子窗口添加一个路由

src/router/index.ts

    {path:"/name",component:()=>import("../views/name.vue")}

src-tauri/tauri.conf.json

"windows": [{"title": "ttvvl","label": "main","width": 800,"height": 600},{"title": "点位窗口","width": 400,"height": 300,"label": "marker-name","resizable": true,"parent": "main","visible": false,"url":"#/name","decorations": false,"center": true}],

参数的具体含义,可以参考官网或者deekssek

{"windows": [{"label": "main", // 窗口的唯一标识符"title": "My Tauri App", // 窗口标题"width": 800, // 窗口宽度"height": 600, // 窗口高度"x": null, // 窗口初始水平位置(null 表示由系统决定)"y": null, // 窗口初始垂直位置(null 表示由系统决定)"minWidth": null, // 窗口最小宽度"minHeight": null, // 窗口最小高度"maxWidth": null, // 窗口最大宽度"maxHeight": null, // 窗口最大高度"resizable": true, // 是否允许调整窗口大小"fullscreen": false, // 是否全屏"focus": true, // 窗口是否在创建时获得焦点"visible": true, // 窗口是否可见"decorations": true, // 是否显示窗口装饰(标题栏、边框等)"alwaysOnTop": false, // 窗口是否始终置顶"maximized": false, // 窗口是否最大化"transparent": false, // 窗口背景是否透明"center": true, // 窗口是否居中"theme": "system", // 窗口主题("system"、"light"、"dark")"url": "index.html", // 窗口加载的页面路径"fileDropEnabled": true, // 是否启用文件拖放功能"skipTaskbar": false, // 是否在任务栏中显示窗口"shadow": true, // 是否显示窗口阴影"acceptFirstMouse": false, // 是否接受首次鼠标点击事件"tabbingIdentifier": null, // macOS 标签页标识符"titleBarStyle": "visible", // macOS 标题栏样式("visible"、"transparent"、"overlay")"hiddenTitle": false // 是否隐藏标题栏标题}]
}

父窗口

src/views/Map.vue

<script setup lang="ts">
//@ts-nocheck
import GetMap from "@/utils/Map.ts";
import {MAP_CONTROL} from "@/constant/data.ts";
import {invoke} from "@tauri-apps/api/core";
import {emit,listen} from "@tauri-apps/api/event";
import {onMounted, ref} from 'vue';
import WindowManager from '@/utils/window';const windowManager = new WindowManager();
let startName=ref<String>('起点');
let endName=ref<String>('终点');
let markerWindow,mainWindow,markerIndex=null;
let map: any = null
let markerList: L.Marker[] = [];
let distance=ref<number>(0);listen('message-to-main-window', (event)=>{  //监听消息let payload:any=event.payloadif(payload.close){windowManager.closeWindow('marker-name')mainWindow.show()}if(markerIndex===null){return}if(markerIndex===0){startName.value='起点'+payload.tooltipContent}else{endName.value='终点'+payload.tooltipContent}markerList[markerIndex].bindTooltip(payload.tooltipContent)
});
async function sendMsg(tooltipContent:String,nowClickMarkerId:Number) {  //发送消息markerWindow=await windowManager.getWindowByLabel('marker-name') //子窗口mainWindow=await windowManager.getWindowByLabel('main')  //父窗口if(markerWindow) {mainWindow.hide()markerWindow.show();markerIndex=markerList.findIndex((marker:L.Marker)=>marker._leaflet_id===nowClickMarkerId)emit('message-to-second-window', { //发送消息 参数lat: markerList[0].getLatLng().lat,lng: markerList[0].getLatLng().lng,markerIndex:markerIndex,tooltipContent:tooltipContent})}
}async function get_distance(){let start=markerList[0].getLatLng(); //开始点let end=markerList[1].getLatLng();   //终点let startArray=[start.lat,start.lng];let endArray=[end.lat,end.lng];distance.value = await invoke('haversine_distance', { //发送给rust tauristart: startArray,end: endArray});
}
onMounted(() => {// openWindow()map = GetMap(MAP_CONTROL);map.on('pm:create', (e: any) => { //  map create事件let marker:L.Marker = e.marker;marker.on('click',(e)=>{ // marker的点击事件let nowClickMarkerId=e.target._leaflet_id  // 获取marker的idsendMsg(e.target.getTooltip(),nowClickMarkerId)  // 发送消息})markerList.push(marker);let length = markerList.length;if(length==2){  // 起始点 和终点get_distance()  //获取距离}else if(length>2){markerList[0].remove(); //移除第一个点markerList.shift();get_distance()   // 获取距离}})})
</script><template>
<div id="map"><div id="distance-text" v-if="markerList.length>=2">从{{startName}}到{{endName}}的距离为</div><div><button type="button" id="show" >{{distance}}</button></div><div id="unit">公里</div></div>
</template><style scoped>
#map {margin: 0 auto;width: 100%;height: 900px;
}
#show {max-width: 200px;max-height: 50px;padding: 10px;background-color: blue;color: white;text-align: center;position: absolute;top: 100px;right: 10px;z-index: 999;border-radius: 20px;
}
#distance-text {max-width: 200px;max-height: 50px;padding: 10px;background-color: #af8433;color: white;text-align: center;position: absolute;top: 10px;right: 10px;z-index: 999;border-radius: 20px;
}
#unit {max-width: 200px;max-height: 50px;padding: 10px;background-color: #af8433;color: white;text-align: center;position: absolute;top: 150px;right: 10px;z-index: 999;border-radius: 20px;
}</style>

子窗口

src/views/Name.vue

<script setup>
import {listen,emit} from "@tauri-apps/api/event";
import {onMounted, ref} from "vue";
let lat = ref(0);
let lng = ref(0);
let markerIndex = ref(0); // 0 替换成起点 1 替换成终点
let inputValue = ref('');
let oldTooltip=nullconst handleClicked = () => {emit('message-to-main-window', {tooltipContent:inputValue.value,close: true,});
}
const handleCancel = () => {emit('message-to-main-window', {tooltipContent:oldTooltip,close: true,});
}
onMounted(() => {listen('message-to-second-window', (event) => {let payload = event.payload;lat.value = payload.lat;lng.value = payload.lng;markerIndex.value = payload.markerIndex;inputValue.value=payload.tooltipContent;oldTooltip=payload.tooltipContent});}
)</script>
<template><div  data-tauri-drag-region class="titlebar"  ><h1>{{markerIndex===0?'起点':'终点'}}</h1><div v-if="lat>0">纬度:{{lat  }}</div><div v-if="lng>0">经度:{{lng  }}</div><div>名称<br/><input v-model="inputValue"></input></div><br/><button type="button" @click="handleClicked" class="sure">确定</button><button type="button" @click="handleCancel" class="cancel">取消</button></div>
</template>
<style >
.sure{padding: 10px ;margin-right: 10vw;background-color: #5174ff;
}
.cancel{padding: 10px ;margin-right: 10vw;background-color: #ffb43e;}</style>

窗口管理

src/utils/window.ts

import { getAllWebviewWindows } from '@tauri-apps/api/webviewWindow';export class WindowManager {async getWindowByLabel(label: string) {const windows = await getAllWebviewWindows();return windows.find((win) => win.label === label);}async closeWindow(label: string) {const window = await this.getWindowByLabel(label);if (window) {window.hide();}}
}export default WindowManager;

运行结果

运行结果倒是没问题,代码写得不行,算了。不管那些。

打包

本地打包

打包成exe命令

pnpm tauri build

因为笔者不是第一次打包,如果是第一次打包,需要下载一些东西,笔者就不展示了,可参考下面这位大佬的过程。 

从零开始的 Tauri 开发 & 打包成 exe 【Windows 平台】_tauri 打包-CSDN博客https://blog.csdn.net/u010263423/article/details/136006546对于leaflet打包,会出现一个bug,解决过程可看这篇文章

解决Vue+Vite打包后Leaflet的marker图标不显示的问题_leaflet l.marker 没有-CSDN博客https://blog.csdn.net/qq_63401240/article/details/139972362?spm=1001.2014.3001.5502总之。打包结果

打包运行结果

可以。

上传到github并打包

在github上通过工作流打包

工作流代码参考下面这位大佬

tauri使用github的action自动发布release,让别人也可以看到下载链接_tauri github action-CSDN博客https://blog.csdn.net/weixin_44786530/article/details/140904091在大佬的基础上做出小的修改

修改了工件的版本,其他改动不大

运行结果如下

发现ubuntu出错了。笔者没有深究。

笔者再次打包,只选择了window系统,结果如下

项目地址

qe-present/ttvvlhttps://github.com/qe-present/ttvvl

总结

简单地使用了tauri,使用了信号通信,多窗口,打包。

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

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

相关文章

深入解析 Svelte:下一代前端框架的革命

深入解析 Svelte&#xff1a;下一代前端框架的革命 1. Svelte 简介 Svelte 是一款前端框架&#xff0c;与 React、Vue 等传统框架不同&#xff0c;它采用 编译时&#xff08;Compile-time&#xff09; 方式来优化前端应用。它不像 React 或 Vue 依赖虚拟 DOM&#xff0c;而是…

关于流水线的理解

还是不太理解&#xff0c;我之前一直以为&#xff0c;对axis总线&#xff0c;每一级的寄存器就像fifo一样&#xff0c;一级一级的分级存储最后一级需要的数据。 像这张图&#xff0c;一开始是在解析axis流形式的数据包&#xff0c;数据包一直都能输入&#xff0c;所以valid一直…

Python代码之美:从规范到艺术

基础规范&#xff1a;代码的"颜值"很重要 &#x1f449;大礼包&#x1f381;&#xff1a;&#x1f448; PEP 8&#xff1a;不只是规范&#xff0c;是写作艺术 良好的代码格式就像优美的书法&#xff0c;让人赏心悦目。比如&#xff1a; # 不推荐的写法 def calcul…

【AI+智造】在阿里云Ubuntu 24.04上部署DeepSeek R1 14B的完整方案

作者&#xff1a;Odoo技术开发/资深信息化负责人 日期&#xff1a;2025年2月28日 一、部署背景与目标 DeepSeek R1作为国产大语言模型的代表&#xff0c;凭借其强化学习驱动的推理能力&#xff0c;在复杂任务&#xff08;如数学问题、编程逻辑&#xff09;中表现优异。本地化部…

8 SpringBoot进阶(上):AOP(面向切面编程技术)、AOP案例之统一操作日志

文章目录 前言1. AOP基础1.1 AOP概述: 什么是AOP?1.2 AOP快速入门1.3 Spring AOP核心中的相关术语(面试)2. AOP进阶2.1 通知类型2.1.1 @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行(通知的代码在业务方法之前和之后都有)2.1.2 @Before:前置通知,此…

【react】快速上手基础教程

目录 一、React 简介 1.什么是 React 2.React 核心特性 二、环境搭建 1. 创建 React 项目 2.关键配置 三、核心概念 1. JSX 语法 表达式嵌入 样式处理 2. 组件 (Component) 3. 状态 (State) 与属性 (Props) 4. 事件处理 合成事件&#xff08;SyntheticEvent) 5. …

七星棋牌 6 端 200 子游戏全开源修复版源码(乐豆 + 防沉迷 + 比赛场 + 控制)

七星棋牌源码 是一款运营级的棋牌产品&#xff0c;覆盖 湖南、湖北、山西、江苏、贵州 等 6 大省区&#xff0c;支持 安卓、iOS 双端&#xff0c;并且 全开源。这个版本是 修复优化后的二开版本&#xff0c;新增了 乐豆系统、比赛场模式、防沉迷机制、AI 智能控制 等功能&#…

【人工智能】Deepseek 与 Kimi 联袂:重塑 PPT 创作,开启智能演示新纪元

我的个人主页 我的专栏&#xff1a;人工智能领域、java-数据结构、Javase、C语言&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞&#x1f44d;收藏❤ 前言 在当今快节奏的工作与学习场景中&#xff0c;PPT 制作常常是一项耗时耗力的任务。从前期的资…

Kafka的高水位、低水位是什么概念?

Kafka 的 高水位&#xff08;High Watermark, HW&#xff09; 和 低水位&#xff08;Low Watermark, LW&#xff09; 是和数据存储、消费进度、日志清理等密切相关的重要概念。我们用一个 “蓄水池” 的比喻来形象地解释它们的作用。 1. Kafka 里的数据像一个蓄水池 Kafka 的数…

基于JAVA+Spring+mysql_快递管理系统源码+设计文档

文末获取源码数据库文档 感兴趣的可以先收藏&#xff0c;有毕设问题&#xff0c;项目以及论文撰写等问题都可以和博主沟通&#xff0c;尽最大努力帮助更多的人&#xff01; 摘 要 随着物流行业信息化的深入使得物流过程中货物的状态和变化透明化&#xff0c;现代信息化的接入使…

Python----数据分析(Numpy:安装,数组创建,切片和索引,数组的属性,数据类型,数组形状,数组的运算,基本函数)

一、 Numpy库简介 1.1、概念 NumPy(Numerical Python)是一个开源的Python科学计算库&#xff0c;旨在为Python提供 高性能的多维数组对象和一系列工具。NumPy数组是Python数据分析的基础&#xff0c;许多 其他的数据处理库&#xff08;如Pandas、SciPy&#xff09;都依赖于Num…

【SQL】MySQL中的字符串处理函数:concat 函数拼接字符串,COALESCE函数处理NULL字符串

MySQL中的字符串处理函数&#xff1a;concat 函数 一、concat &#xff08;&#xff09;函数 1.1、基本语法1.2、示例1.3、特殊用途 二、COALESCE&#xff08;&#xff09;函数 2.1、基本语法2.2、示例2.3、用途 三、进阶练习 3.1 条件和 SQL 语句3.2、解释 一、concat &…

windows下适用msvc编译ffmpeg 适用于ffmpeg-7.1

需要的工具: visual studio 2019 (可以是其他版本&#xff0c;只是本人电脑上装的为2019) msys2 ffmpeg-7.1源码 1. 修改msys2_shell.cmd 在msys2目录修改msys2_shell.cmd 打开后找到行set MSYS2_PATH_TYPEinherit 删除开头的rem 2. 运行msys2 运行x64 Native Tools Command …

2025年软考报名费用是多少?全国费用汇总!

软考报名时间终于确定了&#xff01;想要参加2025年软考的同学们注意啦&#xff01;特别是那些一年只有一次考试机会的科目&#xff0c;千万不要错过哦&#xff01;这里为大家整理了各地的报名时间、科目、费用等信息&#xff0c;快来看看吧&#xff01; 一、2025年软考时间安…

【LeetCode459】重复的子字符串

题目描述 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 思路与算法 关键词&#xff1a;利用字符串的重复性质&#xff1b;字符串的拼接技巧&#xff1b;逆推法假设原始字符串 s 是由某个子串 sub 重复多次构成的。也就是说&#xff0c…

JAVA面试常见题_基础部分_Dubbo面试题(上)

Dubbo 支持哪些协议&#xff0c;每种协议的应用场景&#xff0c;优缺点&#xff1f; • dubbo&#xff1a; 单一长连接和 NIO 异步通讯&#xff0c;适合大并发小数据量的服务调用&#xff0c;以及消费者远大于提供者。传输协议 TCP&#xff0c;异步&#xff0c;Hessian 序列化…

掌握Git:从入门到精通的完整指南

Git是什么&#xff1f; Git是一个分布式版本控制系统&#xff0c;最初由Linus Torvalds在2005年为管理Linux内核开发而创建 它的主要功能是跟踪文件的更改&#xff0c;协调多个开发者之间的工作&#xff0c;并帮助团队高效地管理项目代码。Git不仅适用于大型开源项目&#xf…

数据安全_笔记系列05:数据合规与隐私保护(GDPR、CCPA、中国《数据安全法》)深度解析

数据安全_笔记系列05&#xff1a;数据合规与隐私保护&#xff08;GDPR、CCPA、中国《数据安全法》&#xff09;深度解析 在全球数据跨境流动和隐私保护强监管的背景下&#xff0c;企业需同时满足多法域合规要求。以下从 法规要点、核心差异、实施策略、跨境传输、典型案例 等维…

StableDiffusion打包 项目迁移 项目分发 1

文章目录 SD项目迁移前置知识webui-user.batwebui.batlaunch_utils.py 下一篇开始实践 SD项目迁移 显卡驱动更新&#xff1a;https://www.nvidia.cn/geforce/drivers/ 下载安装三个程序&#xff1a; python3.10.6: https://www.python.org/downloads/release/python-3106/gi…

Leetcode2414:最长的字母序连续子字符串的长度

题目描述&#xff1a; 字母序连续字符串 是由字母表中连续字母组成的字符串。换句话说&#xff0c;字符串 "abcdefghijklmnopqrstuvwxyz" 的任意子字符串都是 字母序连续字符串 。 例如&#xff0c;"abc" 是一个字母序连续字符串&#xff0c;而 "ac…