自助创建 1Panel 应用

news2024/11/26 18:36:51

自助创建 1Panel 应用

前言

1Panel 作为一款开源的 Linux 服务器运维管理面板,其优质的 应用商店 想必也是很多人喜爱它的原因,除了官方的 应用列表 ,开源社区内也涌现出了许多优质的第三方应用商店资源,比如 okxlin/appstore 等等。当然,为了保证应用的长期稳定更新维护,官方商店的入门门槛基本都是 Star 10k+,所以有的时候我们可能需要一些小众应用,就需要自己动手。

官方教程

需要有 docker 和 docker-compose 相关知识

前提

  • 活跃的开源项目
  • 有官方维护的 docker 镜像

1. 创建应用文件 (以 Halo 为例)

v1.3 及以上版本可以在 1Panel 宿主机使用 1panel app init <应用的key> <应用的版本> 来快速初始化应用文件 (注意不是 1pctl 命令)

文件夹格式

├──halo // 以 halo 的 key 命名 ,下面解释什么是 key 
	├── logo.png // 应用 logo , 最好是 180 * 180 px
	├── data.yml // 应用声明文件
	├── README.md // 应用的 README
	├── 2.2.0 // 应用版本 注意不要以 v 开头
	│   ├── data.yml // 应用的参数配置,下面有详细介绍
	│   ├── data // 挂载出来的目录 
	|   ├── scripts // 脚本目录 存放 init.sh upgrade.sh uninstall.sh
	│   └── docker-compose.yml // docker-compose 文件
	└── 2.3.2
	    ├── data.yml
	    ├── data
	    └── docker-compose.yml

应用声明文件 data.yml

本文件主要用于声明应用的一些信息

additionalProperties:  #固定参数
    key: halo   #应用的 key ,仅限英文,用于在 Linux 创建文件夹
    name: Halo  #应用名称
    tags:  
        - WebSite #应用标签,可以有多个,请参照下方的标签列表 
    shortDescZh: 强大易用的开源建站工具 #应用中文描述,不要超过30个字
    shortDescEn: Powerful and easy-to-use open source website builder #应用英文描述 
    type: website  #应用类型,区别于应用分类,只能有一个,请参照下方的类型列表
    crossVersionUpdate: true  #是否可以跨大版本升级
    limit: 0  #应用安装数量限制,0 代表无限制
    website: https://halo.run/  #官网地址
    github: https://github.com/halo-dev/halo #github 地址 
    document: https://docs.halo.run/ #文档地址

应用标签 - tags 字段(持续更新。。。)

keyname
WebSite建站
ServerWeb 服务器
Runtime运行环境
Database数据库
Tool工具
CI/CDCI/CD
Local本地

应用类型 - type 字段

type说明
websitewebsite 类型在 1Panel 中支持在网站中一键部署,wordpress halo 都是此 type
runtimemysql openresty redis 等类型的应用
toolphpMyAdmin redis-commander jenkins 等类型的应用
应用参数配置文件 data.yml (注意区分于应用主目录下面的 data.yaml)

本文件主要用于生成安装时要填写的 form 表单,在应用版本文件夹下面
可以无表单,但是需要有这个 data.yml文件,并且包含 formFields 字段

以安装 halo 时的 form 表单 为例

iShot_2023-03-18_14 03 43

如果要生成上面的表单,需要这么填写 data.yml

additionalProperties:  #固定参数
    formFields:  
        - default: ""  
          envKey: PANEL_DB_HOST  #docker-compose 文件中的参数
          key: mysql  #依赖应用的 key , 例如 mysql
	  labelEn: Database Service  #英文的label
	  labelZh: 数据库服务  #中文的label
	  required: true  #是否必填
          type: service  #如果需要依赖其他应用,例如数据库,使用此 type 
        - default: halo  
          envKey: PANEL_DB_NAME  
          labelEn: Database  
          labelZh: 数据库名  
          random: true  #是否在 default 文字后面,增加随机字符串
          required: true  
          rule: paramCommon  #校验规则
          type: text  #需要手动填写的,使用此 type
        - default: halo  
          envKey: PANEL_DB_USER  
          labelEn: User  
          labelZh: 数据库用户  
          random: true  
          required: true  
          rule: paramCommon  
          type: text  
        - default: halo  
          envKey: PANEL_DB_USER_PASSWORD  
          labelEn: Password  
          labelZh: 数据库用户密码  
          random: true  
          required: true  
          rule: paramComplexity  
          type: password  #密码字段使用此 type
        - default: admin  
          envKey: HALO_ADMIN  
          labelEn: Admin Username  
          labelZh: 超级管理员用户名  
          required: true  
          rule: paramCommon  
          type: text  
        - default: halo  
          envKey: HALO_ADMIN_PASSWORD  
          labelEn: Admin Password  
          labelZh: 超级管理员密码  
          random: true  
          required: true  
          rule: paramComplexity  
          type: password  
        - default: http://localhost:8080  
          edit: true  
          envKey: HALO_EXTERNAL_URL  
          labelEn: External URL  
          labelZh: 外部访问地址  
          required: true  
          rule: paramExtUrl  
          type: text  
        - default: 8080  
          edit: true  
          envKey: PANEL_APP_PORT_HTTP  
          labelEn: Port  
          labelZh: 端口  
          required: true  
          rule: paramPort  
          type: number #端口使用此 type 

关于端口字段:

  1. PANEL_APP_PORT_HTTP 有 web 访问端口的优先使用此 envKey
  2. envKey 中包含 PANEL_APP_PORT 前缀会被认定为端口类型,并且用于安装前的端口占用校验。注意:端口需要是外部端口

关于 type 字段:

type说明
servicetype: service 如果该应用需要依赖其他组件,如 mysql redis 等,可以通过 key: mysql 定义依赖的名称,在创建应用时会要求先创建依赖的应用。
passwordtype: password 敏感信息,如密码相关的字段会默认不显示明文。
texttype: text 一般内容,比如数据库名称,默认明文显示。
numbertype: number 一般用在端口相关的配置上,只允许输入数字。
selecttype: select 选项,比如 true, false,日志等级等。

简单的例子

# type: service,定义一个 mysql 的 service 依赖。
- default: ""  
    envKey: DB_HOST
    key: mysql
    labelEn: Database Service
    labelZh: 数据库服务
    required: true
    type: service

# type: password
- default: Np2qgqtiUayA857GpuVI0Wtg
    edit: true
    envKey: DB_PASSWORD
    labelEn: Database password
    labelZh: 数据库密码
    required: true
    type: password

# type: text
- default: 192.168.100.100
    disabled: true.
    envKey: REDIS_HOST
    labelEn: Redis host
    labelZh: Redis 主机
    type: text

# type: number
- default: 3306
    disabled: true
    envKey: DB_PORT
    labelEn: Database port
    labelZh: 数据库端口
    rule: paramPort
    type: number

# type: select
- default: "ERROR"
    envKey: LOG_LEVEL
    labelEn: Log level
    labelZh: 日志级别
    required: true
    type: select
    values:
        - label: DEBUG
          value: "DEBUG"
        - label: INFO
          value: "INFO"
        - label: WARNING
          value: "WARNING"
        - label: ERROR
          value: "ERROR"
        - label: CRITICAL
          value: "CRITICAL"

rule 字段目前支持的几种校验

rule规则
paramPort用于限制端口范围为 1-65535
paramExtUrl格式为 http(s)😕/(域名/ip):(端口)
paramCommon英文、数字、.-和_,长度2-30
paramComplexity支持英文、数字、.%@$!&~_-,长度6-30,特殊字符不能在首尾

应用 docker-compose.yml 文件

${PANEL_APP_PORT_HTTP} 类型的参数,都在 data.yml 中有声明

services:  
  halo:  
    image: halohub/halo:2.2.0  
    container_name: ${CONTAINER_NAME}  // 固定写法,勿改
    restart: always  
    networks:  
      - 1panel-network  // 1Panel 创建的应用都在此网络下
    volumes:  
      - ./data:/root/.halo2  
    ports:  
      - ${PANEL_APP_PORT_HTTP}:8090  
    command:  
      - --spring.r2dbc.url=r2dbc:pool:${HALO_PLATFORM}://${PANEL_DB_HOST}:${HALO_DB_PORT}/${PANEL_DB_NAME}  
      - --spring.r2dbc.username=${PANEL_DB_USER}  
      - --spring.r2dbc.password=${PANEL_DB_USER_PASSWORD}  
      - --spring.sql.init.platform=${HALO_PLATFORM}  
      - --halo.external-url=${HALO_EXTERNAL_URL}  
      - --halo.security.initializer.superadminusername=${HALO_ADMIN}  
      - --halo.security.initializer.superadminpassword=${HALO_ADMIN_PASSWORD}  
    labels:  
      createdBy: "Apps"  
  
networks:  
  1panel-network:  
    external: true

2. 脚本

1Panel 在 安装之前、升级之前、卸载之后支持执行 .sh 脚本
分别对应 init.sh upgrade.sh uninstall.sh
存放目录(以halo为例) : halo/2.2.0/scripts

3. 本地使用

将应用目录上传到 1Panel 的 /opt/1panel/resource/apps/local 文件夹下
注意:/opt 为 1Panel 默认安装目录,请根据自己的实际情况修改
上传完成后,目录结构如下

├──halo 
	├── logo.png 
	├── data.yml
	├── README.md 
	├── 2.2.0 
		    ├── data.yml 
		    ├── data  
		    └── docker-compose.yml

在 1Panel 应用商店中,点击更新应用列表按钮同步本地应用

v1.2 版本及之前版本的本地应用,请参考这个文档修改

痛点及解决办法

原应用开发痛点

  1. 步骤描述不够详细,各个文件和目录的说明不清晰,对新手不友好。
  2. 需要开发者手动创建多层目录和文件,过程繁琐重复。
  3. 版本和参数配置需要多次切换编辑器,难以把握全貌。

所以,我简单编写了一个自助构建 1Panel 应用的工具,开源在 此处,并通过 Hugging Face 构建了在线运行的 站点

优势

  1. 使用交互式对话框 detailed 描述了每个步骤和文件格式。
  2. 采用了程序化的方式自动化创建目录和文件,省去了开发者的重复工作。
  3. 整合在一个界面内完成版本和配置编写,方便开发者管理。
  4. 直接提供下载压缩包的功能,省去手动压缩步骤。

使用

一览

在这里插入图片描述

示例

  1. 填写基本信息,生成基本信息文件
    在这里插入图片描述

  2. 编写README文件,一般可以从应用开源的地方去复制哟

  3. 填写版本号

  4. 编写 docker-compose.ymldata.yml 文件
    在这里插入图片描述
    若应用官方提供了docker-compose文件,可以直接复制过来,参考上方官方文档中写的参数进行简单替换后写入 data.yml 即可。

  5. 确认下载,即可下载部署完成的应用包。

  6. 将其解压到服务器 /opt/1panel/resource/apps/local (注意:/opt 为 1Panel 默认安装目录,请根据自己的实际情况修改)后刷新应用商店即可找到
    在这里插入图片描述
    在这里插入图片描述

  7. 构建好的应用包在测试无误后也可以在 Github 上推送到官方商店或第三方商店参与开源项目哟~

代码一览

import zipfile
import yaml
from pywebio.input import *
from pywebio.output import *
from pywebio.platform import config
from pywebio.platform.tornado import start_server
from pathlib import Path
import shutil
import logging
import os
import io
import re

# 环境变量
APPS_DIR = Path("apps")
DEFAULT_LOGO = Path("default_logo.png")

# 初始化logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 校验key是否为英文字符串
def is_valid_key(key):
    return bool(re.match(r'^[a-zA-Z]+$', key))

# 校验基本信息
def check_base_info(data):
    required_fields = [
        "name", "key", "tags", "shortDescZh", "shortDescEn",
        "type", "crossVersionUpdate", "website", "github", "document"
    ]
    for field in required_fields:
        if not data[field]:
            return (field, f"{field} 不能为空")
    if len(data["shortDescZh"]) > 30:
        return ("shortDescZh", "中文描述不能超过30个字")
    if not is_valid_key(data["key"]):
        return ("key", "key 必须是纯英文字符串")
    return None

# 保存文件
def save_file(path, content, mode='w', encoding=None):
    try:
        if 'b' in mode:  # 二进制模式
            with open(path, mode) as f:
                f.write(content)
        else:  # 文本模式
            with open(path, mode, encoding=encoding or 'utf-8') as f:
                f.write(content)
        logging.info(f"File saved successfully: {path}")
    except IOError as e:
        logging.error(f"Error saving file {path}: {e}")
        raise

# 复制文件
def copy_file(src, dst):
    try:
        shutil.copy(src, dst)
        logging.info(f"File copied successfully from {src} to {dst}")
    except IOError as e:
        logging.error(f"Error copying file from {src} to {dst}: {e}")
        raise

# 创建目录
def create_directory(path):
    try:
        path.mkdir(parents=True, exist_ok=True)
        logging.info(f"Directory created: {path}")
    except OSError as e:
        logging.error(f"Error creating directory {path}: {e}")
        raise

# 创建版本
def create_version(app_dir, existing_versions):
    while True:
        version = input("请输入应用的版本 (不要以v开头)")
        if version in existing_versions:
            put_error(f"版本 {version} 已存在,请输入一个新的版本号")
        else:
            break

    version_dir = app_dir / version
    create_directory(version_dir)

    version_info = input_group("版本信息", [
        textarea("请编写docker-compose.yml", name="docker_compose", code={"mode": "yaml", "theme": ""}),
        textarea("请编写data.yml", name="data", code={"mode": "yaml", "theme": ""}),
    ])



    save_file(version_dir / "data.yml", version_info["data"])
    save_file(version_dir / "docker-compose.yml", version_info["docker_compose"])
    put_success(f"已成功创建版本 {version}")

    return version

# 压缩文件夹
def zip_folder(folder_path, output_path):
    with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, _, files in os.walk(folder_path):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, folder_path)
                zipf.write(file_path, arcname)

# 主函数
def main():
    base_info = input_group(
        "自助创建 1Panel 应用",
        [
            input("1. 请输入应用名称* ", name="name", type=TEXT),
            input("2. 请输入应用的key* (仅限英文,用于创建文件夹)", name="key", type=TEXT),
            checkbox("3. 选择应用标签*(可以有多个)", inline=True, options=[
                {"label": "建站", "value": "WebSite"},
                {"label": "Web 服务器", "value": "Server"},
                {"label": "运行环境", "value": "Runtime"},
                {"label": "数据库", "value": "Database"},
                {"label": "工具", "value": "Tool"},
                {"label": "CI/CD", "value": "CI/CD"},
                {"label": "本地", "value": "Local"},
            ], name="tags"),
            input("4. 请输入应用中文描述*(不要超过30个字)", name="shortDescZh", type=TEXT),
            input("5. 请输入应用英文描述*", name="shortDescEn", type=TEXT),
            select("6. 选择应用类型*", options=[
                {"label": "工具类应用,如 phpMyAdmin redis-commander jenkins", "value": "tool"},
                {"label": "支持一键部署的站点类应用类型,如 wordpress halo", "value": "website"},
                {"label": "服务类型的运行时应用,如	mysql openresty redis", "value": "runtime"},
            ], name="type"),
            select("7. 是否可跨大版本升级*", options=[
                {"label": "是", "value": True},
                {"label": "否", "value": False},
            ], name="crossVersionUpdate"),
            slider("8. 应用安装数量限制,(0 代表无限制)*", name="limit", min=0, max=100, step=1, value=0),
            input("9. 官网地址*", name="website", type=URL),
            input("10. Github 地址*", name="github", type=URL),
            input("11. 文档地址*", name="document", type=URL),
            file_upload("上传应用Logo图片(最好是 180 * 180 px)(可选): ", name="logo", accept=[".png", ".jpg", ".jpeg"], max_size="5M"),
        ],
        validate=check_base_info,
    )

    app_dir = APPS_DIR / base_info["key"]
    create_directory(app_dir)

    app_info = {
        "additionalProperties": {
            "key": base_info["key"],
            "name": base_info["name"],
            "tags": base_info["tags"],
            "shortDescZh": base_info["shortDescZh"],
            "shortDescEn": base_info["shortDescEn"],
            "type": base_info["type"],
            "crossVersionUpdate": base_info["crossVersionUpdate"],
            "limit": base_info["limit"],
            "website": base_info["website"],
            "github": base_info["github"],
            "document": base_info["document"],
        }
    }

    save_file(app_dir / "data.yml", yaml.dump(app_info, allow_unicode=True))

    if base_info["logo"]:
        _, file_extension = os.path.splitext(base_info["logo"]["filename"])
        logo_filename = f"logo{file_extension.lower()}"
        save_file(app_dir / logo_filename, base_info["logo"]["content"], mode='wb')
    else:
        copy_file(DEFAULT_LOGO, app_dir / "logo.png")

    put_success("已成功创建基本信息")

    readme = textarea("请编写README", code={"mode": "markdown", "theme": ""})
    save_file(app_dir / "README.md", readme)
    put_success("已成功创建README")

    versions = []
    while True:
        version = create_version(app_dir, versions)
        versions.append(version)
        if not actions("是否继续创建新版本?", [
            {"label": "是", "value": "yes"},
            {"label": "否", "value": "no"},
        ]) == "yes":
            break

    # 压缩应用文件夹
    zip_buffer = io.BytesIO()
    zip_folder(app_dir, zip_buffer)
    zip_buffer.seek(0)

    # 美化下载按钮
    put_button(
        f"下载 {base_info['name']} 应用文件",
        onclick=lambda: put_file(f"{base_info['key']}.zip", zip_buffer.getvalue()),
        color="success",
        outline=True
    )

if __name__ == "__main__":
    config(title="自助创建 1Panel 应用")
    start_server(main, debug=False, port=8080, cdn=False)

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

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

相关文章

【MySQL】什么是索引?了解索引的底层原理

索引的概念 索引是一种用于提高数据库查询效率的数据结构。它类似于书籍的目录&#xff0c;通过快速定位数据的方式&#xff0c;减少了数据检索的时间。索引在数据库表中可以被看作是一个指向数据的指针&#xff0c;它们存储了列的值及其对应行的位置&#xff0c;从而使得数据…

《老俞闲话|唯爱和热情不可辜负》读后感

《老俞闲话&#xff5c;唯爱和热情不可辜负》读后感 俞敏洪先生的这篇讲话充满了深情与智慧&#xff0c;他以自己丰富的人生经历和教育实践&#xff0c;向我们展现了一位教育家对于教育事业的热爱和对教师角色的深刻理解。 情感真挚&#xff0c;触动人心 俞敏洪先生的讲话中流…

嵌入式day25

进程线程 多任务编程 1、进程 2、线程 进程&#xff08;process&#xff09; 进行中的程序 --- 正在运行中的程序 进程 --- 程序的一次执行的过程 进程 是程序的一个实例 进程是跑起来的程序 一个程序可以对应多个进程 程序 静态 硬盘 进程 动态 内存 为什么需要进程…

ECharts 数据可视化 入门基本知识 下载安装常用的图表 【1】

ECharts一个基于 JavaScript 的开源可视化图表库&#xff0c;即将数据以图形或图像的方式展现成在屏幕上显示出来&#xff0c;这种方式称为数据可视化。数据可视化有助于我们分析数据&#xff0c;帮助我们更深入更直观的理解数据。今天回顾顺便总结一下echarts的基本知识&#…

基于SpringBoot和Vue框架的体育馆场地预约系统的设计与实现---附源码15155

摘要 本研究设计和开发了一款体育馆场地预约系统。该系统旨在提供一个方便、高效的方式&#xff0c;让用户能够轻松预约体育馆场地&#xff0c;并实现对预约信息的管理与统计。系统主要分为普通用户和管理员两个角色。普通用户可以浏览通知公告、体育资讯&#xff0c;查看各个体…

并发编程(第二天)

interrupt 方法详解 打断 sleep&#xff0c;wait&#xff0c;join 的线程 这几个方法都会让线程进入阻塞状态 打断 sleep 的线程, 会清空打断状态打断正常运行的线程 打断正常运行的线程, 不会清空打断状态打断 park 线程 打断 park 线程, 不会清空打断状态 如果打断标记已经…

Apache Tomcat服务器版本号隐藏

渗透测试时发现有一台服务器的404报错页面中&#xff0c;有Apache Tomcat的版本号信息显示&#xff0c;发生了信息泄露&#xff0c;可能导致服务器被攻击。如下所示&#xff1a; 解决步骤如下&#xff1a; 1. 隐藏HTTP响应头中的Server信息 Tomcat默认会在HTTP响应头中包含S…

Java面试八股之消息队列通常由哪些角色组成

消息队列通常由哪些角色组成 消息队列系统通常涉及几个核心角色&#xff0c;这些角色协同工作以实现消息的传递和处理。主要的角色包括&#xff1a; 生产者&#xff08;Producer&#xff09;&#xff1a; 生产者是消息的创建者&#xff0c;负责将消息发送到消息队列中。生产者…

【八数码】

题目 错误代码 #include<bits/stdc.h> using namespace std; typedef pair<string, int> PII; #define x first #define y secondstring aim "12345678x"; int ans 0x3f3f3f3f; unordered_map<string, int> m; void bfs(string s, int pos) {qu…

《花100块做个摸鱼小网站! 》第二篇—后端应用搭建和完成第一个爬虫

一、前言 大家好呀&#xff0c;我是summo&#xff0c;前面已经教会大家怎么去阿里云买服务器&#xff08;链接在这&#xff0c;需要自取&#xff1a;https://developer.aliyun.com/huodong/dashiblogger?userCodemtbtcjr1&#xff09;&#xff0c;以及怎么搭建JDK、Redis、My…

R 语言学习教程,从入门到精通,R 数据类型(6)

1、R 数据类型 数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。 变量的类型决定了变量存储占用的空间&#xff0c;以及如何解释存储的位模式。 R 语言中的最基本数据类型主要有三种&#xff1a; 数字 逻辑 文本 数字常量主要有两种&#xff1a; 逻辑类型在许多…

vscode配置xdebug断点调试详细教程

注&#xff1a;环境为本地windows开发环境&#xff0c;编辑器为vscode&#xff0c;PHP集成环境工具为EServer vscode安装扩展并配置 安装PHP Debug 扩展中搜索 PHP Debug 并安装&#xff1a; 配置PHP Debug 1、点击扩展设置 2、在设置中&#xff0c;点击 setting.json 3、编…

【C++】从零实现一个高并发内存池

目录 项目简介 技术栈 内存池 内存池解决的主要问题 效率问题 内存碎片问题 整体框架设计 Thread Cache 代码框架 Central Cache 代码框架 Page Cache 代码框架 申请内存流程 Thread Cache Central Cache Page Cache 释放内存流程 Thread Cache Central Cac…

P4213 【模板】杜教筛、P3768 简单的数学题、P3803 【模板】多项式乘法(FFT)

P4213 【模板】杜教筛 题目描述 P4213 【模板】杜教筛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 运行代码 #include <iostream> #include <map>using namespace std;const int N 2000010; long long vis[N], pm[N], mu[N], phi[N], cnt; map<long lon…

解决手机按键失灵!全新检测方案了解一下!

手机按键在手机设备中起着至关重要的作用&#xff0c;手机按键用于执行各种操作&#xff0c;如接听电话、挂断电话、调节音量、开关机等&#xff0c;方便用户进行基本操作。在生产过程中视觉检测需要确保按键的尺寸、形状和表面光滑度符合设计要求&#xff0c;以保证按键的正常…

寒武纪提出视觉AI新高度:不再依赖LLM, Cambrian-1模型让世界看见多模态的力量!

论文标题: Cambrian-1: A Fully Open, Vision-Centric Exploration of Multimodal LLMs 作者团队&#xff1a;纽约大学谢赛宁, Yann LeCun等人 导读&#xff1a; 寒武纪1号(Cambrian-1)&#xff0c;一种以视觉为核心设计的多模态大语言模型&#xff08;MLLMs&#xff09;&…

技术速递|使用 Native Library Interop 为 .NET MAUI 创建绑定

作者&#xff1a;Rachel Kang 排版&#xff1a;Alan Wang 在当今的应用开发领域&#xff0c;通过利用本机功能来扩展 .NET 应用程序的能力非常宝贵。.NET MAUI 处理程序架构使开发人员能够使用 .NET 代码直接操作本机控件&#xff0c;甚至允许无缝创建跨平台自定义控件。然而&a…

【星闪开发连载】WS63E开发板Windows环境的构建

目录 HiSpark Studio安装 Python环境配置 SDK代码下载 新建工程 海思官方在gitee仓库中提供了一个文档介绍fbb_ws63: fbb_ws63代码仓为支持ws63和ws63e解决方案SDK。技术论坛&#xff1a;https://developer.hisilicon.com/forum/0133146886267870001 - Gitee.comhttps://gi…

WPF 数据模板DataTemplate、控件模板ControlTemplate、Style、ItemsPreseter

一言蔽之&#xff0c;Template就是“外衣”—— ControlTemplate是控件的外衣&#xff0c; DataTemplate是数据的外衣。 DataTemplate 它定义了一个数据对象的可视化结构 DataTemplate常用的地方有3处&#xff0c;分别是&#xff1a; ContentControl的ContentTemplate属性&…

提升体验:UI设计的可用性原则

在中国&#xff0c;每年都有数十万设计专业毕业生涌入市场&#xff0c;但只有少数能够进入顶尖企业。尽管如此&#xff0c;所有设计师都怀揣着成长和提升的愿望。在评价产品的用户体验时&#xff0c;我们可能会依赖直觉来决定设计方案&#xff0c;或者在寻找改善产品体验的切入…