Compare commits

..

No commits in common. "dev" and "main" have entirely different histories.
dev ... main

323 changed files with 20341 additions and 44521 deletions

View File

@ -1,14 +1,14 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 招商车研物联网平台管理系统 VITE_APP_TITLE = 招商车研AI测评机器人模拟测试平台
# 开发环境配置 # 开发环境配置
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'
# 招商车研物联网平台/开发环境 # 招商车研物联网平台/开发环境
VITE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API = '/dev-api'
VITE_API_URL = http://192.168.0.222:13080 VITE_API_URL = http://192.168.0.10:13080
VITE_WS_URL = ws://192.168.0.222:13080/ws VITE_WS_URL = ws://192.168.0.10:13080/ws
VITE_PORT = 80 # VITE_API_URL = http://10.148.108.95:13080 //杨溪IP
VITE_INSPECTION_TYPE = "inspection" # VITE_API_URL = http://10.148.108.58:13080 //赵培利IP
VITE_MINIO_URL = "http://192.168.28.10:9000/cmvr-iot/"

View File

@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 招商车研物联网平台管理系统 VITE_APP_TITLE = 智能座舱测评AI机器人
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'production' VITE_APP_ENV = 'production'
@ -10,8 +10,8 @@ VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip VITE_BUILD_COMPRESS = gzip
#node-red 服务地址
VITE_NODE_RED_URL = 'http://10.148.108.59:13080' VITE_NODE_RED_URL = 'http://10.148.108.59:13080'
VITE_API_URL = http://192.168.28.10:13080
VITE_WS_URL = ws://192.168.0.201:13080/ws VITE_API_URL = http://192.168.0.100:13080
VITE_INSPECTION_TYPE = "inspection" VITE_WS_URL = ws://192.168.0.100:13080/ws
VITE_MINIO_URL = "http://192.168.28.10:9000/cmvr-iot/"

View File

@ -4,13 +4,8 @@ VITE_APP_TITLE = 招商车研物联网平台管理系统
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'staging' VITE_APP_ENV = 'staging'
# 招商车研物联网平台/测试环境 # 招商车研物联网平台管理系统/生产环境
VITE_APP_BASE_API = '/stage-api' VITE_APP_BASE_API = '/stage-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip VITE_BUILD_COMPRESS = gzip
VITE_API_URL = http://10.148.121.100:13080
VITE_WS_URL = ws://192.168.0.201:13080/ws
VITE_INSPECTION_TYPE = "inspection"
VITE_MINIO_URL = "http://192.168.28.10:9000/cmvr-iot/"

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
custom: http://doc.ruoyi.vip/ruoyi-vue/other/donate.html

5
.gitignore vendored
View File

@ -18,8 +18,9 @@ selenium-debug.log
*.njsproj *.njsproj
*.sln *.sln
*.local *.local
.history
package-lock.json package-lock.json
yarn.lock yarn.lock
bin/
.history pnpm-lock.yaml

View File

@ -1,10 +0,0 @@
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

View File

@ -1,17 +0,0 @@
# 使用官方的 Node.js 镜像作为基础镜像
FROM node:18
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json如果存在到工作目录
COPY package*.json ./
# 安装项目依赖
RUN npm install
# 复制项目文件到工作目录
COPY . .
# 构建项目
RUN npm run build

125
README.md
View File

@ -1,102 +1,35 @@
# CMVR IoT 管理控制台
招商车研物联网平台 Vue 3 管理端。项目以 RuoYi-Vue3 3.9.2 为新版基础框架,完整迁移原 `cmvr-iot-ui` 的业务页面、API、机器人模型和运行配置并连接 `RuoYi-Vue-master` 中的 Spring Boot 4 后端。 ## 前端运行
旧前端仅作为迁移来源,不参与本项目构建。
## 功能范围
- 系统管理:用户、角色、部门、岗位、菜单、字典、参数、通知和文件。
- 系统监控在线用户、操作日志、登录日志、缓存、服务、Quartz 和 Druid。
- 设备管理:设备注册、终端、机器人、相机、机械臂、灵巧手、机器人头部、麦克风、扬声器和电机。
- 测试平台:测试项目、任务配置、任务编排、检测项、任务实例和执行流程。
- 智能巡检:机器人、地图、点位、任务、执行实例、巡检结果、告警和驾驶舱。
- 交互评价:语音交互、触控交互、评价指标、方案、项目和语料。
- 智能座舱:车机界面、车辆配置、功能配置、流程编辑和执行评价。
- 语料与媒体:语料管理、音频上传、图片预览、视频播放和 WebSocket 实时消息。
- 开发工具:代码生成、表单构建和 Knife4j 接口文档。
## 技术栈
| 组件 | 版本 |
| --- | --- |
| Vue | 3.5.26 |
| Vite | 6.4.1 |
| Element Plus | 2.13.1 |
| Pinia | 3.0.4 |
| Vue Router | 4.6.4 |
| 可视化 | ECharts、Vue Flow、Three.js、Monaco Editor、XGPlayer |
## 环境要求
- Node.js 20 LTS
- pnpm 10
- 后端 `RuoYi-Vue-master/cmvr-iot-admin`
## 开发运行
先启动后端,确认 `http://localhost:13080` 可以访问,然后执行:
```bash ```bash
pnpm install # 安装依赖
pnpm dev yarn --registry=https://registry.npmmirror.com
# 启动服务
yarn dev
# 构建测试环境 yarn build:stage
# 构建生产环境 yarn build:prod
# 前端访问地址 http://localhost:80
``` ```
默认地址: ## 内置功能
- 前端:`http://localhost:80` 1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
- 后端:`http://localhost:13080` 2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
- Knife4j`http://localhost:13080/doc.html` 3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
开发配置位于 `.env.development` 5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
```dotenv 7. 参数管理:对系统动态配置常用参数。
VITE_APP_TITLE=招商车研物联网平台管理系统 8. 通知公告:系统通知公告信息发布维护。
VITE_APP_BASE_API=/dev-api 9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
VITE_API_URL=http://localhost:13080 10. 登录日志:系统登录日志记录查询包含登录异常。
VITE_WS_URL=ws://localhost:13080/ws 11. 在线用户:当前系统中活跃用户状态监控。
VITE_PORT=80 12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
``` 13. 代码生成前后端代码的生成java、html、xml、sql支持CRUD下载 。
14. 系统接口根据业务代码自动生成相关的api接口文档。
## 构建 15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
16. 缓存监控:对系统的缓存信息查询,命令统计等。
```bash 17. 在线构建器拖动表单元素生成相应的HTML代码。
pnpm build:dev 18. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
pnpm build:stage
pnpm build:prod
pnpm preview
```
生产构建输出到 `dist`。部署时需要由 Nginx 或网关转发 `/prod-api`,并支持 `/ws` WebSocket、文件上传和媒体请求。
## 目录说明
```text
public/models 机器人 URDF 和网格模型
public/inspection 巡检机械臂模型
src/adapters URDF/Xacro 模型适配
src/api REST API 封装
src/components 公共及业务组件
src/router 公共路由和隐藏业务路由
src/store Pinia 状态管理
src/views/device 设备管理与设备控制
src/views/test 测试业务
src/views/flow 流程编排
src/views/inspection 智能巡检
src/views/evaluate 评价管理
src/views/vi 语音交互
src/views/is 智能座舱
src/views/corpusManger 语料管理
```
业务菜单由后端 `/getRouters` 动态返回。数据库菜单中的 `component` 必须与 `src/views` 下的实际页面路径一致,权限字符必须与后端控制器保持一致。
## 配置说明
- `.env.development`:本地开发地址。
- `.env.staging`:测试环境地址。
- `.env.production`生产环境地址、Node-RED 和 MinIO。
- `vite.config.js`代理、端口、Monaco 和构建分包。
- `src/settings.js`:布局、页签和版权配置。
生产密码、访问密钥和内部网络配置应通过部署环境注入,不要提交新的敏感信息。

View File

@ -1,12 +0,0 @@
@echo off
echo.
echo [信息] 打包Web工程生成dist文件。
echo.
%~d0
cd %~dp0
cd ..
yarn build:prod
pause

View File

@ -1,12 +0,0 @@
@echo off
echo.
echo [信息] 安装Web工程生成node_modules文件。
echo.
%~d0
cd %~dp0
cd ..
yarn --registry=https://registry.npmmirror.com
pause

View File

@ -1,12 +0,0 @@
@echo off
echo.
echo [信息] 使用 Vite 命令运行 Web 工程。
echo.
%~d0
cd %~dp0
cd ..
yarn dev
pause

View File

@ -7,7 +7,7 @@
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<title>%VITE_APP_TITLE%</title> <title>招商车研物联网平台管理系统</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style> <style>
html, html,

View File

@ -1,35 +1,32 @@
{ {
"name": "cmvr-iot-ui", "name": "CMVR-IOT-UI",
"version": "3.9.2", "version": "3.8.8",
"description": "招商车研物联网平台管理系统前端", "description": "CMVR-IOT平台管理系统前端UI框架",
"author": "CMVR", "author": "CMVR",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build:dev": "vite build --mode development", "build:dev": "vite build --mode development",
"build:prod": "vite build", "build:prod": "vite build --mode production",
"build:stage": "vite build --mode staging", "build:test": "vite build --mode test",
"build:staging": "vite build --mode staging",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.2", "@element-plus/icons-vue": "2.3.1",
"@vue-flow/background": "^1.3.2", "@logicflow/core": "^2.0.13",
"@vue-flow/controls": "^1.1.3", "@logicflow/extension": "^2.0.18",
"@vue-flow/core": "^1.48.2",
"@vueup/vue-quill": "1.2.0", "@vueup/vue-quill": "1.2.0",
"@vueuse/core": "14.1.0", "@vueuse/core": "10.11.0",
"ace-builds": "^1.43.0", "ace-builds": "^1.43.0",
"autofit.js": "^3.2.8", "axios": "0.28.1",
"axios": "1.13.2",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"echarts": "5.6.0", "echarts": "5.5.1",
"element-plus": "2.13.1", "element-plus": "2.9.4",
"file-saver": "2.0.5", "file-saver": "2.0.5",
"fuse.js": "7.1.0", "fuse.js": "6.6.2",
"js-beautify": "1.15.4", "js-beautify": "1.14.11",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"jsencrypt": "3.3.2", "jsencrypt": "3.3.2",
"json-editor-vue": "^0.18.1", "json-editor-vue": "^0.18.1",
@ -38,38 +35,31 @@
"monaco-editor": "^0.55.1", "monaco-editor": "^0.55.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pako": "^2.1.0", "pako": "^2.1.0",
"pinia": "3.0.4", "pinia": "2.1.7",
"pnpm": "^10.23.0",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"sockjs-client": "^1.6.1", "sockjs-client": "^1.6.1",
"sortablejs": "^1.15.6",
"splitpanes": "3.1.5", "splitpanes": "3.1.5",
"three": "^0.181.2", "three": "^0.181.2",
"urdf-loader": "^0.12.6", "urdf-loader": "^0.12.6",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vue": "3.5.26", "vue": "3.4.31",
"vue-cropper": "1.1.1", "vue-cropper": "1.1.1",
"vue-router": "4.6.4", "vue-router": "4.4.0",
"vue3-ace-editor": "^2.2.4", "vue3-ace-editor": "^2.2.4",
"vue3-json-viewer": "2.3.0", "vue3-json-viewer": "2.3.0",
"vuedraggable": "4.1.0", "vuedraggable": "4.1.0",
"xacro-parser": "^0.3.11",
"xgplayer": "^3.0.22" "xgplayer": "^3.0.22"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.2.4", "@vitejs/plugin-vue": "5.0.5",
"sass-embedded": "1.97.2", "file-loader": "^6.2.0",
"unplugin-auto-import": "0.18.6", "sass": "1.77.5",
"unplugin-auto-import": "0.17.6",
"unplugin-vue-setup-extend-plus": "1.0.1", "unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "6.4.1", "vite": "5.3.2",
"vite-plugin-compression": "0.5.1", "vite-plugin-compression": "0.5.1",
"vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "2.0.1" "vite-plugin-svg-icons": "2.0.1"
}, }
"overrides": {
"quill": "2.0.2"
},
"resolutions": {
"quill": "2.0.2"
},
"packageManager": "pnpm@10.24.0"
} }

5973
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
allowBuilds:
core-js: true
es5-ext: true
esbuild: true
json-editor-vue: true
vue-demi: true

1
public/2.smap Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,54 +0,0 @@
<svg
viewBox="0 0 22 22"
xmlns="http://www.w3.org/2000/svg"
fill="none"
>
<rect width="22" height="22" x="0" y="0" />
<path
d="M11 7.33335L11 3.66669L7.33331 3.66669"
fill-rule="nonzero"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.833333"
/>
<rect
width="14.666667"
height="11.000000"
x="3.666687"
y="7.333344"
rx="1.833333"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.833333"
/>
<path
d="M1.83331 12.8333L3.66665 12.8333"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.833333"
/>
<path
d="M18.3333 12.8333L20.1666 12.8333"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.833333"
/>
<path
d="M13.75 11.9167L13.75 13.75"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.833333"
/>
<path
d="M8.25 11.9167L8.25 13.75"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.833333"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,465 +0,0 @@
<?xml version="1.0"?>
<robot name="elfin10" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:arg name="use_real_hardware" default="true" />
<xacro:arg name="use_fake_hardware" default="false" />
<!-- Load TRANSMISSION file: -->
<xacro:include filename="file://$(find elfin_description)/elfin10_transmission.xacro" />
<!-- Load GAZEBO/ROS2_CONTROL file: -->
<xacro:include filename="file://$(find elfin_description)/elfin10_gazebo.xacro" />
<xacro:property name="PI" value="3.1415926535897931"/>
<xacro:property name="density" value="3.09"/>
<!-- <xacro:macro name="elfin10"> -->
<!-- TRANSMISSION: -->
<xacro:elfin10_transmission />
<!-- GAZEBO - ROS2_CONTROL: -->
<xacro:elfin10_gazebo
use_real_hardware="$(arg use_real_hardware)"
use_fake_hardware="$(arg use_fake_hardware)"/>
<link name="world"/>
<joint name="world_base" type="fixed">
<origin
xyz="0 0 0"
rpy="0 0 -1.5708" />
<parent
link="world" />
<child
link="elfin_base_link" />
</joint>
<link name="elfin_base_link"/>
<joint name="elfin_base_joint" type="fixed">
<origin
xyz="0 0 0"
rpy="0 0 ${PI/2}" />
<parent
link="elfin_base_link" />
<child
link="elfin_base" />
</joint>
<link
name="elfin_base">
<inertial>
<origin
xyz="-0.00226511349377665 4.35718066723588E-06 0.0325128562537015"
rpy="0 0 0" />
<mass
value="1.66727740473724" />
<inertia
ixx="0.004269288474386"
ixy="6.02518130623876E-08"
ixz="-6.92087963593712E-05"
iyy="0.00385585336301628"
iyz="-5.51423740998761E-08"
izz="0.0068204473327682" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_base.STL" />
</geometry>
<material
name="">
<color
rgba="0.752941176470588 0.752941176470588 0.752941176470588 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_base.STL" />
</geometry>
</collision>
</link>
<link
name="elfin_link1">
<inertial>
<origin
xyz="7.61162863471406E-07 -0.00300107309756385 -0.0432743915921636"
rpy="0 0 0" />
<mass
value="6.3589066662676" />
<inertia
ixx="0.0391771076676677"
ixy="5.46819690041203E-08"
ixz="4.18972540516346E-07"
iyy="0.0381555138034643"
iyz="0.00661206821297882"
izz="0.0211340101406971" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link1.STL" />
</geometry>
<material
name="">
<color
rgba="0.752941176470588 0.752941176470588 0.752941176470588 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link1.STL" />
</geometry>
</collision>
</link>
<joint
name="elfin_joint1"
type="revolute">
<origin
xyz="0 0 0.26"
rpy="0 0 0" />
<parent
link="elfin_base" />
<child
link="elfin_link1" />
<axis
xyz="0 0 1" />
<limit
lower="-3.14"
upper="3.14"
effort="420"
velocity="1.57" />
</joint>
<link
name="elfin_link2">
<inertial>
<origin
xyz="-1.29217317440473E-07 0.204406209458582 0.145205671136993"
rpy="0 0 0" />
<mass
value="5.51072381215317" />
<inertia
ixx="0.140886684844275"
ixy="-1.98882818733046E-05"
ixz="1.28793862225363E-06"
iyy="0.00904523320332792"
iyz="0.00556623571013066"
izz="0.143882970137024" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link2.STL" />
</geometry>
<material
name="">
<color
rgba="0.752941176470588 0.752941176470588 0.752941176470588 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link2.STL" />
</geometry>
</collision>
</link>
<joint
name="elfin_joint2"
type="revolute">
<origin
xyz="0 0 0"
rpy="1.5708 0 0" />
<parent
link="elfin_link1" />
<child
link="elfin_link2" />
<axis
xyz="0 0 1" />
<limit
lower="-2.35"
upper="2.35"
effort="420"
velocity="1.57" />
</joint>
<link
name="elfin_link3">
<inertial>
<origin
xyz="0.0357493010718184 -2.74303992683623E-06 -0.00441107040289549"
rpy="0 0 0" />
<mass
value="3.25128088148268" />
<inertia
ixx="0.00712273850158261"
ixy="4.14573354600121E-07"
ixz="-0.00240614658407673"
iyy="0.0131139013038093"
iyz="-1.53858261863385E-07"
izz="0.0123861174875263" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link3.STL" />
</geometry>
<material
name="">
<color
rgba="0.752941176470588 0.752941176470588 0.752941176470588 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link3.STL" />
</geometry>
</collision>
</link>
<joint
name="elfin_joint3"
type="revolute">
<origin
xyz="0 0.48 0"
rpy="-3.1416 0 1.5708" />
<parent
link="elfin_link2" />
<child
link="elfin_link3" />
<axis
xyz="0 0 1" />
<limit
lower="-2.61"
upper="2.61"
effort="200"
velocity="1.57" />
</joint>
<link
name="elfin_link4">
<inertial>
<origin
xyz="3.8972274666868E-06 -0.065883038527668 0.300149807420176"
rpy="0 0 0" />
<mass
value="2.45076028364132" />
<inertia
ixx="0.0366043963749035"
ixy="2.14481800816891E-07"
ixz="1.74370308297691E-06"
iyy="0.0331005868546169"
iyz="0.00876542795872678"
izz="0.00689303506560623" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link4.STL" />
</geometry>
<material
name="">
<color
rgba="0.752941176470588 0.752941176470588 0.752941176470588 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link4.STL" />
</geometry>
</collision>
</link>
<joint
name="elfin_joint4"
type="revolute">
<origin
xyz="0 0 0"
rpy="1.5708 0 1.5708" />
<parent
link="elfin_link3" />
<child
link="elfin_link4" />
<axis
xyz="0 0 1" />
<limit
lower="-3.14"
upper="3.14"
effort="200"
velocity="1.57" />
</joint>
<link
name="elfin_link5">
<inertial>
<origin
xyz="5.37084413168812E-06 -0.0262437975997385 -0.00353971828905618"
rpy="0 0 0" />
<mass
value="1.39234539037872" />
<inertia
ixx="0.0033625821599978"
ixy="2.21090591006111E-07"
ixz="-5.5969383984273E-07"
iyy="0.00182229285631246"
iyz="0.000639319386824375"
izz="0.00292731816323284" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link5.STL" />
</geometry>
<material
name="">
<color
rgba="0.752941176470588 0.752941176470588 0.752941176470588 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link5.STL" />
</geometry>
</collision>
</link>
<joint
name="elfin_joint5"
type="revolute">
<origin
xyz="0 0 0.52"
rpy="-1.5708 0 0" />
<parent
link="elfin_link4" />
<child
link="elfin_link5" />
<axis
xyz="0 0 1" />
<limit
lower="-2.56"
upper="2.56"
effort="69"
velocity="1.57" />
</joint>
<link
name="elfin_link6">
<inertial>
<origin
xyz="-8.45231478862452E-06 -0.000388392932234965 0.144636177508013"
rpy="0 0 0" />
<mass
value="0.297803050744826" />
<inertia
ixx="0.000151919382929214"
ixy="-1.8598001023495E-09"
ixz="6.95653480101813E-08"
iyy="0.000155217204136347"
iyz="-6.63866631888914E-08"
izz="0.00027009779924127" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link6.STL" />
</geometry>
<material
name="">
<color
rgba="0.752941176470588 0.752941176470588 0.752941176470588 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="file://$(find elfin_description)/meshes/elfin10/elfin_link6.STL" />
</geometry>
</collision>
</link>
<joint
name="elfin_joint6"
type="revolute">
<origin
xyz="0 0 0"
rpy="1.5708 0 0" />
<parent
link="elfin_link5" />
<child
link="elfin_link6" />
<axis
xyz="0 0 1" />
<limit
lower="-3.14"
upper="3.14"
effort="69"
velocity="1.57" />
</joint>
<link
name="elfin_end_link"/>
<joint
name="elfin_end_joint"
type="fixed">
<origin
xyz="0 0.0 0.1735"
rpy="0 0 0" />
<parent
link="elfin_link6" />
<child
link="elfin_end_link" />
<axis
xyz="0 0 0" />
</joint>
<link name="elfin_dummy_gripper"/>
<joint name="elfin_dummy_joint" type="fixed">
<origin
xyz="0 0.109 0"
rpy="${-PI/2} ${-PI/2} 0" />
<parent
link="elfin_link6" />
<child
link="elfin_dummy_gripper" />
</joint>
</robot>

View File

@ -1,134 +0,0 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="elfin10_gazebo"
params="use_fake_hardware:=false
use_real_hardware:=true">
<gazebo>
<plugin filename="libgazebo_ros2_control.so" name="gazebo_ros2_control">
<robot_sim_type>gazebo_ros2_control/GazeboSystem</robot_sim_type>
<parameters>$(find elfin10_ros2_gazebo)/config/elfin_arm_controller.yaml</parameters>
</plugin>
</gazebo>
<ros2_control name="GazeboSystem" type="system">
<hardware>
<xacro:if value="${use_real_hardware}">
<plugin>elfin_hardware_interface/ElfinHWInterface</plugin>
</xacro:if>
<xacro:unless value="${use_real_hardware}">
<xacro:if value="${use_fake_hardware}">
<plugin>fake_components/GenericSystem</plugin>
</xacro:if>
<xacro:unless value="${use_fake_hardware}">
<plugin>gazebo_ros2_control/GazeboSystem</plugin>
</xacro:unless>
</xacro:unless>
</hardware>
<joint name="elfin_joint1">
<command_interface name="position">
<param name="min">-3.14</param>
<param name="max">3.14</param>
</command_interface>
<command_interface name="velocity">
<param name="min">-1.57</param>
<param name="max">1.57</param>
</command_interface>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="elfin_joint2">
<command_interface name="position">
<param name="min">-2.35</param>
<param name="max">2.35</param>
</command_interface>
<command_interface name="velocity">
<param name="min">-1.57</param>
<param name="max">1.57</param>
</command_interface>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="elfin_joint3">
<command_interface name="position">
<param name="min">-2.61</param>
<param name="max">2.61</param>
</command_interface>
<command_interface name="velocity">
<param name="min">-1.57</param>
<param name="max">1.57</param>
</command_interface>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="elfin_joint4">
<command_interface name="position">
<param name="min">-3.14</param>
<param name="max">3.14</param>
</command_interface>
<command_interface name="velocity">
<param name="min">-1.57</param>
<param name="max">1.57</param>
</command_interface>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="elfin_joint5">
<command_interface name="position">
<param name="min">-2.56</param>
<param name="max">2.56</param>
</command_interface>
<command_interface name="velocity">
<param name="min">-1.57</param>
<param name="max">1.57</param>
</command_interface>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="elfin_joint6">
<command_interface name="position">
<param name="min">-3.14</param>
<param name="max">3.14</param>
</command_interface>
<command_interface name="velocity">
<param name="min">-1.57</param>
<param name="max">1.57</param>
</command_interface>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
</ros2_control>
<gazebo reference="elfin_link2">
<visual name='visual'>
<material>
<script>
<uri>file://media/materials/scripts/gazebo.material</uri>
<name>Gazebo/Grey</name>
</script>
</material>
</visual>
</gazebo>
<gazebo reference="elfin_link4">
<visual name='visual'>
<material>
<script>
<uri>file://media/materials/scripts/gazebo.material</uri>
<name>Gazebo/Grey</name>
</script>
</material>
</visual>
</gazebo>
</xacro:macro>
</robot>

View File

@ -1,69 +0,0 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:macro name="elfin10_transmission" >
<transmission name="elfin_joint1_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="elfin_joint1">
<hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
</joint>
<actuator name="elfin_joint1_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
<transmission name="elfin_joint2_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="elfin_joint2">
<hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
</joint>
<actuator name="elfin_joint2_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
<transmission name="elfin_joint3_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="elfin_joint3">
<hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
</joint>
<actuator name="elfin_joint3_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
<transmission name="elfin_joint4_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="elfin_joint4">
<hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
</joint>
<actuator name="elfin_joint4_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
<transmission name="elfin_joint5_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="elfin_joint5">
<hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
</joint>
<actuator name="elfin_joint5_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
<transmission name="elfin_joint6_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="elfin_joint6">
<hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
</joint>
<actuator name="elfin_joint6_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
</xacro:macro>
</robot>

View File

@ -1,461 +0,0 @@
/**
* URDF Adapter
* Converts urdf-loaders result to unified model
*/
import * as THREE from 'three';
import { UnifiedRobotModel, Link, Joint, JointLimits, VisualGeometry, CollisionGeometry, InertialProperties } from '@/models/UnifiedRobotModel.js';
export class URDFAdapter {
/**
* Convert urdf-loaders robot object to unified model
* @param {THREE.Group} robot - Robot object returned by urdf-loaders
* @param {string} urdfXML - Original URDF XML content (optional, for extracting inertial data)
* @returns {UnifiedRobotModel}
*/
static convert(robot, urdfXML = null) {
const model = new UnifiedRobotModel();
model.name = robot.name || 'robot';
model.threeObject = robot;
// Mark model type as URDF
if (!robot.userData) robot.userData = {};
robot.userData.type = 'urdf';
if (!robot.links || !robot.joints) {
console.warn('URDF model missing links or joints information');
return model;
}
// If XML provided, parse inertial data
let inertialData = {};
if (urdfXML) {
inertialData = this.parseInertialFromXML(urdfXML);
}
// Convert links
Object.values(robot.links).forEach(urdfLink => {
const link = this.convertLink(urdfLink);
// If urdf-loader didn't parse inertial, get from XML
if (!link.inertial && inertialData[urdfLink.name]) {
link.inertial = inertialData[urdfLink.name];
}
model.addLink(link);
});
// Convert joints
Object.values(robot.joints).forEach(urdfJoint => {
const joint = this.convertJoint(urdfJoint);
model.addJoint(joint);
});
// If XML provided, supplement effort and velocity from XML (urdf-loaders may not have parsed)
if (urdfXML) {
this.supplementJointLimitsFromXML(model, urdfXML);
}
// Find root link (link that is not a child of any joint)
const allChildren = new Set(
Array.from(model.joints.values()).map(j => j.child).filter(c => c)
);
const rootLinks = Array.from(model.links.keys()).filter(
name => !allChildren.has(name)
);
if (rootLinks.length > 0) {
model.rootLink = rootLinks[0];
}
// Enhance materials for better lighting (MuJoCo style)
this.enhanceMaterials(robot);
return model;
}
/**
* Enhance materials for better lighting (MuJoCo style)
* Applies consistent shininess and specular properties to all materials
* Directly modifies materials in place to ensure changes persist
*/
static enhanceMaterials(robotObject) {
robotObject.traverse((child) => {
if (child.isMesh && child.material) {
// Handle material arrays
if (Array.isArray(child.material)) {
child.material = child.material.map(mat => {
return this.enhanceSingleMaterial(mat);
});
} else {
child.material = this.enhanceSingleMaterial(child.material);
}
}
});
}
/**
* Enhance a single material with better lighting properties
* Returns enhanced material (may be cloned or modified in place)
* Saves original properties for lighting toggle
*/
static enhanceSingleMaterial(material) {
if (material.isMeshPhongMaterial || material.isMeshStandardMaterial) {
// Save original properties if not already saved (for lighting toggle)
if (material.userData.originalShininess === undefined) {
material.userData.originalShininess = material.shininess !== undefined ? material.shininess : 30;
// IMPORTANT: Save original color to preserve URDF material colors
if (!material.userData.originalColor && material.color) {
material.userData.originalColor = material.color.clone();
}
// Save original specular - if material had no specular, save null
if (!material.specular) {
material.userData.originalSpecular = null;
} else if (material.specular.isColor) {
const spec = material.specular;
if (spec.r < 0.1 && spec.g < 0.1 && spec.b < 0.1) {
material.userData.originalSpecular = null; // Likely default
} else {
material.userData.originalSpecular = spec.clone();
}
} else if (typeof material.specular === 'number') {
if (material.specular === 0x111111 || material.specular < 0x111111) {
material.userData.originalSpecular = null;
} else {
material.userData.originalSpecular = new THREE.Color(material.specular);
}
} else {
material.userData.originalSpecular = null;
}
}
// IMPORTANT: Preserve original color from URDF material definitions
// Do not modify material.color - urdf-loaders already sets the correct color
// Apply enhanced lighting (default enabled)
// Increase shininess for better highlights
if (material.shininess === undefined || material.shininess < 50) {
material.shininess = 50;
}
// Enhance specular reflection - ensure it's a Color object with proper values
if (!material.specular) {
material.specular = new THREE.Color(0.3, 0.3, 0.3);
} else if (material.specular.isColor) {
// If it's already a Color object, update values
if (material.specular.r < 0.2 || material.specular.g < 0.2 || material.specular.b < 0.2) {
material.specular.setRGB(0.3, 0.3, 0.3);
}
} else if (typeof material.specular === 'number') {
// Convert number to Color object
if (material.specular < 0x333333) {
material.specular = new THREE.Color(0.3, 0.3, 0.3);
} else {
// Convert hex to Color
material.specular = new THREE.Color(material.specular);
if (material.specular.r < 0.2) {
material.specular.setRGB(0.3, 0.3, 0.3);
}
}
}
// Mark material as needing update
material.needsUpdate = true;
return material;
} else if (material.type === 'MeshBasicMaterial') {
// Convert MeshBasicMaterial to MeshPhongMaterial
const oldMaterial = material;
const envMap = typeof window !== 'undefined' && window.app?.sceneManager?.environmentManager?.getEnvironmentMap();
const newMaterial = new THREE.MeshPhongMaterial({
color: oldMaterial.color,
map: oldMaterial.map,
transparent: oldMaterial.transparent,
opacity: oldMaterial.opacity,
side: oldMaterial.side,
shininess: 50,
specular: new THREE.Color(0.3, 0.3, 0.3),
envMap: envMap || null,
reflectivity: envMap ? 0.3 : 0
});
// Save original properties for lighting toggle
newMaterial.userData.originalShininess = 30;
newMaterial.userData.originalSpecular = null; // MeshBasicMaterial had no specular
return newMaterial;
}
return material;
}
static convertLink(urdfLink) {
const link = new Link(urdfLink.name);
link.threeObject = urdfLink;
// Convert inertial properties
if (urdfLink.inertial) {
link.inertial = this.convertInertial(urdfLink.inertial);
}
// Note: urdf-loaders has already converted visual and collision to Three.js objects
// We mainly extract metadata, actual meshes are in threeObject
return link;
}
static convertJoint(urdfJoint) {
// URDF joint object has jointType property (not type)
const jointType = urdfJoint.jointType || urdfJoint.type || 'fixed';
const joint = new Joint(urdfJoint.name, jointType);
// Extract parent name (urdf-loader returns Three.js object reference)
joint.parent = urdfJoint.parent?.name || null;
// child may not be in urdfJoint.child, but in Three.js children array
// In Three.js scene graph, Joint is parent node, Child Link is Joint's child node
if (urdfJoint.child && urdfJoint.child.name) {
joint.child = urdfJoint.child.name;
} else if (urdfJoint.children && urdfJoint.children.length > 0) {
// Find Link object from children array
const childLink = urdfJoint.children.find(child =>
child.isURDFLink || child.type === 'URDFLink'
);
if (childLink) {
joint.child = childLink.name;
}
}
joint.threeObject = urdfJoint;
// Convert origin
if (urdfJoint.origin) {
joint.origin = {
xyz: urdfJoint.origin.xyz || [0, 0, 0],
rpy: urdfJoint.origin.rpy || [0, 0, 0]
};
}
// Convert axis
if (urdfJoint.axis) {
joint.axis = { xyz: urdfJoint.axis.xyz || [0, 0, 1] };
}
// Convert limits
if (urdfJoint.limit) {
joint.limits = this.convertLimits(urdfJoint.limit);
}
// Get current value
if (urdfJoint.angle !== undefined) {
joint.currentValue = urdfJoint.angle;
} else if (urdfJoint.jointValue !== undefined) {
joint.currentValue = urdfJoint.jointValue;
}
return joint;
}
static convertLimits(limit) {
const limits = new JointLimits();
if (limit.lower !== undefined) limits.lower = limit.lower;
if (limit.upper !== undefined) limits.upper = limit.upper;
if (limit.effort !== undefined) limits.effort = limit.effort;
if (limit.velocity !== undefined) limits.velocity = limit.velocity;
return limits;
}
/**
* Supplement joint effort and velocity information from URDF XML
* (because urdf-loaders may not have parsed these attributes)
*/
static supplementJointLimitsFromXML(model, urdfXML) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(urdfXML, 'text/xml');
const jointElements = doc.querySelectorAll('joint');
jointElements.forEach(jointEl => {
const jointName = jointEl.getAttribute('name');
const joint = model.joints.get(jointName);
if (joint) {
const limitEl = jointEl.querySelector('limit');
if (limitEl) {
if (!joint.limits) {
joint.limits = new JointLimits();
}
// Read effort and velocity
const effort = limitEl.getAttribute('effort');
const velocity = limitEl.getAttribute('velocity');
if (effort !== null) {
joint.limits.effort = parseFloat(effort);
}
if (velocity !== null) {
joint.limits.velocity = parseFloat(velocity);
}
// Also ensure lower and upper are correctly set
const lower = limitEl.getAttribute('lower');
const upper = limitEl.getAttribute('upper');
if (lower !== null) {
joint.limits.lower = parseFloat(lower);
}
if (upper !== null) {
joint.limits.upper = parseFloat(upper);
}
}
}
});
} catch (error) {
console.error('Failed to supplement joint limit information from XML:', error);
}
}
static convertInertial(inertial) {
const props = new InertialProperties();
if (inertial.mass !== undefined) props.mass = inertial.mass;
if (inertial.origin) {
props.origin = {
xyz: inertial.origin.xyz || [0, 0, 0],
rpy: inertial.origin.rpy || [0, 0, 0]
};
}
if (inertial.ixx !== undefined) props.ixx = inertial.ixx;
if (inertial.iyy !== undefined) props.iyy = inertial.iyy;
if (inertial.izz !== undefined) props.izz = inertial.izz;
if (inertial.ixy !== undefined) props.ixy = inertial.ixy;
if (inertial.ixz !== undefined) props.ixz = inertial.ixz;
if (inertial.iyz !== undefined) props.iyz = inertial.iyz;
return props;
}
/**
* Parse inertial data from URDF XML
* @param {string} xmlContent - URDF XML content
* @returns {Object} Mapping from link names to inertial data
*/
static parseInertialFromXML(xmlContent) {
const inertialData = {};
try {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlContent, 'text/xml');
// Get all link elements
const links = xmlDoc.getElementsByTagName('link');
for (let i = 0; i < links.length; i++) {
const linkElement = links[i];
const linkName = linkElement.getAttribute('name');
// Find inertial element
const inertialElement = linkElement.getElementsByTagName('inertial')[0];
if (!inertialElement) continue;
const inertial = new InertialProperties();
// Parse mass
const massElement = inertialElement.getElementsByTagName('mass')[0];
if (massElement) {
inertial.mass = parseFloat(massElement.getAttribute('value'));
}
// Parse origin
const originElement = inertialElement.getElementsByTagName('origin')[0];
if (originElement) {
const xyz = originElement.getAttribute('xyz');
const rpy = originElement.getAttribute('rpy');
inertial.origin = {
xyz: xyz ? xyz.split(' ').map(parseFloat) : [0, 0, 0],
rpy: rpy ? rpy.split(' ').map(parseFloat) : [0, 0, 0]
};
} else {
inertial.origin = { xyz: [0, 0, 0], rpy: [0, 0, 0] };
}
// Parse inertia
const inertiaElement = inertialElement.getElementsByTagName('inertia')[0];
if (inertiaElement) {
inertial.ixx = parseFloat(inertiaElement.getAttribute('ixx')) || 0;
inertial.iyy = parseFloat(inertiaElement.getAttribute('iyy')) || 0;
inertial.izz = parseFloat(inertiaElement.getAttribute('izz')) || 0;
inertial.ixy = parseFloat(inertiaElement.getAttribute('ixy')) || 0;
inertial.ixz = parseFloat(inertiaElement.getAttribute('ixz')) || 0;
inertial.iyz = parseFloat(inertiaElement.getAttribute('iyz')) || 0;
}
inertialData[linkName] = inertial;
}
} catch (error) {
console.error('Failed to parse URDF XML inertial data:', error);
}
return inertialData;
}
/**
* Set joint angle (using urdf-loaders' setJointValue method)
* Reference URDFClasses.js setJointValue implementation
*/
static setJointAngle(joint, angle, ignoreLimits = false) {
joint.currentValue = angle;
// URDF format: use urdf-loader's setJointValue method
if (joint.threeObject) {
// If ignoring limits, temporarily modify URDF joint object's limit values
let originalLimits = null;
if (ignoreLimits && joint.threeObject.limit) {
originalLimits = {
lower: joint.threeObject.limit.lower,
upper: joint.threeObject.limit.upper
};
joint.threeObject.limit.lower = -Math.PI * 2;
joint.threeObject.limit.upper = Math.PI * 2;
}
// Prefer setJointValue method (urdf-loader's standard method)
if (typeof joint.threeObject.setJointValue === 'function') {
joint.threeObject.setJointValue(angle);
// Restore original limits
if (originalLimits && joint.threeObject.limit) {
joint.threeObject.limit.lower = originalLimits.lower;
joint.threeObject.limit.upper = originalLimits.upper;
}
return;
} else if (typeof joint.threeObject.setAngle === 'function') {
joint.threeObject.setAngle(angle);
// Restore original limits
if (originalLimits && joint.threeObject.limit) {
joint.threeObject.limit.lower = originalLimits.lower;
joint.threeObject.limit.upper = originalLimits.upper;
}
return;
}
// Restore original limits (if none of the above methods executed)
if (originalLimits && joint.threeObject.limit) {
joint.threeObject.limit.lower = originalLimits.lower;
joint.threeObject.limit.upper = originalLimits.upper;
}
// If none available, manually set rotation
// Note: For URDF, we usually shouldn't reach here, as urdf-loader should provide setJointValue
console.warn(`Joint ${joint.name} has no setJointValue or setAngle method, attempting manual setup`);
if (joint.type === 'revolute' || joint.type === 'continuous') {
const axis = joint.axis ? new THREE.Vector3(...joint.axis.xyz).normalize() : new THREE.Vector3(0, 0, 1);
// Note: joint.threeObject may not be a Three.js object, cannot directly set rotation
if (joint.threeObject.rotation) {
joint.threeObject.rotation.setFromAxisAngle(axis, angle);
}
} else if (joint.type === 'prismatic') {
const axis = joint.axis ? new THREE.Vector3(...joint.axis.xyz).normalize() : new THREE.Vector3(1, 0, 0);
if (joint.threeObject.position) {
joint.threeObject.position.copy(axis.multiplyScalar(angle));
}
}
} else {
console.warn(`Joint ${joint.name} has no threeObject`);
}
}
}

View File

@ -1,616 +0,0 @@
/**
* Xacro Adapter
*
* Parses ROS Xacro files and converts them to URDF format using xacro-parser.
*
* Supported Features:
* - Property definitions and substitutions (xacro:property)
* - Macro definitions with parameters (xacro:macro)
* - Conditional blocks (xacro:if, xacro:unless)
* - File inclusions (xacro:include) with package:// and relative paths
* - Python-style boolean constants (True/False)
* - Arithmetic expressions and property evaluation
*
* Compatibility:
* - ROS Jade and later (inOrder=true, requirePrefix=true, localProperties=true)
* - Automatically injects True/False constants for compatibility with ROS xacro files
*
* Usage:
* - Upload all related xacro files (main file and included files)
* - The adapter automatically resolves file inclusions from the uploaded file map
* - Mesh files are resolved using package:// paths or relative paths
*/
import { XacroParser } from 'xacro-parser';
import { URDFAdapter } from './URDFAdapter.js';
export class XacroAdapter {
/**
* Parse xacro content and convert to unified model
* @param {string} xacroContent - Xacro file content
* @param {string} fileName - Xacro file name (for working path)
* @param {Map} fileMap - File map (path -> File object)
* @param {File} file - Original file object (optional)
* @returns {Promise<UnifiedRobotModel>}
*/
static async parse(xacroContent, fileName, fileMap = null, file = null) {
try {
// Create xacro parser
const parser = new XacroParser();
// Configure parser for ROS Jade and later (default settings)
parser.inOrder = true;
parser.requirePrefix = true;
parser.localProperties = true;
// Set working path (directory where xacro file is located)
const workingPath = fileName.includes('/')
? fileName.substring(0, fileName.lastIndexOf('/') + 1)
: '';
parser.workingPath = workingPath;
// Inject Python-style boolean constants as xacro properties
// Some xacro files use True/False (capitalized) in conditions
xacroContent = this.injectBooleanConstants(xacroContent);
// Extract and set xacro arguments with their default values
// Also add common ROS arguments that might be used without definition
const xacroArgs = this.extractXacroArguments(xacroContent);
// Add common ROS arguments with sensible defaults if not already defined
if (!xacroArgs.hasOwnProperty('DEBUG')) {
xacroArgs.DEBUG = 'false';
}
if (!xacroArgs.hasOwnProperty('SELF_COLLIDE')) {
xacroArgs.SELF_COLLIDE = 'false';
}
parser.arguments = xacroArgs;
// If fileMap provided, setup custom file loader
if (fileMap) {
parser.getFileContents = async (path) => {
return await this.loadFileFromMap(path, fileMap, workingPath);
};
}
// Parse xacro to URDF XML
const urdfXML = await parser.parse(xacroContent);
// Convert XMLDocument to string
const serializer = new XMLSerializer();
let urdfString = serializer.serializeToString(urdfXML);
// Clean up the XML string - remove empty xmlns attributes that might cause issues
urdfString = urdfString.replace(/\sxmlns=""/g, '');
// Re-parse the URDF string with DOMParser to create a clean XMLDocument
// This removes any xacro-specific nodes that urdf-loader might not handle
const domParser = new DOMParser();
const cleanUrdfXML = domParser.parseFromString(urdfString, 'text/xml');
// Check for parsing errors
const parseError = cleanUrdfXML.querySelector('parsererror');
if (parseError) {
throw new Error('Generated URDF has invalid XML: ' + parseError.textContent);
}
// Remove problematic elements that urdf-loader might not handle well
// Remove gazebo elements (plugins, sensors, etc.)
const gazeboElements = cleanUrdfXML.querySelectorAll('gazebo');
gazeboElements.forEach(el => el.parentNode?.removeChild(el));
// Remove transmission elements (these are for Gazebo/ROS control)
const transmissionElements = cleanUrdfXML.querySelectorAll('transmission');
transmissionElements.forEach(el => el.parentNode?.removeChild(el));
// Remove visual/collision elements with empty geometry tags
this.removeEmptyGeometry(cleanUrdfXML);
// Clean up empty text nodes and comments that might cause issues
this.cleanXMLNodes(cleanUrdfXML.documentElement);
// Convert the clean XMLDocument back to string for URDFLoader
const finalUrdfString = serializer.serializeToString(cleanUrdfXML);
// Now use existing URDF loading infrastructure
// Import URDFLoader dynamically
const urdfModule = await import('urdf-loader');
const URDFLoader = urdfModule.URDFLoader || urdfModule.default || urdfModule;
return new Promise((resolve, reject) => {
const loader = new URDFLoader();
loader.parseCollision = true;
// Extract directory where URDF file is located
const urdfDir = workingPath;
// If file map provided, setup resource loader (same as URDF loading)
if (fileMap) {
// Parse URDF content, find all used package names
const packages = this.extractPackagesFromURDF(finalUrdfString);
// Build package map
const packageMap = {};
packages.forEach(pkg => {
packageMap[pkg] = pkg;
});
packageMap[''] = '';
loader.packages = packageMap;
// Set URL Modifier (same as URDF loading)
const urlModifier = (url) => {
// Handle blob URLs
if (url.startsWith('blob:')) {
const blobMatch = url.match(/^blob:https?:\/\/[^\/]+\/(.+)$/);
if (blobMatch && blobMatch[1]) {
const fileName = blobMatch[1];
if (/\.(jpg|jpeg|png|gif|bmp|tga|tiff|webp|dae|stl|obj|gltf|glb)$/i.test(fileName)) {
url = fileName;
} else {
return url;
}
} else {
return url;
}
}
const isTextureFile = /\.(jpg|jpeg|png|gif|bmp|tga|tiff|webp)$/i.test(url);
const isMeshFile = /\.(dae|stl|obj|gltf|glb)$/i.test(url);
let meshPath = url;
// Remove http:// or https:// prefix
if (meshPath.startsWith('http://') || meshPath.startsWith('https://')) {
try {
const urlObj = new URL(meshPath);
meshPath = urlObj.pathname;
if (meshPath.startsWith('/')) {
meshPath = meshPath.substring(1);
}
} catch (e) {
// Invalid URL, use as-is
}
}
// Remove package:// prefix
if (meshPath.startsWith('package://')) {
meshPath = meshPath.replace(/^package:\/\//, '');
const parts = meshPath.split('/');
if (parts.length > 1) {
meshPath = parts.slice(1).join('/');
}
}
// Remove leading ./
meshPath = meshPath.replace(/^\.\//, '');
// Handle relative paths
let normalizedPath = meshPath;
if (meshPath.includes('../')) {
const parts = meshPath.split('/');
const resolvedParts = [];
for (const part of parts) {
if (part === '..') {
resolvedParts.pop();
} else if (part !== '.' && part !== '') {
resolvedParts.push(part);
}
}
normalizedPath = resolvedParts.join('/');
}
// Build full path
const fullPath = urdfDir + normalizedPath;
const altPath = urdfDir + meshPath;
// Find file in fileMap
let matchedFile = fileMap.get(fullPath);
if (!matchedFile && altPath !== fullPath) {
matchedFile = fileMap.get(altPath);
}
if (!matchedFile) {
matchedFile = fileMap.get(normalizedPath);
}
if (!matchedFile) {
matchedFile = fileMap.get(meshPath);
}
if (!matchedFile) {
const targetFileName = normalizedPath.split('/').pop() || meshPath.split('/').pop();
for (const [key, file] of fileMap.entries()) {
const keyFileName = key.split('/').pop();
if (keyFileName === targetFileName) {
matchedFile = file;
break;
}
}
}
if (matchedFile) {
const bloburl = URL.createObjectURL(matchedFile);
return bloburl;
}
return url;
};
loader.manager.setURLModifier(urlModifier);
// Custom loadMeshCb (same as URDF loading)
const originalLoadMeshCb = loader.loadMeshCb || loader.defaultMeshLoader.bind(loader);
loader.loadMeshCb = (path, manager, done) => {
this.findFileInMapByPath(path, fileMap, urdfDir).then(file => {
if (file) {
const ext = (file.name || path).toLowerCase().split('.').pop();
this.loadMeshFileAsync(file, ext, manager).then(meshObject => {
if (meshObject) {
done(meshObject, null);
} else {
done(null, new Error(`Failed to load mesh file: ${path}`));
}
}).catch(err => {
console.error(`Failed to load mesh: ${path}`, err);
done(null, err);
});
} else {
let stlPath = path
stlPath = stlPath.replace('file:///', 'inspection/elfin10')
originalLoadMeshCb(path, manager, done);
}
}).catch(error => {
console.error(`Failed to find file: ${path}`, error);
originalLoadMeshCb(path, manager, done);
});
};
}
// Create temporary URL for URDF content
const blob = new Blob([finalUrdfString], { type: 'text/xml' });
const url = URL.createObjectURL(blob);
loader.load(url, (robot) => {
URL.revokeObjectURL(url);
// Convert to unified model using URDFAdapter
try {
const model = URDFAdapter.convert(robot, finalUrdfString);
resolve(model);
} catch (error) {
console.error('[XacroAdapter] URDF conversion error:', error);
reject(new Error('URDF conversion failed: ' + error.message));
}
}, undefined, (error) => {
URL.revokeObjectURL(url);
console.error('[XacroAdapter] URDF loading error:', error);
reject(new Error('URDF loading failed: ' + (error.message || error)));
});
});
} catch (error) {
console.error('[XacroAdapter] Xacro parsing error:', error);
throw new Error('Xacro parsing failed: ' + error.message);
}
}
/**
* Load file from fileMap for xacro includes
* @param {string} path - File path
* @param {Map} fileMap - File map
* @param {string} workingPath - Working directory
* @returns {Promise<string>}
*/
static async loadFileFromMap(path, fileMap, workingPath) {
// Clean path - remove leading slash if present
let cleanPath = path;
// if (cleanPath.startsWith('/')) {
// cleanPath = cleanPath.substring(1);
// }
// // Remove leading ./
// cleanPath = cleanPath.replace(/^\.\//, '');
cleanPath = cleanPath.split('///')[1];
// Try different path combinations
const possiblePaths = [
cleanPath,
workingPath + cleanPath,
path,
workingPath + path.replace(/^\/+/, ''),
// Also try without the first directory component
cleanPath.includes('/') ? cleanPath.substring(cleanPath.indexOf('/') + 1) : cleanPath,
];
// Try each path
for (const tryPath of possiblePaths) {
const file = fileMap.get(tryPath);
if (file) {
const content = await file.text();
return content;
}
}
// Try filename only match
const fileName = cleanPath.split('/').pop();
for (const [key, file] of fileMap.entries()) {
const keyFileName = key.split('/').pop();
if (keyFileName === fileName) {
const content = await file.text();
return content;
}
}
console.error('[XacroAdapter] Cannot find included file:', path);
throw new Error(`Cannot find included file: ${path}`);
}
/**
* Remove visual/collision elements with empty geometry tags
* xacro-parser may not fully expand conditional geometry (xacro:if)
* We'll just remove these empty visual elements rather than try to fix them
* @param {XMLDocument} xmlDoc - XML document to clean
*/
static removeEmptyGeometry(xmlDoc) {
// Find all geometry elements
const geometryElements = xmlDoc.querySelectorAll('geometry');
geometryElements.forEach(geom => {
// Check if geometry is empty (no child elements)
if (!geom.children || geom.children.length === 0) {
// Find the parent visual or collision element
let parent = geom.parentNode;
if (parent) {
const grandParent = parent.parentNode;
if (grandParent) {
if (parent.nodeName === 'visual' || parent.nodeName === 'collision') {
// Remove empty visual/collision elements
grandParent.removeChild(parent);
}
}
}
}
});
}
/**
* Recursively clean XML nodes - remove empty text nodes and comments
* @param {Element} element - Element to clean
*/
static cleanXMLNodes(element) {
if (!element || !element.childNodes) return;
const nodesToRemove = [];
// Collect nodes to remove
for (let i = 0; i < element.childNodes.length; i++) {
const node = element.childNodes[i];
// Remove comments
if (node.nodeType === Node.COMMENT_NODE) {
nodesToRemove.push(node);
}
// Remove empty or whitespace-only text nodes
else if (node.nodeType === Node.TEXT_NODE) {
if (!node.nodeValue || node.nodeValue.trim() === '') {
nodesToRemove.push(node);
}
}
// Recursively clean element nodes
else if (node.nodeType === Node.ELEMENT_NODE) {
this.cleanXMLNodes(node);
}
}
// Remove collected nodes
nodesToRemove.forEach(node => {
element.removeChild(node);
});
}
/**
* Extract package names from URDF content
* @param {string} urdfContent - URDF content
* @returns {Set<string>}
*/
static extractPackagesFromURDF(urdfContent) {
const packages = new Set();
const packageRegex = /package:\/\/([^\/]+)/g;
let match;
while ((match = packageRegex.exec(urdfContent)) !== null) {
packages.add(match[1]);
}
return packages;
}
/**
* Find file in fileMap by path
* @param {string} path - File path
* @param {Map} fileMap - File map
* @param {string} urdfDir - URDF directory
* @returns {Promise<File|null>}
*/
static async findFileInMapByPath(path, fileMap, urdfDir) {
let meshPath = path;
// Remove blob: prefix
meshPath = meshPath.replace(/^blob:[^\/]+\//, '');
// Remove package:// prefix
if (meshPath.startsWith('package://')) {
meshPath = meshPath.replace(/^package:\/\//, '');
const parts = meshPath.split('/');
if (parts.length > 1) {
meshPath = parts.slice(1).join('/');
}
}
// Remove leading ./
meshPath = meshPath.replace(/^\.\//, '');
// Build full path
const fullPath = urdfDir + meshPath;
// Try full path
let file = fileMap.get(fullPath);
if (file) return file;
// Try path without directory
file = fileMap.get(meshPath);
if (file) return file;
// Try filename match
const targetFileName = meshPath.split('/').pop();
for (const [key, f] of fileMap.entries()) {
const keyFileName = key.split('/').pop();
if (keyFileName === targetFileName) {
return f;
}
}
return null;
}
/**
* Load mesh file asynchronously
* @param {File} file - File object
* @param {string} ext - File extension
* @param {THREE.LoadingManager} manager - Loading manager
* @returns {Promise<THREE.Object3D>}
*/
static async loadMeshFileAsync(file, ext, manager) {
const THREE = await import('three');
const blobUrl = URL.createObjectURL(file);
try {
let meshObject = null;
switch (ext) {
case 'stl': {
const { STLLoader } = await import('three/examples/jsm/loaders/STLLoader.js');
const stlLoader = new STLLoader(manager);
const stlGeometry = await new Promise((resolve, reject) => {
stlLoader.load(blobUrl, resolve, undefined, reject);
});
const stlMaterial = new THREE.MeshPhongMaterial();
meshObject = new THREE.Mesh(stlGeometry, stlMaterial);
break;
}
case 'dae': {
const { ColladaLoader } = await import('three/examples/jsm/loaders/ColladaLoader.js');
const colladaLoader = new ColladaLoader(manager);
const colladaModel = await new Promise((resolve, reject) => {
colladaLoader.load(blobUrl, resolve, undefined, reject);
});
meshObject = colladaModel.scene;
// Remove lights
if (meshObject && meshObject.traverse) {
const lightsToRemove = [];
meshObject.traverse(child => {
if (child.isLight) {
lightsToRemove.push(child);
}
});
lightsToRemove.forEach(light => {
if (light.parent) {
light.parent.remove(light);
}
});
}
break;
}
case 'obj': {
const { OBJLoader } = await import('three/examples/jsm/loaders/OBJLoader.js');
const objLoader = new OBJLoader(manager);
meshObject = await new Promise((resolve, reject) => {
objLoader.load(blobUrl, resolve, undefined, reject);
});
break;
}
case 'gltf':
case 'glb': {
const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js');
const gltfLoader = new GLTFLoader(manager);
const gltfModel = await new Promise((resolve, reject) => {
gltfLoader.load(blobUrl, resolve, undefined, reject);
});
meshObject = gltfModel.scene;
break;
}
default:
console.warn(`Unsupported file format: ${ext}`);
URL.revokeObjectURL(blobUrl);
return null;
}
URL.revokeObjectURL(blobUrl);
return meshObject;
} catch (error) {
URL.revokeObjectURL(blobUrl);
console.error(`Failed to load mesh file: ${file.name}`, error);
throw error;
}
}
/**
* Inject Python-style boolean constants (True/False) into xacro content
* Some xacro files use capitalized True/False in conditional expressions
* The parameters are passed as strings ("True", "False") but compared to constants
* We define the constants to match the string values for comparison to work
* @param {string} xacroContent - Original xacro content
* @returns {string} - Modified xacro content with boolean constants defined
*/
static injectBooleanConstants(xacroContent) {
// Find the robot tag opening
const robotTagMatch = xacroContent.match(/(<robot[^>]*>)/);
if (!robotTagMatch) {
return xacroContent;
}
// Define True/False as string properties for comparison
// When mirror_dae="True" is passed, it becomes the string "True"
// We want ${mirror_dae == True} to work, so True must also be "True"
const booleanProperties = `
<!-- Injected by XacroAdapter: Python-style boolean constants -->
<xacro:property name="True" value="True"/>
<xacro:property name="False" value="False"/>
`;
const insertPosition = robotTagMatch.index + robotTagMatch[0].length;
return xacroContent.substring(0, insertPosition) +
booleanProperties +
xacroContent.substring(insertPosition);
}
/**
* Extract xacro arguments and their default values from xacro content
* Xacro files can define arguments using <xacro:arg name="..." default="..."/>
* These need to be provided to the parser via parser.arguments
* @param {string} xacroContent - Xacro file content
* @returns {Object} - Object with argument names as keys and default values
*/
static extractXacroArguments(xacroContent) {
const args = {};
// Match <xacro:arg name="NAME" default="VALUE"/>
// Also match <arg name="NAME" default="VALUE"/> (without xacro: prefix)
const argPattern = /<(?:xacro:)?arg\s+name=["']([^"']+)["'](?:\s+default=["']([^"']*)["'])?/g;
let match;
while ((match = argPattern.exec(xacroContent)) !== null) {
const argName = match[1];
const defaultValue = match[2] || 'false'; // Default to 'false' if no default specified
args[argName] = defaultValue;
}
return args;
}
}

View File

@ -1,46 +0,0 @@
import request from '@/utils/request'
export function addCategory(data) {
return request({
url: '/tts/corpus/category',
method: 'post',
data: data
})
}
export function getCategoryTreeApi() {
return request({
url: '/tts/corpus/category/tree',
method: 'get'
})
}
export function updateCategoryTreeApi(data) {
return request({
url: '/tts/corpus/category',
method: 'put',
data: data
})
}
export function deleteCategory(id) {
return request({
url: `/tts/corpus/category/${id}`,
method: 'delete',
})
}
export function getCorpusListApi(params) {
return request({
url: '/tts/corpus/list',
method: 'get',
params: params
})
}
export function getDashboardApi() {
return request({
url: '/tts/corpus/dashboard/stats',
method: 'get'
})
}

View File

@ -1,69 +0,0 @@
import request from '@/utils/request'
export function taskApi(data) {
return request({
url: '/tts/task',
method: 'post',
data: data
})
}
export function taskListApi(params) {
return request({
url: '/tts/task/list',
method: 'get',
params: params
})
}
export function getDashboardApi(params) {
return request({
url: '/tts/task/dashboard/stats',
method: 'get'
})
}
export function downloadWavApi(id) {
return request({
url: `/tts/task/audio/${id}`,
method: 'get',
responseType: 'blob'
})
}
export function deleteTask(ids) {
return request({
url: `/tts/task/${ids}`,
method: 'delete'
})
}
export function regenerateApi(id) {
return request({
url: `/tts/task/regenerate/${id}`,
method: 'post'
})
}
export function generalizationApi(data) {
return request({
url: '/tts/task/llm/query',
method: 'post',
data: data
})
}
export function batchTaskApi(data) {
return request({
url: '/tts/task/batch',
method: 'post',
data: data
})
}
export function saveTaskApi(data) {
return request({
url: `/tts/task/save-to-corpus/${data.taskId}/${data.categoryId}`,
method: 'post'
})
}

View File

@ -1,79 +1,63 @@
import request from '@/utils/request' import request from '@/utils/request'
// 发布流程 // 发布流程
export function flowDeploy(data) { export function flowDeploy(data) {
return request({ return request({
url: '/flow/publish', url: '/flow/publish',
method: 'post', method: 'post',
data: data data: data
}) })
} }
export function flowExecute(data) { export function flowExecute(data) {
return request({ return request({
url: '/flow/execute', url: '/flow/execute',
method: 'post', method: 'post',
data: data data: data
}) })
} }
export function flowAction(data) { export function flowAction(data) {
return request({ return request({
url: '/flow/action', url: '/flow/action',
method: 'post', method: 'post',
data: data data: data
}) })
} }
export function flowExecuteTrial(data) { export function flowExecuteTrial(data) {
return request({ return request({
url: '/flow/executeTrial', url: '/flow/executeTrial',
method: 'post', method: 'post',
data: data data: data
}) })
} }
export function flowView(data) { export function flowView(data) {
return request({ return request({
url: '/flow/view', url: '/flow/view',
method: 'post', method: 'post',
data: data data: data
}) })
} }
export function flowPause(instId) { export function flowPause(instId) {
return request({ return request({
url: `/flow/pause/${instId}`, url: `/flow/pause/${instId}`,
method: 'post' method: 'post'
}) })
} }
export function flowResume(instId) { export function flowResume(instId) {
return request({ return request({
url: `/flow/resume/${instId}`, url: `/flow/resume/${instId}`,
method: 'post' method: 'post'
}) })
} }
export function flowStop(instId) { export function flowStop(instId) {
return request({ return request({
url: `/flow/stop/${instId}`, url: `/flow/stop/${instId}`,
method: 'post' method: 'post'
}) })
}
export function getArmStatus(params) {
return request({
url: '/api/arm/getPose',
method: 'get',
params: params
})
}
export function getAgvStatus(params) {
return request({
url: '/api/agv/getRuntimeState',
method: 'get',
params: params
})
} }

View File

@ -5,7 +5,7 @@ export function startMicrophoneApi(data) {
return request({ return request({
url: '/api/Microphone/start', url: '/api/Microphone/start',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -14,7 +14,7 @@ export function pauseMicrophoneApi(data) {
return request({ return request({
url: '/api/Microphone/pause', url: '/api/Microphone/pause',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -23,7 +23,7 @@ export function resumeMicrophoneApi(data) {
return request({ return request({
url: '/api/Microphone/resume', url: '/api/Microphone/resume',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -32,7 +32,7 @@ export function stopMicrophoneApi(data) {
return request({ return request({
url: '/api/Microphone/stop', url: '/api/Microphone/stop',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -41,6 +41,6 @@ export function playSpeaker(data) {
return request({ return request({
url: '/api/Speaker/play', url: '/api/Speaker/play',
method: 'post', method: 'post',
data: data params: data
}) })
} }

View File

@ -5,7 +5,7 @@ export function pause(data) {
return request({ return request({
url: '/api/Speaker/pause', url: '/api/Speaker/pause',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -14,7 +14,7 @@ export function play(data) {
return request({ return request({
url: '/api/Speaker/play', url: '/api/Speaker/play',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -23,7 +23,7 @@ export function resume(data) {
return request({ return request({
url: '/api/Speaker/resume', url: '/api/Speaker/resume',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -41,7 +41,7 @@ export function stop(data) {
return request({ return request({
url: '/api/Speaker/stop', url: '/api/Speaker/stop',
method: 'post', method: 'post',
data: data params: data
}) })
} }
@ -59,7 +59,7 @@ export function setVolume(data) {
return request({ return request({
url: '/api/Speaker/volume', url: '/api/Speaker/volume',
method: 'post', method: 'post',
data: data params: data
}) })
} }

View File

@ -1,97 +0,0 @@
import request from '@/utils/request'
export function getPoseApi(params) {
return request({
url: '/api/arm/getPose',
method: 'get',
params: params
})
}
export function getJointStateApi(params) {
return request({
url: '/api/arm/getJointState',
method: 'get',
params: params
})
}
export function speedLApi(data) {
return request({
url: '/api/arm/speedL',
method: 'post',
data: data
})
}
export function moveJApi(data) {
return request({
url: '/api/arm/moveJ',
method: 'post',
data: data
})
}
export function stopMotionApi(params) {
return request({
url: '/api/arm/stopMotion',
method: 'get',
params: params
})
}
export function torqueOnApi(params) {
return request({
url: '/api/arm/torqueOn',
method: 'get',
params: params
})
}
export function speedJApi(data) {
return request({
url: '/api/arm/speedJ',
method: 'post',
data: data
})
}
export function clearFault(params) {
return request({
url: '/api/arm/clearFault',
method: 'get',
params: params
})
}
export function getMicVolume(params) {
return request({
url: '/api/Microphone/volume',
method: 'get',
params: params
})
}
export function setMicVolume(data) {
return request({
url: '/api/Microphone/volume',
method: 'post',
data: data
})
}
export function getSpeakerVolume(params) {
return request({
url: '/api/Speaker/volume',
method: 'get',
params: params
})
}
export function setSpeakerVolume(data) {
return request({
url: '/api/Speaker/volume',
method: 'post',
data: data
})
}

View File

@ -1,33 +0,0 @@
import request from '@/utils/request'
export function getMapList(params) {
return request({
url: '/inspection/map/list',
method: 'get',
params: params
})
}
export function addMap(data) {
return request({
url: '/inspection/map',
method: 'post',
data: data
})
}
export function updateMap(data) {
return request({
url: '/inspection/map',
method: 'put',
data: data
})
}
export function deleteMap(id) {
return request({
url: `/inspection/map/${id}`,
method: 'delete'
})
}

View File

@ -1,32 +0,0 @@
import request from '@/utils/request'
export function getPointList(params) {
return request({
url: '/inspection/waypoint/list',
method: 'get',
params: params
})
}
export function addPoint(data) {
return request({
url: '/inspection/waypoint',
method: 'post',
data: data
})
}
export function updatePoint(data) {
return request({
url: '/inspection/waypoint',
method: 'put',
data: data
})
}
export function deletePoint(id) {
return request({
url: `/inspection/waypoint/${id}`,
method: 'delete'
})
}

View File

@ -1,23 +0,0 @@
import request from '@/utils/request'
/**
* 查询巡检结果列表
*/
export function getInspectionResultList(params) {
return request({
url: '/inspection/result/list',
method: 'get',
params
})
}
/**
* 提交人工复核结果
*/
export function reviewInspectionResult(id, data) {
return request({
url: `/inspection/result/${id}/review`,
method: 'put',
data
})
}

View File

@ -1,107 +0,0 @@
import request from '@/utils/request'
export function getRobotList(params) {
return request({
url: '/inspection/robot/list',
method: 'get',
params: params
})
}
export function addRobot(data) {
return request({
url: '/inspection/robot',
method: 'post',
data: data
})
}
export function updateRobot(data) {
return request({
url: '/inspection/robot',
method: 'put',
data: data
})
}
export function deleteRobot(id) {
return request({
url: `/inspection/robot/${id}`,
method: 'delete'
})
}
/**
* 获取机器人地图
* @param {*} robotId
* @returns
*/
export function getMapByRobot(robotId) {
return request({
url: `/inspection/robot/${robotId}/maps`,
method: 'get'
})
}
/**
* 获取地图JSON数据
* @param {*} params
* @returns
*/
export function getMapJson(params) {
return request({
url: `/inspection/robot/${params.robotId}/download-map`,
method: 'get',
params
})
}
/**
* 获取机器人位置
* @param {*} robotId
* @returns
*/
export function getRobotLocation(robotId) {
return request({
url: `/inspection/robot/${robotId}/location`,
method: 'get'
})
}
/**
* 获取机器人状态
* @param {*} robotId
* @returns
*/
export function getRobotStatus(robotId) {
return request({
url: `/inspection/robot/{robotId}/sync-status`,
method: 'get'
})
}
/**
* 机器人运动控制
* @param {*} data
* @returns
*/
export function moveRobot(data) {
return request({
url: '/api/agv/setVelocity',
method: 'post',
data: data
})
}
/**
* 机器人停止运动
* @param {*} data
* @returns
*/
export function stopRobot(data) {
return request({
url: '/api/agv/stopVelocityControl',
method: 'post',
data: data
})
}

View File

@ -1,75 +0,0 @@
import request from '@/utils/request'
export function getRunTaskList(params) {
return request({
url: '/inspection/taskInstance/list',
method: 'get',
params: params
})
}
export function getRunTaskDetail(id) {
return request({
url: `/inspection/taskInstance/${id}`,
method: 'get'
})
}
export function addRunTask(data) {
return request({
url: '/inspection/taskInstance',
method: 'post',
data: data
})
}
export function updateRunTask(data) {
return request({
url: '/inspection/taskInstance',
method: 'put',
data: data
})
}
export function deleteRunTask(id) {
return request({
url: `/inspection/taskInstance/${id}`,
method: 'delete'
})
}
export function startRunTask(id) {
return request({
url: `/inspection/taskInstance/start/${id}`,
method: 'put'
})
}
export function stopRunTask(id) {
return request({
url: `/inspection/taskInstance/stop/${id}`,
method: 'put'
})
}
export function pauseRunTask(id) {
return request({
url: `/inspection/taskInstance/pause/${id}`,
method: 'put'
})
}
export function resumeRunTask(id) {
return request({
url: `/inspection/taskInstance/resume/${id}`,
method: 'put'
})
}
export function getTaskLogListApi(params) {
return request({
url: '/inspection/taskLog/list',
method: 'get',
params: params
})
}

View File

@ -1,47 +0,0 @@
import request from '@/utils/request'
export function getTaskList(params) {
return request({
url: '/inspection/task/list',
method: 'get',
params: params
})
}
export function addTask(data) {
return request({
url: '/inspection/task',
method: 'post',
data: data
})
}
export function updateTask(data) {
return request({
url: '/inspection/task',
method: 'put',
data: data
})
}
export function deleteTask(id) {
return request({
url: `/inspection/task/${id}`,
method: 'delete'
})
}
export function getWaypointByTaskId(taskId) {
return request({
url: `/inspection/task/${taskId}/waypoints`,
method: 'get'
})
}
export function bindWaypointsToTask(taskId, waypointIds) {
return request({
url: `/inspection/task/${taskId}/waypoints`,
method: 'post',
data: waypointIds
})
}

View File

@ -1,32 +0,0 @@
import request from '@/utils/request'
export function addVehicle(data) {
return request({
url: '/aima/vehicle',
method: 'post',
data: data
})
}
export function getVehicleList(params) {
return request({
url: '/aima/vehicle/list',
method: 'get',
params: params
})
}
export function updateVehicle(data) {
return request({
url: '/aima/vehicle',
method: 'put',
data: data
})
}
export function deleteVehicle(ids) {
return request({
url: `/aima/vehicle/${ids}`,
method: 'delete'
})
}

View File

@ -1,32 +0,0 @@
import request from '@/utils/request'
export function addPhone(data) {
return request({
url: '/aima/phone',
method: 'post',
data: data
})
}
export function getPhoneList(params) {
return request({
url: '/aima/phone/list',
method: 'get',
params: params
})
}
export function updatePhone(data) {
return request({
url: '/aima/phone',
method: 'put',
data: data
})
}
export function deletePhone(ids) {
return request({
url: `/aima/phone/${ids}`,
method: 'delete'
})
}

View File

@ -1,69 +0,0 @@
import request from '@/utils/request'
import { method } from 'lodash'
export function addTaskinstance(data) {
return request({
url: '/aima/taskinstance',
method: 'post',
data: data
})
}
export function getTaskinstanceList(params) {
return request({
url: '/aima/taskinstance/list',
method: 'get',
params: params
})
}
export function updateTaskinstance(data) {
return request({
url: '/aima/taskinstance',
method: 'put',
data: data
})
}
export function deleteTaskinstance(ids) {
return request({
url: `/aima/taskinstance/{ids}`,
method: 'delete'
})
}
export function startTaskinstance(id) {
return request({
url: `/aima/taskinstance/start/${id}`,
method: 'put'
})
}
export function pauseTaskinstance(id) {
return request({
url: `/aima/taskinstance/pause/${id}`,
method: 'put'
})
}
export function stopTaskinstance(id) {
return request({
url: `/aima/taskinstance/stop/${id}`,
method: 'put'
})
}
export function resumeTaskinstance(id) {
return request({
url: `/aima/taskinstance/resume/${id}`,
method: 'put'
})
}
export function getTaskLogListApi(params) {
return request({
url: '/aima/testlog/list',
method: 'get',
params: params
})
}

View File

@ -1,47 +0,0 @@
import request from '@/utils/request'
export function addTask(data) {
return request({
url: '/aima/task',
method: 'post',
data: data
})
}
export function getTaskList(params) {
return request({
url: '/aima/task/list',
method: 'get',
params: params
})
}
export function updateTask(data) {
return request({
url: '/aima/task',
method: 'put',
data: data
})
}
export function deleteTask(ids) {
return request({
url: `/aima/task/${ids}`,
method: 'delete'
})
}
export function getTestCaseByTaskId(taskId) {
return request({
url: `/aima/task/testCases/${taskId}`,
method: 'get',
})
}
export function bindTastCase(taskId, ids) {
return request({
url: `/aima/task/bindTestCases/${taskId}`,
method: 'post',
data: ids
})
}

View File

@ -1,32 +0,0 @@
import request from '@/utils/request'
export function addTestCase(data) {
return request({
url: '/aima/testcase',
method: 'post',
data: data
})
}
export function getTestCaseList(params) {
return request({
url: '/aima/testcase/list',
method: 'get',
params: params
})
}
export function updateTestCase(data) {
return request({
url: '/aima/testcase',
method: 'put',
data: data
})
}
export function deleteTestCase(ids) {
return request({
url: `/aima/testcase/${ids}`,
method: 'delete'
})
}

View File

@ -39,15 +39,6 @@ export function getInfo() {
}) })
} }
// 解锁屏幕
export function unlockScreen(password) {
return request({
url: '/unlockscreen',
method: 'post',
data: { password }
})
}
// 退出方法 // 退出方法
export function logout() { export function logout() {
return request({ return request({

View File

@ -43,15 +43,6 @@ export function updateDept(data) {
}) })
} }
// 保存部门排序
export function updateDeptSort(data) {
return request({
url: '/system/dept/updateSort',
method: 'put',
data: data
})
}
// 删除部门 // 删除部门
export function delDept(deptId) { export function delDept(deptId) {
return request({ return request({

View File

@ -51,15 +51,6 @@ export function updateMenu(data) {
}) })
} }
// 保存菜单排序
export function updateMenuSort(data) {
return request({
url: '/system/menu/updateSort',
method: 'put',
data: data
})
}
// 删除菜单 // 删除菜单
export function delMenu(menuId) { export function delMenu(menuId) {
return request({ return request({

View File

@ -41,39 +41,4 @@ export function delNotice(noticeId) {
url: '/system/notice/' + noticeId, url: '/system/notice/' + noticeId,
method: 'delete' method: 'delete'
}) })
} }
// 首页顶部公告列表(带已读状态)
export function listNoticeTop() {
return request({
url: '/system/notice/listTop',
method: 'get'
})
}
// 标记公告已读
export function markNoticeRead(noticeId) {
return request({
url: '/system/notice/markRead',
method: 'post',
params: { noticeId }
})
}
// 批量标记已读
export function markNoticeReadAll(ids) {
return request({
url: '/system/notice/markReadAll',
method: 'post',
params: { ids }
})
}
// 查询公告已读用户列表
export function listNoticeReadUsers(query) {
return request({
url: '/system/notice/readUsers/list',
method: 'get',
params: query
})
}

View File

@ -96,7 +96,7 @@ export function updateUserPwd(oldPassword, newPassword) {
return request({ return request({
url: '/system/user/profile/updatePwd', url: '/system/user/profile/updatePwd',
method: 'put', method: 'put',
data: data params: data
}) })
} }

View File

@ -62,7 +62,7 @@ export function getInst(query) {
} }
// 查询任务实例日志 // 查询任务实例日志
export function getFlowRuntimeLog(query) { export function getLogicFlow(query) {
return request({ return request({
url: '/test/inst/log/query', url: '/test/inst/log/query',
method: 'get', method: 'get',

View File

@ -1,4 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1783317138800" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12777" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M898.004474 894.186428 798.735272 894.186428 798.735272 993.45563C798.735272 1009.90301 785.401891 1023.236391 768.954512 1023.236391 752.507132 1023.236391 739.173751 1009.90301 739.173751 993.45563L739.173751 894.186428 639.904549 894.186428C623.457169 894.186428 610.123788 880.853047 610.123788 864.405667 610.123788 847.958288 623.457169 834.624907 639.904549 834.624907L739.173751 834.624907 739.173751 735.355705C739.173751 718.908325 752.507132 705.574944 768.954512 705.574944 785.401891 705.574944 798.735272 718.908325 798.735272 735.355705L798.735272 834.624907 898.004474 834.624907C914.451854 834.624907 927.785235 847.958288 927.785235 864.405667 927.785235 880.853047 914.451854 894.186428 898.004474 894.186428ZM768.954512 641.049963C752.507132 641.049963 739.173751 627.716582 739.173751 611.269202L739.173751 60.32513 415.012081 60.32513C415.013608 60.452653 415.021626 60.579031 415.021626 60.706935L415.021626 291.316928C415.021626 307.764307 401.688245 321.097688 385.240865 321.097688L155.776286 321.097688 155.776286 836.152125 515.054437 836.152125C531.501817 836.152125 544.835198 849.485506 544.835198 865.932886 544.835198 882.380265 531.501817 895.713647 515.054437 895.713647L125.995526 895.713647C109.548146 895.713647 96.214765 882.380265 96.214765 865.932886L96.214765 242.064131C96.214765 237.412987 97.282291 233.011544 99.18406 229.089647 99.90567 226.826309 101.078574 224.786327 102.737515 223.086151L309.85087 10.837906C310.512919 10.088805 311.210476 9.372158 311.943541 8.693309L314.300802 6.277632C317.094849 3.414097 320.879296 1.89146 325.120764 1.610452 327.381429 1.061417 329.7406 0.763609 332.170022 0.763609L768.954512 0.763609C785.401891 0.763609 798.735272 14.09699 798.735272 30.54437L798.735272 611.269202C798.735272 627.716582 785.401891 641.049963 768.954512 641.049963ZM344.780647 60.32513 155.776286 254.015761 155.776286 261.536167 355.460104 261.536167 355.460104 60.706935C355.460104 60.579031 355.468122 60.452653 355.46965 60.32513L344.780647 60.32513Z" p-id="12778" fill="currentColor"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773923748724" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5930" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 212l48.8 12c101.6 24.8 176 117.6 176 220.8v254.4l18.4 18.4 24.8 25.6h-536l24.8-25.6 18.4-18.4V444.8c0-103.2 73.6-196.8 176-220.8l48.8-12M512 64c-36.8 0-64 30.4-64 68v30.4C320.8 192 223.2 307.2 223.2 444.8v228.8L136 763.2v44.8h752v-44.8l-87.2-89.6V444.8c0-137.6-97.6-252.8-224.8-283.2v-28.8c0-32-17.6-60.8-48-67.2-5.6-1.6-11.2-1.6-16-1.6z m88 808H424c0 49.6 38.4 88 88 88s88-38.4 88-88z" p-id="5931"></path></svg>

Before

Width:  |  Height:  |  Size: 750 B

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1783317313942" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17054" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M672.881778 502.215111a225.28 225.28 0 0 1-77.141334 50.688A225.450667 225.450667 0 0 1 512 568.888889a225.450667 225.450667 0 0 1-83.740444-15.928889 225.28 225.28 0 0 1-77.141334-50.744889 225.223111 225.223111 0 0 1-50.688-77.141333A225.564444 225.564444 0 0 1 284.444444 341.333333c0-28.672 5.347556-57.116444 15.928889-83.740444 11.605333-29.240889 28.501333-54.954667 50.744889-77.141333a225.28 225.28 0 0 1 77.141334-50.688A225.564444 225.564444 0 0 1 512 113.777778c28.672 0 57.116444 5.347556 83.740444 15.928889 29.240889 11.605333 54.954667 28.501333 77.141334 50.744889 22.186667 22.186667 39.139556 47.900444 50.688 77.141333 10.638222 26.624 16.042667 55.068444 15.985778 83.740444 0 28.672-5.404444 57.116444-15.928889 83.740445a225.28 225.28 0 0 1-50.744889 77.141333z m-50.801778-270.961778a154.168889 154.168889 0 0 0-52.792889-34.702222A154.339556 154.339556 0 0 0 512 185.628444c-19.626667 0-39.082667 3.697778-57.287111 10.922667a154.112 154.112 0 0 0-52.792889 34.702222c-15.075556 15.018667-26.908444 32.995556-34.702222 52.792889-7.224889 18.204444-10.922667 37.660444-10.922667 57.287111 0 19.797333 3.640889 38.912 10.922667 57.287111 7.793778 19.797333 19.626667 37.774222 34.702222 52.792889 15.018667 15.075556 32.995556 26.908444 52.792889 34.702223 18.204444 7.224889 37.660444 10.979556 57.287111 10.922666 19.626667 0 39.082667-3.697778 57.287111-10.922666 19.797333-7.793778 37.774222-19.626667 52.792889-34.702223 15.075556-15.018667 26.908444-32.995556 34.702222-52.792889 7.224889-18.204444 10.922667-37.660444 10.922667-57.287111 0-19.626667-3.697778-39.082667-10.922667-57.287111a153.998222 153.998222 0 0 0-34.702222-52.792889z" p-id="17055" fill="currentColor"></path><path d="M228.750222 616.220444a388.152889 388.152889 0 0 1 122.481778-88.177777 368.64 368.64 0 0 1 134.371556-33.052445 33.678222 33.678222 0 0 1 25.144888 9.671111c6.883556 6.599111 10.979556 15.928889 11.434667 25.770667a38.912 38.912 0 0 1-8.931555 26.851556 34.133333 34.133333 0 0 1-24.177778 12.288 302.819556 302.819556 0 0 0-110.364445 27.192888 318.691556 318.691556 0 0 0-100.579555 72.362667 342.357333 342.357333 0 0 0-68.380445 106.951111 359.992889 359.992889 0 0 0-26.055111 117.703111c-1.024 20.593778-17.578667 36.408889-36.864 35.271111a34.133333 34.133333 0 0 1-24.120889-12.401777 38.912 38.912 0 0 1-8.874666-26.965334 438.385778 438.385778 0 0 1 31.687111-143.246222 416.938667 416.938667 0 0 1 83.228444-130.275555z m426.894222 333.824c-19.342222 0-34.986667-16.725333-34.986666-37.319111v-231.537777c0-20.650667 15.644444-37.319111 34.986666-37.319112 19.342222 0 34.986667 16.668444 34.986667 37.319112v231.537777c0 20.593778-15.644444 37.319111-34.986667 37.319111z m-111.957333-61.610666c-19.342222 0-34.986667-16.725333-34.986667-37.319111V744.675556c0-20.650667 15.644444-37.376 34.986667-37.376 19.342222 0 34.986667 16.725333 34.986667 37.376v106.382222c0 20.650667-15.701333 37.376-34.986667 37.376z m223.971556 0c-19.342222 0-34.986667-16.725333-34.986667-37.319111V744.675556c0-20.650667 15.644444-37.376 34.986667-37.376 19.342222 0 34.986667 16.725333 34.986666 37.376v106.382222c0 20.650667-15.701333 37.376-34.986666 37.376zM967.111111 797.013333c0 20.593778-15.644444 37.319111-34.986667 37.319111h-45.511111c-19.342222 0-34.986667-16.725333-34.986666-37.319111 0-20.650667 15.644444-37.376 34.986666-37.376h45.511111c19.342222 0 34.986667 16.725333 34.986667 37.376z" p-id="17056" fill="currentColor"></path></svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746590936918" class="icon" viewBox="0 0 1194 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5378" xmlns:xlink="http://www.w3.org/1999/xlink" width="233.203125" height="200"><path d="M1151.9144 325.11999969V89.12a57.04000031 57.04000031 0 0 0-28.8-49.44 58.15999969 58.15999969 0 0 0-57.76000031 0 57.04000031 57.04000031 0 0 0-28.8 49.44v235.99999969c0.24 84.31999969-33.6 152.56000031-94.08 212.00000062-60.07999969 59.83999969-141.84 80.64-227.04 80.4H225.91440031L388.07439969 457.11999969a56.80000031 56.80000031 0 0 0 12.40000031-62.16 57.76000031 57.76000031 0 0 0-94.00000031-18.63999938L48.8744 631.20000031a56.88 56.88 0 0 0 0 80.79999938l264.96 262.56a58.08 58.08 0 0 0 96.55999969-25.59999938 56.80000031 56.80000031 0 0 0-14.95999969-55.2L232.07439969 731.67999969h483.44000062c116.56000031 0 226.15999969-32.08000031 308.64-113.76 82.15999969-80.80000031 128.23999969-178.15999969 127.83999938-292.87999969" p-id="5379"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746760911144" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12537" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M395.21211 182.914448c0 62.669318 49.541323 113.472378 110.642936 113.472378 61.093427 0 110.652146-50.80306 110.65214601-113.472378 0-62.685691-49.559742-113.487727-110.65214601-113.487727C444.75241 69.426721 395.21211 120.22978 395.21211 182.914448zM395.21211 523.34693101c0 62.668295 49.541323 113.487727 110.642936 113.48772699 61.093427 0 110.652146-50.820456 110.652146-113.487727 0-62.669318-49.559742-113.472378-110.652146-113.472378C444.75241 409.874553 395.21211 460.67761301 395.21211 523.34693101zM395.21211 841.084529c0 62.686714 49.541323 113.488751 110.642936 113.488751 61.093427 0 110.652146-50.80203599 110.65214601-113.488751 0-62.669318-49.559742-113.471354-110.65214601-113.471354C444.75241 727.614198 395.21211 778.416234 395.21211 841.084529z" p-id="12538"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1780372291375" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5453" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M624.941176 360.929882c-12.408471 0-22.528 13.432471-22.528 30.117647v301.176471c0 16.624941 10.119529 30.117647 22.528 30.117647h45.296942c12.408471 0 22.528-13.492706 22.528-30.117647v-301.176471c0-16.685176-10.119529-30.117647-22.528-30.117647h-45.296942z m-271.058823 0c-12.408471 0-22.528 13.432471-22.528 30.117647v301.176471c0 16.624941 10.119529 30.117647 22.528 30.117647h45.296941c12.408471 0 22.528-13.492706 22.528-30.117647v-301.176471c0-16.685176-10.119529-30.117647-22.528-30.117647h-45.296941z" fill="currentColor" p-id="5454"></path>
<path d="M250.036706 138.059294a141.673412 141.673412 0 0 0-141.552941 141.552941v524.047059a141.673412 141.673412 0 0 0 141.552941 141.552941h524.047059a141.673412 141.673412 0 0 0 141.552941-141.552941v-524.047059a141.673412 141.673412 0 0 0-141.552941-141.552941h-524.047059z m524.047059 885.458824h-524.047059a220.099765 220.099765 0 0 1-219.858824-219.858824v-524.047059a220.099765 220.099765 0 0 1 219.858824-219.858823h524.047059a220.099765 220.099765 0 0 1 219.858823 219.858823v524.047059a220.099765 220.099765 0 0 1-219.858823 219.858824z" fill="currentColor" p-id="5455"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1780472431337" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19258" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M814.29 136.567H207.664c-55.752 0-101.274 13.3-101.274 56.776v26.086h808.663v-26.086c0.511-42.965-45.011-56.776-100.763-56.776" p-id="19259" fill="#2c2c2c"></path><path d="M723.245 191.808l-19.948-140.66c-3.58-27.62-29.667-50.125-57.287-50.125H376.456c-28.132 0-53.707 22.505-57.799 50.126l-19.948 141.17c-3.58 27.621 15.856 22.506 43.477 22.506h337.07c28.133-0.511 47.57 4.604 43.989-23.017z m-360.6-28.643L377.99 51.66h265.463l15.344 111.505H362.645zM831.17 282.342H190.785c-36.827 0-64.959 30.177-61.378 67.005l55.24 607.648c3.58 36.827 36.316 67.005 73.655 67.005h505.35c36.828 0 70.074-30.178 73.655-67.005l55.24-607.137c3.58-36.827-24.04-67.516-61.378-67.516zM376.456 953.415H245.514l-43.476-592.816h174.418v592.816z m221.474 0H423.512V360.599H597.93v592.816z m177.487 0H646.01V360.599h172.883l-43.476 592.816z" p-id="19260" fill="#2c2c2c"></path></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path fill="#5a5e66" d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg> <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg>

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 211 B

View File

@ -1,6 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1780371806721" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1692" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M703.146667 479.146667L469.333333 285.866667a42.666667 42.666667 0 0 0-69.973333 32.853333v386.56a42.666667 42.666667 0 0 0 69.973333 32.853333l232.106667-193.28a42.666667 42.666667 0 0 0 1.706667-65.706666z" fill="currentColor" p-id="1693"></path>
<path d="M250.036706 138.059294a141.673412 141.673412 0 0 0-141.552941 141.552941v524.047059a141.673412 141.673412 0 0 0 141.552941 141.552941h524.047059a141.673412 141.673412 0 0 0 141.552941-141.552941v-524.047059a141.673412 141.673412 0 0 0-141.552941-141.552941h-524.047059z m524.047059 885.458824h-524.047059a220.099765 220.099765 0 0 1-219.858824-219.858824v-524.047059a220.099765 220.099765 0 0 1 219.858824-219.858823h524.047059a220.099765 220.099765 0 0 1 219.858823 219.858823v524.047059a220.099765 220.099765 0 0 1-219.858823 219.858824z" fill="currentColor" p-id="5455"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1780378438689" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15665" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M877.696 0H146.24A146.24 146.24 0 0 0 0 146.24v731.456A146.24 146.24 0 0 0 146.24 1024h731.456A146.24 146.24 0 0 0 1024 877.696V146.24A146.24 146.24 0 0 0 877.696 0z m73.152 877.696c0 40.448-32.768 73.216-73.216 73.216H146.24a73.216 73.216 0 0 1-73.152-73.216V146.24c0-40.384 32.768-73.152 73.152-73.152h731.456c40.448 0 73.216 32.768 73.216 73.152v731.456z" fill="currentColor" p-id="15666"></path>
<path d="M688.512 644.224a40.32 40.32 0 0 1-40.384 40.32H371.072a40.32 40.32 0 0 1-40.32-40.32V367.168a40.32 40.32 0 0 1 40.32-40.32h277.056a40.32 40.32 0 0 1 40.384 40.32v277.056z" fill="currentColor" p-id="15667"></path>
</svg>

Before

Width:  |  Height:  |  Size: 966 B

View File

@ -1,5 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1783317214101" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14894" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M799.197483 1024H224.802517A130.013483 130.013483 0 0 1 94.881079 894.067056V227.374022A130.013483 130.013483 0 0 1 224.802517 97.452584h47.65627v64.960719h-47.65627a65.018247 65.018247 0 0 0-64.972225 64.960719v666.693034a65.018247 65.018247 0 0 0 64.960719 64.960719h574.406472a65.018247 65.018247 0 0 0 64.960719-64.960719V227.374022a65.018247 65.018247 0 0 0-64.960719-64.960719h-42.099056V97.452584h42.099056a130.013483 130.013483 0 0 1 129.921438 129.921438v666.693034A130.013483 130.013483 0 0 1 799.197483 1024z" fill="currentColor" p-id="14895"></path><path d="M644.308854 259.854382h-264.629214a129.932944 129.932944 0 0 1 0-259.854382h264.629214a129.932944 129.932944 0 0 1 0 259.854382z m-264.629214-194.893663a64.972225 64.972225 0 0 0 0 129.932944h264.629214a64.972225 64.972225 0 0 0 0-129.932944z" fill="currentColor" p-id="14896"></path>
<path d="M241.796315 374.910562m32.480359 0l475.446652 0q32.48036 0 32.480359 32.480359l0 0q0 32.48036-32.480359 32.48036l-475.446652 0q-32.48036 0-32.480359-32.48036l0 0q0-32.48036 32.480359-32.480359Z" fill="currentColor" p-id="14897"></path><path d="M241.796315 541.845573m32.480359 0l475.446652 0q32.48036 0 32.480359 32.48036l0 0q0 32.48036-32.480359 32.480359l-475.446652 0q-32.48036 0-32.480359-32.480359l0 0q0-32.48036 32.480359-32.48036Z" fill="currentColor" p-id="14898"></path><path d="M241.796315 708.780584m32.480359 0l475.446652 0q32.48036 0 32.480359 32.48036l0 0q0 32.48036-32.480359 32.480359l-475.446652 0q-32.48036 0-32.480359-32.480359l0 0q0-32.48036 32.480359-32.48036Z" fill="currentColor" p-id="14899"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,4 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1783317427036" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19916" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M874.18 149.92C777.456 53.254 648.91 0 512.102 0 375.343 0 246.78 53.255 150.097 149.93 53.38 246.635 0.118 375.225 0.118 512.003 0.094 648.796 53.314 777.357 150.02 874.122 246.69 970.736 375.266 1024 512.007 1024c136.807 0 265.425-53.298 362.172-149.979 199.588-199.608 199.619-524.417 0-724.102z m-47.186 676.907C742.864 910.946 631 957.32 512.008 957.32c-118.907 0-230.744-46.35-314.82-130.392-84.077-84.093-130.368-195.934-130.35-314.924 0-118.973 46.314-230.826 130.42-314.899C281.354 113.03 393.154 66.731 512.104 66.731c118.955 0 230.779 46.297 314.891 130.38 173.59 173.631 173.59 456.144 0 629.716z m-93.643-189.351L556.37 535.125V256c0-23.557-19.111-42.667-42.674-42.667-23.545 0-42.656 19.11-42.656 42.667v298.67c0 15.826 19.671 29.096 32.468 36.437 3.538 5.33 13.268 10.153 19.2 13.565l176 106.673c20.388 11.77 42.495 4.775 54.27-15.62 11.776-20.4 0.812-46.469-19.63-58.244v-0.005z m0 0" p-id="19917" fill="currentColor"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,4 +1,4 @@
@use './variables.module.scss' as *; @import './variables.module.scss';
@mixin colorBtn($color) { @mixin colorBtn($color) {
background: $color; background: $color;

View File

@ -1,11 +1,10 @@
@use './variables.module.scss'; @import './variables.module.scss';
@use './mixin.scss'; @import './mixin.scss';
@use './transition.scss'; @import './transition.scss';
@use './element-ui.scss'; @import './element-ui.scss';
@use './sidebar.scss'; @import './sidebar.scss';
@use './btn.scss'; @import './btn.scss';
@use './ruoyi.scss'; @import './ruoyi.scss';
@use './legacy-layout.scss';
body { body {
height: 100%; height: 100%;

View File

@ -1,6 +0,0 @@
/* Keep the migrated application visually consistent with the original CMVR UI. */
#ruoyi-git,
#ruoyi-doc,
.user-nickname {
display: none !important;
}

View File

@ -1,10 +1,7 @@
@use './variables.module.scss' as *;
#app { #app {
.main-container { .main-container {
// min-height: 100%; min-height: 100%;
height: 100%;
transition: margin-left .28s; transition: margin-left .28s;
margin-left: $base-sidebar-width; margin-left: $base-sidebar-width;
position: relative; position: relative;

View File

@ -1,286 +1,286 @@
// base color // base color
$blue: #324157; $blue: #324157;
$light-blue: #333c46; $light-blue: #333c46;
$red: #c03639; $red: #c03639;
$pink: #e65d6e; $pink: #e65d6e;
$green: #30b08f; $green: #30b08f;
$tiffany: #4ab7bd; $tiffany: #4ab7bd;
$yellow: #fec171; $yellow: #fec171;
$panGreen: #30b08f; $panGreen: #30b08f;
// 默认主题变量 // 默认主题变量
$menuText: #bfcbd9; $menuText: #bfcbd9;
$menuActiveText: #409eff; $menuActiveText: #409eff;
$menuBg: #304156; $menuBg: #304156;
$menuHover: #263445; $menuHover: #263445;
// 浅色主题theme-light // 浅色主题theme-light
$menuLightBg: #ffffff; $menuLightBg: #ffffff;
$menuLightHover: #f0f1f5; $menuLightHover: #f0f1f5;
$menuLightText: #303133; $menuLightText: #303133;
$menuLightActiveText: #409eff; $menuLightActiveText: #409eff;
// 基础变量 // 基础变量
$base-sidebar-width: 200px; $base-sidebar-width: 200px;
$sideBarWidth: 200px; $sideBarWidth: 200px;
// 菜单暗色变量 // 菜单暗色变量
$base-menu-color: #bfcbd9; $base-menu-color: #bfcbd9;
$base-menu-color-active: #f4f4f5; $base-menu-color-active: #f4f4f5;
$base-menu-background: #304156; $base-menu-background: #304156;
$base-sub-menu-background: #1f2d3d; $base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528; $base-sub-menu-hover: #001528;
// 组件变量 // 组件变量
$--color-primary: #409eff; $--color-primary: #409eff;
$--color-success: #67c23a; $--color-success: #67c23a;
$--color-warning: #e6a23c; $--color-warning: #e6a23c;
$--color-danger: #f56c6c; $--color-danger: #f56c6c;
$--color-info: #909399; $--color-info: #909399;
:export { :export {
menuText: $menuText; menuText: $menuText;
menuActiveText: $menuActiveText; menuActiveText: $menuActiveText;
menuBg: $menuBg; menuBg: $menuBg;
menuHover: $menuHover; menuHover: $menuHover;
menuLightBg: $menuLightBg; menuLightBg: $menuLightBg;
menuLightHover: $menuLightHover; menuLightHover: $menuLightHover;
menuLightText: $menuLightText; menuLightText: $menuLightText;
menuLightActiveText: $menuLightActiveText; menuLightActiveText: $menuLightActiveText;
sideBarWidth: $sideBarWidth; sideBarWidth: $sideBarWidth;
// 导出基础颜色 // 导出基础颜色
blue: $blue; blue: $blue;
lightBlue: $light-blue; lightBlue: $light-blue;
red: $red; red: $red;
pink: $pink; pink: $pink;
green: $green; green: $green;
tiffany: $tiffany; tiffany: $tiffany;
yellow: $yellow; yellow: $yellow;
panGreen: $panGreen; panGreen: $panGreen;
// 导出组件颜色 // 导出组件颜色
colorPrimary: $--color-primary; colorPrimary: $--color-primary;
colorSuccess: $--color-success; colorSuccess: $--color-success;
colorWarning: $--color-warning; colorWarning: $--color-warning;
colorDanger: $--color-danger; colorDanger: $--color-danger;
colorInfo: $--color-info; colorInfo: $--color-info;
} }
// CSS变量定义 // CSS变量定义
:root { :root {
/* 亮色模式变量 */ /* 亮色模式变量 */
--sidebar-bg: #{$menuBg}; --sidebar-bg: #{$menuBg};
--sidebar-text: #{$menuText}; --sidebar-text: #{$menuText};
--menu-hover: #{$menuHover}; --menu-hover: #{$menuHover};
--navbar-bg: #ffffff; --navbar-bg: #ffffff;
--navbar-text: #303133; --navbar-text: #303133;
/* splitpanes default-theme 变量 */ /* splitpanes default-theme 变量 */
--splitpanes-default-bg: #ffffff; --splitpanes-default-bg: #ffffff;
--main-fill-bg: rgb(242, 243, 245); --main-fill-bg: rgb(242, 243, 245);
} }
// 暗黑模式变量 // 暗黑模式变量
html.dark { html.dark {
--main-fill-bg: rgba(255, 255, 255, 0.08); --main-fill-bg: rgba(255, 255, 255, 0.08);
/* 默认通用 */ /* 默认通用 */
--el-bg-color: #141414; --el-bg-color: #141414;
--el-bg-color-overlay: #1d1e1f; --el-bg-color-overlay: #1d1e1f;
--el-text-color-primary: #ffffff; --el-text-color-primary: #ffffff;
--el-text-color-regular: #d0d0d0; --el-text-color-regular: #d0d0d0;
--el-border-color: #434343; --el-border-color: #434343;
--el-border-color-light: #434343; --el-border-color-light: #434343;
/* 侧边栏 */ /* 侧边栏 */
--sidebar-bg: #141414; --sidebar-bg: #141414;
--sidebar-text: #ffffff; --sidebar-text: #ffffff;
--menu-hover: #2d2d2d; --menu-hover: #2d2d2d;
--menu-active-text: #{$menuActiveText}; --menu-active-text: #{$menuActiveText};
/* 顶部导航栏 */ /* 顶部导航栏 */
--navbar-bg: #141414; --navbar-bg: #141414;
--navbar-text: #ffffff; --navbar-text: #ffffff;
--navbar-hover: #141414; --navbar-hover: #141414;
/* 标签栏 */ /* 标签栏 */
--tags-bg: #141414; --tags-bg: #141414;
--tags-item-bg: #1d1e1f; --tags-item-bg: #1d1e1f;
--tags-item-border: #303030; --tags-item-border: #303030;
--tags-item-text: #d0d0d0; --tags-item-text: #d0d0d0;
--tags-item-hover: #2d2d2d; --tags-item-hover: #2d2d2d;
--tags-close-hover: #64666a; --tags-close-hover: #64666a;
/* splitpanes 组件暗黑模式变量 */ /* splitpanes 组件暗黑模式变量 */
--splitpanes-bg: #141414; --splitpanes-bg: #141414;
--splitpanes-border: #303030; --splitpanes-border: #303030;
--splitpanes-splitter-bg: #1d1e1f; --splitpanes-splitter-bg: #1d1e1f;
--splitpanes-splitter-hover-bg: #2d2d2d; --splitpanes-splitter-hover-bg: #2d2d2d;
/* blockquote 暗黑模式变量 */ /* blockquote 暗黑模式变量 */
--blockquote-bg: #1d1e1f; --blockquote-bg: #1d1e1f;
--blockquote-border: #303030; --blockquote-border: #303030;
--blockquote-text: #d0d0d0; --blockquote-text: #d0d0d0;
/* Cron 时间表达式 模式变量 */ /* Cron 时间表达式 模式变量 */
--cron-border: #303030; --cron-border: #303030;
/* splitpanes default-theme 暗黑模式变量 */ /* splitpanes default-theme 暗黑模式变量 */
--splitpanes-default-bg: #141414; --splitpanes-default-bg: #141414;
/* 侧边栏菜单覆盖 */ /* 侧边栏菜单覆盖 */
.sidebar-container { .sidebar-container {
.el-menu-item, .el-menu-item,
.menu-title { .menu-title {
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
} }
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title, & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
& .theme-dark .el-sub-menu .el-menu-item { & .theme-dark .el-sub-menu .el-menu-item {
background-color: var(--el-bg-color) !important; background-color: var(--el-bg-color) !important;
} }
} }
/* 顶部栏栏菜单覆盖 */ /* 顶部栏栏菜单覆盖 */
.el-menu--horizontal { .el-menu--horizontal {
.el-menu-item { .el-menu-item {
&:not(.is-disabled) { &:not(.is-disabled) {
&:hover, &:hover,
&:focus { &:focus {
background-color: var(--navbar-hover) !important; background-color: var(--navbar-hover) !important;
} }
} }
} }
} }
/* 分割窗格覆盖 */ /* 分割窗格覆盖 */
.splitpanes { .splitpanes {
background-color: var(--splitpanes-bg); background-color: var(--splitpanes-bg);
.splitpanes__pane { .splitpanes__pane {
background-color: var(--splitpanes-bg); background-color: var(--splitpanes-bg);
border-color: var(--splitpanes-border); border-color: var(--splitpanes-border);
} }
.splitpanes__splitter { .splitpanes__splitter {
background-color: var(--splitpanes-splitter-bg); background-color: var(--splitpanes-splitter-bg);
border-color: var(--splitpanes-border); border-color: var(--splitpanes-border);
&:hover { &:hover {
background-color: var(--splitpanes-splitter-hover-bg); background-color: var(--splitpanes-splitter-hover-bg);
} }
&:before, &:before,
&:after { &:after {
background-color: var(--splitpanes-border); background-color: var(--splitpanes-border);
} }
} }
} }
/* 表格样式覆盖 */ /* 表格样式覆盖 */
.el-table { .el-table {
--el-table-header-bg-color: var(--el-bg-color-overlay) !important; --el-table-header-bg-color: var(--el-bg-color-overlay) !important;
--el-table-header-text-color: var(--el-text-color-regular) !important; --el-table-header-text-color: var(--el-text-color-regular) !important;
--el-table-border-color: var(--el-border-color-light) !important; --el-table-border-color: var(--el-border-color-light) !important;
--el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important; --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
.el-table__header-wrapper, .el-table__header-wrapper,
.el-table__fixed-header-wrapper { .el-table__fixed-header-wrapper {
th { th {
background-color: var(--el-bg-color-overlay, #f8f8f9) !important; background-color: var(--el-bg-color-overlay, #f8f8f9) !important;
color: var(--el-text-color-regular, #515a6e); color: var(--el-text-color-regular, #515a6e);
} }
} }
} }
/* 树组件高亮样式覆盖 */ /* 树组件高亮样式覆盖 */
.el-tree { .el-tree {
.el-tree-node.is-current > .el-tree-node__content { .el-tree-node.is-current > .el-tree-node__content {
background-color: var(--el-bg-color-overlay) !important; background-color: var(--el-bg-color-overlay) !important;
color: var(--el-color-primary); color: var(--el-color-primary);
} }
.el-tree-node__content:hover { .el-tree-node__content:hover {
background-color: var(--el-bg-color-overlay); background-color: var(--el-bg-color-overlay);
} }
} }
/* 下拉菜单样式覆盖 */ /* 下拉菜单样式覆盖 */
.el-dropdown-menu__item:not(.is-disabled):focus, .el-dropdown-menu__item:not(.is-disabled):focus,
.el-dropdown-menu__item:not(.is-disabled):hover { .el-dropdown-menu__item:not(.is-disabled):hover {
background-color: var(--navbar-hover) !important; background-color: var(--navbar-hover) !important;
} }
/* blockquote样式覆盖 */ /* blockquote样式覆盖 */
blockquote { blockquote {
background-color: var(--blockquote-bg) !important; background-color: var(--blockquote-bg) !important;
border-left-color: var(--blockquote-border) !important; border-left-color: var(--blockquote-border) !important;
color: var(--blockquote-text) !important; color: var(--blockquote-text) !important;
} }
/* 时间表达式标题样式覆盖 */ /* 时间表达式标题样式覆盖 */
.popup-result .title { .popup-result .title {
background: var(--cron-border); background: var(--cron-border);
} }
.el-aside { .el-aside {
background: #4d4d4d; background: #4d4d4d;
} }
.el-header { .el-header {
border-bottom: 1px solid #282828 !important; border-bottom: 1px solid #282828 !important;
} }
.el-collapse { .el-collapse {
--el-collapse-header-bg-color: #000; --el-collapse-header-bg-color: #000;
.collapseItem { .collapseItem {
.el-collapse-item__wrap { .el-collapse-item__wrap {
background-color: #3c3c3c; background-color: #3c3c3c;
.node { .node {
background-color: #000; background-color: #000;
.title_container { .title_container {
color: #fff; color: #fff;
} }
.node_desc { .node_desc {
color: #f7f7f7; color: #f7f7f7;
} }
} }
} }
} }
} }
.node__container { .node__container {
background: #000; background: #000;
// border: 2px solid #000; // border: 2px solid #000;
.subTitle { .subTitle {
color: #fbfbfb !important; color: #fbfbfb !important;
} }
} }
.node__box { .node__box {
background: #000; background: #000;
border: 2px solid #000; border: 2px solid #000;
.subTitle { .subTitle {
color: #fbfbfb !important; color: #fbfbfb !important;
} }
.input__container { .input__container {
background: #2d2d2d; background: #2d2d2d;
.title { .title {
.text { .text {
color: #fbfbfb !important; color: #fbfbfb !important;
} }
} }
} }
} }
.lf-canvas-overlay { .lf-canvas-overlay {
background: #3e3e3e !important; background: #3e3e3e !important;
} }
} }

View File

@ -95,4 +95,4 @@ getBreadcrumb()
cursor: text; cursor: text;
} }
} }
</style> </style>

View File

@ -171,4 +171,4 @@ function onRadioChange() {
.el-select, .el-select--small { .el-select, .el-select--small {
width: 18.8rem; width: 18.8rem;
} }
</style> </style>

View File

@ -78,10 +78,10 @@ watch(() => props.cron.hour, value => changeRadioValue(value))
watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange()) watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
function changeRadioValue(value) { function changeRadioValue(value) {
if (props.cron.min === '*') { if (props.cron.min === '*') {
emit('update', 'min', '0', 'hour') emit('update', 'min', '0', 'hour');
} }
if (props.cron.second === '*') { if (props.cron.second === '*') {
emit('update', 'second', '0', 'hour') emit('update', 'second', '0', 'hour');
} }
if (value === '*') { if (value === '*') {
radioValue.value = 1 radioValue.value = 1
@ -130,4 +130,4 @@ function onRadioChange() {
.el-select, .el-select--small { .el-select, .el-select--small {
width: 18.8rem; width: 18.8rem;
} }
</style> </style>

View File

@ -70,46 +70,42 @@
<p class="title">时间表达式</p> <p class="title">时间表达式</p>
<table> <table>
<thead> <thead>
<tr> <th v-for="item of tabTitles" :key="item">{{item}}</th>
<th v-for="item of tabTitles" :key="item">{{item}}</th> <th>Cron 表达式</th>
<th>Cron 表达式</th>
</tr>
</thead> </thead>
<tbody> <tbody>
<tr> <td>
<td> <span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span>
<span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span> <el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip> </td>
</td> <td>
<td> <span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span>
<span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span> <el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip> </td>
</td> <td>
<td> <span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span>
<span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span> <el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip> </td>
</td> <td>
<td> <span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span>
<span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span> <el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip> </td>
</td> <td>
<td> <span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span>
<span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span> <el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip> </td>
</td> <td>
<td> <span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span>
<span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span> <el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip> </td>
</td> <td>
<td> <span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span>
<span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span> <el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip> </td>
</td> <td class="result">
<td class="result"> <span v-if="crontabValueString.length < 90">{{crontabValueString}}</span>
<span v-if="crontabValueString.length < 90">{{crontabValueString}}</span> <el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip>
<el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip> </td>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -310,4 +306,4 @@ onMounted(() => {
height: 10em; height: 10em;
overflow-y: auto; overflow-y: auto;
} }
</style> </style>

View File

@ -123,4 +123,4 @@ function onRadioChange() {
.el-select, .el-select--small { .el-select, .el-select--small {
width: 19.8rem; width: 19.8rem;
} }
</style> </style>

View File

@ -138,4 +138,4 @@ function onRadioChange() {
.el-select, .el-select--small { .el-select, .el-select--small {
width: 18.8rem; width: 18.8rem;
} }
</style> </style>

View File

@ -26,289 +26,289 @@ watch(() => props.ex, () => expressionChange())
// //
function expressionChange() { function expressionChange() {
// - // -
isShow.value = false isShow.value = false;
// [0123456] // [0123456]
let ruleArr = props.ex.split(' ') let ruleArr = props.ex.split(' ');
// //
let nums = 0 let nums = 0;
// //
let resultArr = [] let resultArr = [];
// [] // []
let nTime = new Date() let nTime = new Date();
let nYear = nTime.getFullYear() let nYear = nTime.getFullYear();
let nMonth = nTime.getMonth() + 1 let nMonth = nTime.getMonth() + 1;
let nDay = nTime.getDate() let nDay = nTime.getDate();
let nHour = nTime.getHours() let nHour = nTime.getHours();
let nMin = nTime.getMinutes() let nMin = nTime.getMinutes();
let nSecond = nTime.getSeconds() let nSecond = nTime.getSeconds();
// 100 // 100
getSecondArr(ruleArr[0]) getSecondArr(ruleArr[0]);
getMinArr(ruleArr[1]) getMinArr(ruleArr[1]);
getHourArr(ruleArr[2]) getHourArr(ruleArr[2]);
getDayArr(ruleArr[3]) getDayArr(ruleArr[3]);
getMonthArr(ruleArr[4]) getMonthArr(ruleArr[4]);
getWeekArr(ruleArr[5]) getWeekArr(ruleArr[5]);
getYearArr(ruleArr[6], nYear) getYearArr(ruleArr[6], nYear);
// -便使 // -便使
let sDate = dateArr.value[0] let sDate = dateArr.value[0];
let mDate = dateArr.value[1] let mDate = dateArr.value[1];
let hDate = dateArr.value[2] let hDate = dateArr.value[2];
let DDate = dateArr.value[3] let DDate = dateArr.value[3];
let MDate = dateArr.value[4] let MDate = dateArr.value[4];
let YDate = dateArr.value[5] let YDate = dateArr.value[5];
// //
let sIdx = getIndex(sDate, nSecond) let sIdx = getIndex(sDate, nSecond);
let mIdx = getIndex(mDate, nMin) let mIdx = getIndex(mDate, nMin);
let hIdx = getIndex(hDate, nHour) let hIdx = getIndex(hDate, nHour);
let DIdx = getIndex(DDate, nDay) let DIdx = getIndex(DDate, nDay);
let MIdx = getIndex(MDate, nMonth) let MIdx = getIndex(MDate, nMonth);
let YIdx = getIndex(YDate, nYear) let YIdx = getIndex(YDate, nYear);
// () // ()
const resetSecond = function () { const resetSecond = function () {
sIdx = 0 sIdx = 0;
nSecond = sDate[sIdx] nSecond = sDate[sIdx]
} }
const resetMin = function () { const resetMin = function () {
mIdx = 0 mIdx = 0;
nMin = mDate[mIdx] nMin = mDate[mIdx]
resetSecond() resetSecond();
} }
const resetHour = function () { const resetHour = function () {
hIdx = 0 hIdx = 0;
nHour = hDate[hIdx] nHour = hDate[hIdx]
resetMin() resetMin();
} }
const resetDay = function () { const resetDay = function () {
DIdx = 0 DIdx = 0;
nDay = DDate[DIdx] nDay = DDate[DIdx]
resetHour() resetHour();
} }
const resetMonth = function () { const resetMonth = function () {
MIdx = 0 MIdx = 0;
nMonth = MDate[MIdx] nMonth = MDate[MIdx]
resetDay() resetDay();
} }
// //
if (nYear !== YDate[YIdx]) { if (nYear !== YDate[YIdx]) {
resetMonth() resetMonth();
} }
// //
if (nMonth !== MDate[MIdx]) { if (nMonth !== MDate[MIdx]) {
resetDay() resetDay();
} }
// //
if (nDay !== DDate[DIdx]) { if (nDay !== DDate[DIdx]) {
resetHour() resetHour();
} }
// //
if (nHour !== hDate[hIdx]) { if (nHour !== hDate[hIdx]) {
resetMin() resetMin();
} }
// //
if (nMin !== mDate[mIdx]) { if (nMin !== mDate[mIdx]) {
resetSecond() resetSecond();
} }
// //
goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) { goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) {
let YY = YDate[Yi] let YY = YDate[Yi];
// //
if (nMonth > MDate[MDate.length - 1]) { if (nMonth > MDate[MDate.length - 1]) {
resetMonth() resetMonth();
continue continue;
} }
// //
goMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) { goMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) {
// 便 // 便
let MM = MDate[Mi]; let MM = MDate[Mi];
MM = MM < 10 ? '0' + MM : MM MM = MM < 10 ? '0' + MM : MM;
// //
if (nDay > DDate[DDate.length - 1]) { if (nDay > DDate[DDate.length - 1]) {
resetDay() resetDay();
if (Mi === MDate.length - 1) { if (Mi === MDate.length - 1) {
resetMonth() resetMonth();
continue goYear continue goYear;
} }
continue continue;
} }
// //
goDay: for (let Di = DIdx; Di < DDate.length; Di++) { goDay: for (let Di = DIdx; Di < DDate.length; Di++) {
// 便 // 便
let DD = DDate[Di] let DD = DDate[Di];
let thisDD = DD < 10 ? '0' + DD : DD let thisDD = DD < 10 ? '0' + DD : DD;
// //
if (nHour > hDate[hDate.length - 1]) { if (nHour > hDate[hDate.length - 1]) {
resetHour() resetHour();
if (Di === DDate.length - 1) { if (Di === DDate.length - 1) {
resetDay() resetDay();
if (Mi === MDate.length - 1) { if (Mi === MDate.length - 1) {
resetMonth() resetMonth();
continue goYear continue goYear;
} }
continue goMonth continue goMonth;
} }
continue continue;
} }
// //
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && dayRule.value !== 'workDay' && dayRule.value !== 'lastWeek' && dayRule.value !== 'lastDay') { if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && dayRule.value !== 'workDay' && dayRule.value !== 'lastWeek' && dayRule.value !== 'lastDay') {
resetDay() resetDay();
continue goMonth continue goMonth;
} }
// //
if (dayRule.value === 'lastDay') { if (dayRule.value === 'lastDay') {
// //
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD-- DD--;
thisDD = DD < 10 ? '0' + DD : DD thisDD = DD < 10 ? '0' + DD : DD;
} }
} }
} else if (dayRule.value === 'workDay') { } else if (dayRule.value === 'workDay') {
// 230 // 230
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD-- DD--;
thisDD = DD < 10 ? '0' + DD : DD thisDD = DD < 10 ? '0' + DD : DD;
} }
} }
// X // X
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week') let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
// //
if (thisWeek === 1) { if (thisWeek === 1) {
// //
DD++ DD++;
thisDD = DD < 10 ? '0' + DD : DD thisDD = DD < 10 ? '0' + DD : DD;
// //
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD -= 3 DD -= 3;
} }
} else if (thisWeek === 7) { } else if (thisWeek === 7) {
// 61 // 61
if (dayRuleSup.value !== 1) { if (dayRuleSup.value !== 1) {
DD-- DD--;
} else { } else {
DD += 2 DD += 2;
} }
} }
} else if (dayRule.value === 'weekDay') { } else if (dayRule.value === 'weekDay') {
// //
// //
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week') let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
// dayRuleSup // dayRuleSup
if (dayRuleSup.value.indexOf(thisWeek) < 0) { if (dayRuleSup.value.indexOf(thisWeek) < 0) {
// //
if (Di === DDate.length - 1) { if (Di === DDate.length - 1) {
resetDay() resetDay();
if (Mi === MDate.length - 1) { if (Mi === MDate.length - 1) {
resetMonth() resetMonth();
continue goYear continue goYear;
} }
continue goMonth continue goMonth;
} }
continue continue;
} }
} else if (dayRule.value === 'assWeek') { } else if (dayRule.value === 'assWeek') {
// //
// 1 // 1
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week') let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
if (dayRuleSup.value[1] >= thisWeek) { if (dayRuleSup.value[1] >= thisWeek) {
DD = (dayRuleSup.value[0] - 1) * 7 + dayRuleSup.value[1] - thisWeek + 1 DD = (dayRuleSup.value[0] - 1) * 7 + dayRuleSup.value[1] - thisWeek + 1;
} else { } else {
DD = dayRuleSup.value[0] * 7 + dayRuleSup.value[1] - thisWeek + 1 DD = dayRuleSup.value[0] * 7 + dayRuleSup.value[1] - thisWeek + 1;
} }
} else if (dayRule.value === 'lastWeek') { } else if (dayRule.value === 'lastWeek') {
// //
// 230 // 230
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD-- DD--;
thisDD = DD < 10 ? '0' + DD : DD thisDD = DD < 10 ? '0' + DD : DD;
} }
} }
// //
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week') let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
// //
if (dayRuleSup.value < thisWeek) { if (dayRuleSup.value < thisWeek) {
DD -= thisWeek - dayRuleSup.value DD -= thisWeek - dayRuleSup.value;
} else if (dayRuleSup.value > thisWeek) { } else if (dayRuleSup.value > thisWeek) {
DD -= 7 - (dayRuleSup.value - thisWeek) DD -= 7 - (dayRuleSup.value - thisWeek)
} }
} }
// 1005 // 1005
DD = DD < 10 ? '0' + DD : DD DD = DD < 10 ? '0' + DD : DD;
// //
goHour: for (let hi = hIdx; hi < hDate.length; hi++) { goHour: for (let hi = hIdx; hi < hDate.length; hi++) {
let hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi] let hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi]
// //
if (nMin > mDate[mDate.length - 1]) { if (nMin > mDate[mDate.length - 1]) {
resetMin() resetMin();
if (hi === hDate.length - 1) { if (hi === hDate.length - 1) {
resetHour() resetHour();
if (Di === DDate.length - 1) { if (Di === DDate.length - 1) {
resetDay() resetDay();
if (Mi === MDate.length - 1) { if (Mi === MDate.length - 1) {
resetMonth() resetMonth();
continue goYear continue goYear;
} }
continue goMonth continue goMonth;
} }
continue goDay continue goDay;
} }
continue continue;
} }
// "" // ""
goMin: for (let mi = mIdx; mi < mDate.length; mi++) { goMin: for (let mi = mIdx; mi < mDate.length; mi++) {
let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi] let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi];
// //
if (nSecond > sDate[sDate.length - 1]) { if (nSecond > sDate[sDate.length - 1]) {
resetSecond() resetSecond();
if (mi === mDate.length - 1) { if (mi === mDate.length - 1) {
resetMin() resetMin();
if (hi === hDate.length - 1) { if (hi === hDate.length - 1) {
resetHour() resetHour();
if (Di === DDate.length - 1) { if (Di === DDate.length - 1) {
resetDay() resetDay();
if (Mi === MDate.length - 1) { if (Mi === MDate.length - 1) {
resetMonth() resetMonth();
continue goYear continue goYear;
} }
continue goMonth continue goMonth;
} }
continue goDay continue goDay;
} }
continue goHour continue goHour;
} }
continue continue;
} }
// "" // ""
goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) { goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) {
let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si] let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si];
// //
if (MM !== '00' && DD !== '00') { if (MM !== '00' && DD !== '00') {
resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss) resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss)
nums++ nums++;
} }
// 退 // 退
if (nums === 5) break goYear if (nums === 5) break goYear;
// //
if (si === sDate.length - 1) { if (si === sDate.length - 1) {
resetSecond() resetSecond();
if (mi === mDate.length - 1) { if (mi === mDate.length - 1) {
resetMin() resetMin();
if (hi === hDate.length - 1) { if (hi === hDate.length - 1) {
resetHour() resetHour();
if (Di === DDate.length - 1) { if (Di === DDate.length - 1) {
resetDay() resetDay();
if (Mi === MDate.length - 1) { if (Mi === MDate.length - 1) {
resetMonth() resetMonth();
continue goYear continue goYear;
} }
continue goMonth continue goMonth;
} }
continue goDay continue goDay;
} }
continue goHour continue goHour;
} }
continue goMin continue goMin;
} }
} //goSecond } //goSecond
} //goMin } //goMin
@ -318,31 +318,31 @@ function expressionChange() {
} }
// 100 // 100
if (resultArr.length === 0) { if (resultArr.length === 0) {
resultList.value = ['没有达到条件的结果!'] resultList.value = ['没有达到条件的结果!'];
} else { } else {
resultList.value = resultArr resultList.value = resultArr;
if (resultArr.length !== 5) { if (resultArr.length !== 5) {
resultList.value.push('最近100年内只有上面' + resultArr.length + '条结果!') resultList.value.push('最近100年内只有上面' + resultArr.length + '条结果!')
} }
} }
// - // -
isShow.value = true isShow.value = true;
} }
// //
function getIndex(arr, value) { function getIndex(arr, value) {
if (value <= arr[0] || value > arr[arr.length - 1]) { if (value <= arr[0] || value > arr[arr.length - 1]) {
return 0 return 0;
} else { } else {
for (let i = 0; i < arr.length - 1; i++) { for (let i = 0; i < arr.length - 1; i++) {
if (value > arr[i] && value <= arr[i + 1]) { if (value > arr[i] && value <= arr[i + 1]) {
return i + 1 return i + 1;
} }
} }
} }
} }
// "" // ""
function getYearArr(rule, year) { function getYearArr(rule, year) {
dateArr.value[5] = getOrderArr(year, year + 100) dateArr.value[5] = getOrderArr(year, year + 100);
if (rule !== undefined) { if (rule !== undefined) {
if (rule.indexOf('-') >= 0) { if (rule.indexOf('-') >= 0) {
dateArr.value[5] = getCycleArr(rule, year + 100, false) dateArr.value[5] = getCycleArr(rule, year + 100, false)
@ -355,7 +355,7 @@ function getYearArr(rule, year) {
} }
// "" // ""
function getMonthArr(rule) { function getMonthArr(rule) {
dateArr.value[4] = getOrderArr(1, 12) dateArr.value[4] = getOrderArr(1, 12);
if (rule.indexOf('-') >= 0) { if (rule.indexOf('-') >= 0) {
dateArr.value[4] = getCycleArr(rule, 12, false) dateArr.value[4] = getCycleArr(rule, 12, false)
} else if (rule.indexOf('/') >= 0) { } else if (rule.indexOf('/') >= 0) {
@ -369,58 +369,58 @@ function getWeekArr(rule) {
// //
if (dayRule.value === '' && dayRuleSup.value === '') { if (dayRule.value === '' && dayRuleSup.value === '') {
if (rule.indexOf('-') >= 0) { if (rule.indexOf('-') >= 0) {
dayRule.value = 'weekDay' dayRule.value = 'weekDay';
dayRuleSup.value = getCycleArr(rule, 7, false) dayRuleSup.value = getCycleArr(rule, 7, false)
} else if (rule.indexOf('#') >= 0) { } else if (rule.indexOf('#') >= 0) {
dayRule.value = 'assWeek' dayRule.value = 'assWeek';
let matchRule = rule.match(/[0-9]{1}/g) let matchRule = rule.match(/[0-9]{1}/g);
dayRuleSup.value = [Number(matchRule[1]), Number(matchRule[0])] dayRuleSup.value = [Number(matchRule[1]), Number(matchRule[0])];
dateArr.value[3] = [1] dateArr.value[3] = [1];
if (dayRuleSup.value[1] === 7) { if (dayRuleSup.value[1] === 7) {
dayRuleSup.value[1] = 0 dayRuleSup.value[1] = 0;
} }
} else if (rule.indexOf('L') >= 0) { } else if (rule.indexOf('L') >= 0) {
dayRule.value = 'lastWeek' dayRule.value = 'lastWeek';
dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]) dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]);
dateArr.value[3] = [31] dateArr.value[3] = [31];
if (dayRuleSup.value === 7) { if (dayRuleSup.value === 7) {
dayRuleSup.value = 0 dayRuleSup.value = 0;
} }
} else if (rule !== '*' && rule !== '?') { } else if (rule !== '*' && rule !== '?') {
dayRule.value = 'weekDay' dayRule.value = 'weekDay';
dayRuleSup.value = getAssignArr(rule) dayRuleSup.value = getAssignArr(rule)
} }
} }
} }
// ""- // ""-
function getDayArr(rule) { function getDayArr(rule) {
dateArr.value[3] = getOrderArr(1, 31) dateArr.value[3] = getOrderArr(1, 31);
dayRule.value = '' dayRule.value = '';
dayRuleSup.value = '' dayRuleSup.value = '';
if (rule.indexOf('-') >= 0) { if (rule.indexOf('-') >= 0) {
dateArr.value[3] = getCycleArr(rule, 31, false) dateArr.value[3] = getCycleArr(rule, 31, false)
dayRuleSup.value = 'null' dayRuleSup.value = 'null';
} else if (rule.indexOf('/') >= 0) { } else if (rule.indexOf('/') >= 0) {
dateArr.value[3] = getAverageArr(rule, 31) dateArr.value[3] = getAverageArr(rule, 31)
dayRuleSup.value = 'null' dayRuleSup.value = 'null';
} else if (rule.indexOf('W') >= 0) { } else if (rule.indexOf('W') >= 0) {
dayRule.value = 'workDay' dayRule.value = 'workDay';
dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]) dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]);
dateArr.value[3] = [dayRuleSup.value] dateArr.value[3] = [dayRuleSup.value];
} else if (rule.indexOf('L') >= 0) { } else if (rule.indexOf('L') >= 0) {
dayRule.value = 'lastDay' dayRule.value = 'lastDay';
dayRuleSup.value = 'null' dayRuleSup.value = 'null';
dateArr.value[3] = [31] dateArr.value[3] = [31];
} else if (rule !== '*' && rule !== '?') { } else if (rule !== '*' && rule !== '?') {
dateArr.value[3] = getAssignArr(rule) dateArr.value[3] = getAssignArr(rule)
dayRuleSup.value = 'null' dayRuleSup.value = 'null';
} else if (rule === '*') { } else if (rule === '*') {
dayRuleSup.value = 'null' dayRuleSup.value = 'null';
} }
} }
// "" // ""
function getHourArr(rule) { function getHourArr(rule) {
dateArr.value[2] = getOrderArr(0, 23) dateArr.value[2] = getOrderArr(0, 23);
if (rule.indexOf('-') >= 0) { if (rule.indexOf('-') >= 0) {
dateArr.value[2] = getCycleArr(rule, 24, true) dateArr.value[2] = getCycleArr(rule, 24, true)
} else if (rule.indexOf('/') >= 0) { } else if (rule.indexOf('/') >= 0) {
@ -431,7 +431,7 @@ function getHourArr(rule) {
} }
// "" // ""
function getMinArr(rule) { function getMinArr(rule) {
dateArr.value[1] = getOrderArr(0, 59) dateArr.value[1] = getOrderArr(0, 59);
if (rule.indexOf('-') >= 0) { if (rule.indexOf('-') >= 0) {
dateArr.value[1] = getCycleArr(rule, 60, true) dateArr.value[1] = getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) { } else if (rule.indexOf('/') >= 0) {
@ -442,7 +442,7 @@ function getMinArr(rule) {
} }
// "" // ""
function getSecondArr(rule) { function getSecondArr(rule) {
dateArr.value[0] = getOrderArr(0, 59) dateArr.value[0] = getOrderArr(0, 59);
if (rule.indexOf('-') >= 0) { if (rule.indexOf('-') >= 0) {
dateArr.value[0] = getCycleArr(rule, 60, true) dateArr.value[0] = getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) { } else if (rule.indexOf('/') >= 0) {
@ -453,88 +453,88 @@ function getSecondArr(rule) {
} }
// min-max // min-max
function getOrderArr(min, max) { function getOrderArr(min, max) {
let arr = [] let arr = [];
for (let i = min; i <= max; i++) { for (let i = min; i <= max; i++) {
arr.push(i) arr.push(i);
} }
return arr return arr;
} }
// //
function getAssignArr(rule) { function getAssignArr(rule) {
let arr = [] let arr = [];
let assiginArr = rule.split(',') let assiginArr = rule.split(',');
for (let i = 0; i < assiginArr.length; i++) { for (let i = 0; i < assiginArr.length; i++) {
arr[i] = Number(assiginArr[i]) arr[i] = Number(assiginArr[i])
} }
arr.sort(compare) arr.sort(compare)
return arr return arr;
} }
// //
function getAverageArr(rule, limit) { function getAverageArr(rule, limit) {
let arr = [] let arr = [];
let agArr = rule.split('/') let agArr = rule.split('/');
let min = Number(agArr[0]) let min = Number(agArr[0]);
let step = Number(agArr[1]) let step = Number(agArr[1]);
while (min <= limit) { while (min <= limit) {
arr.push(min) arr.push(min);
min += step min += step;
} }
return arr return arr;
} }
// //
function getCycleArr(rule, limit, status) { function getCycleArr(rule, limit, status) {
// status--01 // status--01
let arr = [] let arr = [];
let cycleArr = rule.split('-') let cycleArr = rule.split('-');
let min = Number(cycleArr[0]) let min = Number(cycleArr[0]);
let max = Number(cycleArr[1]) let max = Number(cycleArr[1]);
if (min > max) { if (min > max) {
max += limit max += limit;
} }
for (let i = min; i <= max; i++) { for (let i = min; i <= max; i++) {
let add = 0 let add = 0;
if (status === false && i % limit === 0) { if (status === false && i % limit === 0) {
add = limit add = limit;
} }
arr.push(Math.round(i % limit + add)) arr.push(Math.round(i % limit + add))
} }
arr.sort(compare) arr.sort(compare)
return arr return arr;
} }
// Array.sort // Array.sort
function compare(value1, value2) { function compare(value1, value2) {
if (value2 - value1 > 0) { if (value2 - value1 > 0) {
return -1 return -1;
} else { } else {
return 1 return 1;
} }
} }
// 2017-9-19 18:04:33 // 2017-9-19 18:04:33
function formatDate(value, type) { function formatDate(value, type) {
// //
let time = typeof value == 'number' ? new Date(value) : value let time = typeof value == 'number' ? new Date(value) : value;
let Y = time.getFullYear() let Y = time.getFullYear();
let M = time.getMonth() + 1 let M = time.getMonth() + 1;
let D = time.getDate() let D = time.getDate();
let h = time.getHours() let h = time.getHours();
let m = time.getMinutes() let m = time.getMinutes();
let s = time.getSeconds() let s = time.getSeconds();
let week = time.getDay() let week = time.getDay();
// type // type
if (type === undefined) { if (type === undefined) {
return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s) return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
} else if (type === 'week') { } else if (type === 'week') {
// quartz 1 // quartz 1
return week + 1 return week + 1;
} }
} }
// //
function checkDate(value) { function checkDate(value) {
let time = new Date(value) let time = new Date(value);
let format = formatDate(time) let format = formatDate(time)
return value === format return value === format;
} }
onMounted(() => { onMounted(() => {
expressionChange() expressionChange()
}) })
</script> </script>

View File

@ -125,4 +125,4 @@ function onRadioChange() {
.el-select, .el-select--small { .el-select, .el-select--small {
width: 18.8rem; width: 18.8rem;
} }
</style> </style>

View File

@ -194,4 +194,4 @@ function onRadioChange() {
.el-select.multiselect, .el-select--small.multiselect { .el-select.multiselect, .el-select--small.multiselect {
width: 17.8rem; width: 17.8rem;
} }
</style> </style>

View File

@ -61,24 +61,22 @@ const props = defineProps({
} }
} }
}) })
const fullYear = ref(0)
const fullYear = Number(new Date().getFullYear()) const maxFullYear = ref(0)
const maxFullYear = fullYear + 10
const radioValue = ref(1) const radioValue = ref(1)
const cycle01 = ref(fullYear) const cycle01 = ref(0)
const cycle02 = ref(fullYear + 1) const cycle02 = ref(0)
const average01 = ref(fullYear) const average01 = ref(0)
const average02 = ref(1) const average02 = ref(1)
const checkboxList = ref([]) const checkboxList = ref([])
const checkCopy = ref([fullYear]) const checkCopy = ref([])
const cycleTotal = computed(() => { const cycleTotal = computed(() => {
cycle01.value = props.check(cycle01.value, fullYear, maxFullYear - 1) cycle01.value = props.check(cycle01.value, fullYear.value, maxFullYear.value - 1)
cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear) cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear.value)
return cycle01.value + '-' + cycle02.value return cycle01.value + '-' + cycle02.value
}) })
const averageTotal = computed(() => { const averageTotal = computed(() => {
average01.value = props.check(average01.value, fullYear, maxFullYear - 1) average01.value = props.check(average01.value, fullYear.value, maxFullYear.value - 1)
average02.value = props.check(average02.value, 1, 10) average02.value = props.check(average02.value, 1, 10)
return average01.value + '/' + average02.value return average01.value + '/' + average02.value
}) })
@ -99,8 +97,8 @@ function changeRadioValue(value) {
radioValue.value = 3 radioValue.value = 3
} else if (value.indexOf("/") > -1) { } else if (value.indexOf("/") > -1) {
const indexArr = value.split('/') const indexArr = value.split('/')
average01.value = Number(indexArr[0]) average01.value = Number(indexArr[1])
average02.value = Number(indexArr[1]) average02.value = Number(indexArr[0])
radioValue.value = 4 radioValue.value = 4
} else { } else {
checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))] checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
@ -131,6 +129,14 @@ function onRadioChange() {
break break
} }
} }
onMounted(() => {
fullYear.value = Number(new Date().getFullYear())
maxFullYear.value = fullYear.value + 10
cycle01.value = fullYear.value
cycle02.value = cycle01.value + 1
average01.value = fullYear.value
checkCopy.value = [fullYear.value]
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -140,4 +146,4 @@ function onRadioChange() {
.el-select, .el-select--small { .el-select, .el-select--small {
width: 18.8rem; width: 18.8rem;
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<template v-for="(item, index) in options"> <template v-for="(item, index) in options">
<template v-if="isValueMatch(item.value)"> <template v-if="values.includes(item.value)">
<span <span
v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)" v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)"
:key="item.value" :key="item.value"
@ -26,7 +26,7 @@
<script setup> <script setup>
// //
const unmatchArray = ref([]) const unmatchArray = ref([]);
const props = defineProps({ const props = defineProps({
// //
@ -45,38 +45,33 @@ const props = defineProps({
type: String, type: String,
default: ",", default: ",",
} }
}) });
const values = computed(() => { const values = computed(() => {
if (props.value === null || typeof props.value === 'undefined' || props.value === '') return [] if (props.value === null || typeof props.value === 'undefined' || props.value === '') return [];
if (typeof props.value === 'number' || typeof props.value === 'boolean') return [props.value] return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator) });
})
const unmatch = computed(() => { const unmatch = computed(() => {
unmatchArray.value = [] unmatchArray.value = [];
// value // value
if (props.value === null || typeof props.value === 'undefined' || props.value === '' || !Array.isArray(props.options) || props.options.length === 0) return false if (props.value === null || typeof props.value === 'undefined' || props.value === '' || !Array.isArray(props.options) || props.options.length === 0) return false
// //
let unmatch = false // let unmatch = false //
values.value.forEach(item => { values.value.forEach(item => {
if (!props.options.some(v => v.value == item)) { if (!props.options.some(v => v.value === item)) {
unmatchArray.value.push(item) unmatchArray.value.push(item)
unmatch = true // true unmatch = true // true
} }
}) })
return unmatch // return unmatch //
}) });
function handleArray(array) { function handleArray(array) {
if (array.length === 0) return "" if (array.length === 0) return "";
return array.reduce((pre, cur) => { return array.reduce((pre, cur) => {
return pre + " " + cur return pre + " " + cur;
}) });
}
function isValueMatch(itemValue) {
return values.value.some(val => val == itemValue)
} }
</script> </script>

View File

@ -27,18 +27,17 @@
</template> </template>
<script setup> <script setup>
import axios from 'axios' import { QuillEditor } from "@vueup/vue-quill";
import { QuillEditor } from "@vueup/vue-quill" import "@vueup/vue-quill/dist/vue-quill.snow.css";
import "@vueup/vue-quill/dist/vue-quill.snow.css" import { getToken } from "@/utils/auth";
import { getToken } from "@/utils/auth"
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance();
const quillEditorRef = ref() const quillEditorRef = ref();
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload") // const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); //
const headers = ref({ const headers = ref({
Authorization: "Bearer " + getToken() Authorization: "Bearer " + getToken()
}) });
const props = defineProps({ const props = defineProps({
/* 编辑器的内容 */ /* 编辑器的内容 */
@ -70,7 +69,7 @@ const props = defineProps({
type: String, type: String,
default: "url", default: "url",
} }
}) });
const options = ref({ const options = ref({
theme: "snow", theme: "snow",
@ -93,60 +92,59 @@ const options = ref({
}, },
placeholder: "请输入内容", placeholder: "请输入内容",
readOnly: props.readOnly readOnly: props.readOnly
}) });
const styles = computed(() => { const styles = computed(() => {
let style = {} let style = {};
if (props.minHeight) { if (props.minHeight) {
style.minHeight = `${props.minHeight}px` style.minHeight = `${props.minHeight}px`;
} }
if (props.height) { if (props.height) {
style.height = `${props.height}px` style.height = `${props.height}px`;
} }
return style return style;
}) });
const content = ref("") const content = ref("");
watch(() => props.modelValue, (v) => { watch(() => props.modelValue, (v) => {
if (v !== content.value) { if (v !== content.value) {
content.value = v == undefined ? "<p></p>" : v content.value = v == undefined ? "<p></p>" : v;
} }
}, { immediate: true }) }, { immediate: true });
// //
onMounted(() => { onMounted(() => {
if (props.type == 'url') { if (props.type == 'url') {
let quill = quillEditorRef.value.getQuill() let quill = quillEditorRef.value.getQuill();
let toolbar = quill.getModule("toolbar") let toolbar = quill.getModule("toolbar");
toolbar.addHandler("image", (value) => { toolbar.addHandler("image", (value) => {
if (value) { if (value) {
proxy.$refs.uploadRef.click() proxy.$refs.uploadRef.click();
} else { } else {
quill.format("image", false) quill.format("image", false);
} }
}) });
quill.root.addEventListener('paste', handlePasteCapture, true)
} }
}) });
// //
function handleBeforeUpload(file) { function handleBeforeUpload(file) {
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"] const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
const isJPG = type.includes(file.type) const isJPG = type.includes(file.type);
// //
if (!isJPG) { if (!isJPG) {
proxy.$modal.msgError(`图片格式错误!`) proxy.$modal.msgError(`图片格式错误!`);
return false return false;
} }
// //
if (props.fileSize) { if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) { if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`) proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false return false;
} }
} }
return true return true;
} }
// //
@ -154,44 +152,21 @@ function handleUploadSuccess(res, file) {
// //
if (res.code == 200) { if (res.code == 200) {
// //
let quill = toRaw(quillEditorRef.value).getQuill() let quill = toRaw(quillEditorRef.value).getQuill();
// //
let length = quill.selection.savedRange.index let length = quill.selection.savedRange.index;
// res.url // res.url
quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName) quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName);
// //
quill.setSelection(length + 1) quill.setSelection(length + 1);
} else { } else {
proxy.$modal.msgError("图片插入失败") proxy.$modal.msgError("图片插入失败");
} }
} }
// //
function handleUploadError() { function handleUploadError() {
proxy.$modal.msgError("图片插入失败") proxy.$modal.msgError("图片插入失败");
}
//
function handlePasteCapture(e) {
const clipboard = e.clipboardData || window.clipboardData
if (clipboard && clipboard.items) {
for (let i = 0; i < clipboard.items.length; i++) {
const item = clipboard.items[i]
if (item.type.indexOf('image') !== -1) {
e.preventDefault()
const file = item.getAsFile()
insertImage(file)
}
}
}
}
function insertImage(file) {
const formData = new FormData()
formData.append("file", file)
axios.post(uploadUrl.value, formData, { headers: { "Content-Type": "multipart/form-data", Authorization: headers.value.Authorization } }).then(res => {
handleUploadSuccess(res.data)
})
} }
</script> </script>

View File

@ -1,137 +0,0 @@
<template>
<el-dialog :title="title" v-model="visible" :width="width" append-to-body @close="handleClose">
<el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="headers" :action="uploadUrl" :disabled="isUploading" :on-progress="handleProgress" :on-change="handleFileChange" :on-remove="handleFileRemove" :on-success="handleSuccess" :auto-upload="false" drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<div class="el-upload__tip">
<el-checkbox v-model="updateSupport"> {{ updateSupportLabel }} </el-checkbox>
</div>
<span>仅允许导入xlsxlsx格式文件</span>
<el-link v-if="templateUrl" type="primary" underline="never" style="font-size: 12px; vertical-align: baseline" @click="handleDownloadTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { getToken } from '@/utils/auth'
const { proxy } = getCurrentInstance()
const props = defineProps({
//
title: {
type: String,
default: '数据导入'
},
//
width: {
type: String,
default: '400px'
},
//
action: {
type: String,
required: true
},
//
templateAction: {
type: String,
default: ''
},
//
templateFileName: {
type: String,
default: 'template'
},
//
updateSupportLabel: {
type: String,
default: '是否更新已经存在的数据'
}
})
const emit = defineEmits(['success'])
const uploadRef = ref(null)
const visible = ref(false)
const selectedFile = ref(null)
const isUploading = ref(false)
const updateSupport = ref(false)
const headers = { Authorization: 'Bearer ' + getToken() }
const uploadUrl = computed(() => {
return import.meta.env.VITE_APP_BASE_API + props.action + '?updateSupport=' + (updateSupport.value ? 1 : 0)
})
const templateUrl = computed(() => !!props.templateAction)
// ref
function open() {
updateSupport.value = false
isUploading.value = false
visible.value = true
nextTick(() => {
selectedFile.value = null
uploadRef.value?.clearFiles()
})
}
//
function handleClose() {
isUploading.value = false
selectedFile.value = null
uploadRef.value?.clearFiles()
}
//
function handleDownloadTemplate() {
proxy.download(props.templateAction, {}, `${props.templateFileName}_${new Date().getTime()}.xlsx`)
}
//
function handleProgress() {
isUploading.value = true
}
/** 文件选择处理 */
const handleFileChange = (file, fileList) => {
selectedFile.value = file
}
/** 文件删除处理 */
const handleFileRemove = (file, fileList) => {
selectedFile.value = null
}
//
function handleSuccess(response) {
visible.value = false
isUploading.value = false
selectedFile.value = null
uploadRef.value?.clearFiles()
proxy.$alert("<div style='overflow:auto;overflow-x:hidden;max-height:70vh;padding:10px 20px 0;'>" + response.msg + '</div>', '导入结果', { dangerouslyUseHTMLString: true })
emit('success')
}
//
function handleSubmit() {
const file = selectedFile.value
if (!file || file.length === 0 || !file.name.toLowerCase().endsWith('.xls') && !file.name.toLowerCase().endsWith('.xlsx')) {
proxy.$modal.msgError("请选择后缀为 “xls”或“xlsx”的文件。")
return
}
uploadRef.value.submit()
}
defineExpose({ open })
</script>

View File

@ -1,251 +1,213 @@
<template> <template>
<div class="upload-file"> <div class="upload-file">
<el-upload <el-upload
multiple multiple
:action="uploadFileUrl" :action="uploadFileUrl"
:before-upload="handleBeforeUpload" :before-upload="handleBeforeUpload"
:file-list="fileList" :file-list="fileList"
:data="data" :limit="limit"
:limit="limit" :on-error="handleUploadError"
:on-error="handleUploadError" :on-exceed="handleExceed"
:on-exceed="handleExceed" :on-success="handleUploadSuccess"
:on-success="handleUploadSuccess" :show-file-list="false"
:show-file-list="false" :headers="headers"
:headers="headers" class="upload-file-uploader"
class="upload-file-uploader" ref="fileUpload"
ref="fileUpload" >
v-if="!disabled" <!-- 上传按钮 -->
> <el-button type="primary">选取文件</el-button>
<!-- 上传按钮 --> </el-upload>
<el-button type="primary">选取文件</el-button> <!-- 上传提示 -->
</el-upload> <div class="el-upload__tip" v-if="showTip">
<!-- 上传提示 --> 请上传
<div class="el-upload__tip" v-if="showTip && !disabled"> <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
请上传 <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> 的文件
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template> </div>
的文件 <!-- 文件列表 -->
</div> <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<!-- 文件列表 --> <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<transition-group ref="uploadFileList" class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList"> <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
<el-link :href="`${baseUrl}${file.url}`" underline="never" target="_blank"> </el-link>
<span class="el-icon-document"> {{ getFileName(file.name) }} </span> <div class="ele-upload-list__item-content-action">
</el-link> <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
<div class="ele-upload-list__item-content-action"> </div>
<el-link underline="never" @click="handleDelete(index)" type="danger" v-if="!disabled">&nbsp;删除</el-link> </li>
</div> </transition-group>
</li> </div>
</transition-group> </template>
</div>
</template> <script setup>
import { getToken } from "@/utils/auth";
<script setup>
import { getToken } from "@/utils/auth" const props = defineProps({
import Sortable from 'sortablejs' modelValue: [String, Object, Array],
//
const props = defineProps({ limit: {
modelValue: [String, Object, Array], type: Number,
// default: 5,
action: { },
type: String, // (MB)
default: "/common/upload" fileSize: {
}, type: Number,
// default: 5,
data: { },
type: Object // , ['png', 'jpg', 'jpeg']
}, fileType: {
// type: Array,
limit: { default: () => ["doc", "xls", "ppt", "txt", "pdf"],
type: Number, },
default: 5 //
}, isShowTip: {
// (MB) type: Boolean,
fileSize: { default: true
type: Number, }
default: 5 });
},
// , ['png', 'jpg', 'jpeg'] const { proxy } = getCurrentInstance();
fileType: { const emit = defineEmits();
type: Array, const number = ref(0);
default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"] const uploadList = ref([]);
}, const baseUrl = import.meta.env.VITE_APP_BASE_API;
// const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); //
isShowTip: { const headers = ref({ Authorization: "Bearer " + getToken() });
type: Boolean, const fileList = ref([]);
default: true const showTip = computed(
}, () => props.isShowTip && (props.fileType || props.fileSize)
// );
disabled: {
type: Boolean, watch(() => props.modelValue, val => {
default: false if (val) {
}, let temp = 1;
// //
drag: { const list = Array.isArray(val) ? val : props.modelValue.split(',');
type: Boolean, //
default: true fileList.value = list.map(item => {
} if (typeof item === "string") {
}) item = { name: item, url: item };
}
const { proxy } = getCurrentInstance() item.uid = item.uid || new Date().getTime() + temp++;
const emit = defineEmits() return item;
const number = ref(0) });
const uploadList = ref([]) } else {
const baseUrl = import.meta.env.VITE_APP_BASE_API fileList.value = [];
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // return [];
const headers = ref({ Authorization: "Bearer " + getToken() }) }
const fileList = ref([]) },{ deep: true, immediate: true });
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize) //
) function handleBeforeUpload(file) {
//
watch(() => props.modelValue, val => { if (props.fileType.length) {
if (val) { const fileName = file.name.split('.');
let temp = 1 const fileExt = fileName[fileName.length - 1];
// const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
const list = Array.isArray(val) ? val : props.modelValue.split(',') if (!isTypeOk) {
// proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}格式文件!`);
fileList.value = list.map(item => { return false;
if (typeof item === "string") { }
item = { name: item, url: item } }
} //
item.uid = item.uid || new Date().getTime() + temp++ if (file.name.includes(',')) {
return item proxy.$modal.msgError('文件名不正确,不能包含英文逗号!');
}) return false;
} else { }
fileList.value = [] //
return [] if (props.fileSize) {
} const isLt = file.size / 1024 / 1024 < props.fileSize;
},{ deep: true, immediate: true }) if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
// return false;
function handleBeforeUpload(file) { }
// }
if (props.fileType.length) { proxy.$modal.loading("正在上传文件,请稍候...");
const fileName = file.name.split('.') number.value++;
const fileExt = fileName[fileName.length - 1] return true;
const isTypeOk = props.fileType.indexOf(fileExt) >= 0 }
if (!isTypeOk) {
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}格式文件!`) //
return false function handleExceed() {
} proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
} }
//
if (file.name.includes(',')) { //
proxy.$modal.msgError('文件名不正确,不能包含英文逗号!') function handleUploadError(err) {
return false proxy.$modal.msgError("上传文件失败");
} }
//
if (props.fileSize) { //
const isLt = file.size / 1024 / 1024 < props.fileSize function handleUploadSuccess(res, file) {
if (!isLt) { if (res.code === 200) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`) uploadList.value.push({ name: res.fileName, url: res.fileName });
return false uploadedSuccessfully();
} } else {
} number.value--;
proxy.$modal.loading("正在上传文件,请稍候...") proxy.$modal.closeLoading();
number.value++ proxy.$modal.msgError(res.msg);
return true proxy.$refs.fileUpload.handleRemove(file);
} uploadedSuccessfully();
}
// }
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`) //
} function handleDelete(index) {
fileList.value.splice(index, 1);
// emit("update:modelValue", listToString(fileList.value));
function handleUploadError(err) { }
proxy.$modal.msgError("上传文件失败")
proxy.$modal.closeLoading() //
} function uploadedSuccessfully() {
if (number.value > 0 && uploadList.value.length === number.value) {
// fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
function handleUploadSuccess(res, file) { uploadList.value = [];
if (res.code === 200) { number.value = 0;
uploadList.value.push({ name: res.fileName, url: res.fileName }) console.log('fileList.value', fileList.value)
uploadedSuccessfully() emit("update:modelValue", listToString(fileList.value));
} else { proxy.$modal.closeLoading();
number.value-- }
proxy.$modal.closeLoading() }
proxy.$modal.msgError(res.msg)
proxy.$refs.fileUpload.handleRemove(file) //
uploadedSuccessfully() function getFileName(name) {
} // url
} if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1);
// } else {
function handleDelete(index) { return name;
fileList.value.splice(index, 1) }
emit("update:modelValue", listToString(fileList.value)) }
}
//
// function listToString(list, separator) {
function uploadedSuccessfully() { let strs = "";
if (number.value > 0 && uploadList.value.length === number.value) { separator = separator || ",";
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value) for (let i in list) {
uploadList.value = [] if (list[i].url) {
number.value = 0 strs += list[i].url + separator;
emit("update:modelValue", listToString(fileList.value)) }
proxy.$modal.closeLoading() }
} return strs != '' ? strs.substr(0, strs.length - 1) : '';
} }
</script>
//
function getFileName(name) { <style scoped lang="scss">
// url .upload-file-uploader {
if (name.lastIndexOf("/") > -1) { margin-bottom: 5px;
return name.slice(name.lastIndexOf("/") + 1) }
} else { .upload-file-list .el-upload-list__item {
return name border: 1px solid #e4e7ed;
} line-height: 2;
} margin-bottom: 10px;
position: relative;
// }
function listToString(list, separator) { .upload-file-list .ele-upload-list__item-content {
let strs = "" display: flex;
separator = separator || "," justify-content: space-between;
for (let i in list) { align-items: center;
if (list[i].url) { color: inherit;
strs += list[i].url + separator }
} .ele-upload-list__item-content-action .el-link {
} margin-right: 10px;
return strs != '' ? strs.substr(0, strs.length - 1) : '' }
} </style>
//
onMounted(() => {
if (props.drag && !props.disabled) {
nextTick(() => {
const element = proxy.$refs.uploadFileList?.$el || proxy.$refs.uploadFileList
Sortable.create(element, {
ghostClass: 'file-upload-darg',
onEnd: (evt) => {
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
fileList.value.splice(evt.newIndex, 0, movedItem)
emit('update:modelValue', listToString(fileList.value))
}
})
})
}
})
</script>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>

View File

@ -24,7 +24,7 @@ defineProps({
const emit = defineEmits() const emit = defineEmits()
const toggleClick = () => { const toggleClick = () => {
emit('toggleClick') emit('toggleClick');
} }
</script> </script>

View File

@ -1,78 +1,19 @@
<template> <template>
<div class="header-search"> <div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-dialog <el-select
v-model="show" ref="headerSearchSelectRef"
width="600" v-model="search"
@close="close" :remote-method="querySearch"
@opened="onDialogOpened" filterable
:show-close="false" default-first-option
append-to-body remote
placeholder="Search"
class="header-search-select"
@change="change"
> >
<el-input <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
v-model="search" </el-select>
ref="headerSearchSelectRef"
size="large"
@input="querySearch"
prefix-icon="Search"
placeholder="菜单搜索支持标题、URL模糊查询"
clearable
@keyup.enter="selectActiveResult"
@keydown.up.prevent="navigateResult('up')"
@keydown.down.prevent="navigateResult('down')"
>
</el-input>
<div class="result-count" v-if="search && options.length > 0">
找到 <strong>{{ options.length }}</strong> 个结果
</div>
<div class="result-wrap">
<el-scrollbar>
<template v-if="options.length > 0">
<div
class="search-item"
tabindex="1"
v-for="(item, index) in options"
:key="item.path"
:class="{ 'is-active': index === activeIndex }"
:style="activeStyle(index)"
@mouseenter="activeIndex = index"
@mouseleave="activeIndex = -1"
>
<div class="left">
<svg-icon class="menu-icon" :icon-class="item.icon" />
</div>
<div class="search-info" @click="change(item)">
<div class="menu-title" v-html="highlightText(item.title.join(' / '))"></div>
<div class="menu-path" v-html="highlightText(item.path)"></div>
</div>
<svg-icon icon-class="enter" v-show="index === activeIndex" />
</div>
</template>
<div class="empty-state" v-else-if="search && options.length === 0">
<el-icon class="empty-icon"><Search /></el-icon>
<p class="empty-text">未找到 "<strong>{{ search }}</strong>" 相关菜单</p>
<p class="empty-tip">试试其他关键词或路径</p>
</div>
</el-scrollbar>
</div>
<div class="search-footer">
<span class="shortcut-item">
<kbd></kbd><kbd></kbd> 切换
</span>
<span class="shortcut-item">
<kbd></kbd> 选择
</span>
<span class="shortcut-item">
<kbd>Esc</kbd> 关闭
</span>
</div>
</el-dialog>
</div> </div>
</template> </template>
@ -80,66 +21,54 @@
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi' import { getNormalPath } from '@/utils/ruoyi'
import { isHttp } from '@/utils/validate' import { isHttp } from '@/utils/validate'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission' import usePermissionStore from '@/store/modules/permission'
const search = ref('') const search = ref('');
const options = ref([]) const options = ref([]);
const searchPool = ref([]) const searchPool = ref([]);
const activeIndex = ref(-1) const show = ref(false);
const show = ref(false) const fuse = ref(undefined);
const fuse = ref(undefined) const headerSearchSelectRef = ref(null);
const headerSearchSelectRef = ref(null) const router = useRouter();
const router = useRouter() const routes = computed(() => usePermissionStore().routes);
const theme = computed(() => useSettingsStore().theme)
const routes = computed(() => usePermissionStore().defaultRoutes)
function click() { function click() {
show.value = !show.value show.value = !show.value
if (show.value) { if (show.value) {
options.value = searchPool.value
}
}
function onDialogOpened() {
nextTick(() => {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus() headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
}) }
} };
function close() { function close() {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur() headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
search.value = '' options.value = []
options.value = searchPool.value
show.value = false show.value = false
activeIndex.value = -1
} }
function change(val) { function change(val) {
const p = val.path const path = val.path;
const query = val.query const query = val.query;
if (isHttp(p)) { if (isHttp(path)) {
// http(s):// // http(s)://
const pindex = p.indexOf("http") const pindex = path.indexOf("http");
window.open(p.substr(pindex, p.length), "_blank") window.open(path.substr(pindex, path.length), "_blank");
} else { } else {
if (query) { if (query) {
router.push({ path: p, query: JSON.parse(query) }) router.push({ path: path, query: JSON.parse(query) });
} else { } else {
router.push(p) router.push(path)
} }
} }
search.value = '' search.value = ''
options.value = searchPool.value options.value = []
nextTick(() => { nextTick(() => {
show.value = false show.value = false
}) })
} }
function initFuse(list) { function initFuse(list) {
fuse.value = new Fuse(list, { fuse.value = new Fuse(list, {
shouldSort: true, shouldSort: true,
threshold: 0.2, threshold: 0.4,
location: 0,
distance: 100, distance: 100,
minMatchCharLength: 1, minMatchCharLength: 1,
keys: [{ keys: [{
@ -151,27 +80,34 @@ function initFuse(list) {
}] }]
}) })
} }
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
function generateRoutes(routes, basePath = '', prefixTitle = []) { function generateRoutes(routes, basePath = '', prefixTitle = []) {
let res = [] let res = []
for (const r of routes) { for (const r of routes) {
// skip hidden router
if (r.hidden) { continue } if (r.hidden) { continue }
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = { const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path, path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle], title: [...prefixTitle]
icon: ''
} }
if (r.meta && r.meta.title) { if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title] data.title = [...data.title, r.meta.title]
data.icon = r.meta.icon
if (r.redirect !== "noRedirect") { if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data) res.push(data)
} }
} }
if (r.query) { if (r.query) {
data.query = r.query data.query = r.query
} }
// recursive child routes
if (r.children) { if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title) const tempRoutes = generateRoutes(r.children, data.path, data.title)
if (tempRoutes.length >= 1) { if (tempRoutes.length >= 1) {
@ -181,65 +117,30 @@ function generateRoutes(routes, basePath = '', prefixTitle = []) {
} }
return res return res
} }
function querySearch(query) { function querySearch(query) {
activeIndex.value = -1
if (query !== '') { if (query !== '') {
const q = query.toLowerCase() options.value = fuse.value.search(query)
const pathMatches = searchPool.value.filter(item =>
item.path.toLowerCase().includes(q)
)
const fuseMatches = fuse.value.search(query).map(item => item.item)
const merged = [...pathMatches]
fuseMatches.forEach(item => {
if (!merged.find(m => m.path === item.path)) {
merged.push(item)
}
})
options.value = merged
} else { } else {
options.value = searchPool.value options.value = []
} }
} }
function activeStyle(index) {
if (index !== activeIndex.value) return {}
return {
"background-color": theme.value,
"color": "#fff"
}
}
function navigateResult(direction) {
if (direction === "up") {
activeIndex.value = activeIndex.value <= 0 ? options.value.length - 1 : activeIndex.value - 1
} else if (direction === "down") {
activeIndex.value = activeIndex.value >= options.value.length - 1 ? 0 : activeIndex.value + 1
}
}
function selectActiveResult() {
if (options.value.length > 0 && activeIndex.value >= 0) {
change(options.value[activeIndex.value])
}
}
function highlightText(text) {
if (!text) return ''
if (!search.value) return text
const keyword = escapeRegExp(search.value)
const reg = new RegExp(`(${keyword})`, 'gi')
return text.replace(reg, '<span class="highlight">$1</span>')
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
onMounted(() => { onMounted(() => {
searchPool.value = generateRoutes(routes.value);
})
watchEffect(() => {
searchPool.value = generateRoutes(routes.value) searchPool.value = generateRoutes(routes.value)
}) })
watch(show, (value) => {
if (value) {
document.body.addEventListener('click', close)
} else {
document.body.removeEventListener('click', close)
}
})
watch(searchPool, (list) => { watch(searchPool, (list) => {
initFuse(list) initFuse(list)
}) })

View File

@ -30,11 +30,11 @@ const props = defineProps({
activeIcon: { activeIcon: {
type: String type: String
} }
}) });
const iconName = ref('') const iconName = ref('');
const iconList = ref(icons) const iconList = ref(icons);
const emit = defineEmits(['selected']) const emit = defineEmits(['selected']);
function filterIcons() { function filterIcons() {
iconList.value = icons iconList.value = icons
@ -108,4 +108,4 @@ defineExpose({
} }
} }
} }
</style> </style>

View File

@ -1,8 +1,8 @@
let icons = [] let icons = []
const modules = import.meta.glob('./../../assets/icons/svg/*.svg') const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
for (const path in modules) { for (const path in modules) {
const p = path.split('assets/icons/svg/')[1].split('.svg')[0] const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
icons.push(p) icons.push(p);
} }
export default icons export default icons

View File

@ -15,7 +15,7 @@
</template> </template>
<script setup> <script setup>
import { isExternal } from "@/utils/validate" import { isExternal } from "@/utils/validate";
const props = defineProps({ const props = defineProps({
src: { src: {
@ -30,41 +30,41 @@ const props = defineProps({
type: [Number, String], type: [Number, String],
default: "" default: ""
} }
}) });
const realSrc = computed(() => { const realSrc = computed(() => {
if (!props.src) { if (!props.src) {
return return;
} }
let real_src = props.src.split(",")[0] let real_src = props.src.split(",")[0];
if (isExternal(real_src)) { if (isExternal(real_src)) {
return real_src return real_src;
} }
return import.meta.env.VITE_APP_BASE_API + real_src return import.meta.env.VITE_APP_BASE_API + real_src;
}) });
const realSrcList = computed(() => { const realSrcList = computed(() => {
if (!props.src) { if (!props.src) {
return return;
} }
let real_src_list = props.src.split(",") let real_src_list = props.src.split(",");
let srcList = [] let srcList = [];
real_src_list.forEach(item => { real_src_list.forEach(item => {
if (isExternal(item)) { if (isExternal(item)) {
return srcList.push(item) return srcList.push(item);
} }
return srcList.push(import.meta.env.VITE_APP_BASE_API + item) return srcList.push(import.meta.env.VITE_APP_BASE_API + item);
}) });
return srcList return srcList;
}) });
const realWidth = computed(() => const realWidth = computed(() =>
typeof props.width == "string" ? props.width : `${props.width}px` typeof props.width == "string" ? props.width : `${props.width}px`
) );
const realHeight = computed(() => const realHeight = computed(() =>
typeof props.height == "string" ? props.height : `${props.height}px` typeof props.height == "string" ? props.height : `${props.height}px`
) );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -2,12 +2,10 @@
<div class="component-upload-image"> <div class="component-upload-image">
<el-upload <el-upload
multiple multiple
:disabled="disabled"
:action="uploadImgUrl" :action="uploadImgUrl"
list-type="picture-card" list-type="picture-card"
:on-success="handleUploadSuccess" :on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload" :before-upload="handleBeforeUpload"
:data="data"
:limit="limit" :limit="limit"
:on-error="handleUploadError" :on-error="handleUploadError"
:on-exceed="handleExceed" :on-exceed="handleExceed"
@ -22,7 +20,7 @@
<el-icon class="avatar-uploader-icon"><plus /></el-icon> <el-icon class="avatar-uploader-icon"><plus /></el-icon>
</el-upload> </el-upload>
<!-- 上传提示 --> <!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip && !disabled"> <div class="el-upload__tip" v-if="showTip">
请上传 请上传
<template v-if="fileSize"> <template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
@ -48,202 +46,166 @@
</template> </template>
<script setup> <script setup>
import { getToken } from "@/utils/auth" import { getToken } from "@/utils/auth";
import { isExternal } from "@/utils/validate" import { isExternal } from "@/utils/validate";
import Sortable from 'sortablejs'
const props = defineProps({ const props = defineProps({
modelValue: [String, Object, Array], modelValue: [String, Object, Array],
//
action: {
type: String,
default: "/common/upload"
},
//
data: {
type: Object
},
// //
limit: { limit: {
type: Number, type: Number,
default: 5 default: 5,
}, },
// (MB) // (MB)
fileSize: { fileSize: {
type: Number, type: Number,
default: 5 default: 5,
}, },
// , ['png', 'jpg', 'jpeg'] // , ['png', 'jpg', 'jpeg']
fileType: { fileType: {
type: Array, type: Array,
default: () => ["png", "jpg", "jpeg"] default: () => ["png", "jpg", "jpeg"],
}, },
// //
isShowTip: { isShowTip: {
type: Boolean, type: Boolean,
default: true default: true
}, },
// });
disabled: {
type: Boolean,
default: false
},
//
drag: {
type: Boolean,
default: true
}
})
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance();
const emit = defineEmits() const emit = defineEmits();
const number = ref(0) const number = ref(0);
const uploadList = ref([]) const uploadList = ref([]);
const dialogImageUrl = ref("") const dialogImageUrl = ref("");
const dialogVisible = ref(false) const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); //
const headers = ref({ Authorization: "Bearer " + getToken() }) const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]) const fileList = ref([]);
const showTip = computed( const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize) () => props.isShowTip && (props.fileType || props.fileSize)
) );
watch(() => props.modelValue, val => { watch(() => props.modelValue, val => {
if (val) { if (val) {
// //
const list = Array.isArray(val) ? val : props.modelValue.split(",") const list = Array.isArray(val) ? val : props.modelValue.split(",");
// //
fileList.value = list.map(item => { fileList.value = list.map(item => {
if (typeof item === "string") { if (typeof item === "string") {
if (item.indexOf(baseUrl) === -1 && !isExternal(item)) { if (item.indexOf(baseUrl) === -1 && !isExternal(item)) {
item = { name: baseUrl + item, url: baseUrl + item } item = { name: baseUrl + item, url: baseUrl + item };
} else { } else {
item = { name: item, url: item } item = { name: item, url: item };
} }
} }
return item return item;
}) });
} else { } else {
fileList.value = [] fileList.value = [];
return [] return [];
} }
},{ deep: true, immediate: true }) },{ deep: true, immediate: true });
// loading // loading
function handleBeforeUpload(file) { function handleBeforeUpload(file) {
let isImg = false let isImg = false;
if (props.fileType.length) { if (props.fileType.length) {
let fileExtension = "" let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) { if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1) fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
} }
isImg = props.fileType.some(type => { isImg = props.fileType.some(type => {
if (file.type.indexOf(type) > -1) return true if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false return false;
}) });
} else { } else {
isImg = file.type.indexOf("image") > -1 isImg = file.type.indexOf("image") > -1;
} }
if (!isImg) { if (!isImg) {
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}图片格式文件!`) proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}图片格式文件!`);
return false return false;
} }
if (file.name.includes(',')) { if (file.name.includes(',')) {
proxy.$modal.msgError('文件名不正确,不能包含英文逗号!') proxy.$modal.msgError('文件名不正确,不能包含英文逗号!');
return false return false;
} }
if (props.fileSize) { if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) { if (!isLt) {
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`) proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
return false return false;
} }
} }
proxy.$modal.loading("正在上传图片,请稍候...") proxy.$modal.loading("正在上传图片,请稍候...");
number.value++ number.value++;
} }
// //
function handleExceed() { function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`) proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
} }
// //
function handleUploadSuccess(res, file) { function handleUploadSuccess(res, file) {
if (res.code === 200) { if (res.code === 200) {
uploadList.value.push({ name: res.fileName, url: res.fileName }) uploadList.value.push({ name: res.fileName, url: res.fileName });
uploadedSuccessfully() uploadedSuccessfully();
} else { } else {
number.value-- number.value--;
proxy.$modal.closeLoading() proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg) proxy.$modal.msgError(res.msg);
proxy.$refs.imageUpload.handleRemove(file) proxy.$refs.imageUpload.handleRemove(file);
uploadedSuccessfully() uploadedSuccessfully();
} }
} }
// //
function handleDelete(file) { function handleDelete(file) {
const findex = fileList.value.map(f => f.name).indexOf(file.name) const findex = fileList.value.map(f => f.name).indexOf(file.name);
if (findex > -1 && uploadList.value.length === number.value) { if (findex > -1 && uploadList.value.length === number.value) {
fileList.value.splice(findex, 1) fileList.value.splice(findex, 1);
emit("update:modelValue", listToString(fileList.value)) emit("update:modelValue", listToString(fileList.value));
return false return false;
} }
} }
// //
function uploadedSuccessfully() { function uploadedSuccessfully() {
if (number.value > 0 && uploadList.value.length === number.value) { if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value) fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [] uploadList.value = [];
number.value = 0 number.value = 0;
emit("update:modelValue", listToString(fileList.value)) emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading() proxy.$modal.closeLoading();
} }
} }
// //
function handleUploadError() { function handleUploadError() {
proxy.$modal.msgError("上传图片失败") proxy.$modal.msgError("上传图片失败");
proxy.$modal.closeLoading() proxy.$modal.closeLoading();
} }
// //
function handlePictureCardPreview(file) { function handlePictureCardPreview(file) {
dialogImageUrl.value = file.url dialogImageUrl.value = file.url;
dialogVisible.value = true dialogVisible.value = true;
} }
// //
function listToString(list, separator) { function listToString(list, separator) {
let strs = "" let strs = "";
separator = separator || "," separator = separator || ",";
for (let i in list) { for (let i in list) {
if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) { if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
strs += list[i].url.replace(baseUrl, "") + separator strs += list[i].url.replace(baseUrl, "") + separator;
} }
} }
return strs != "" ? strs.substr(0, strs.length - 1) : "" return strs != "" ? strs.substr(0, strs.length - 1) : "";
} }
//
onMounted(() => {
if (props.drag && !props.disabled) {
nextTick(() => {
const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list')
Sortable.create(element, {
onEnd: (evt) => {
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
fileList.value.splice(evt.newIndex, 0, movedItem)
emit('update:modelValue', listToString(fileList.value))
}
})
})
}
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -251,4 +213,4 @@ onMounted(() => {
:deep(.hide .el-upload--picture-card) { :deep(.hide .el-upload--picture-card) {
display: none; display: none;
} }
</style> </style>

View File

@ -59,7 +59,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits() const emit = defineEmits();
const currentPage = computed({ const currentPage = computed({
get() { get() {
return props.page return props.page
@ -76,7 +76,6 @@ const pageSize = computed({
emit('update:limit', val) emit('update:limit', val)
} }
}) })
function handleSizeChange(val) { function handleSizeChange(val) {
if (currentPage.value * val > props.total) { if (currentPage.value * val > props.total) {
currentPage.value = 1 currentPage.value = 1
@ -86,13 +85,13 @@ function handleSizeChange(val) {
scrollTo(0, 800) scrollTo(0, 800)
} }
} }
function handleCurrentChange(val) { function handleCurrentChange(val) {
emit('pagination', { page: val, limit: pageSize.value }) emit('pagination', { page: val, limit: pageSize.value })
if (props.autoScroll) { if (props.autoScroll) {
scrollTo(0, 800) scrollTo(0, 800)
} }
} }
</script> </script>
<style scoped> <style scoped>
@ -103,4 +102,4 @@ function handleCurrentChange(val) {
.pagination-container.hidden { .pagination-container.hidden {
display: none; display: none;
} }
</style> </style>

View File

@ -1,26 +1,21 @@
<template> <template>
<div class="top-right-btn" :style="style"> <div class="top-right-btn" :style="style">
<el-row> <el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search"> <!-- <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">-->
<el-button circle icon="Search" @click="toggleSearch()" /> <!-- <el-button circle icon="Search" @click="toggleSearch()" />-->
</el-tooltip> <!-- </el-tooltip>-->
<el-tooltip class="item" effect="dark" content="刷新" placement="top"> <el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle icon="Refresh" @click="refresh()" /> <el-button circle icon="Refresh" @click="refresh()" />
</el-tooltip> </el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="Object.keys(columns).length > 0"> <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/> <el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'"> <el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button circle icon="Menu" /> <el-button circle icon="Menu" />
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<!-- 全选/反选 按钮 --> <template v-for="item in columns" :key="item.key">
<el-dropdown-item>
<el-checkbox :indeterminate="isIndeterminate" v-model="isChecked" @change="toggleCheckAll"> 列展示 </el-checkbox>
</el-dropdown-item>
<div class="check-line"></div>
<template v-for="(item, key) in columns" :key="item.key">
<el-dropdown-item> <el-dropdown-item>
<el-checkbox v-model="item.visible" @change="checkboxChange($event, key)" :label="item.label" /> <el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" />
</el-dropdown-item> </el-dropdown-item>
</template> </template>
</el-dropdown-menu> </el-dropdown-menu>
@ -32,7 +27,7 @@
<el-transfer <el-transfer
:titles="['显示', '隐藏']" :titles="['显示', '隐藏']"
v-model="value" v-model="value"
:data="transferData" :data="columns"
@change="dataChange" @change="dataChange"
></el-transfer> ></el-transfer>
</el-dialog> </el-dialog>
@ -40,191 +35,87 @@
</template> </template>
<script setup> <script setup>
import cache from '@/plugins/cache'
const props = defineProps({ const props = defineProps({
/* 是否显示检索条件 */ /* 是否显示检索条件 */
showSearch: { showSearch: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
/* 显隐列信息(数组格式、对象格式) */ /* 显隐列信息 */
columns: { columns: {
type: [Array, Object], type: Array,
default: () => ({})
}, },
/* 是否显示检索图标 */ /* 是否显示检索图标 */
search: { search: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
/* 显隐列类型transfer穿梭框、checkbox复选框 */ /* 显隐列类型transfer穿梭框、checkbox复选框 */
showColumnsType: { showColumnsType: {
type: String, type: String,
default: "checkbox" default: "checkbox",
}, },
/* 右外边距 */ /* 右外边距 */
gutter: { gutter: {
type: Number, type: Number,
default: 10 default: 10,
}, },
/* 列显隐状态记忆的 localStorage key传入则启用记忆不传则不记忆 */
storageKey: {
type: String,
default: ""
}
}) })
const emits = defineEmits(['update:showSearch', 'queryTable']) const emits = defineEmits(['update:showSearch', 'queryTable']);
// //
const value = ref([]) const value = ref([]);
// //
const title = ref("显示/隐藏") const title = ref("显示/隐藏");
// //
const open = ref(false) const open = ref(false);
const style = computed(() => { const style = computed(() => {
const ret = {} const ret = {};
if (props.gutter) { if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px` ret.marginRight = `${props.gutter / 2}px`;
} }
return ret return ret;
}) });
// /
const isChecked = computed({
get: () => Array.isArray(props.columns) ? props.columns.every(col => col.visible) : Object.values(props.columns).every((col) => col.visible),
set: () => {}
})
const isIndeterminate = computed(() => Array.isArray(props.columns) ? props.columns.some((col) => col.visible) && !isChecked.value : Object.values(props.columns).some((col) => col.visible) && !isChecked.value)
const transferData = computed(() => Array.isArray(props.columns) ? props.columns.map((item, index) => ({ key: index, label: item.label })) : Object.keys(props.columns).map((key, index) => ({ key: index, label: props.columns[key].label })))
// //
const { proxy } = getCurrentInstance()
function toggleSearch() { function toggleSearch() {
let el = proxy.$el emits("update:showSearch", !props.showSearch);
let formEl = null
while ((el = el.parentElement) && el !== document.body) {
if ((formEl = el.querySelector('.el-form'))) break
}
if (!formEl) return emits('update:showSearch', !props.showSearch)
animateSearch(formEl, props.showSearch)
}
function animateSearch(el, isHide) {
const DURATION = 260
const TRANSITION = 'max-height 0.25s ease, opacity 0.2s ease'
const clear = () => Object.assign(el.style, { transition: '', maxHeight: '', opacity: '', overflow: '' })
Object.assign(el.style, { overflow: 'hidden', transition: '' })
if (isHide) {
Object.assign(el.style, { maxHeight: el.scrollHeight + 'px', opacity: '1', transition: TRANSITION })
requestAnimationFrame(() => Object.assign(el.style, { maxHeight: '0', opacity: '0' }))
setTimeout(() => { emits('update:showSearch', false); clear() }, DURATION)
} else {
emits('update:showSearch', true)
nextTick(() => {
Object.assign(el.style, { maxHeight: '0', opacity: '0' })
requestAnimationFrame(() => requestAnimationFrame(() => {
Object.assign(el.style, { transition: TRANSITION, maxHeight: el.scrollHeight + 'px', opacity: '1' })
}))
setTimeout(clear, DURATION)
})
}
} }
// //
function refresh() { function refresh() {
emits("queryTable") emits("queryTable");
} }
// //
function dataChange(data) { function dataChange(data) {
if (Array.isArray(props.columns)) { for (let item in props.columns) {
for (let item in props.columns) { const key = props.columns[item].key;
const key = props.columns[item].key props.columns[item].visible = !data.includes(key);
props.columns[item].visible = !data.includes(key)
}
} else {
Object.keys(props.columns).forEach((key, index) => {
props.columns[key].visible = !data.includes(index)
})
} }
saveStorage()
} }
// dialog // dialog
function showColumn() { function showColumn() {
open.value = true open.value = true;
} }
// storageKey localStorage if (props.showColumnsType == 'transfer') {
if (props.storageKey) { //
try { for (let item in props.columns) {
const saved = cache.local.getJSON(props.storageKey) if (props.columns[item].visible === false) {
if (saved && typeof saved === 'object') { value.value.push(parseInt(item));
if (Array.isArray(props.columns)) {
props.columns.forEach((col, index) => {
if (saved[index] !== undefined) col.visible = saved[index]
})
} else {
Object.keys(props.columns).forEach(key => {
if (saved[key] !== undefined) props.columns[key].visible = saved[key]
})
}
} }
} catch (e) {}
}
if (props.showColumnsType == "transfer") {
// transfer穿
if (Array.isArray(props.columns)) {
for (let item in props.columns) {
if (props.columns[item].visible === false) {
value.value.push(parseInt(item))
}
}
} else {
Object.keys(props.columns).forEach((key, index) => {
if (props.columns[key].visible === false) {
value.value.push(index)
}
})
} }
} }
// //
function checkboxChange(event, key) { function checkboxChange(event, label) {
if (Array.isArray(props.columns)) { props.columns.filter(item => item.label == label)[0].visible = event;
props.columns.filter(item => item.key == key)[0].visible = event
} else {
props.columns[key].visible = event
}
saveStorage()
} }
// /
function toggleCheckAll() {
const newValue = !isChecked.value
if (Array.isArray(props.columns)) {
props.columns.forEach((col) => (col.visible = newValue))
} else {
Object.values(props.columns).forEach((col) => (col.visible = newValue))
}
saveStorage()
}
// localStorage
function saveStorage() {
if (!props.storageKey) return
try {
let state = {}
if (Array.isArray(props.columns)) {
props.columns.forEach((col, index) => { state[index] = col.visible })
} else {
Object.keys(props.columns).forEach(key => { state[key] = props.columns[key].visible })
}
cache.local.setJSON(props.storageKey, state)
} catch (e) {}
}
</script> </script>
<style lang='scss' scoped> <style lang='scss' scoped>

View File

@ -5,7 +5,7 @@
</template> </template>
<script setup> <script setup>
const url = ref(import.meta.env.VITE_APP_BASE_API + '/doc.html') const url = ref('http://doc.ruoyi.vip/ruoyi-vue');
function goto() { function goto() {
window.open(url.value) window.open(url.value)

View File

@ -5,7 +5,7 @@
</template> </template>
<script setup> <script setup>
const url = ref(import.meta.env.VITE_APP_BASE_API + '/doc.html') const url = ref('https://gitee.com/y_project/RuoYi-Vue');
function goto() { function goto() {
window.open(url.value) window.open(url.value)

View File

@ -7,7 +7,7 @@
<script setup> <script setup>
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core'
const { isFullscreen, toggle } = useFullscreen() const { isFullscreen, enter, exit, toggle } = useFullscreen();
</script> </script>
<style lang='scss' scoped> <style lang='scss' scoped>
@ -19,4 +19,4 @@ const { isFullscreen, toggle } = useFullscreen()
height: 20px; height: 20px;
vertical-align: 10px; vertical-align: 10px;
} }
</style> </style>

View File

@ -16,21 +16,23 @@
</template> </template>
<script setup> <script setup>
import useAppStore from "@/store/modules/app" import useAppStore from "@/store/modules/app";
const appStore = useAppStore() const appStore = useAppStore();
const size = computed(() => appStore.size) const size = computed(() => appStore.size);
const { proxy } = getCurrentInstance() const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance();
const sizeOptions = ref([ const sizeOptions = ref([
{ label: "较大", value: "large" }, { label: "较大", value: "large" },
{ label: "默认", value: "default" }, { label: "默认", value: "default" },
{ label: "稍小", value: "small" }, { label: "稍小", value: "small" },
]) ]);
function handleSetSize(size) { function handleSetSize(size) {
proxy.$modal.loading("正在设置布局大小,请稍候...") proxy.$modal.loading("正在设置布局大小,请稍候...");
appStore.setSize(size) appStore.setSize(size);
setTimeout("window.location.reload()", 1000) setTimeout("window.location.reload()", 1000);
} }
</script> </script>
@ -40,4 +42,4 @@ function handleSetSize(size) {
line-height: 50px; line-height: 50px;
padding-right: 7px; padding-right: 7px;
} }
</style> </style>

View File

@ -1,36 +1,30 @@
<template> <template>
<svg <svg
:class="svgClass" :width="size"
:width="resolvedSize" :height="size"
:height="resolvedSize"
:style="svgStyle" :style="svgStyle"
class="svg-icon"
aria-hidden="true" aria-hidden="true"
> >
<use :xlink:href="symbolId" :fill="color || 'currentColor'" /> <use :xlink:href="symbolId" />
</svg> </svg>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'
const props = defineProps({ const props = defineProps({
iconClass: {
type: String,
default: ''
},
name: { name: {
type: String, type: String,
default: '' required: true
},
className: {
type: String,
default: ''
}, },
size: { size: {
type: [String, Number], type: [String, Number],
default: undefined default: 24
}, },
color: { color: {
type: String, type: String,
default: '' default: 'currentColor'
}, },
customStyle: { customStyle: {
type: Object, type: Object,
@ -38,22 +32,11 @@ const props = defineProps({
} }
}) })
const icon = computed(() => props.name || props.iconClass) const symbolId = computed(() => `#icon-${props.name}`)
const symbolId = computed(() => `#icon-${icon.value}`)
const resolvedSize = computed(() => props.size ?? (props.name ? 24 : undefined))
const svgClass = computed(() => props.className ? `svg-icon ${props.className}` : 'svg-icon')
const svgStyle = computed(() => ({ const svgStyle = computed(() => ({
color: props.color || undefined, verticalAlign: 'middle',
color: props.color, // color fill
...props.customStyle ...props.customStyle
})) }))
</script> </script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

View File

@ -1,10 +1,10 @@
import * as components from '@element-plus/icons-vue' import * as components from '@element-plus/icons-vue'
export default { export default {
install: (app) => { install: (app) => {
for (const key in components) { for (const key in components) {
const componentConfig = components[key] const componentConfig = components[key];
app.component(componentConfig.name, componentConfig) app.component(componentConfig.name, componentConfig);
} }
} },
} };

View File

@ -1,69 +1,69 @@
<script setup> <script setup>
import {ArrowDown, ArrowUp} from "@element-plus/icons-vue"; import {ArrowDown, ArrowUp} from "@element-plus/icons-vue";
const props = defineProps({ const props = defineProps({
queryParams: { queryParams: {
type: Object, type: Object,
}, },
queryRef: { queryRef: {
type: String, type: String,
default: 'queryRef' default: 'queryRef'
}, },
inline: { inline: {
type: Boolean, type: Boolean,
default: false default: false
}, },
showSearch: { showSearch: {
type: Boolean, type: Boolean,
}, },
labelWidth: { labelWidth: {
type: String, type: String,
default: '68px' default: '68px'
} }
}) })
const emits = defineEmits(['search','refresh']) const emits = defineEmits(['search','refresh'])
const {proxy} = getCurrentInstance() const {proxy} = getCurrentInstance()
const formData = reactive({ const formData = reactive({
search: false search: false
}); });
const handleQuery = () => { const handleQuery = () => {
emits('search') emits('search')
} }
const resetQuery = () => { const resetQuery = () => {
proxy.resetForm(props.queryRef) proxy.resetForm(props.queryRef)
emits('refresh') emits('refresh')
} }
</script> </script>
<template> <template>
<el-form <el-form
:model="queryParams" :model="queryParams"
:ref="queryRef" :ref="queryRef"
:inline="inline" :inline="inline"
v-show="showSearch" v-show="showSearch"
:label-width="labelWidth" :label-width="labelWidth"
> >
<el-row :gutter="16"> <el-row :gutter="16">
<slot name="one"></slot> <slot name="one"></slot>
<slot name="other" v-if="formData.search"></slot> <slot name="other" v-if="formData.search"></slot>
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="6" :xxl="6" style="padding-bottom: 10px;"> <el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="6" :xxl="6" style="padding-bottom: 10px;">
<el-space> <el-space>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" text @click="formData.search = !formData.search"> <el-button type="primary" text @click="formData.search = !formData.search">
<template #icon> <template #icon>
<ArrowUp v-if="formData.search"/> <ArrowUp v-if="formData.search"/>
<ArrowDown v-else/> <ArrowDown v-else/>
</template> </template>
<template #default>{{ formData.search ? "收起" : "展开" }}</template> <template #default>{{ formData.search ? "收起" : "展开" }}</template>
</el-button> </el-button>
</el-space> </el-space>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
</style> </style>

View File

@ -1,756 +0,0 @@
<template>
<div class="tree-sidebar" :class="{ collapsed: collapsed, resizing: isResizing, 'no-initial-transition': isLoadingFromStorage}" :style="{ width: sidebarWidth + 'px' }">
<!-- 右侧拖动条 -->
<div v-if="!collapsed" class="resize-handle" @mousedown="startResize" @touchstart="startResize" :class="{ active: isResizing }" />
<div class="tree-header">
<span class="tree-title" v-show="!collapsed">
<el-icon><component :is="titleIcon" /></el-icon> {{ title }}
</span>
<div class="tree-actions" v-show="!collapsed">
<el-tooltip :content="isExpandedAll ? '收起全部' : '展开全部'" placement="right">
<el-icon class="tree-action-icon" @click="toggleExpandAll">
<ArrowDown v-if="isExpandedAll" />
<ArrowUp v-else />
</el-icon>
</el-tooltip>
<el-tooltip content="刷新" placement="right">
<el-icon class="tree-action-icon" @click="handleRefresh"><Refresh /></el-icon>
</el-tooltip>
<slot name="actions"></slot>
</div>
</div>
<!-- 侧边栏展开/收起按钮 -->
<div class="collapse-button-container">
<el-tooltip :content="collapsed ? '展开' : '收起'" placement="right">
<el-icon class="collapse-button" @click="toggleCollapsed">
<DArrowRight v-if="collapsed" />
<DArrowLeft v-else />
</el-icon>
</el-tooltip>
</div>
<div class="tree-search" v-show="!collapsed" v-if="showSearch">
<el-input v-model="searchKeyword" :placeholder="searchPlaceholder" clearable>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<div class="tree-wrap" v-show="!collapsed">
<el-tree
ref="treeRef"
:data="treeData"
:props="treeProps"
:expand-on-click-node="expandOnClickNode"
:filter-node-method="filterNodeMethod"
:default-expand-all="defaultExpandAll"
:default-expanded-keys="defaultExpandedKeys"
:node-key="nodeKey"
:check-strictly="checkStrictly"
:show-checkbox="showCheckbox"
@node-click="onNodeClick"
@check="onCheck"
@node-expand="onNodeExpand"
@node-collapse="onNodeCollapse"
>
<template #default="{ node, data }">
<slot name="node" :node="node" :data="data">
<span class="tree-node">
<el-icon class="node-icon">
<Folder v-if="data.children && data.children.length" />
<Document v-else />
</el-icon>
<span class="node-label" :title="node.label">{{ node.label }}</span>
</span>
</slot>
</template>
</el-tree>
</div>
</div>
</template>
<script setup>
const props = defineProps({
//
treeData: {
type: Array,
default: () => []
},
//
title: {
type: String,
default: '树形结构'
},
//
titleIcon: {
type: [String, Object],
default: 'OfficeBuilding'
},
//
showSearch: {
type: Boolean,
default: true
},
//
searchPlaceholder: {
type: String,
default: '请输入名称'
},
//
defaultCollapsed: {
type: Boolean,
default: false
},
//
treeProps: {
type: Object,
default: () => ({
children: "children",
label: "label"
})
},
//
nodeKey: {
type: String,
default: 'id'
},
//
expandOnClickNode: {
type: Boolean,
default: false
},
//
showCheckbox: {
type: Boolean,
default: false
},
//
checkStrictly: {
type: Boolean,
default: false
},
//
defaultExpandAll: {
type: Boolean,
default: false
},
// key
defaultExpandedKeys: {
type: Array,
default: () => []
},
//
defaultWidth: {
type: Number,
default: 220
},
//
collapsedWidth: {
type: Number,
default: 20
},
//
minWidth: {
type: Number,
default: 180
},
//
maxWidth: {
type: Number,
default: 400
},
// key
storageKey: {
type: String,
default: 'tree-sidebar-width'
},
//
enableStorage: {
type: Boolean,
default: true
},
//
filterMethod: {
type: Function,
default: null
}
})
const emit = defineEmits([
'collapsed-change',
'expanded-all-change',
'refresh',
'node-click',
'check',
'node-expand',
'node-collapse',
'search'
])
const treeRef = ref(null)
//
const searchKeyword = ref('')
const collapsed = ref(props.defaultCollapsed)
const sidebarWidth = ref(props.defaultCollapsed ? props.collapsedWidth : props.defaultWidth)
const isResizing = ref(false)
const startX = ref(0)
const startWidth = ref(0)
const saveWidthTimer = ref(null)
const rafId = ref(null)
const isLoadingFromStorage = ref(false)
const expandedAll = ref(props.defaultExpandAll)
//
const isExpandedAll = computed({
get: () => expandedAll.value,
set: (val) => {
expandedAll.value = val
}
})
//
const filterNodeMethod = (value, data) => {
if (props.filterMethod) {
return props.filterMethod(value, data)
}
if (!value) return true
return data.label && data.label.indexOf(value) !== -1
}
//
watch(collapsed, (newVal, oldVal) => {
if (newVal !== oldVal) {
handleCollapseChange(newVal)
emit('collapsed-change', newVal)
}
})
// /
watch(expandedAll, (newVal) => {
nextTick(() => {
if (newVal) {
expandAllNodes()
} else {
collapseAllNodes()
}
})
emit('expanded-all-change', newVal)
})
//
watch(searchKeyword, (val) => {
if (treeRef.value) {
treeRef.value.filter(val)
emit('search', val)
}
})
//
const cleanup = () => {
if (rafId.value) {
cancelAnimationFrame(rafId.value)
rafId.value = null
}
if (saveWidthTimer.value) {
clearTimeout(saveWidthTimer.value)
saveWidthTimer.value = null
}
}
// /
const handleCollapseChange = (isCollapsed) => {
if (isCollapsed) {
saveWidthToStorage()
sidebarWidth.value = props.collapsedWidth
} else {
const savedWidth = getSavedWidth()
sidebarWidth.value = savedWidth !== null ? savedWidth : props.defaultWidth
}
}
//
const getSavedWidth = () => {
if (!props.enableStorage) {
return null
}
try {
const savedWidth = localStorage.getItem(props.storageKey)
if (savedWidth) {
const width = parseInt(savedWidth, 10)
if (!isNaN(width) && width >= props.minWidth && width <= props.maxWidth) {
return width
}
}
} catch (error) {
console.warn(`Failed to load sidebar width from storage with key ${props.storageKey}:`, error)
}
return null
}
//
const saveWidthToStorage = () => {
if (collapsed.value || !props.enableStorage) return
try {
localStorage.setItem(props.storageKey, sidebarWidth.value.toString())
} catch (error) {
console.warn(`Failed to save sidebar width to storage with key ${props.storageKey}:`, error)
}
}
// /
const toggleCollapsed = () => {
collapsed.value = !collapsed.value
}
// /
const toggleExpandAll = () => {
expandedAll.value = !expandedAll.value
}
//
const expandAllNodes = () => {
if (!treeRef.value) return
const allNodes = getAllNodes(treeRef.value.root)
allNodes.forEach(node => {
if (node.expanded !== undefined && !node.expanded) {
node.expanded = true
}
})
}
//
const getAllNodes = (rootNode) => {
const nodes = []
const traverse = (node) => {
if (!node) return
nodes.push(node)
if (node.childNodes && node.childNodes.length) {
node.childNodes.forEach(child => traverse(child))
}
}
traverse(rootNode)
return nodes
}
//
const collapseAllNodes = () => {
if (!treeRef.value) return
const allNodes = getAllNodes(treeRef.value.root)
allNodes.forEach(node => {
if (node.expanded !== undefined && node.expanded) {
node.expanded = false
}
})
}
//
const handleRefresh = () => {
emit('refresh')
}
//
const onNodeClick = (data, node, e) => {
emit('node-click', data, node, e)
}
//
const onCheck = (data, checkedInfo) => {
emit('check', data, checkedInfo)
}
//
const onNodeExpand = (data, node, e) => {
emit('node-expand', data, node, e)
}
//
const onNodeCollapse = (data, node, e) => {
emit('node-collapse', data, node, e)
}
const setCurrentKey = (key) => {
if (treeRef.value) {
treeRef.value.setCurrentKey(key)
}
}
const getCurrentNode = () => {
if (treeRef.value) {
return treeRef.value.getCurrentNode()
}
return null
}
const getCurrentKey = () => {
if (treeRef.value) {
return treeRef.value.getCurrentKey()
}
return null
}
const setCheckedKeys = (keys) => {
if (treeRef.value && props.showCheckbox) {
treeRef.value.setCheckedKeys(keys)
}
}
const getCheckedKeys = () => {
if (treeRef.value && props.showCheckbox) {
return treeRef.value.getCheckedKeys()
}
return []
}
const getCheckedNodes = () => {
if (treeRef.value && props.showCheckbox) {
return treeRef.value.getCheckedNodes()
}
return []
}
const clearSearch = () => {
searchKeyword.value = ""
if (treeRef.value) {
treeRef.value.filter("")
}
}
const filter = (value) => {
searchKeyword.value = value
}
const startResize = (e) => {
e.preventDefault()
e.stopPropagation()
isResizing.value = true
startX.value = e.type === 'mousedown' ? e.clientX : e.touches[0].clientX
startWidth.value = sidebarWidth.value
if (e.type === 'mousedown') {
document.addEventListener('mousemove', handleResizeMove)
document.addEventListener('mouseup', stopResize)
} else {
document.addEventListener('touchmove', handleResizeMove, { passive: false })
document.addEventListener('touchend', stopResize)
}
disableUserSelect()
}
const handleResizeMove = (e) => {
if (!isResizing.value) return
if (rafId.value) {
cancelAnimationFrame(rafId.value)
}
rafId.value = requestAnimationFrame(() => {
e.preventDefault()
e.stopPropagation()
const clientX = e.type === 'mousemove' ? e.clientX : e.touches[0].clientX
const deltaX = clientX - startX.value
const newWidth = startWidth.value + deltaX
const clampedWidth = Math.max(props.minWidth, Math.min(props.maxWidth, newWidth))
if (Math.abs(clampedWidth - sidebarWidth.value) >= 1) {
sidebarWidth.value = clampedWidth
}
})
}
const stopResize = () => {
if (!isResizing.value) return
isResizing.value = false
if (rafId.value) {
cancelAnimationFrame(rafId.value)
rafId.value = null
}
startX.value = 0
startWidth.value = 0
document.removeEventListener('mousemove', handleResizeMove)
document.removeEventListener('mouseup', stopResize)
document.removeEventListener('touchmove', handleResizeMove)
document.removeEventListener('touchend', stopResize)
enableUserSelect()
saveWidthToStorage()
}
const disableUserSelect = () => {
document.body.style.userSelect = 'none'
document.body.style.webkitUserSelect = 'none'
document.body.style.mozUserSelect = 'none'
document.body.style.msUserSelect = 'none'
}
const enableUserSelect = () => {
document.body.style.userSelect = ''
document.body.style.webkitUserSelect = ''
document.body.style.mozUserSelect = ''
document.body.style.msUserSelect = ''
}
const resetWidth = () => {
sidebarWidth.value = props.defaultWidth
saveWidthToStorage()
}
const getCurrentWidth = () => {
return sidebarWidth.value
}
const setWidth = (width) => {
if (typeof width === 'number' && width >= props.minWidth && width <= props.maxWidth) {
sidebarWidth.value = width
if (!collapsed.value) {
saveWidthToStorage()
}
}
}
defineExpose({
setCurrentKey,
getCurrentNode,
getCurrentKey,
setCheckedKeys,
getCheckedKeys,
getCheckedNodes,
clearSearch,
filter,
resetWidth,
getCurrentWidth,
setWidth,
expandAllNodes,
collapseAllNodes,
toggleCollapsed,
treeRef
})
onMounted(() => {
isLoadingFromStorage.value = true
if (!collapsed.value && props.enableStorage) {
const savedWidth = getSavedWidth()
if (savedWidth !== null) {
sidebarWidth.value = savedWidth
}
}
nextTick(() => {
isLoadingFromStorage.value = false
})
if (expandedAll.value) {
nextTick(() => {
expandAllNodes()
})
}
})
onBeforeUnmount(() => {
cleanup()
})
</script>
<style lang="scss" scoped>
.tree-sidebar {
flex-shrink: 0;
width: 220px;
background: #fff;
border-right: 1px solid #e8eaed;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
transition: width 0.25s ease;
&.collapsed {
width: 42px;
}
&.resizing {
transition: none;
will-change: width;
* {
pointer-events: none !important;
}
}
&.no-initial-transition {
transition: none;
}
}
.resize-handle {
position: absolute;
top: 0;
right: 0;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 20;
background: transparent;
transition: background 0.2s;
&:hover {
background: rgba(64, 158, 255, 0.3);
}
&.active {
background: rgba(64, 158, 255, 0.5);
}
}
.collapse-button-container {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
width: 15px;
height: 20px;
background: #fff;
border-radius: 0 4px 4px 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
.tree-sidebar.collapsed & {
right: 0;
background: #f7f8fa;
border-radius: 0 4px 4px 0;
}
.tree-sidebar.resizing & {
pointer-events: none;
}
}
.collapse-button {
font-size: 20px;
color: #909399;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
&:hover {
color: #409eff;
background: #ecf5ff;
}
}
.tree-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
height: 40px;
border-bottom: 1px solid #e8eaed;
background: #f7f8fa;
flex-shrink: 0;
.tree-title {
font-size: 13px;
font-weight: 600;
color: #303133;
white-space: nowrap;
overflow: hidden;
display: flex;
align-items: center;
gap: 5px;
.el-icon {
color: #409eff;
font-size: 16px;
}
}
.tree-actions {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
}
.tree-action-icon {
font-size: 20px;
color: #909399;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
&:hover {
color: #409eff;
background: #ecf5ff;
}
}
.tree-search {
padding: 10px 10px 4px;
flex-shrink: 0;
}
.tree-wrap {
flex: 1;
overflow-y: auto;
padding: 6px 6px 12px;
.tree-sidebar.resizing & {
overflow: hidden;
}
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 4px;
&:hover {
background: #c0c4cc;
}
}
:deep(.el-tree-node__content) {
height: 32px;
border-radius: 4px;
margin-bottom: 1px;
&:hover {
background: #f0f7ff;
}
}
:deep(.el-tree-node.is-current > .el-tree-node__content) {
background: #e6f0fd;
color: #409eff;
font-weight: 600;
.node-icon {
color: #409eff !important;
}
}
}
.tree-node {
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
overflow: hidden;
.node-icon {
font-size: 14px;
color: #f5a623;
flex-shrink: 0;
}
.node-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More