HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

vue3.5+deepseek-v3多轮流式AI对话助手

vue3

2025实战ai开发vue3.5整合deepseek-v3搭建一款仿DeepSeek/ChatGPT网页版流式聊天AI对话模板。
集成 Vite6 对接 DeepSeek-V3 API 流式打字输出对话大模型。支持上下文多轮对话、代码高亮、本地缓存,亮色+暗黑主题。

img

img

使用技术

  • 技术框架:vite^6.2.0+vue^3.5.13+vue-router^4.5.0
  • AI框架:DeepSeek-R1 + OpenAI
  • 组件库:arco-design^2.57.0 (字节桌面端组件库)
  • 状态管理:pinia^3.0.1
  • 本地存储:pinia-plugin-persistedstate^4.2.0
  • 高亮插件:highlight.js^11.11.1
  • markdown解析:markdown-it

img

img

项目特色

  1. 流式响应:Vue3+DeepSeek实现逐行打字输出效果
  2. 丝滑极速:基于Vite6构建,接入DeepSeek,性能更优,聊天丝滑流畅
  3. 特色功能:支持各种代码高亮,利于展示和分享代码片段、支持暗黑+亮色主题模式
  4. 风格:采用arco-design组件库,风格统一,美观大气

img

项目框架结构

img

img

img

img

img

img

img

img

想要了解更多的技术实现细节可以去参考下面这篇分享文章。
vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果

继续阅读 »

2025实战ai开发vue3.5整合deepseek-v3搭建一款仿DeepSeek/ChatGPT网页版流式聊天AI对话模板。
集成 Vite6 对接 DeepSeek-V3 API 流式打字输出对话大模型。支持上下文多轮对话、代码高亮、本地缓存,亮色+暗黑主题。

img

img

使用技术

  • 技术框架:vite^6.2.0+vue^3.5.13+vue-router^4.5.0
  • AI框架:DeepSeek-R1 + OpenAI
  • 组件库:arco-design^2.57.0 (字节桌面端组件库)
  • 状态管理:pinia^3.0.1
  • 本地存储:pinia-plugin-persistedstate^4.2.0
  • 高亮插件:highlight.js^11.11.1
  • markdown解析:markdown-it

img

img

项目特色

  1. 流式响应:Vue3+DeepSeek实现逐行打字输出效果
  2. 丝滑极速:基于Vite6构建,接入DeepSeek,性能更优,聊天丝滑流畅
  3. 特色功能:支持各种代码高亮,利于展示和分享代码片段、支持暗黑+亮色主题模式
  4. 风格:采用arco-design组件库,风格统一,美观大气

img

项目框架结构

img

img

img

img

img

img

img

img

想要了解更多的技术实现细节可以去参考下面这篇分享文章。
vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果

收起阅读 »

记:安卓端长按选择文本后无法弹出复制等按钮,而IOS有

因为参照默认的项目开发,把这个加在了全局js里面,而导致安卓端按钮无法显示

document.oncontextmenu = function() {  
    return false;  
};

同时如果有需要禁用文本复制的也可以增加这个,IOS可以配合css user-select: none 来禁用

继续阅读 »

因为参照默认的项目开发,把这个加在了全局js里面,而导致安卓端按钮无法显示

document.oncontextmenu = function() {  
    return false;  
};

同时如果有需要禁用文本复制的也可以增加这个,IOS可以配合css user-select: none 来禁用

收起阅读 »

H5 app 安卓应用 连接 蓝牙打印机 打印标签

打印 蓝牙 h5+

H5 app 安卓应用 连接 蓝牙打印机 打印标签

项目需求:

商超类管理端APP(H5 )能够连接蓝牙打印机,打印促销价签。

个人说明:

1、前端水平为入门级。
2、第一次搞硬件对接。

所以写的内容可能很基础,或者有错误。欢迎各位大佬指正。

开发思路:

本人负责从该功能的设计,数据库、后端、前端的所有开发。
首先需要验证H5 项目是否能够链接驱动打印机。
验证成功后再进行其他开发。
本文主要介绍:H5 项目的蓝牙打印机验证。

项目研究及同类搜索:

客户发过来的蓝牙打印机是 芝柯便携式打印机 CC3 。

1、商家技术支持:商家拉了一个技术对接群。发了一个500M左右的技术支持包。
问有没有相关的demo。答:“里面您自己找下 ,有的话就有,没有就没有了”。
这个群最后一句话是“明天这边 反馈下厂家开发”。
所以商家基本上没有支持(没错,就是在吐槽)。

2、同类搜索:在mui 及其他网站搜索 “H5 蓝牙打印机”。找到了六七个项目下载后挨个测试使用。
发现有些是H5WEB项目,有些是vue项目。符合要求的有三个。
==CSDN 搜包小弟快递 (优点:在同一个页面进行蓝牙配对、)
==雨滴科技蓝牙打印Demo https://ask.dcloud.net.cn/article/38125 buleprintDemo.zip(优点:能够自动连接已保存蓝牙)
==CSDN 搜 html5-bluetooth-HBuilderX(优点:丰富的操作)

经测试后发现有些问题无法解决。
如:Uncaught java.lang.SecurityException: Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@8a1bcf4c: Starting discovery.;at android.bluetooth.BluetoothAdapter.startDiscovery at printer.html:1

很多文章下也有这个提问,但是暂无回答 。后来解决后 我也尽量去相关问题下回答了。

相关知识:

打印机的相关文档 和 网上搜索的demo中,每个写法都不太一样。如:不同的打印指令,不同的链接蓝牙的方法。
看的多了更乱了。发现自己关于打印机开发的相关知识还不够。

这里梳理了一些相关知识,给跟我一样刚接触的人一些提示。

1、蓝牙打印机

不同品牌的蓝牙打印机样子都差不多,相关指令也一样,可能有一些字体和字号设置不同。其他都一样。
拿到新的打印机后,先打印一个自检页,相面有相关数据 。如:mac地址,支持的指令集,支持的字体等。

2、蓝牙的连接方式

蓝牙的连接方式有两种:传统连接、ble连接(低功耗链接)。
两种链接用的方法不一样,从搜索,连接,传送指令,断开连接 等都不一样。
简单来说,方法名中 带有ble 字样的就是ble连接。

用ble连接的场景:微信小程序只支持ble蓝牙连接。 所以如果你用vue后期要编译成小程序的 ,需要用ble。

打印机支持的链接模式:打印机会有说明 ,是否支持双模连接。支持双模连接的 会有两个mac地址,ble蓝牙名称比传统蓝牙名称多一个L。

===我用的是传统连接。

3、打印机指令

我这个打印机自检页中 说支持的指令集有很多种: TSPL,ZPL,EPL,CPCL。

网上搜索的指令集一般用两种:CPCL命令集‌、ESC命令集‌

一开始我用ESC命令集‌,传送命令没有报错,但是打印机没反应。后来发现,这个打印机不支持。

最后用的CPCL命令集‌,也建议大家用这个指令集。因为:
1、CPCL指令集 更通用。
2、ESC指令集用的十六进制的指令,阅读困难。CPCL指令集阅读友好。

===我用的是CPCL命令集‌。

4、在什么介质上打印

热敏打印机可以在两种介质上打印。
1、热敏小票纸。就是你去超市结账后给的小票
2、热敏标签。就是你在超市买菜时,电子秤上打印的标签。

这两种打印在指令上略有不同,如果只打印一种,只看相关指令就行。

===我用的是热敏标签打印。

根据我的需求和各个例子的功能,最后选择了在 雨滴科技蓝牙打印demo 上进行修改应用。

改进的地方

1、已经在manifast中添加了相关权限,但还是在搜索蓝牙时报错Need android.permission.

var permissions = [  
                        "android.permission.BLUETOOTH",  
                        "android.permission.BLUETOOTH_ADMIN",  
                        "android.permission.BLUETOOTH_SCAN",  
                        "android.permission.BLUETOOTH_CONNECT"  
                    ];  
                    plus.android.requestPermissions(  
                        permissions,  
                        function(result) {  
                            // 权限申请成功后的回调  
                            console.log("权限申请成功");  
                        },  
                        function(error) {  
                            console.error("权限申请失败:"   error.message);  
                        }  
                    );  

2、搜索蓝牙有大量重复,需要去重。

// 防止重复出现  
                    if (JSON.stringify(BleDeviceObjAry).indexOf(JSON.stringify(BleDevice)) != -1) {  
                        console.log("重复");  
                    } else {  
                        console.log("增加");  
                        BleDeviceObjAry.push(BleDevice);  
                        self.SetpairedListHtml(unpairedList, bleName, bleId);  
                    }  

3、页面没有蓝牙连接标志。

<script type="text/javascript" src="js/jquery.min.js"></script>  
$('#J_printer_status').val('打印机已就绪');  
$('#J_printer_status').css('color', 'green');  

4、第一次链接失败后,在连接还是失败。Uncaught java.io.IOException: read failed, socket might closed or timeout, read ret: -1;at android.bluetooth.BluetoothSocket.connect at index.html:1

if(!bluetoothSocket.isConnected()) {  
            try{  
                bluetoothSocket.connect();  

            }catch(e){  
                // $('#J_printer_status').val(data.value);  
                bluetoothSocket.close();  
                $('#J_printer_status').val('打印机未连接')  
                $('#J_printer_status').css('color', 'red');  
                localStorage.setItem("printer_status", "N");  
                console.log(e)  
                return;  
            }  

        }  

5、增加按钮 ,能够手动进入打印机配对界面

6、修改label_set_page 方法,能够传入打印数量

欢迎指正并一起讨论学习

继续阅读 »

H5 app 安卓应用 连接 蓝牙打印机 打印标签

项目需求:

商超类管理端APP(H5 )能够连接蓝牙打印机,打印促销价签。

个人说明:

1、前端水平为入门级。
2、第一次搞硬件对接。

所以写的内容可能很基础,或者有错误。欢迎各位大佬指正。

开发思路:

本人负责从该功能的设计,数据库、后端、前端的所有开发。
首先需要验证H5 项目是否能够链接驱动打印机。
验证成功后再进行其他开发。
本文主要介绍:H5 项目的蓝牙打印机验证。

项目研究及同类搜索:

客户发过来的蓝牙打印机是 芝柯便携式打印机 CC3 。

1、商家技术支持:商家拉了一个技术对接群。发了一个500M左右的技术支持包。
问有没有相关的demo。答:“里面您自己找下 ,有的话就有,没有就没有了”。
这个群最后一句话是“明天这边 反馈下厂家开发”。
所以商家基本上没有支持(没错,就是在吐槽)。

2、同类搜索:在mui 及其他网站搜索 “H5 蓝牙打印机”。找到了六七个项目下载后挨个测试使用。
发现有些是H5WEB项目,有些是vue项目。符合要求的有三个。
==CSDN 搜包小弟快递 (优点:在同一个页面进行蓝牙配对、)
==雨滴科技蓝牙打印Demo https://ask.dcloud.net.cn/article/38125 buleprintDemo.zip(优点:能够自动连接已保存蓝牙)
==CSDN 搜 html5-bluetooth-HBuilderX(优点:丰富的操作)

经测试后发现有些问题无法解决。
如:Uncaught java.lang.SecurityException: Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@8a1bcf4c: Starting discovery.;at android.bluetooth.BluetoothAdapter.startDiscovery at printer.html:1

很多文章下也有这个提问,但是暂无回答 。后来解决后 我也尽量去相关问题下回答了。

相关知识:

打印机的相关文档 和 网上搜索的demo中,每个写法都不太一样。如:不同的打印指令,不同的链接蓝牙的方法。
看的多了更乱了。发现自己关于打印机开发的相关知识还不够。

这里梳理了一些相关知识,给跟我一样刚接触的人一些提示。

1、蓝牙打印机

不同品牌的蓝牙打印机样子都差不多,相关指令也一样,可能有一些字体和字号设置不同。其他都一样。
拿到新的打印机后,先打印一个自检页,相面有相关数据 。如:mac地址,支持的指令集,支持的字体等。

2、蓝牙的连接方式

蓝牙的连接方式有两种:传统连接、ble连接(低功耗链接)。
两种链接用的方法不一样,从搜索,连接,传送指令,断开连接 等都不一样。
简单来说,方法名中 带有ble 字样的就是ble连接。

用ble连接的场景:微信小程序只支持ble蓝牙连接。 所以如果你用vue后期要编译成小程序的 ,需要用ble。

打印机支持的链接模式:打印机会有说明 ,是否支持双模连接。支持双模连接的 会有两个mac地址,ble蓝牙名称比传统蓝牙名称多一个L。

===我用的是传统连接。

3、打印机指令

我这个打印机自检页中 说支持的指令集有很多种: TSPL,ZPL,EPL,CPCL。

网上搜索的指令集一般用两种:CPCL命令集‌、ESC命令集‌

一开始我用ESC命令集‌,传送命令没有报错,但是打印机没反应。后来发现,这个打印机不支持。

最后用的CPCL命令集‌,也建议大家用这个指令集。因为:
1、CPCL指令集 更通用。
2、ESC指令集用的十六进制的指令,阅读困难。CPCL指令集阅读友好。

===我用的是CPCL命令集‌。

4、在什么介质上打印

热敏打印机可以在两种介质上打印。
1、热敏小票纸。就是你去超市结账后给的小票
2、热敏标签。就是你在超市买菜时,电子秤上打印的标签。

这两种打印在指令上略有不同,如果只打印一种,只看相关指令就行。

===我用的是热敏标签打印。

根据我的需求和各个例子的功能,最后选择了在 雨滴科技蓝牙打印demo 上进行修改应用。

改进的地方

1、已经在manifast中添加了相关权限,但还是在搜索蓝牙时报错Need android.permission.

var permissions = [  
                        "android.permission.BLUETOOTH",  
                        "android.permission.BLUETOOTH_ADMIN",  
                        "android.permission.BLUETOOTH_SCAN",  
                        "android.permission.BLUETOOTH_CONNECT"  
                    ];  
                    plus.android.requestPermissions(  
                        permissions,  
                        function(result) {  
                            // 权限申请成功后的回调  
                            console.log("权限申请成功");  
                        },  
                        function(error) {  
                            console.error("权限申请失败:"   error.message);  
                        }  
                    );  

2、搜索蓝牙有大量重复,需要去重。

// 防止重复出现  
                    if (JSON.stringify(BleDeviceObjAry).indexOf(JSON.stringify(BleDevice)) != -1) {  
                        console.log("重复");  
                    } else {  
                        console.log("增加");  
                        BleDeviceObjAry.push(BleDevice);  
                        self.SetpairedListHtml(unpairedList, bleName, bleId);  
                    }  

3、页面没有蓝牙连接标志。

<script type="text/javascript" src="js/jquery.min.js"></script>  
$('#J_printer_status').val('打印机已就绪');  
$('#J_printer_status').css('color', 'green');  

4、第一次链接失败后,在连接还是失败。Uncaught java.io.IOException: read failed, socket might closed or timeout, read ret: -1;at android.bluetooth.BluetoothSocket.connect at index.html:1

if(!bluetoothSocket.isConnected()) {  
            try{  
                bluetoothSocket.connect();  

            }catch(e){  
                // $('#J_printer_status').val(data.value);  
                bluetoothSocket.close();  
                $('#J_printer_status').val('打印机未连接')  
                $('#J_printer_status').css('color', 'red');  
                localStorage.setItem("printer_status", "N");  
                console.log(e)  
                return;  
            }  

        }  

5、增加按钮 ,能够手动进入打印机配对界面

6、修改label_set_page 方法,能够传入打印数量

欢迎指正并一起讨论学习

收起阅读 »

vue3仿deepseek/chatgpt流式ai聊天会话

vite vue3

基于vue3+deepseek实战纯撸一款仿DeepSeek/ChatGPT流式聊天AI对话聊天。
集成Vue3 对接 DeepSeek Web API 流式打字输出对话大模型。支持代码高亮、本地缓存,支持移动端+PC端完美显示。

img

img

img

img

img

特性

  • 流式响应:逐字显示 AI 回复,提供更好的用户体验
    优雅的 UI 设计:
    • 气泡式对话界面
    • 打字机效果
    • 平滑的动画过渡
    • 响应式布局

img

img

img

想要了解更多的技术实现细节可以去参考下面这篇分享文章。
Vue3-DeepSeek流式AI聊天|vite6+vant4+deepseek智能ai对话助手

继续阅读 »

基于vue3+deepseek实战纯撸一款仿DeepSeek/ChatGPT流式聊天AI对话聊天。
集成Vue3 对接 DeepSeek Web API 流式打字输出对话大模型。支持代码高亮、本地缓存,支持移动端+PC端完美显示。

img

img

img

img

img

特性

  • 流式响应:逐字显示 AI 回复,提供更好的用户体验
    优雅的 UI 设计:
    • 气泡式对话界面
    • 打字机效果
    • 平滑的动画过渡
    • 响应式布局

img

img

img

想要了解更多的技术实现细节可以去参考下面这篇分享文章。
Vue3-DeepSeek流式AI聊天|vite6+vant4+deepseek智能ai对话助手

收起阅读 »

15天纯手撸flutter3.27预约酒店app应用

flutter

前段时间有分享一款Uniapp+vue3跨端仿携程app酒店预订。这次带来全新研发的Flutter3.27跨平台旅行预约酒店app应用。

uni-app+vue3酒店预订app模板|uniapp+pinia2+uv-ui仿携程

效果如下

img

使用技术

  • 编辑器:Vscode
  • 技术框架:Flutter3.27.1+Dart3.6.0
  • 路由/状态管理:get: ^4.6.6
  • 本地缓存:get_storage: ^2.1.1
  • 图片轮播组件:card_swiper^3.0.1
  • 日期选择插件:syncfusion_flutter_datepicker^28.2.5
  • 弹层提示:shirne_dialog^4.8.3
  • 瀑布流组件:flutter_staggered_grid_view^0.7.0
  • 滚动定位组件:scrollable_positioned_list^0.3.8

img

img

预订模块包括热门城市列表/位置品牌选择、入住离店日期区间选择、价格/星级选择等功能。

img

img

项目结构图

img

flutter3-trip酒店预订app项目已经发布到我的原创作品小铺。

原创Flutter3.27仿携程app酒店预约系统

再分享三篇之前研发的flutter3跨平台实战项目。

flutter3-dymall:仿抖音直播+短视频商城|Flutter3.27实战抖音app

flutter3-winchat:桌面端聊天实例|Flutter3.x+Dart3+Getx仿微信Exe程序

flutter3-chat:聊天室|Flutter3跨平台仿微信App语音聊天/朋友圈

img

img

项目入口文件

import 'dart:io';  
import 'package:flutter/material.dart';  
import 'package:get/get.dart';  
import 'package:get_storage/get_storage.dart';  
import 'package:intl/date_symbol_data_local.dart';  
import 'package:shirne_dialog/shirne_dialog.dart';  

import 'utils/common.dart';  

// 引入布局页面  
import 'layouts/index.dart';  

// 引入路由配置  
import 'router/index.dart';  

void main() async {  
  // 初始化get_storage存储  
  await GetStorage.init();  
  // 初始化国际化语言  
  initializeDateFormatting('zh_CN');  

  runApp(const App());  
}  

class App extends StatelessWidget {  
  const App({super.key});  

  @override  
  Widget build(BuildContext context) {  
    return GetMaterialApp(  
      title: 'Flutter3 Trip',  
      debugShowCheckedModeBanner: false,  
      theme: ThemeData(  
        colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF006ff6)),  
        useMaterial3: true,  
        // 修正windows下字体不一致情况  
        fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null  
      ),  
      home: const Layout(),  
      // 初始化路由  
      initialRoute: Common.isLogin() ? '/' : '/login',  
      // 路由页面  
      getPages: routePages,  
      // 初始化弹窗key  
      navigatorKey: MyDialog.navigatorKey,  
    );  
  }  
}

img

flutter3自定义一个滚动指示器

如下图:实现一个滚动指示槽组件。
img

采用 SingleChildScrollViewStack 组件实现功能。

late ScrollController indicatorController = ScrollController();  
// 滚动位置  
double indicatorOffset = 0;  

@override  
void initState() {  
  super.initState();  
  indicatorController.addListener(() {  
    setState(() {  
      indicatorOffset = indicatorController.position.pixels / indicatorController.position.maxScrollExtent;  
    });  
  });  
  ...  
}  

Column(  
  children: [  
    Expanded(  
      child: SingleChildScrollView(  
        controller: indicatorController,  
        scrollDirection: Axis.horizontal,  
        child: Row(  
          ...  
        ),  
      ),  
    ),  
    // 指示槽  
    Stack(  
      children: [  
        Container(  
          decoration: BoxDecoration(  
            color: Color(0xFFE1EFFF),  
            borderRadius: BorderRadius.circular(50.0),  
          ),  
          height: 4.0,  
          width: 50.0,  
        ),  
        Container(  
          margin: EdgeInsets.only(left: indicatorOffset * 30.0),  
          decoration: BoxDecoration(  
            color: Color(0xFF006ff6),  
            borderRadius: BorderRadius.circular(50.0),  
          ),  
          width: 20.0,  
          height: 4.0,  
        )  
      ]  
    )  
  ]  
)

img

img

img

img

img

flutter3酒店预订模板

img

img

酒店日期区间选择,支持设置开始和结束日期,可滑动选择多个日期区间。

img

// 入住日期  
DateTime startDate = DateTime.now();  
// 离店日期  
DateTime endDate = DateTime.now().add(Duration(days: 1));  

GestureDetector(  
  child: Container(  
    padding: EdgeInsets.all(10.0),  
    decoration: BoxDecoration(  
      border: Border(bottom: BorderSide(color: Color(0xfff5f5f5))),  
    ),  
    child: Row(  
      spacing: 10.0,  
      children: [  
        Icon(Icons.calendar_month_outlined),  
        Expanded(  
          child: Row(  
            mainAxisAlignment: MainAxisAlignment.spaceBetween,  
            children: [  
              Column(  
                crossAxisAlignment: CrossAxisAlignment.start,  
                spacing: 3.0,  
                children: [  
                  Text('入住', style: TextStyle(color: Colors.grey, fontSize: 12.0)),  
                  Text('${DateFormat('MM-dd').format(startDate)} ${DateFormat('E', 'zh_CN').format(startDate)}'),  
                ],  
              ),  
              Container(  
                color: Colors.grey[50],  
                padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 1.0),  
                // DateTime 类提供了 difference 方法,可以计算两个日期之间的时间差,返回一个 Duration 对象。通过 Duration 的 inDays 属性,可以获取天数差。  
                child: Text('共${endDate.difference(startDate).inDays}晚'),  
              ),  
              Column(  
                crossAxisAlignment: CrossAxisAlignment.end,  
                spacing: 3.0,  
                children: [  
                  Text('离店', style: TextStyle(color: Colors.grey, fontSize: 12.0)),  
                  Text('${DateFormat('MM-dd').format(endDate)} ${DateFormat('E', 'zh_CN').format(endDate)}'),  
                ],  
              ),  
            ],  
          ),  
        ),  
        Icon(Icons.arrow_forward_ios_rounded, color: Colors.grey, size: 12.0,)  
      ],  
    ),  
  ),  
  onTap: () {  
    handleCalendar();  
  }  
)

Ok,综上就是flutter3实现酒店预订模板的一些知识分享,希望对大家有些帮助哈~

往期热文

vite6+tauri2.0客户端仿MacOS桌面|tauri2+rust+vue3桌面os
基于uniapp+vite5+pinia2跨端预订酒店app系统
tauri2.0桌面端后台Exe系统|tauri2+rust+vite5管理系统后台模板
基于vite5+electron31+elementPlus仿微信聊天Exe
基于uniapp+vue3+uv-ui聊天实例|uni-app+vite4仿微信app应用

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000046132699
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

前段时间有分享一款Uniapp+vue3跨端仿携程app酒店预订。这次带来全新研发的Flutter3.27跨平台旅行预约酒店app应用。

uni-app+vue3酒店预订app模板|uniapp+pinia2+uv-ui仿携程

效果如下

img

使用技术

  • 编辑器:Vscode
  • 技术框架:Flutter3.27.1+Dart3.6.0
  • 路由/状态管理:get: ^4.6.6
  • 本地缓存:get_storage: ^2.1.1
  • 图片轮播组件:card_swiper^3.0.1
  • 日期选择插件:syncfusion_flutter_datepicker^28.2.5
  • 弹层提示:shirne_dialog^4.8.3
  • 瀑布流组件:flutter_staggered_grid_view^0.7.0
  • 滚动定位组件:scrollable_positioned_list^0.3.8

img

img

预订模块包括热门城市列表/位置品牌选择、入住离店日期区间选择、价格/星级选择等功能。

img

img

项目结构图

img

flutter3-trip酒店预订app项目已经发布到我的原创作品小铺。

原创Flutter3.27仿携程app酒店预约系统

再分享三篇之前研发的flutter3跨平台实战项目。

flutter3-dymall:仿抖音直播+短视频商城|Flutter3.27实战抖音app

flutter3-winchat:桌面端聊天实例|Flutter3.x+Dart3+Getx仿微信Exe程序

flutter3-chat:聊天室|Flutter3跨平台仿微信App语音聊天/朋友圈

img

img

项目入口文件

import 'dart:io';  
import 'package:flutter/material.dart';  
import 'package:get/get.dart';  
import 'package:get_storage/get_storage.dart';  
import 'package:intl/date_symbol_data_local.dart';  
import 'package:shirne_dialog/shirne_dialog.dart';  

import 'utils/common.dart';  

// 引入布局页面  
import 'layouts/index.dart';  

// 引入路由配置  
import 'router/index.dart';  

void main() async {  
  // 初始化get_storage存储  
  await GetStorage.init();  
  // 初始化国际化语言  
  initializeDateFormatting('zh_CN');  

  runApp(const App());  
}  

class App extends StatelessWidget {  
  const App({super.key});  

  @override  
  Widget build(BuildContext context) {  
    return GetMaterialApp(  
      title: 'Flutter3 Trip',  
      debugShowCheckedModeBanner: false,  
      theme: ThemeData(  
        colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF006ff6)),  
        useMaterial3: true,  
        // 修正windows下字体不一致情况  
        fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null  
      ),  
      home: const Layout(),  
      // 初始化路由  
      initialRoute: Common.isLogin() ? '/' : '/login',  
      // 路由页面  
      getPages: routePages,  
      // 初始化弹窗key  
      navigatorKey: MyDialog.navigatorKey,  
    );  
  }  
}

img

flutter3自定义一个滚动指示器

如下图:实现一个滚动指示槽组件。
img

采用 SingleChildScrollViewStack 组件实现功能。

late ScrollController indicatorController = ScrollController();  
// 滚动位置  
double indicatorOffset = 0;  

@override  
void initState() {  
  super.initState();  
  indicatorController.addListener(() {  
    setState(() {  
      indicatorOffset = indicatorController.position.pixels / indicatorController.position.maxScrollExtent;  
    });  
  });  
  ...  
}  

Column(  
  children: [  
    Expanded(  
      child: SingleChildScrollView(  
        controller: indicatorController,  
        scrollDirection: Axis.horizontal,  
        child: Row(  
          ...  
        ),  
      ),  
    ),  
    // 指示槽  
    Stack(  
      children: [  
        Container(  
          decoration: BoxDecoration(  
            color: Color(0xFFE1EFFF),  
            borderRadius: BorderRadius.circular(50.0),  
          ),  
          height: 4.0,  
          width: 50.0,  
        ),  
        Container(  
          margin: EdgeInsets.only(left: indicatorOffset * 30.0),  
          decoration: BoxDecoration(  
            color: Color(0xFF006ff6),  
            borderRadius: BorderRadius.circular(50.0),  
          ),  
          width: 20.0,  
          height: 4.0,  
        )  
      ]  
    )  
  ]  
)

img

img

img

img

img

flutter3酒店预订模板

img

img

酒店日期区间选择,支持设置开始和结束日期,可滑动选择多个日期区间。

img

// 入住日期  
DateTime startDate = DateTime.now();  
// 离店日期  
DateTime endDate = DateTime.now().add(Duration(days: 1));  

GestureDetector(  
  child: Container(  
    padding: EdgeInsets.all(10.0),  
    decoration: BoxDecoration(  
      border: Border(bottom: BorderSide(color: Color(0xfff5f5f5))),  
    ),  
    child: Row(  
      spacing: 10.0,  
      children: [  
        Icon(Icons.calendar_month_outlined),  
        Expanded(  
          child: Row(  
            mainAxisAlignment: MainAxisAlignment.spaceBetween,  
            children: [  
              Column(  
                crossAxisAlignment: CrossAxisAlignment.start,  
                spacing: 3.0,  
                children: [  
                  Text('入住', style: TextStyle(color: Colors.grey, fontSize: 12.0)),  
                  Text('${DateFormat('MM-dd').format(startDate)} ${DateFormat('E', 'zh_CN').format(startDate)}'),  
                ],  
              ),  
              Container(  
                color: Colors.grey[50],  
                padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 1.0),  
                // DateTime 类提供了 difference 方法,可以计算两个日期之间的时间差,返回一个 Duration 对象。通过 Duration 的 inDays 属性,可以获取天数差。  
                child: Text('共${endDate.difference(startDate).inDays}晚'),  
              ),  
              Column(  
                crossAxisAlignment: CrossAxisAlignment.end,  
                spacing: 3.0,  
                children: [  
                  Text('离店', style: TextStyle(color: Colors.grey, fontSize: 12.0)),  
                  Text('${DateFormat('MM-dd').format(endDate)} ${DateFormat('E', 'zh_CN').format(endDate)}'),  
                ],  
              ),  
            ],  
          ),  
        ),  
        Icon(Icons.arrow_forward_ios_rounded, color: Colors.grey, size: 12.0,)  
      ],  
    ),  
  ),  
  onTap: () {  
    handleCalendar();  
  }  
)

Ok,综上就是flutter3实现酒店预订模板的一些知识分享,希望对大家有些帮助哈~

往期热文

vite6+tauri2.0客户端仿MacOS桌面|tauri2+rust+vue3桌面os
基于uniapp+vite5+pinia2跨端预订酒店app系统
tauri2.0桌面端后台Exe系统|tauri2+rust+vite5管理系统后台模板
基于vite5+electron31+elementPlus仿微信聊天Exe
基于uniapp+vue3+uv-ui聊天实例|uni-app+vite4仿微信app应用

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000046132699
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »

Flutter3.27实战2025抖音app直播商城

flutter

原创自研flutter3.27+dart3.6实战抖音短视频+聊天+直播电商带货app商城应用程序。

Flutter3.27仿抖音短视频+直播+聊天app商城系统

img

img

技术栈

  • 编辑器:vscode
  • 技术框架:flutter3.27.1+Dart3.6.0
  • 路由/状态管理:get: ^4.6.6
  • 本地缓存服务:get_storage: ^2.1.1
  • 瀑布流组件:flutter_staggered_grid_view^0.7.0
  • 轮播图组件:card_swiper^3.0.1
  • toast弹窗组件:shirne_dialog^4.8.3
  • 视频套件:media_kit: ^1.1.11

img

img

实现类似抖音app首页左右滑动切换页面内容,上下滑动切换短视频效果。

img

项目框架

img

目前flutter3-douyin-mall短视频直播商城项目已经同步到我的原创作品集。
Flutter3.27仿抖音短视频+直播+聊天app商城系统

img

img

img

flutter3实现首页轮播图+tab滚动吸附

img

return Scaffold(  
  backgroundColor: Colors.grey[50],  
  body: ScrollConfiguration(  
    behavior: CustomScrollBehavior().copyWith(scrollbars: false),  
    child: CustomScrollView(  
      scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),  
      controller: scrollController,  
      slivers: [  
        SliverAppBar(  
          backgroundColor: Colors.transparent,  
          foregroundColor: Colors.white,  
          pinned: true,  
          expandedHeight: 200.0,  
          titleSpacing: 10.0,  
          // 搜索框(高斯模糊背景)  
          title: ClipRRect(  
            borderRadius: BorderRadius.circular(30.0),  
            child: BackdropFilter(  
              filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),  
              child: Container(  
                ...  
              ),  
            ),  
          ),  
          actions: [  
            IconButton(icon: Icon(Icons.shopping_cart_outlined), onPressed: () {},),  
          ],  
          // 自定义伸缩区域(轮播图)  
          flexibleSpace: Container(  
            decoration: BoxDecoration(  
              gradient: LinearGradient(  
                begin: Alignment.topLeft,  
                end: Alignment.bottomRight,  
                colors: [  
                  Color(0xFFFF5000), Color(0xFFfcaec4)  
                ]  
              )  
            ),  
            child: FlexibleSpaceBar(  
              background: Swiper.children(  
                pagination: SwiperPagination(  
                  builder: DotSwiperPaginationBuilder(  
                    color: Colors.white70,  
                    activeColor: Colors.white,  
                  )  
                ),  
                indicatorLayout: PageIndicatorLayout.SCALE,  
                children: [  
                  Image.network('https://m.360buyimg.com/babel/jfs/t20271217/224114/35/38178/150060/6760d559Fd654f946/968c156726b6e822.png',),  
                  Image.network('https://m.360buyimg.com/babel/jfs/t20280117/88832/5/48468/139826/6789cbcfF4e0b2a3d/9dc54355b6f65c40.jpg',),  
                  Image.network('https://m.360buyimg.com/babel/jfs/t20280108/255505/29/10540/137372/677ddbc1F6cdbbed0/bc477fadedef22a8.jpg',),  
                ],  
              ),  
            ),  
          ),  
        ),  

        ...  

        // tabbar列表  
        SliverPersistentHeader(  
          pinned: true,  
          delegate: CustomStickyHeader(  
            child: PreferredSize(  
              preferredSize: Size.fromHeight(45.0),  
              child: Container(  
                ...  
              ),  
            ),  
          ),  
        ),  

        // 瀑布流列表  
        ...  
      ],  
    ),  
  ),  
  // 返回顶部  
  floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset),  
);

img

img

img

img

flutter3实现短视频功能

img

img

@override  
Widget build(BuildContext context) {  
  return Scaffold(  
    key: scaffoldKey,  
    extendBodyBehindAppBar: true,  
    appBar: AppBar(  
      forceMaterialTransparency: true,  
      backgroundColor: [0, 1, 4, 5].contains(videoModuleController.videoTabIndex.value) ? null : Colors.transparent,  
      foregroundColor: [0, 1, 4, 5].contains(videoModuleController.videoTabIndex.value) ? Colors.black : Colors.white,  
      titleSpacing: 1.0,  
      leading: Obx(() => IconButton(  
        icon: Badge.count(  
          backgroundColor: Colors.red,  
          count: 6,  
          child: Icon(Icons.sort_rounded, color: tabColor(),),  
        ),  
        onPressed: () {  
          // 自定义打开右侧drawer  
          scaffoldKey.currentState?.openDrawer();  
        },  
      )),  
      title: Obx(() {  
        return ScrollConfiguration(  
          behavior: CustomScrollBehavior().copyWith(scrollbars: false),  
          child: TabBar(  
            ...  
          ),  
        );  
      }),  
      actions: [  
        Obx(() => IconButton(icon: Icon(Icons.search_rounded, color: tabColor(),), onPressed: () {},),),  
      ],  
    ),  
    body: ScrollConfiguration(  
      behavior: CustomScrollBehavior().copyWith(scrollbars: false),  
      child: PageView(  
        controller: pageController,  
        onPageChanged: (index) {  
          videoModuleController.updateVideoTabIndex(index);  
          setState(() {  
            tabController.animateTo(index, duration: Duration(milliseconds: 200), curve: Curves.easeInOut);  
          });  
        },  
        children: [  
          ...tabModules  
        ],  
      ),  
    ),  
    // 侧边栏  
    drawer: Drawer(  
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(right: Radius.circular(15.0))),  
      clipBehavior: Clip.antiAlias,  
      width: 300,  
      child: Container(  
        ...  
      ),  
    ),  
  );  
}

img

img

img

img

img

img

img

img

flutter3实现直播功能

img

img

// flutter3直播模块  Q:282310962  

@override  
Widget build(BuildContext context) {  
  return Scaffold(  
    backgroundColor: Colors.black,  
    extendBodyBehindAppBar: true,  
    appBar: AppBar(  
      forceMaterialTransparency: true,  
      backgroundColor: Colors.black,  
      foregroundColor: Colors.white,  
      toolbarHeight: 0,  
    ),  
    body: Column(  
      children: [  
        Expanded(  
          child: Stack(  
            children: [  
              PageView.builder(  
                scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),  
                scrollDirection: Axis.vertical,  
                controller: pageVerticalController,  
                onPageChanged: (index) async {  
                  setState(() {  
                    liveIndex = index;  
                  });  
                  player.stop();  
                  await player.open(Media(liveJson[index]['src']));  
                },  
                itemCount: liveJson.length,  
                itemBuilder: (context, index) {  
                  return Stack(  
                    children: [  
                      // 视频区域  
                      Positioned(  
                        ...  
                      ),  

                      /// 水平滚动模块(清屏/浮层)  
                      PageView(  
                        scrollDirection: Axis.horizontal,  
                        controller: pageHorizontalController,  
                        onPageChanged: (index) {  
                          // ...  
                        },  
                        children: [  
                          // 直播清屏  
                          Container(  
                            ...  
                          ),  
                          // 直播浮层  
                          Stack(  
                            children: [  
                              // 顶部区域  
                              Positioned(  
                                top: MediaQuery.of(context).padding.top + 7,  
                                left: 10.0,  
                                right: 0,  
                                child: Column(  
                                  crossAxisAlignment: CrossAxisAlignment.start,  
                                  children: [  
                                    // 直播间头像  
                                    Container(  
                                      ...  
                                    ),  
                                    // 排名统计  
                                    Container(  
                                      ...  
                                    ),  
                                    // 红包活动  
                                    Container(  
                                      ...  
                                    ),  
                                  ],  
                                ),  
                              ),  
                              // 底部区域  
                              Positioned(  
                                child: Column(  
                                  crossAxisAlignment: CrossAxisAlignment.start,  
                                  children: [  
                                    // 商品购买动效  
                                    Container(  
                                      ...  
                                    ),  

                                    // 送礼物动效  
                                    AnimationLiveGift(  
                                      giftQueryList: [  
                                        {'label': '小心心', 'gift': 'assets/images/gift/gift1.png', 'user': 'Jack', 'avatar': 'assets/images/avatar/img02.jpg', 'num': 12},  
                                        {'label': '棒棒糖', 'gift': 'assets/images/gift/gift2.png', 'user': 'Andy', 'avatar': 'assets/images/avatar/img06.jpg', 'num': 36},  
                                        {'label': '大啤酒', 'gift': 'assets/images/gift/gift3.png', 'user': '一条咸鱼', 'avatar': 'assets/images/avatar/img01.jpg', 'num': 162},  
                                        ...  
                                      ],  
                                    ),  

                                    // 加入直播间动效  
                                    AnimationLiveJoin(  
                                      joinQueryList: [  
                                        {'avatar': 'assets/images/logo.png', 'name': 'andy'},  
                                        {'avatar': 'assets/images/logo.png', 'name': 'jack'},  
                                        ...  
                                      ],  
                                    ),  

                                    // 直播弹幕+商品讲解  
                                    Container(  
                                      margin: EdgeInsets.only(top: 7.0),  
                                      height: 200.0,  
                                      child: Row(  
                                        ...  
                                      ),  
                                    ),  

                                    // 底部工具栏  
                                    Container(  
                                      margin: const EdgeInsets.only(top: 7.0),  
                                      child: Row(  
                                        ...  
                                      ),  
                                    ),  
                                  ],  
                                ),  
                              ),  
                            ],  
                          ),  
                        ],  
                      ),  
                    ],  
                  );  
                },  
              ),  
            ],  
          ),  
        ),  
      ],  
    ),  
  );  
}

以上就是flutter3.27.1实战抖音app商城的一些知识分享,整个项目涉及到知识点蛮多。希望以上分享对小伙伴们有些帮助哈~~

往期热文

https://segmentfault.com/a/1190000045042968
https://segmentfault.com/a/1190000045245775
https://segmentfault.com/a/1190000045381943
https://segmentfault.com/a/1190000045556523
https://segmentfault.com/a/1190000045667190

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000046075489
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

原创自研flutter3.27+dart3.6实战抖音短视频+聊天+直播电商带货app商城应用程序。

Flutter3.27仿抖音短视频+直播+聊天app商城系统

img

img

技术栈

  • 编辑器:vscode
  • 技术框架:flutter3.27.1+Dart3.6.0
  • 路由/状态管理:get: ^4.6.6
  • 本地缓存服务:get_storage: ^2.1.1
  • 瀑布流组件:flutter_staggered_grid_view^0.7.0
  • 轮播图组件:card_swiper^3.0.1
  • toast弹窗组件:shirne_dialog^4.8.3
  • 视频套件:media_kit: ^1.1.11

img

img

实现类似抖音app首页左右滑动切换页面内容,上下滑动切换短视频效果。

img

项目框架

img

目前flutter3-douyin-mall短视频直播商城项目已经同步到我的原创作品集。
Flutter3.27仿抖音短视频+直播+聊天app商城系统

img

img

img

flutter3实现首页轮播图+tab滚动吸附

img

return Scaffold(  
  backgroundColor: Colors.grey[50],  
  body: ScrollConfiguration(  
    behavior: CustomScrollBehavior().copyWith(scrollbars: false),  
    child: CustomScrollView(  
      scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),  
      controller: scrollController,  
      slivers: [  
        SliverAppBar(  
          backgroundColor: Colors.transparent,  
          foregroundColor: Colors.white,  
          pinned: true,  
          expandedHeight: 200.0,  
          titleSpacing: 10.0,  
          // 搜索框(高斯模糊背景)  
          title: ClipRRect(  
            borderRadius: BorderRadius.circular(30.0),  
            child: BackdropFilter(  
              filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),  
              child: Container(  
                ...  
              ),  
            ),  
          ),  
          actions: [  
            IconButton(icon: Icon(Icons.shopping_cart_outlined), onPressed: () {},),  
          ],  
          // 自定义伸缩区域(轮播图)  
          flexibleSpace: Container(  
            decoration: BoxDecoration(  
              gradient: LinearGradient(  
                begin: Alignment.topLeft,  
                end: Alignment.bottomRight,  
                colors: [  
                  Color(0xFFFF5000), Color(0xFFfcaec4)  
                ]  
              )  
            ),  
            child: FlexibleSpaceBar(  
              background: Swiper.children(  
                pagination: SwiperPagination(  
                  builder: DotSwiperPaginationBuilder(  
                    color: Colors.white70,  
                    activeColor: Colors.white,  
                  )  
                ),  
                indicatorLayout: PageIndicatorLayout.SCALE,  
                children: [  
                  Image.network('https://m.360buyimg.com/babel/jfs/t20271217/224114/35/38178/150060/6760d559Fd654f946/968c156726b6e822.png',),  
                  Image.network('https://m.360buyimg.com/babel/jfs/t20280117/88832/5/48468/139826/6789cbcfF4e0b2a3d/9dc54355b6f65c40.jpg',),  
                  Image.network('https://m.360buyimg.com/babel/jfs/t20280108/255505/29/10540/137372/677ddbc1F6cdbbed0/bc477fadedef22a8.jpg',),  
                ],  
              ),  
            ),  
          ),  
        ),  

        ...  

        // tabbar列表  
        SliverPersistentHeader(  
          pinned: true,  
          delegate: CustomStickyHeader(  
            child: PreferredSize(  
              preferredSize: Size.fromHeight(45.0),  
              child: Container(  
                ...  
              ),  
            ),  
          ),  
        ),  

        // 瀑布流列表  
        ...  
      ],  
    ),  
  ),  
  // 返回顶部  
  floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset),  
);

img

img

img

img

flutter3实现短视频功能

img

img

@override  
Widget build(BuildContext context) {  
  return Scaffold(  
    key: scaffoldKey,  
    extendBodyBehindAppBar: true,  
    appBar: AppBar(  
      forceMaterialTransparency: true,  
      backgroundColor: [0, 1, 4, 5].contains(videoModuleController.videoTabIndex.value) ? null : Colors.transparent,  
      foregroundColor: [0, 1, 4, 5].contains(videoModuleController.videoTabIndex.value) ? Colors.black : Colors.white,  
      titleSpacing: 1.0,  
      leading: Obx(() => IconButton(  
        icon: Badge.count(  
          backgroundColor: Colors.red,  
          count: 6,  
          child: Icon(Icons.sort_rounded, color: tabColor(),),  
        ),  
        onPressed: () {  
          // 自定义打开右侧drawer  
          scaffoldKey.currentState?.openDrawer();  
        },  
      )),  
      title: Obx(() {  
        return ScrollConfiguration(  
          behavior: CustomScrollBehavior().copyWith(scrollbars: false),  
          child: TabBar(  
            ...  
          ),  
        );  
      }),  
      actions: [  
        Obx(() => IconButton(icon: Icon(Icons.search_rounded, color: tabColor(),), onPressed: () {},),),  
      ],  
    ),  
    body: ScrollConfiguration(  
      behavior: CustomScrollBehavior().copyWith(scrollbars: false),  
      child: PageView(  
        controller: pageController,  
        onPageChanged: (index) {  
          videoModuleController.updateVideoTabIndex(index);  
          setState(() {  
            tabController.animateTo(index, duration: Duration(milliseconds: 200), curve: Curves.easeInOut);  
          });  
        },  
        children: [  
          ...tabModules  
        ],  
      ),  
    ),  
    // 侧边栏  
    drawer: Drawer(  
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(right: Radius.circular(15.0))),  
      clipBehavior: Clip.antiAlias,  
      width: 300,  
      child: Container(  
        ...  
      ),  
    ),  
  );  
}

img

img

img

img

img

img

img

img

flutter3实现直播功能

img

img

// flutter3直播模块  Q:282310962  

@override  
Widget build(BuildContext context) {  
  return Scaffold(  
    backgroundColor: Colors.black,  
    extendBodyBehindAppBar: true,  
    appBar: AppBar(  
      forceMaterialTransparency: true,  
      backgroundColor: Colors.black,  
      foregroundColor: Colors.white,  
      toolbarHeight: 0,  
    ),  
    body: Column(  
      children: [  
        Expanded(  
          child: Stack(  
            children: [  
              PageView.builder(  
                scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),  
                scrollDirection: Axis.vertical,  
                controller: pageVerticalController,  
                onPageChanged: (index) async {  
                  setState(() {  
                    liveIndex = index;  
                  });  
                  player.stop();  
                  await player.open(Media(liveJson[index]['src']));  
                },  
                itemCount: liveJson.length,  
                itemBuilder: (context, index) {  
                  return Stack(  
                    children: [  
                      // 视频区域  
                      Positioned(  
                        ...  
                      ),  

                      /// 水平滚动模块(清屏/浮层)  
                      PageView(  
                        scrollDirection: Axis.horizontal,  
                        controller: pageHorizontalController,  
                        onPageChanged: (index) {  
                          // ...  
                        },  
                        children: [  
                          // 直播清屏  
                          Container(  
                            ...  
                          ),  
                          // 直播浮层  
                          Stack(  
                            children: [  
                              // 顶部区域  
                              Positioned(  
                                top: MediaQuery.of(context).padding.top + 7,  
                                left: 10.0,  
                                right: 0,  
                                child: Column(  
                                  crossAxisAlignment: CrossAxisAlignment.start,  
                                  children: [  
                                    // 直播间头像  
                                    Container(  
                                      ...  
                                    ),  
                                    // 排名统计  
                                    Container(  
                                      ...  
                                    ),  
                                    // 红包活动  
                                    Container(  
                                      ...  
                                    ),  
                                  ],  
                                ),  
                              ),  
                              // 底部区域  
                              Positioned(  
                                child: Column(  
                                  crossAxisAlignment: CrossAxisAlignment.start,  
                                  children: [  
                                    // 商品购买动效  
                                    Container(  
                                      ...  
                                    ),  

                                    // 送礼物动效  
                                    AnimationLiveGift(  
                                      giftQueryList: [  
                                        {'label': '小心心', 'gift': 'assets/images/gift/gift1.png', 'user': 'Jack', 'avatar': 'assets/images/avatar/img02.jpg', 'num': 12},  
                                        {'label': '棒棒糖', 'gift': 'assets/images/gift/gift2.png', 'user': 'Andy', 'avatar': 'assets/images/avatar/img06.jpg', 'num': 36},  
                                        {'label': '大啤酒', 'gift': 'assets/images/gift/gift3.png', 'user': '一条咸鱼', 'avatar': 'assets/images/avatar/img01.jpg', 'num': 162},  
                                        ...  
                                      ],  
                                    ),  

                                    // 加入直播间动效  
                                    AnimationLiveJoin(  
                                      joinQueryList: [  
                                        {'avatar': 'assets/images/logo.png', 'name': 'andy'},  
                                        {'avatar': 'assets/images/logo.png', 'name': 'jack'},  
                                        ...  
                                      ],  
                                    ),  

                                    // 直播弹幕+商品讲解  
                                    Container(  
                                      margin: EdgeInsets.only(top: 7.0),  
                                      height: 200.0,  
                                      child: Row(  
                                        ...  
                                      ),  
                                    ),  

                                    // 底部工具栏  
                                    Container(  
                                      margin: const EdgeInsets.only(top: 7.0),  
                                      child: Row(  
                                        ...  
                                      ),  
                                    ),  
                                  ],  
                                ),  
                              ),  
                            ],  
                          ),  
                        ],  
                      ),  
                    ],  
                  );  
                },  
              ),  
            ],  
          ),  
        ),  
      ],  
    ),  
  );  
}

以上就是flutter3.27.1实战抖音app商城的一些知识分享,整个项目涉及到知识点蛮多。希望以上分享对小伙伴们有些帮助哈~~

往期热文

https://segmentfault.com/a/1190000045042968
https://segmentfault.com/a/1190000045245775
https://segmentfault.com/a/1190000045381943
https://segmentfault.com/a/1190000045556523
https://segmentfault.com/a/1190000045667190

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000046075489
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »

浏览器信息检测小工具

浏览器

https://passer-by.com/browser/

进入上方网页可以检测浏览器信息

https://passer-by.com/browser/

进入上方网页可以检测浏览器信息

vue3.5+tauri2.0+arco桌面版OS系统|vite6.0+tauri2仿macos/windows桌面

vite vue3

经过了三周的爆肝研发,我的又一款原创跨平台重磅新作tauri2.1+vite6+vue3 setup+pinia2+arco.design桌面客户端OS管理系统Tauri2Vue3OS,正式宣告完结了。支持macoswindows两种桌面风格。

Tauri2.0-Vue3-MacOS桌面端os平台|tauri2+vite6.0+arco电脑版OS管理系统

img

img

vue3-tauri2-os系统提供macos和windows11桌面风格、自研拖拽式栅格桌面引擎、封装tauri2多窗口管理、自定义json配置桌面/Dock菜单。

img

img

实现技术

  • 技术框架:vite^6.0.3+vue^3.5.13+vue-router^4.5.0
  • 跨平台框架:tauri^2.1.1
  • 组件库:@arco-design/web-vue^2.56.3 (字节桌面版vue3组件库)
  • 状态管理:pinia^2.3.0
  • 拖拽插件:sortablejs^1.15.6
  • 滑屏组件:swiper^11.1.15
  • 图表组件:echarts^5.5.1
  • markdown编辑器:md-editor-v3^5.1.1
  • 模拟数据:mockjs^1.1.0

img

img

tauri2.0-vue3os已经正式发布到我的原创作品集,感兴趣的可以去看看。

https://gf.bilibili.com/item/detail/1107621011

项目框架目录结构

使用最新版tauri2.0跨平台框架技术,整合vite6构建工具。
img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

tauri2.0-vue3os布局模板

img

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  // 引入布局模板  
  import MacosLayout from './template/macos.vue'  
  import WindowsLayout from './template/windows.vue'  

  const appstate = appState()  

  const DeskLayout = {  
    macos: MacosLayout,  
    windows: WindowsLayout  
  }  
</script>  

<template>  
  <div class="vu__container flexbox" :style="{'--themeSkin': appstate.config.skin}">  
    <component :is="DeskLayout[appstate.config.layout]" />  
  </div>  
</template>

img

img

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  import Titlebar from '@/layouts/components/titlebar/index.vue'  
  import Desk from '@/layouts/components/mac/desk.vue'  
  import Dock from '@/layouts/components/mac/dock.vue'  

  const appstate = appState()  
</script>  

<template>  
  <div class="vu__layout flexbox flex-col">  
    <div class="vu__layout-header">  
      <Titlebar />  
    </div>  
    <div class="vu__layout-body flex1 flexbox">  
      <Desk />  
    </div>  
    <div class="vu__layout-footer">  
      <Dock v-if="appstate.config.dockEnable" />  
    </div>  
  </div>  
</template>

tauri2+vue3栅格布局

img

img

栅格桌面菜单支持如下参数配置:

/**  
 * label 图标标题  
 * imgico 图标(本地或网络图片) 支持Arco Design内置图标或自定义iconfont图标  
 * path 跳转路由页面  
 * link 跳转外部链接  
 * hideLabel 是否隐藏图标标题  
 * filter 是否禁用拖拽  
 * background 自定义图标背景色  
 * color 自定义图标颜色  
 * size 栅格磁贴布局 1x1 ... 12x12  
 * padding 内边距  
 * onClick 点击图标回调函数  
 * isNewin 新窗口打开路由页面  
 * children 二级菜单  
 */

支持children配置二级菜单。
img

img

img

tauri2.0+vue3自定义底部Dock菜单

img

Dock菜单配置参数:

/**  
 * label 图标tooltip提示  
 * imgico 图标(本地或网络图片) 支持Arco Design内置图标或自定义iconfont图标  
 * path 跳转路由页面  
 * link 跳转外部链接  
 * filter 是否禁用拖拽  
 * color 自定义图标颜色  
 * onClick 点击图标回调函数  
 * isNewin 新窗口打开路由页面  
 * children 二级菜单  
 */

另外系统托盘采用tauri2+vue3自定义弹窗实现系统托盘右键功能。
img

OK,综上就是Tauri2.0+Vue3+Arco实战桌面端os管理系统的一些知识分享。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045667190
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

经过了三周的爆肝研发,我的又一款原创跨平台重磅新作tauri2.1+vite6+vue3 setup+pinia2+arco.design桌面客户端OS管理系统Tauri2Vue3OS,正式宣告完结了。支持macoswindows两种桌面风格。

Tauri2.0-Vue3-MacOS桌面端os平台|tauri2+vite6.0+arco电脑版OS管理系统

img

img

vue3-tauri2-os系统提供macos和windows11桌面风格、自研拖拽式栅格桌面引擎、封装tauri2多窗口管理、自定义json配置桌面/Dock菜单。

img

img

实现技术

  • 技术框架:vite^6.0.3+vue^3.5.13+vue-router^4.5.0
  • 跨平台框架:tauri^2.1.1
  • 组件库:@arco-design/web-vue^2.56.3 (字节桌面版vue3组件库)
  • 状态管理:pinia^2.3.0
  • 拖拽插件:sortablejs^1.15.6
  • 滑屏组件:swiper^11.1.15
  • 图表组件:echarts^5.5.1
  • markdown编辑器:md-editor-v3^5.1.1
  • 模拟数据:mockjs^1.1.0

img

img

tauri2.0-vue3os已经正式发布到我的原创作品集,感兴趣的可以去看看。

https://gf.bilibili.com/item/detail/1107621011

项目框架目录结构

使用最新版tauri2.0跨平台框架技术,整合vite6构建工具。
img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

tauri2.0-vue3os布局模板

img

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  // 引入布局模板  
  import MacosLayout from './template/macos.vue'  
  import WindowsLayout from './template/windows.vue'  

  const appstate = appState()  

  const DeskLayout = {  
    macos: MacosLayout,  
    windows: WindowsLayout  
  }  
</script>  

<template>  
  <div class="vu__container flexbox" :style="{'--themeSkin': appstate.config.skin}">  
    <component :is="DeskLayout[appstate.config.layout]" />  
  </div>  
</template>

img

img

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  import Titlebar from '@/layouts/components/titlebar/index.vue'  
  import Desk from '@/layouts/components/mac/desk.vue'  
  import Dock from '@/layouts/components/mac/dock.vue'  

  const appstate = appState()  
</script>  

<template>  
  <div class="vu__layout flexbox flex-col">  
    <div class="vu__layout-header">  
      <Titlebar />  
    </div>  
    <div class="vu__layout-body flex1 flexbox">  
      <Desk />  
    </div>  
    <div class="vu__layout-footer">  
      <Dock v-if="appstate.config.dockEnable" />  
    </div>  
  </div>  
</template>

tauri2+vue3栅格布局

img

img

栅格桌面菜单支持如下参数配置:

/**  
 * label 图标标题  
 * imgico 图标(本地或网络图片) 支持Arco Design内置图标或自定义iconfont图标  
 * path 跳转路由页面  
 * link 跳转外部链接  
 * hideLabel 是否隐藏图标标题  
 * filter 是否禁用拖拽  
 * background 自定义图标背景色  
 * color 自定义图标颜色  
 * size 栅格磁贴布局 1x1 ... 12x12  
 * padding 内边距  
 * onClick 点击图标回调函数  
 * isNewin 新窗口打开路由页面  
 * children 二级菜单  
 */

支持children配置二级菜单。
img

img

img

tauri2.0+vue3自定义底部Dock菜单

img

Dock菜单配置参数:

/**  
 * label 图标tooltip提示  
 * imgico 图标(本地或网络图片) 支持Arco Design内置图标或自定义iconfont图标  
 * path 跳转路由页面  
 * link 跳转外部链接  
 * filter 是否禁用拖拽  
 * color 自定义图标颜色  
 * onClick 点击图标回调函数  
 * isNewin 新窗口打开路由页面  
 * children 二级菜单  
 */

另外系统托盘采用tauri2+vue3自定义弹窗实现系统托盘右键功能。
img

OK,综上就是Tauri2.0+Vue3+Arco实战桌面端os管理系统的一些知识分享。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045667190
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »

Android平台5+离线打包

离线打包

本教程一笔化整个离线打包,不列举其他选情况,主要解决官方文档一次看不懂,后续熟悉流程可自行根据官方文档修改自定义设置。参考链接如下:

  1. Andriod离线打包

开发环境及工具

软件的下载演示为MAC,Windows下载选择Windows环境即可。

HbuilderX与SDK版本需要要求一致,PS:有相关bug提出新SDK(4.36)会白屏。本次演示使用4.29版本

  1. android studio

  2. HBuilderX

  3. 离线SDK

使用新版以及后续SDK注意,新版由本次演示后的版本在注意事项中标明Andriod Studio相关环境使用时仔细阅读使用环境(后续下载SDK官方应该会修改环境),本次演示不包含该内容。

  1. java 1.8

Andriod离线打包

Android Studio打开项目

可能遇到的问题:

  • 下载gradle失败

  • 分不清build.gradle

下载gradle失败

需要修改为国内镜像,参考文档:Android Studio项目gradle下载慢问题

不想去看的话直接修改gradle/wrapper/gradle-wrapper.properties下distributionUrl配置

修改为

distributionBase=GRADLE_USER_HOME  
distributionPath=wrapper/dists  
zipStoreBase=GRADLE_USER_HOME  
zipStorePath=wrapper/dists  
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-6.5-all.zip

分不清build.gradle

打开的项目加载完成后会切换为Andriod战士方式,配置时建议切换为项目展示方式

项目中包含两个build.gradle文件分别为项目级和应用级,后续教程内以该规则进行描述

生成证书

可参考文档: 生成签名证书

需要JAVA JDK环境1.8

选择APK,点击下一步

选择创建新的

填写信息,确定

证书名称: simpleDemo.keystore  
证书密码: 123456  
别名: simple  
别名密码: 123123  
证书信息:   
demo,demo,demo,changsha,hunan,CN

下一步

创建

申请APP离线key

打开终端

移动到证书所在目录

查看证书信息

命令如下

cd simpleDemo  

ls  

keytool -list -v -keystore simpleDemo.keystore  

123456(这里是我生成的  个人配置的输入个人的)

证书指纹

可能遇到的错误:

  • java环境

java环境是未进行详细解释,如果是完全按照教程流程唯一可能遇到java环境导致生成不正常意外报错,如果有可以问下评论区大佬。

打开应用管理

应用管理

各平台信息,新增

填写信息,提交,内容参考上面的证书详情

创建离线key

创建

查看离线key

生成本地打包资源

生成资源

找到资源

替换资源

修改APPID

修改app离线key

修改证书

修改应用级build.gradle,别名密码和证书密码是可以一致的,区别开是为了更好的分辨某个填写至某个

运行

修改build.gradle后需要点击一次Sync Now或者资源重新加载参考教程内 同步gradle配置想·

完成后点击运行

运行完成

继续阅读 »

本教程一笔化整个离线打包,不列举其他选情况,主要解决官方文档一次看不懂,后续熟悉流程可自行根据官方文档修改自定义设置。参考链接如下:

  1. Andriod离线打包

开发环境及工具

软件的下载演示为MAC,Windows下载选择Windows环境即可。

HbuilderX与SDK版本需要要求一致,PS:有相关bug提出新SDK(4.36)会白屏。本次演示使用4.29版本

  1. android studio

  2. HBuilderX

  3. 离线SDK

使用新版以及后续SDK注意,新版由本次演示后的版本在注意事项中标明Andriod Studio相关环境使用时仔细阅读使用环境(后续下载SDK官方应该会修改环境),本次演示不包含该内容。

  1. java 1.8

Andriod离线打包

Android Studio打开项目

可能遇到的问题:

  • 下载gradle失败

  • 分不清build.gradle

下载gradle失败

需要修改为国内镜像,参考文档:Android Studio项目gradle下载慢问题

不想去看的话直接修改gradle/wrapper/gradle-wrapper.properties下distributionUrl配置

修改为

distributionBase=GRADLE_USER_HOME  
distributionPath=wrapper/dists  
zipStoreBase=GRADLE_USER_HOME  
zipStorePath=wrapper/dists  
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-6.5-all.zip

分不清build.gradle

打开的项目加载完成后会切换为Andriod战士方式,配置时建议切换为项目展示方式

项目中包含两个build.gradle文件分别为项目级和应用级,后续教程内以该规则进行描述

生成证书

可参考文档: 生成签名证书

需要JAVA JDK环境1.8

选择APK,点击下一步

选择创建新的

填写信息,确定

证书名称: simpleDemo.keystore  
证书密码: 123456  
别名: simple  
别名密码: 123123  
证书信息:   
demo,demo,demo,changsha,hunan,CN

下一步

创建

申请APP离线key

打开终端

移动到证书所在目录

查看证书信息

命令如下

cd simpleDemo  

ls  

keytool -list -v -keystore simpleDemo.keystore  

123456(这里是我生成的  个人配置的输入个人的)

证书指纹

可能遇到的错误:

  • java环境

java环境是未进行详细解释,如果是完全按照教程流程唯一可能遇到java环境导致生成不正常意外报错,如果有可以问下评论区大佬。

打开应用管理

应用管理

各平台信息,新增

填写信息,提交,内容参考上面的证书详情

创建离线key

创建

查看离线key

生成本地打包资源

生成资源

找到资源

替换资源

修改APPID

修改app离线key

修改证书

修改应用级build.gradle,别名密码和证书密码是可以一致的,区别开是为了更好的分辨某个填写至某个

运行

修改build.gradle后需要点击一次Sync Now或者资源重新加载参考教程内 同步gradle配置想·

完成后点击运行

运行完成

收起阅读 »

uni-admin,H5发布后,报错“未找到集合[xxx]对应的schema”的一种问题原因分享

uni-admin

当然,造成该问题的原因,最好还是看一下对应的schema是不是上传到了unicloud,或者写错了名字

我的故障现象是:在本地测试时,完全没问题,但发布后,就会报错未找到集合[xxx]对应的schema,就偶尔几个表,其他大部分也没事。而且检查云数据库里,确实该表是存在的。

我最终发现,问题出在,我建的数据库表超过100个了,至于怎么出来101个表,我也不知道。

解决办法是删没用的表,删到100个以内,问题就解决了。

希望能帮到大家,这问题耽误了好几个小时。

继续阅读 »

当然,造成该问题的原因,最好还是看一下对应的schema是不是上传到了unicloud,或者写错了名字

我的故障现象是:在本地测试时,完全没问题,但发布后,就会报错未找到集合[xxx]对应的schema,就偶尔几个表,其他大部分也没事。而且检查云数据库里,确实该表是存在的。

我最终发现,问题出在,我建的数据库表超过100个了,至于怎么出来101个表,我也不知道。

解决办法是删没用的表,删到100个以内,问题就解决了。

希望能帮到大家,这问题耽误了好几个小时。

收起阅读 »

vue3.5+tauri v2桌面版后台管理系统|vite5+tauri2+element-plus客户端后台模板

经过大半个月高强度实战开发,又一款原创跨平台新作tauri2.0+vue3+pinia2+elementPlus+mockjs电脑端通用权限后台管理系统,正式结束了。实现4种通用布局模板,支持vue-i18n国际化、面包屑导航、tab标签路由等功能。

tauri2.0-vue3admin桌面端管理系统|tauri2+vite5+element-plus后台EXE程序

img

img

使用技术

  • 开发工具:VScode
  • 技术框架:tauri2.0+vite^5.4.8+vue^3.5.11+vue-router^4.4.5
  • 状态管理:pinia^2.2.4
  • 存储服务:pinia-plugin-persistedstate^4.1.1
  • 组件库:element-plus^2.8.5
  • 图表组件:echarts^5.5.1
  • 国际化:vue-i18n^10.0.4
  • 富文本编辑器:@vueup/vue-quill^1.2.0
  • md编辑器:md-editor-v3^4.20.3
  • 模拟数据:mockjs^1.1.0
  • 预处理样式:sass^1.79.4

img

img

目前该项目Tauri2-Vue3Admin已经同步发布到我的原创作品集。

https://gf.bilibili.com/item/detail/1107226011

项目结构

img

img

img

img

tauri2-admin后台布局模板

img

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  import Toolbar from '@/layouts/components/Toolbar.vue'  
  import Sidebar from '@/layouts/components/sidebar/index.vue'  
  import Menus from '@/layouts/components/menus/index.vue'  
  import Breadcrumb from '@/layouts/components/Breadcrumb.vue'  
  import Tabview from '@/layouts/components/Tabview.vue'  
  import Main from '@/layouts/components/Main.vue'  

  const appstate = appState()  
</script>  

<template>  
  <div class="vuadmin__layout flexbox flex-col">  
    <Toolbar />  

    <div class="vuadmin__layout-body flex1 flexbox">  
      <!-- 侧边栏 -->  
      <div class="vuadmin__layout-sidebar">  
        <Sidebar />  
      </div>  

      <!-- 菜单栏 -->  
      <div class="vuadmin__layout-menus" :class="{'hidden': appstate.config.collapsed}">  
        <el-scrollbar>  
          <Menus :rootRouteEnable="false" />  
        </el-scrollbar>  
      </div>  

      <!-- 右侧主内容区 -->  
      <div class="vuadmin__layout-main flex1 flexbox flex-col">  
        <!-- 面包屑导航 -->  
        <Breadcrumb v-if="appstate.config.breadcrumb" />  

        <!-- 标签页 -->  
        <Tabview v-if="appstate.config.tabview" />  

        <!-- 内容区 -->  
        <Main />  
      </div>  
    </div>  
  </div>  
</template>

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

tauri2-admin国际化配置

img

img

import { createI18n } from 'vue-i18n'  
import { appState } from '@/pinia/modules/app'  

// 引入语言配置  
import enUS from './en-US'  
import zhCN from './zh-CN'  
import zhTW from './zh-TW'  

// 默认语言  
export const langVal = 'zh-CN'  

export default async (app) => {  
  const appstate = appState()  
  const lang = appstate.lang || langVal  
  appstate.setLang(lang)  

  const i18n = createI18n({  
    legacy: false,  
    locale: lang,  
    messages: {  
      'en': enUS,  
      'zh-CN': zhCN,  
      'zh-TW': zhTW  
    }  
  })  

  app.use(i18n)  
}

tauri2-admin实现自定义导航栏

img

<script setup>  
  import { ref, markRaw } from 'vue'  
  import { ElMessageBox } from 'element-plus'  
  import { QuestionFilled, SwitchButton } from '@element-plus/icons-vue'  
  import { getCurrentWindow } from '@tauri-apps/api/window'  
  import { listen } from '@tauri-apps/api/event'  
  import { exit } from '@tauri-apps/plugin-process'  

  import { isTrue } from '@/utils'  
  import { authState } from '@/pinia/modules/auth'  

  const authstate = authState()  

  const props = defineProps({  
    color: String,  
    // 窗口是否可最小化  
    minimizable: {type: [Boolean, String], default: true},  
    // 窗口是否可最大化  
    maximizable: {type: [Boolean, String], default: true},  
    // 窗口是否可关闭  
    closable: {type: [Boolean, String], default: true},  
    // 层级  
    zIndex: {type: [Number, String], default: 2024},  
  })  

  const hasMaximized = ref(false)  
  const isResizable = ref(true)  
  const isMaximizable = ref(true)  

  // 用户是否可以手动调整窗口大小  
  getCurrentWindow().isResizable().then(res => {  
    isResizable.value = res  
  })  
  // 窗口是否可以最大化  
  getCurrentWindow().isMaximizable().then(res => {  
    isMaximizable.value = res  
  })  
  // 初始监听窗口是否最大化  
  getCurrentWindow().isMaximized().then(res => {  
    hasMaximized.value = res  
  })  
  // 实时监听窗口是否最大化  
  listen('tauri://resize', async() => {  
    hasMaximized.value = await getCurrentWindow().isMaximized()  
  })  

  // 最小化  
  const handleWinMin = async() => {  
    await getCurrentWindow().minimize()  
  }  
  // 最大化/还原  
  const handleWinToggle = async() => {  
    await getCurrentWindow().toggleMaximize()  
  }  
  // 关闭  
  const handleWinClose = async() => {  
    const isMajor = getCurrentWindow().label.indexOf('main') > -1  
    if(isMajor) {  
      ElMessageBox.confirm('是否最小化到系统托盘,不退出程序?', '提示', {  
        type: 'warning',  
        icon: markRaw(QuestionFilled),  
        confirmButtonText: '残忍退出',  
        cancelButtonText: '最小化到托盘',  
        customStyle: {'width': '300px'},  
        draggable: true,  
        roundButton: true,  
        center: true,  
        buttonSize: 'small',  
        distinguishCancelAndClose: true,  
      }).then(async() => {  
        authstate.logout()  
        await exit()  
      }).catch(async(action) => {  
        if(action === 'cancel') {  
          await getCurrentWindow().hide()  
        }  
      })  
    }else {  
      await getCurrentWindow().close()  
    }  
  }  
</script>  

<template>  
  <div class="ev__winbtns flexbox flex-alignc vu__drag" :style="{'z-index': zIndex}">  
    <div class="ev__winbtns-actions flexbox flex-alignc vu__undrag" :style="{'color': color}">  
      <a v-if="isTrue(minimizable)" class="wbtn min" title="最小化" @click="handleWinMin"><i class="wicon iconfont elec-icon-min"></i></a>  
      <a v-if="isTrue(maximizable)" class="wbtn toggle" :title="hasMaximized ? '向下还原' : '最大化'" @click="handleWinToggle">  
        <i class="wicon iconfont" :class="hasMaximized ? 'elec-icon-restore' : 'elec-icon-max'"></i>  
      </a>  
      <a v-if="isTrue(closable)" class="wbtn close" title="关闭" @click="handleWinClose"><i class="wicon iconfont elec-icon-quit"></i></a>  
    </div>  
  </div>  
</template>  

<style lang="scss" scoped>  
  @import './index.scss';  
</style>

tauri2-vue3admin自定义标签栏路由

img

img

<template>  
  <div class="vu__tabview">  
    <el-tabs  
      v-model="activeTab"  
      class="vu__tabview-tabs"  
      @tab-change="changeTabs"  
      @tab-remove="removeTab"  
    >  
      <el-tab-pane  
        v-for="(item, index) in tabList"  
        :key="index"  
        :name="item.path"  
        :closable="!item?.meta?.isAffix"  
      >  
        <template #label>  
          <el-dropdown ref="dropdownRef" trigger="contextmenu" :id="item.path" @visible-change="handleDropdownChange($event, item.path)" @command="handleDropdownCommand($event, item)">  
            <span class="vu__tabview-tabs__label">  
              <span>{{$t(item?.meta?.title)}}</span>  
            </span>  
            <template #dropdown>  
              <el-dropdown-menu>  
                <el-dropdown-item command="refresh" :icon="Refresh">{{$t('tabview__contextmenu-refresh')}}</el-dropdown-item>  
                <el-dropdown-item command="close" :icon="Close" :disabled="item.meta.isAffix">{{$t('tabview__contextmenu-close')}}</el-dropdown-item>  
                <el-dropdown-item command="closeOther" :icon="Switch">{{$t('tabview__contextmenu-closeother')}}</el-dropdown-item>  
                <el-dropdown-item command="closeLeft" :icon="DArrowLeft">{{$t('tabview__contextmenu-closeleft')}}</el-dropdown-item>  
                <el-dropdown-item command="closeRight" :icon="DArrowRight">{{$t('tabview__contextmenu-closeright')}}</el-dropdown-item>  
                <el-dropdown-item command="closeAll" :icon="CircleCloseFilled">{{$t('tabview__contextmenu-closeall')}}</el-dropdown-item>  
              </el-dropdown-menu>  
            </template>  
          </el-dropdown>  
        </template>  
      </el-tab-pane>  
    </el-tabs>  
  </div>  
</template>

img

tauri2+vue3实现自定义托盘右键菜单功能。

img

End,以上就是tauri2+vue3+pinia2实战开发桌面版后台管理系统模板的一些知识分享。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045381943
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

经过大半个月高强度实战开发,又一款原创跨平台新作tauri2.0+vue3+pinia2+elementPlus+mockjs电脑端通用权限后台管理系统,正式结束了。实现4种通用布局模板,支持vue-i18n国际化、面包屑导航、tab标签路由等功能。

tauri2.0-vue3admin桌面端管理系统|tauri2+vite5+element-plus后台EXE程序

img

img

使用技术

  • 开发工具:VScode
  • 技术框架:tauri2.0+vite^5.4.8+vue^3.5.11+vue-router^4.4.5
  • 状态管理:pinia^2.2.4
  • 存储服务:pinia-plugin-persistedstate^4.1.1
  • 组件库:element-plus^2.8.5
  • 图表组件:echarts^5.5.1
  • 国际化:vue-i18n^10.0.4
  • 富文本编辑器:@vueup/vue-quill^1.2.0
  • md编辑器:md-editor-v3^4.20.3
  • 模拟数据:mockjs^1.1.0
  • 预处理样式:sass^1.79.4

img

img

目前该项目Tauri2-Vue3Admin已经同步发布到我的原创作品集。

https://gf.bilibili.com/item/detail/1107226011

项目结构

img

img

img

img

tauri2-admin后台布局模板

img

<script setup>  
  import { appState } from '@/pinia/modules/app'  

  import Toolbar from '@/layouts/components/Toolbar.vue'  
  import Sidebar from '@/layouts/components/sidebar/index.vue'  
  import Menus from '@/layouts/components/menus/index.vue'  
  import Breadcrumb from '@/layouts/components/Breadcrumb.vue'  
  import Tabview from '@/layouts/components/Tabview.vue'  
  import Main from '@/layouts/components/Main.vue'  

  const appstate = appState()  
</script>  

<template>  
  <div class="vuadmin__layout flexbox flex-col">  
    <Toolbar />  

    <div class="vuadmin__layout-body flex1 flexbox">  
      <!-- 侧边栏 -->  
      <div class="vuadmin__layout-sidebar">  
        <Sidebar />  
      </div>  

      <!-- 菜单栏 -->  
      <div class="vuadmin__layout-menus" :class="{'hidden': appstate.config.collapsed}">  
        <el-scrollbar>  
          <Menus :rootRouteEnable="false" />  
        </el-scrollbar>  
      </div>  

      <!-- 右侧主内容区 -->  
      <div class="vuadmin__layout-main flex1 flexbox flex-col">  
        <!-- 面包屑导航 -->  
        <Breadcrumb v-if="appstate.config.breadcrumb" />  

        <!-- 标签页 -->  
        <Tabview v-if="appstate.config.tabview" />  

        <!-- 内容区 -->  
        <Main />  
      </div>  
    </div>  
  </div>  
</template>

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

img

tauri2-admin国际化配置

img

img

import { createI18n } from 'vue-i18n'  
import { appState } from '@/pinia/modules/app'  

// 引入语言配置  
import enUS from './en-US'  
import zhCN from './zh-CN'  
import zhTW from './zh-TW'  

// 默认语言  
export const langVal = 'zh-CN'  

export default async (app) => {  
  const appstate = appState()  
  const lang = appstate.lang || langVal  
  appstate.setLang(lang)  

  const i18n = createI18n({  
    legacy: false,  
    locale: lang,  
    messages: {  
      'en': enUS,  
      'zh-CN': zhCN,  
      'zh-TW': zhTW  
    }  
  })  

  app.use(i18n)  
}

tauri2-admin实现自定义导航栏

img

<script setup>  
  import { ref, markRaw } from 'vue'  
  import { ElMessageBox } from 'element-plus'  
  import { QuestionFilled, SwitchButton } from '@element-plus/icons-vue'  
  import { getCurrentWindow } from '@tauri-apps/api/window'  
  import { listen } from '@tauri-apps/api/event'  
  import { exit } from '@tauri-apps/plugin-process'  

  import { isTrue } from '@/utils'  
  import { authState } from '@/pinia/modules/auth'  

  const authstate = authState()  

  const props = defineProps({  
    color: String,  
    // 窗口是否可最小化  
    minimizable: {type: [Boolean, String], default: true},  
    // 窗口是否可最大化  
    maximizable: {type: [Boolean, String], default: true},  
    // 窗口是否可关闭  
    closable: {type: [Boolean, String], default: true},  
    // 层级  
    zIndex: {type: [Number, String], default: 2024},  
  })  

  const hasMaximized = ref(false)  
  const isResizable = ref(true)  
  const isMaximizable = ref(true)  

  // 用户是否可以手动调整窗口大小  
  getCurrentWindow().isResizable().then(res => {  
    isResizable.value = res  
  })  
  // 窗口是否可以最大化  
  getCurrentWindow().isMaximizable().then(res => {  
    isMaximizable.value = res  
  })  
  // 初始监听窗口是否最大化  
  getCurrentWindow().isMaximized().then(res => {  
    hasMaximized.value = res  
  })  
  // 实时监听窗口是否最大化  
  listen('tauri://resize', async() => {  
    hasMaximized.value = await getCurrentWindow().isMaximized()  
  })  

  // 最小化  
  const handleWinMin = async() => {  
    await getCurrentWindow().minimize()  
  }  
  // 最大化/还原  
  const handleWinToggle = async() => {  
    await getCurrentWindow().toggleMaximize()  
  }  
  // 关闭  
  const handleWinClose = async() => {  
    const isMajor = getCurrentWindow().label.indexOf('main') > -1  
    if(isMajor) {  
      ElMessageBox.confirm('是否最小化到系统托盘,不退出程序?', '提示', {  
        type: 'warning',  
        icon: markRaw(QuestionFilled),  
        confirmButtonText: '残忍退出',  
        cancelButtonText: '最小化到托盘',  
        customStyle: {'width': '300px'},  
        draggable: true,  
        roundButton: true,  
        center: true,  
        buttonSize: 'small',  
        distinguishCancelAndClose: true,  
      }).then(async() => {  
        authstate.logout()  
        await exit()  
      }).catch(async(action) => {  
        if(action === 'cancel') {  
          await getCurrentWindow().hide()  
        }  
      })  
    }else {  
      await getCurrentWindow().close()  
    }  
  }  
</script>  

<template>  
  <div class="ev__winbtns flexbox flex-alignc vu__drag" :style="{'z-index': zIndex}">  
    <div class="ev__winbtns-actions flexbox flex-alignc vu__undrag" :style="{'color': color}">  
      <a v-if="isTrue(minimizable)" class="wbtn min" title="最小化" @click="handleWinMin"><i class="wicon iconfont elec-icon-min"></i></a>  
      <a v-if="isTrue(maximizable)" class="wbtn toggle" :title="hasMaximized ? '向下还原' : '最大化'" @click="handleWinToggle">  
        <i class="wicon iconfont" :class="hasMaximized ? 'elec-icon-restore' : 'elec-icon-max'"></i>  
      </a>  
      <a v-if="isTrue(closable)" class="wbtn close" title="关闭" @click="handleWinClose"><i class="wicon iconfont elec-icon-quit"></i></a>  
    </div>  
  </div>  
</template>  

<style lang="scss" scoped>  
  @import './index.scss';  
</style>

tauri2-vue3admin自定义标签栏路由

img

img

<template>  
  <div class="vu__tabview">  
    <el-tabs  
      v-model="activeTab"  
      class="vu__tabview-tabs"  
      @tab-change="changeTabs"  
      @tab-remove="removeTab"  
    >  
      <el-tab-pane  
        v-for="(item, index) in tabList"  
        :key="index"  
        :name="item.path"  
        :closable="!item?.meta?.isAffix"  
      >  
        <template #label>  
          <el-dropdown ref="dropdownRef" trigger="contextmenu" :id="item.path" @visible-change="handleDropdownChange($event, item.path)" @command="handleDropdownCommand($event, item)">  
            <span class="vu__tabview-tabs__label">  
              <span>{{$t(item?.meta?.title)}}</span>  
            </span>  
            <template #dropdown>  
              <el-dropdown-menu>  
                <el-dropdown-item command="refresh" :icon="Refresh">{{$t('tabview__contextmenu-refresh')}}</el-dropdown-item>  
                <el-dropdown-item command="close" :icon="Close" :disabled="item.meta.isAffix">{{$t('tabview__contextmenu-close')}}</el-dropdown-item>  
                <el-dropdown-item command="closeOther" :icon="Switch">{{$t('tabview__contextmenu-closeother')}}</el-dropdown-item>  
                <el-dropdown-item command="closeLeft" :icon="DArrowLeft">{{$t('tabview__contextmenu-closeleft')}}</el-dropdown-item>  
                <el-dropdown-item command="closeRight" :icon="DArrowRight">{{$t('tabview__contextmenu-closeright')}}</el-dropdown-item>  
                <el-dropdown-item command="closeAll" :icon="CircleCloseFilled">{{$t('tabview__contextmenu-closeall')}}</el-dropdown-item>  
              </el-dropdown-menu>  
            </template>  
          </el-dropdown>  
        </template>  
      </el-tab-pane>  
    </el-tabs>  
  </div>  
</template>

img

tauri2+vue3实现自定义托盘右键菜单功能。

img

End,以上就是tauri2+vue3+pinia2实战开发桌面版后台管理系统模板的一些知识分享。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045381943
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »

vue3+tauri2.0+element-plus桌面端exe聊天模板|vite5+tauri2仿QQ/微信客户端程序

vue3

趁着国庆假期,我的又一款原创重磅新作Vue3.5+Tauri2.0+Vite5.4+Pinia2+ElementPlus跨平台实战仿QQ/微信电脑端聊天Exe程序Vue3Tauri2Chat,正式的完结了。整体UI采用全新无边框透明圆角阴影窗体

Vue3+Tauri2.0聊天实例|tauri2+vite5+element-plus仿微信|tauri聊天应用

img

img

封装tauri2.x多开窗口管理、换肤壁纸、自定义系统托盘闪烁/右键菜单功能。实现聊天、联系人、收藏、朋友圈、短视频、我的等页面模块。

img

img

运用技术

  • 编码工具:Vscode
  • 技术框架:tauri2.0+vite^5.4+vue^3.5+vue-router^4.4.5
  • 状态管理:pinia^2.2.2
  • 本地存储插件:pinia-plugin-persistedstate^4.0.2
  • 组件库:element-plus^2.8.3
  • 富文本编辑器:@vueup/vue-quill^1.2.0
  • 样式预处理:sass^1.79.3
  • 视频滑动组件:swiper^11.1.14

img

img

img

img

img

img

img

img

img

img

目前tauri2-vue3chat聊天项目已经发布到我的原创作品集,有需要的话可以去瞅瞅~
https://gf.bilibili.com/item/detail/1107133011

tauri2-vue3chat项目布局模板

img

img

<template>  
  <div class="vu__chatbox">  
    <template v-if="!route?.meta?.isNewWin">  
      <div class="vu__container flexbox flex-alignc flex-justifyc">  
        <div class="vu__layout flexbox flex-col">  
          <div class="vu__layout-body flex1 flexbox" @contextmenu.prevent>  
            <!-- 菜单栏 -->  
            <slot v-if="!route?.meta?.hideMenuBar" name="menubar">  
              <MenuBar />  
            </slot>  

            <!-- 侧边栏 -->  
            <div v-if="route?.meta?.showSideBar" class="vu__layout-sidebar flexbox">  
              <aside class="vu__layout-sidebar__body flexbox flex-col">  
                <slot name="sidebar">  
                  <SideBar />  
                </slot>  
              </aside>  
            </div>  

            <!-- 主内容区 -->  
            <div class="vu__layout-main flex1 flexbox flex-col">  
              <ToolBar v-if="!route?.meta?.hideToolBar" />  
              <router-view v-slot="{ Component, route }">  
                <keep-alive>  
                  <component :is="Component" :key="route.path" />  
                </keep-alive>  
              </router-view>  
            </div>  
          </div>  
        </div>  
      </div>  
    </template>  
    <template v-else>  
      <WinLayout />  
    </template>  
  </div>  
</template>

img

img

img

tauri2多窗口实践

img

// 朋友圈窗口  
const handleFzone = () => {  
  winCreate({  
    label: 'win-fzone',  
    url: '/win/fzone',  
    title: '朋友圈',  
    width: 500,  
    height: 695,  
    minWidth: 350,  
    minHeight: 500,  
    maximizable: false,  
  })  
}  

// 短视频窗口  
const handleFvideo = () => {  
  winCreate({  
    label: 'win-fvideo',  
    url: '/win/fvideo',  
    title: '短视频',  
    width: 575,  
    height: 675,  
    minWidth: 415,  
    minHeight: 545  
  })  
}  

// 换肤壁纸窗口  
const handleSkin = () => {  
  winCreate({  
    label: 'win-skin',  
    url: '/win/skin',  
    title: '壁纸',  
    width: 375,  
    height: 480,  
    resizable: false,  
    maximizable: false,  
    visible: true,  
  })  
}  

// 界面管理器  
const handleManageUI = () => {  
  winCreate({  
    label: 'win-manage',  
    url: '/win/manage',  
    title: '界面管理器',  
    width: 320,  
    height: 360,  
    resizable: false,  
    maximizable: false,  
  })  
}

之前有过一篇关于tauri2创建多窗口应用的分享文章,可以去看看。
基于Tauri2+Vite5搭建桌面端程序|tauri2+vue3多窗口|消息提醒|托盘闪烁

img

img

img

vue3+tauri2自定义透明圆角阴影窗体

img

.vu__chatbox {height: calc(100vh); padding: 5px; overflow: hidden;}  
.vu__layout {  
  background-color: #f5f5f5;  
  overflow: hidden;  
  height: 100%; width: 100%;  
  position: relative; z-index: 100;  
  border-radius: 8px;  
  box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.15),0 1px 5px -1px rgba(0, 0, 0, 0.1),0 2px 5px rgba(0, 0, 0, 0.1);  
}

img

<script setup>  
  /**  
   * tauri2.0自定义系统最大化/最小化/关闭  by andy  Q:282310962  
   */  

  // ...  

  const props = defineProps({  
    color: String,  
    // 窗口是否可最小化  
    minimizable: {type: [Boolean, String], default: true},  
    // 窗口是否可最大化  
    maximizable: {type: [Boolean, String], default: true},  
    // 窗口是否可关闭  
    closable: {type: [Boolean, String], default: true},  
    // 层级  
    zIndex: {type: [Number, String], default: 2024},  

    // 关闭前回调,会暂停实例关闭 function(done),done用于关闭  
        beforeClose: Function  
  })  

  const hasMaximized = ref(false)  
  const isResizable = ref(true)  
  const isMaximizable = ref(true)  

  // 用户是否可以手动调整窗口大小  
  getCurrentWindow().isResizable().then(res => {  
    isResizable.value = res  
  })  
  // 窗口是否可以最大化  
  getCurrentWindow().isMaximizable().then(res => {  
    isMaximizable.value = res  
  })  
  // 初始监听窗口是否最大化  
  getCurrentWindow().isMaximized().then(res => {  
    hasMaximized.value = res  
  })  
  // 实时监听窗口是否最大化  
  listen('tauri://resize', async() => {  
    hasMaximized.value = await getCurrentWindow().isMaximized()  
  })  

  // 最小化  
  const handleWinMin = async() => {  
    // winSet('minimize')  
    await getCurrentWindow().minimize()  
  }  
  // 最大化/还原  
  const handleWinToggle = async() => {  
    // winSet('max2min')  
    await getCurrentWindow().toggleMaximize()  
  }  
  // 关闭  
  const handleClose = async() => {  
    const isMajor = getCurrentWindow().label.indexOf('main') > -1  
    if(isMajor) {  
      let el = layer({  
        type: 'android',  
        content: '是否最小化到托盘,不退出程序?',  
        layerStyle: 'background: #f9f9f9; border-radius: 8px;',  
        closable: false,  
        resize: false,  
        btns: [  
          {  
            text: '最小化托盘',  
            style: 'color: #646cff',  
            click: () => {  
              layer.close(el)  
              // winSet('hide')  
              await getCurrentWindow().hide()  
            }  
          },  
          {  
            text: '退出程序',  
            style: 'color: #fa5151',  
            click: async() => {  
              authstate.logout()  
              await exit()  
            }  
          }  
        ]  
      })  
    }else {  
      // winSet('close')  
      await getCurrentWindow().close()  
    }  
  }  
</script>  

<template>  
  <div class="ev__winbtns vu__drag" :style="{'z-index': zIndex}">  
    <div class="ev__winbtns-actions vu__undrag" :style="{'color': color}">  
      <a v-if="isTrue(minimizable)" class="wbtn min" title="最小化" @click="handleWinMin"><i class="wicon elec-icon elec-icon-min"></i></a>  
      <a v-if="isTrue(maximizable) && isResizable && isMaximizable" class="wbtn toggle" :title="hasMaximized ? '向下还原' : '最大化'" @click="handleWinToggle">  
        <i class="wicon elec-icon iconfont" :class="hasMaximized ? 've-icon-shrink' : 've-icon-arrowsalt'"></i>  
      </a>  
      <a v-if="isTrue(closable)" class="wbtn close" title="关闭" @click="handleClose"><i class="wicon elec-icon elec-icon-quit"></i></a>  
    </div>  
  </div>  
</template>

img

img

img

img

img

综上就是vue3+tauri2.x实战桌面端聊天项目的一些知识分享。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045331960
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

趁着国庆假期,我的又一款原创重磅新作Vue3.5+Tauri2.0+Vite5.4+Pinia2+ElementPlus跨平台实战仿QQ/微信电脑端聊天Exe程序Vue3Tauri2Chat,正式的完结了。整体UI采用全新无边框透明圆角阴影窗体

Vue3+Tauri2.0聊天实例|tauri2+vite5+element-plus仿微信|tauri聊天应用

img

img

封装tauri2.x多开窗口管理、换肤壁纸、自定义系统托盘闪烁/右键菜单功能。实现聊天、联系人、收藏、朋友圈、短视频、我的等页面模块。

img

img

运用技术

  • 编码工具:Vscode
  • 技术框架:tauri2.0+vite^5.4+vue^3.5+vue-router^4.4.5
  • 状态管理:pinia^2.2.2
  • 本地存储插件:pinia-plugin-persistedstate^4.0.2
  • 组件库:element-plus^2.8.3
  • 富文本编辑器:@vueup/vue-quill^1.2.0
  • 样式预处理:sass^1.79.3
  • 视频滑动组件:swiper^11.1.14

img

img

img

img

img

img

img

img

img

img

目前tauri2-vue3chat聊天项目已经发布到我的原创作品集,有需要的话可以去瞅瞅~
https://gf.bilibili.com/item/detail/1107133011

tauri2-vue3chat项目布局模板

img

img

<template>  
  <div class="vu__chatbox">  
    <template v-if="!route?.meta?.isNewWin">  
      <div class="vu__container flexbox flex-alignc flex-justifyc">  
        <div class="vu__layout flexbox flex-col">  
          <div class="vu__layout-body flex1 flexbox" @contextmenu.prevent>  
            <!-- 菜单栏 -->  
            <slot v-if="!route?.meta?.hideMenuBar" name="menubar">  
              <MenuBar />  
            </slot>  

            <!-- 侧边栏 -->  
            <div v-if="route?.meta?.showSideBar" class="vu__layout-sidebar flexbox">  
              <aside class="vu__layout-sidebar__body flexbox flex-col">  
                <slot name="sidebar">  
                  <SideBar />  
                </slot>  
              </aside>  
            </div>  

            <!-- 主内容区 -->  
            <div class="vu__layout-main flex1 flexbox flex-col">  
              <ToolBar v-if="!route?.meta?.hideToolBar" />  
              <router-view v-slot="{ Component, route }">  
                <keep-alive>  
                  <component :is="Component" :key="route.path" />  
                </keep-alive>  
              </router-view>  
            </div>  
          </div>  
        </div>  
      </div>  
    </template>  
    <template v-else>  
      <WinLayout />  
    </template>  
  </div>  
</template>

img

img

img

tauri2多窗口实践

img

// 朋友圈窗口  
const handleFzone = () => {  
  winCreate({  
    label: 'win-fzone',  
    url: '/win/fzone',  
    title: '朋友圈',  
    width: 500,  
    height: 695,  
    minWidth: 350,  
    minHeight: 500,  
    maximizable: false,  
  })  
}  

// 短视频窗口  
const handleFvideo = () => {  
  winCreate({  
    label: 'win-fvideo',  
    url: '/win/fvideo',  
    title: '短视频',  
    width: 575,  
    height: 675,  
    minWidth: 415,  
    minHeight: 545  
  })  
}  

// 换肤壁纸窗口  
const handleSkin = () => {  
  winCreate({  
    label: 'win-skin',  
    url: '/win/skin',  
    title: '壁纸',  
    width: 375,  
    height: 480,  
    resizable: false,  
    maximizable: false,  
    visible: true,  
  })  
}  

// 界面管理器  
const handleManageUI = () => {  
  winCreate({  
    label: 'win-manage',  
    url: '/win/manage',  
    title: '界面管理器',  
    width: 320,  
    height: 360,  
    resizable: false,  
    maximizable: false,  
  })  
}

之前有过一篇关于tauri2创建多窗口应用的分享文章,可以去看看。
基于Tauri2+Vite5搭建桌面端程序|tauri2+vue3多窗口|消息提醒|托盘闪烁

img

img

img

vue3+tauri2自定义透明圆角阴影窗体

img

.vu__chatbox {height: calc(100vh); padding: 5px; overflow: hidden;}  
.vu__layout {  
  background-color: #f5f5f5;  
  overflow: hidden;  
  height: 100%; width: 100%;  
  position: relative; z-index: 100;  
  border-radius: 8px;  
  box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.15),0 1px 5px -1px rgba(0, 0, 0, 0.1),0 2px 5px rgba(0, 0, 0, 0.1);  
}

img

<script setup>  
  /**  
   * tauri2.0自定义系统最大化/最小化/关闭  by andy  Q:282310962  
   */  

  // ...  

  const props = defineProps({  
    color: String,  
    // 窗口是否可最小化  
    minimizable: {type: [Boolean, String], default: true},  
    // 窗口是否可最大化  
    maximizable: {type: [Boolean, String], default: true},  
    // 窗口是否可关闭  
    closable: {type: [Boolean, String], default: true},  
    // 层级  
    zIndex: {type: [Number, String], default: 2024},  

    // 关闭前回调,会暂停实例关闭 function(done),done用于关闭  
        beforeClose: Function  
  })  

  const hasMaximized = ref(false)  
  const isResizable = ref(true)  
  const isMaximizable = ref(true)  

  // 用户是否可以手动调整窗口大小  
  getCurrentWindow().isResizable().then(res => {  
    isResizable.value = res  
  })  
  // 窗口是否可以最大化  
  getCurrentWindow().isMaximizable().then(res => {  
    isMaximizable.value = res  
  })  
  // 初始监听窗口是否最大化  
  getCurrentWindow().isMaximized().then(res => {  
    hasMaximized.value = res  
  })  
  // 实时监听窗口是否最大化  
  listen('tauri://resize', async() => {  
    hasMaximized.value = await getCurrentWindow().isMaximized()  
  })  

  // 最小化  
  const handleWinMin = async() => {  
    // winSet('minimize')  
    await getCurrentWindow().minimize()  
  }  
  // 最大化/还原  
  const handleWinToggle = async() => {  
    // winSet('max2min')  
    await getCurrentWindow().toggleMaximize()  
  }  
  // 关闭  
  const handleClose = async() => {  
    const isMajor = getCurrentWindow().label.indexOf('main') > -1  
    if(isMajor) {  
      let el = layer({  
        type: 'android',  
        content: '是否最小化到托盘,不退出程序?',  
        layerStyle: 'background: #f9f9f9; border-radius: 8px;',  
        closable: false,  
        resize: false,  
        btns: [  
          {  
            text: '最小化托盘',  
            style: 'color: #646cff',  
            click: () => {  
              layer.close(el)  
              // winSet('hide')  
              await getCurrentWindow().hide()  
            }  
          },  
          {  
            text: '退出程序',  
            style: 'color: #fa5151',  
            click: async() => {  
              authstate.logout()  
              await exit()  
            }  
          }  
        ]  
      })  
    }else {  
      // winSet('close')  
      await getCurrentWindow().close()  
    }  
  }  
</script>  

<template>  
  <div class="ev__winbtns vu__drag" :style="{'z-index': zIndex}">  
    <div class="ev__winbtns-actions vu__undrag" :style="{'color': color}">  
      <a v-if="isTrue(minimizable)" class="wbtn min" title="最小化" @click="handleWinMin"><i class="wicon elec-icon elec-icon-min"></i></a>  
      <a v-if="isTrue(maximizable) && isResizable && isMaximizable" class="wbtn toggle" :title="hasMaximized ? '向下还原' : '最大化'" @click="handleWinToggle">  
        <i class="wicon elec-icon iconfont" :class="hasMaximized ? 've-icon-shrink' : 've-icon-arrowsalt'"></i>  
      </a>  
      <a v-if="isTrue(closable)" class="wbtn close" title="关闭" @click="handleClose"><i class="wicon elec-icon elec-icon-quit"></i></a>  
    </div>  
  </div>  
</template>

img

img

img

img

img

综上就是vue3+tauri2.x实战桌面端聊天项目的一些知识分享。

作者:xiaoyan2017
链接: https://segmentfault.com/a/1190000045331960
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »