[
  {
    "path": ".eslintrc-auto-import.json",
    "content": "{\n  \"globals\": {\n    \"Component\": true,\n    \"ComponentPublicInstance\": true,\n    \"ComputedRef\": true,\n    \"EffectScope\": true,\n    \"ExtractDefaultPropTypes\": true,\n    \"ExtractPropTypes\": true,\n    \"ExtractPublicPropTypes\": true,\n    \"InjectionKey\": true,\n    \"PropType\": true,\n    \"Ref\": true,\n    \"VNode\": true,\n    \"WritableComputedRef\": true,\n    \"computed\": true,\n    \"createApp\": true,\n    \"customRef\": true,\n    \"defineAsyncComponent\": true,\n    \"defineComponent\": true,\n    \"effectScope\": true,\n    \"getCurrentInstance\": true,\n    \"getCurrentScope\": true,\n    \"h\": true,\n    \"inject\": true,\n    \"isProxy\": true,\n    \"isReactive\": true,\n    \"isReadonly\": true,\n    \"isRef\": true,\n    \"markRaw\": true,\n    \"nextTick\": true,\n    \"onActivated\": true,\n    \"onBeforeMount\": true,\n    \"onBeforeRouteLeave\": true,\n    \"onBeforeRouteUpdate\": true,\n    \"onBeforeUnmount\": true,\n    \"onBeforeUpdate\": true,\n    \"onDeactivated\": true,\n    \"onErrorCaptured\": true,\n    \"onMounted\": true,\n    \"onRenderTracked\": true,\n    \"onRenderTriggered\": true,\n    \"onScopeDispose\": true,\n    \"onServerPrefetch\": true,\n    \"onUnmounted\": true,\n    \"onUpdated\": true,\n    \"provide\": true,\n    \"reactive\": true,\n    \"readonly\": true,\n    \"ref\": true,\n    \"resolveComponent\": true,\n    \"shallowReactive\": true,\n    \"shallowReadonly\": true,\n    \"shallowRef\": true,\n    \"toRaw\": true,\n    \"toRef\": true,\n    \"toRefs\": true,\n    \"toValue\": true,\n    \"triggerRef\": true,\n    \"unref\": true,\n    \"useAttrs\": true,\n    \"useCssModule\": true,\n    \"useCssVars\": true,\n    \"useLink\": true,\n    \"useRoute\": true,\n    \"useRouter\": true,\n    \"useSlots\": true,\n    \"watch\": true,\n    \"watchEffect\": true,\n    \"watchPostEffect\": true,\n    \"watchSyncEffect\": true,\n    \"ElCheckbox\": true,\n    \"ElInput\": true,\n    \"ElInputNumber\": true,\n    \"ElRadio\": true,\n    \"ElSelect\": true,\n    \"ElDivider\": true\n  }\n}\n"
  },
  {
    "path": ".eslintrc.cjs",
    "content": "/* eslint-env node */\nrequire('@rushstack/eslint-patch/modern-module-resolution')\n\nmodule.exports = {\n  root: true,\n  'extends': [\n    'plugin:vue/vue3-essential',\n    'eslint:recommended',\n    '@vue/eslint-config-typescript',\n    '@vue/eslint-config-prettier/skip-formatting'\n  ],\n  parserOptions: {\n    ecmaVersion: 'latest'\n  },\n  rules: {\n    'vue/no-mutating-props': ['error', {\n      'shallowOnly': true\n    }],\n    'vue/multi-word-component-names': 'off'\n  }\n}\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "name: GitHub Pages\n# 触发脚本的条件，develop分支push代码的时候\non:\n  push:\n    branches:\n      - main\n# 要执行的任务\njobs:\n  # 任务名称\n  build_and_deploy:\n    # runs-on 指定job任务运行所需要的虚拟机环境（必填）\n    runs-on: ubuntu-latest\n    # 任务步骤\n    steps:\n      - name: 迁出代码\n        # 使用action库  actions/checkout获取源码\n        uses: actions/checkout@v3 # 使用的工具\n      # 使用 pnpm\n      - name: 使用 pnpm\n        uses: pnpm/action-setup@v2\n        with:\n          version: 8.5.0\n      # 安装node\n      - name: 安装node.js\n        # 使用action库 actions/setup-node 安装node\n        uses: actions/setup-node@v3\n        with:\n          node-version: 16.20.0\n          cache: 'pnpm'\n      # 安装\n      - name: 安装依赖\n        run: pnpm install\n      # 打包\n      - name: 打包\n        run: pnpm build:test\n      # 部署\n      - name: 部署到gh-pages分支\n        uses: peaceiris/actions-gh-pages@v3\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: ./dist\n          # 指定推送分支，默认为：gh-pages\n          publish_branch: gh-pages\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Store\ndist\ndist-ssr\ncoverage\n*.local\n\n/cypress/videos/\n/cypress/screenshots/\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n*.tsbuildinfo\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/prettierrc\",\n  \"semi\": false,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"printWidth\": 100,\n  \"trailingComma\": \"none\"\n}"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"Vue.volar\",\n    \"dbaeumer.vscode-eslint\",\n    \"esbenp.prettier-vscode\"\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Victor Tsai\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.en.md",
    "content": "<div align=\"center\">\n  <h1>lowflow-design</h1>\n  <p>Low-code Workflow Designer</p>\n  <p>\n    <a href=\"./README.md\">中文</a> | <a href=\"./README.en.md\">English</a>\n  </p>\n</div>\n\n## Overview\n`lowflow-design` is a workflow designer built with `Vue 3`, `Vite`, `TypeScript`, and `Element Plus`. It is designed for low-code/no-code platforms that need visual workflow configuration.\n\nYou can create workflows visually and convert workflow JSON to BPMN XML with the backend converter project:\n- Backend converter: [GitHub](https://github.com/tsai996/lowflow-design-converter) | [Gitee](https://gitee.com/cai_xiao_feng/lowflow-design-converter)\n- Traditional `bpmn-js` workflow designer: [GitHub](https://github.com/tsai996/vue-bpmn-designer) | [Gitee](https://gitee.com/cai_xiao_feng/vue-bpmn-designer)\n\n## Live Demo\n- Preview: <https://tsai996.github.io/lowflow-design/>\n- Full demo: <https://demo.lowflow.vip/>\n\n## Screenshots\n<p>\n  <img alt=\"workflow designer\" src=\"public/flow.png\" style=\"display: inline-block\"/>\n  <img alt=\"property panel\" src=\"public/penal.png\" style=\"display: inline-block\"/>\n</p>\n\n## Features\n- Approval node: supports single user, multiple users, roles, departments, initiator, manager, and custom approvers.\n- CC node: supports single user, multiple users, roles, departments, initiator, manager, and custom CC recipients.\n- Conditional branches: supports condition groups and combination logic.\n- Timer wait: supports second, minute, hour, day, week, month, and custom durations.\n- Notification node: supports in-app, email, WeCom, DingTalk, Feishu, SMS, and more.\n\n## Tech Stack\n- Vue 3\n- TypeScript\n- Vite\n- Element Plus\n- Pinia\n- Vue Router\n- UnoCSS\n\n## Quick Start\n### 1. Install dependencies\n```bash\nnpm install\n```\n\n### 2. Start development server\n```bash\nnpm run dev\n```\n\n### 3. Build\n```bash\nnpm run build\n```\n\n### 4. Preview production build\n```bash\nnpm run preview\n```\n\n## Scripts\n```bash\nnpm run dev         # run dev server\nnpm run build       # production build (with type check)\nnpm run build:dev   # build in development mode\nnpm run build:test  # build in test mode\nnpm run preview     # preview production build\nnpm run type-check  # run type checking\nnpm run lint        # run ESLint (with --fix)\nnpm run format      # format src with Prettier\n```\n\n## Project Structure\n```text\n.\n|-- public/\n|-- src/\n|   |-- api/\n|   |-- assets/\n|   |-- components/\n|   |-- hooks/\n|   |-- mock/\n|   |-- router/\n|   |-- stores/\n|   |-- styles/\n|   |-- typings/\n|   |-- views/\n|   |   |-- flowDesign/\n|   |   |   |-- nodes/\n|   |   |   |-- panels/\n|   |   |   `-- index.vue\n|   |-- App.vue\n|   `-- main.ts\n|-- package.json\n|-- vite.config.ts\n`-- README.md\n```\n\n## Repositories\n| Platform | Frontend | Backend Converter |\n| --- | --- | --- |\n| GitHub | <https://github.com/tsai996/lowflow-design> | <https://github.com/tsai996/lowflow-design-converter> |\n| Gitee | <https://gitee.com/cai_xiao_feng/lowflow-design> | <https://gitee.com/cai_xiao_feng/lowflow-design-converter> |\n\n## Community and Support\n### Community Groups\n<p>\n  <img alt=\"WeChat\" src=\"public/wx.jpg\" width=\"240\" height=\"400\" style=\"display: inline-block\"/>\n  <img alt=\"QQ Group\" src=\"public/qq_qun.jpg\" width=\"240\" height=\"400\" style=\"display: inline-block\"/>\n</p>\n\n### Sponsor\nIf this project helps you, sponsorship is welcome.\n\n<p>\n  <img alt=\"WeChat Pay\" src=\"public/wxpay.png\" height=\"240\" width=\"240\" style=\"display: inline-block\"/>\n  <img alt=\"Alipay\" src=\"public/alipay.png\" height=\"240\" width=\"240\" style=\"display: inline-block\"/>\n</p>\n\n## Recommended Reading\n*In-depth Flowable Workflow Engine: Core Principles and Advanced Practice*:\n<https://item.jd.com/14804836.html>\n\n![flowable](public/flowable.jpg)\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <h1>lowflow-design</h1>\n  <p>低代码流程设计器</p>\n  <p>\n    <a href=\"./README.md\">中文</a> | <a href=\"./README.en.md\">English</a>\n  </p>\n</div>\n\n## 项目简介\n`lowflow-design` 是一个基于 `Vue 3`、`Vite`、`TypeScript`、`Element Plus` 的流程设计器，适用于低代码/无代码平台中的流程配置场景。\n\n项目支持通过可视化方式快速搭建流程，并可配合后端转换项目将流程 JSON 转换为 BPMN XML：\n- 后端转换器：[GitHub](https://github.com/tsai996/lowflow-design-converter) | [Gitee](https://gitee.com/cai_xiao_feng/lowflow-design-converter)\n- 传统 `bpmn-js` 流程设计器：[GitHub](https://github.com/tsai996/vue-bpmn-designer) | [Gitee](https://gitee.com/cai_xiao_feng/vue-bpmn-designer)\n\n## 在线体验\n- 预览地址：<https://tsai996.github.io/lowflow-design/>\n- 成品示例：<https://demo.lowflow.vip/>\n\n## 效果预览\n<p>\n  <img alt=\"流程设计器\" src=\"public/flow.png\" style=\"display: inline-block\"/>\n  <img alt=\"属性面板\" src=\"public/penal.png\" style=\"display: inline-block\"/>\n</p>\n\n## 功能特性\n- 审批节点：支持单人、多人、角色、部门、发起人、上级领导、自定义审批人等。\n- 抄送节点：支持单人、多人、角色、部门、发起人、上级领导、自定义抄送人等。\n- 条件分支：支持条件组及组合逻辑。\n- 计时等待：支持秒、分、时、天、周、月及自定义时长。\n- 消息通知：支持站内信、邮件、企业微信、钉钉、飞书、短信等通知方式。\n\n## 技术栈\n- Vue 3\n- TypeScript\n- Vite\n- Element Plus\n- Pinia\n- Vue Router\n- UnoCSS\n\n## 快速开始\n### 1. 安装依赖\n```bash\nnpm install\n```\n\n### 2. 启动开发环境\n```bash\nnpm run dev\n```\n\n### 3. 构建\n```bash\nnpm run build\n```\n\n### 4. 预览构建产物\n```bash\nnpm run preview\n```\n\n## 常用脚本\n```bash\nnpm run dev         # 本地开发\nnpm run build       # 生产构建（含类型检查）\nnpm run build:dev   # development 模式构建\nnpm run build:test  # test 模式构建\nnpm run preview     # 预览构建产物\nnpm run type-check  # 类型检查\nnpm run lint        # ESLint（自动修复）\nnpm run format      # Prettier 格式化（src）\n```\n\n## 项目结构\n```text\n.\n|-- public/\n|-- src/\n|   |-- api/\n|   |-- assets/\n|   |-- components/\n|   |-- hooks/\n|   |-- mock/\n|   |-- router/\n|   |-- stores/\n|   |-- styles/\n|   |-- typings/\n|   |-- views/\n|   |   |-- flowDesign/\n|   |   |   |-- nodes/\n|   |   |   |-- panels/\n|   |   |   `-- index.vue\n|   |-- App.vue\n|   `-- main.ts\n|-- package.json\n|-- vite.config.ts\n`-- README.md\n```\n\n## 源码仓库\n| 平台 | 前端 | 后端转换器 |\n| --- | --- | --- |\n| GitHub | <https://github.com/tsai996/lowflow-design> | <https://github.com/tsai996/lowflow-design-converter> |\n| Gitee | <https://gitee.com/cai_xiao_feng/lowflow-design> | <https://gitee.com/cai_xiao_feng/lowflow-design-converter> |\n\n## 交流与支持\n### 交流群\n<p>\n  <img alt=\"微信\" src=\"public/wx.jpg\" width=\"240\" height=\"400\" style=\"display: inline-block\"/>\n  <img alt=\"QQ群\" src=\"public/qq_qun.jpg\" width=\"240\" height=\"400\" style=\"display: inline-block\"/>\n</p>\n\n### 赞助\n如果这个项目对你有帮助，欢迎赞助支持。\n\n<p>\n  <img alt=\"微信赞助\" src=\"public/wxpay.png\" height=\"240\" width=\"240\" style=\"display: inline-block\"/>\n  <img alt=\"支付宝赞助\" src=\"public/alipay.png\" height=\"240\" width=\"240\" style=\"display: inline-block\"/>\n</p>\n\n## 推荐阅读\n推荐搭配阅读《深入 Flowable 流程引擎：核心原理与高阶实战》：\n<https://item.jd.com/14804836.html>\n\n![flowable](public/flowable.jpg)\n"
  },
  {
    "path": "env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ninterface ImportMetaEnv {\n    readonly VITE_API_URL: string;\n    readonly VITE_PUBLIC_PATH: string;\n}\n\ninterface ImportMeta {\n    readonly env: ImportMetaEnv\n}\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\">\n    <link rel=\"icon\" href=\"/favicon.ico\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Vite App</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"lowflow-design\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"run-p type-check \\\"build-only {@}\\\" --\",\n    \"build:dev\": \"vite build --mode development\",\n    \"build:test\": \"vite build --mode test\",\n    \"preview\": \"vite preview\",\n    \"build-only\": \"vite build\",\n    \"type-check\": \"vue-tsc --build --force\",\n    \"lint\": \"eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore\",\n    \"format\": \"prettier --write src/\"\n  },\n  \"dependencies\": {\n    \"@element-plus/icons-vue\": \"^2.3.1\",\n    \"@vueuse/core\": \"^10.9.0\",\n    \"axios\": \"^1.6.8\",\n    \"element-plus\": \"^2.7.2\",\n    \"file-saver\": \"^2.0.5\",\n    \"lodash-es\": \"^4.17.21\",\n    \"pinia\": \"^2.1.7\",\n    \"vue\": \"^3.4.21\",\n    \"vue-router\": \"^4.3.0\"\n  },\n  \"devDependencies\": {\n    \"@rushstack/eslint-patch\": \"^1.8.0\",\n    \"@tsconfig/node20\": \"^20.1.4\",\n    \"@types/file-saver\": \"^2.0.7\",\n    \"@types/lodash-es\": \"^4.17.12\",\n    \"@types/node\": \"^20.12.5\",\n    \"@vitejs/plugin-vue\": \"^5.0.4\",\n    \"@vitejs/plugin-vue-jsx\": \"^3.1.0\",\n    \"@vue/eslint-config-prettier\": \"^9.0.0\",\n    \"@vue/eslint-config-typescript\": \"^13.0.0\",\n    \"@vue/tsconfig\": \"^0.5.1\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-plugin-vue\": \"^9.23.0\",\n    \"mockjs\": \"^1.1.0\",\n    \"npm-run-all2\": \"^6.1.2\",\n    \"prettier\": \"^3.2.5\",\n    \"sass\": \"^1.77.0\",\n    \"typescript\": \"~5.4.0\",\n    \"unocss\": \"^0.59.4\",\n    \"unplugin-auto-import\": \"^0.17.5\",\n    \"unplugin-vue-components\": \"^0.27.0\",\n    \"vite\": \"^5.2.8\",\n    \"vite-plugin-mock\": \"^2.9.6\",\n    \"vite-plugin-svg-icons\": \"^2.0.1\",\n    \"vite-plugin-vue-setup-extend\": \"^0.4.0\",\n    \"vue-tsc\": \"^2.0.11\"\n  }\n}\n"
  },
  {
    "path": "public/CNAME",
    "content": "vite-starter.element-plus.org\n"
  },
  {
    "path": "src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport zhCn from 'element-plus/es/locale/lang/zh-cn'\nimport { ElNotification } from 'element-plus'\n\nonMounted(() => {\n  setTimeout(() => {\n    ElNotification({\n      type: 'info',\n      position: 'bottom-left',\n      duration: 10000,\n      title: '求职信息',\n      message: h('div', [\n        h('p', '96年失业（全栈开发）求收留'),\n        h('p', '地点：杭州'),\n        h('p', '微信：xfcai1216')\n      ])\n    })\n  }, 2000)\n})\n</script>\n\n<template>\n  <el-config-provider namespace=\"el\" :locale=\"zhCn\">\n    <router-view></router-view>\n  </el-config-provider>\n</template>\n\n<style scoped>\n#app {\n  color: var(--el-text-color-primary);\n}\n</style>\n"
  },
  {
    "path": "src/api/index.ts",
    "content": "import axios, {\n  type AxiosInstance,\n  AxiosError,\n  type AxiosRequestConfig,\n  type InternalAxiosRequestConfig,\n  type AxiosResponse\n} from 'axios'\nimport { ElNotification } from 'element-plus'\n\nexport interface Result {\n  code: number\n  success: boolean\n  message: string\n}\n\nexport interface ResultData<T = any> extends Result {\n  data: T\n}\n\n/**\n * axios配置\n */\nconst config = {\n  baseURL: import.meta.env.VITE_API_URL,\n  timeout: 8000\n}\n\nclass RequestHttp {\n  service: AxiosInstance\n\n  /**\n   * 请求构造函数\n   * @param config\n   */\n  public constructor(config: AxiosRequestConfig) {\n    this.service = axios.create(config)\n    this.service.interceptors.request.use(\n      (config: InternalAxiosRequestConfig) => {\n        return config\n      },\n      (error: AxiosError) => {\n        return Promise.reject(error)\n      }\n    )\n    this.service.interceptors.response.use(\n      (response: AxiosResponse) => {\n        const { data } = response\n        return data\n      },\n      (error: AxiosError) => {\n        const { response, message } = error\n        const data = response?.data as ResultData\n        const errMsg = data ? data.message : message\n        ElNotification.error(errMsg || '未知错误')\n        return Promise.reject(response?.data || error)\n      }\n    )\n  }\n\n  /**\n   * get请求\n   * @param url\n   * @param params\n   * @param config\n   */\n  get<T>(url: string, params?: object, config = {}): Promise<ResultData<T>> {\n    return this.service.get(url, { params, ...config })\n  }\n\n  /**\n   * post请求\n   * @param url\n   * @param data\n   * @param config\n   */\n  post<T>(url: string, data?: object, config = {}): Promise<ResultData<T>> {\n    return this.service.post(url, data, config)\n  }\n\n  /**\n   * request请求\n   * @param config\n   */\n  request<T>(config: AxiosRequestConfig): Promise<ResultData<T>> {\n    return this.service.request(config)\n  }\n\n  /**\n   * 下载文件\n   * @param url\n   * @param data\n   * @param config\n   */\n  download(url: string, data?: object, config = {}): Promise<BlobPart> {\n    return this.service.post(url, data, { ...config, responseType: 'blob' })\n  }\n}\n\nexport default new RequestHttp(config)\n"
  },
  {
    "path": "src/api/modules/model.ts",
    "content": "import http from '@/api'\nimport FileSaver from 'file-saver'\n\nexport const downloadXml = async (data: object) => {\n  const res = await http.download('https://demo.lowflow.vip/api/model/download', data)\n  FileSaver.saveAs(\n    new Blob([res], { type: 'application/octet-stream;charset=utf-8' }),\n    '测试流程.bpmn20.xml'\n  )\n}\n"
  },
  {
    "path": "src/api/modules/role.ts",
    "content": "import http from '@/api/index'\n\nexport interface Role {\n  id: string\n  name: string\n}\n\n/**\n * 获取角色信息\n * @param id\n */\nexport const getById = (id: string) => {\n  return http.get<Role>(`/role/info`, { id: id })\n}\n\n/**\n * 查询角色列表\n */\nexport const getList = (roleIds?: string[]) => {\n  const params = roleIds ? { roleIds: roleIds } : {}\n  return http.post<Role[]>('/role/list', params)\n}\n"
  },
  {
    "path": "src/api/modules/user.ts",
    "content": "import http from '@/api/index'\n\nexport interface User {\n  id: string\n  username: string\n  name: string\n  avatar: string\n}\n\n/**\n * 获取用户信息\n * @param username\n */\nexport const getByUsername = (username: string) => {\n  return http.get<User>(`/user/info`, { username: username })\n}\n\n/**\n * 查询用户列表\n */\nexport const getList = (userIds?: string[]) => {\n  const params = userIds ? { userIds: userIds } : {}\n  return http.post<User[]>('/user/list', params)\n}\n"
  },
  {
    "path": "src/components/AdvancedFilter/Operator.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\n\nconst $props = defineProps<{\n  modelValue: string\n}>()\nconst operatorOptions = [\n  {\n    value: 'eq',\n    label: '等于'\n  },\n  {\n    value: 'ne',\n    label: '不等于'\n  },\n  {\n    label: '包含',\n    value: 'in'\n  },\n  {\n    label: '不包含',\n    value: 'ni'\n  }\n]\nconst $emits = defineEmits<{\n  (e: 'update:modelValue', modelValue: any): void\n}>()\nconst data = useVModel($props, 'modelValue', $emits)\n</script>\n\n<template>\n  <el-select class=\"operator-container\" v-model=\"data\" filterable placeholder=\"筛选符\">\n    <el-option\n      v-for=\"item in operatorOptions\"\n      :key=\"item.value\"\n      :label=\"item.label\"\n      :value=\"item.value\"\n    />\n  </el-select>\n</template>\n\n<style scoped lang=\"scss\">\n.operator-container {\n  width: 100%;\n  flex-shrink: 0;\n}\n</style>\n"
  },
  {
    "path": "src/components/AdvancedFilter/Trigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Field } from '@/components/Render/type'\nimport type { FilterRules } from '@/components/AdvancedFilter/type'\nimport { useVModel } from '@vueuse/core'\n\nconst $props = defineProps<{\n  modelValue: any\n  options: Field[]\n  filterRules: FilterRules\n}>()\nconst $emits = defineEmits<{\n  (e: 'update:modelValue', modelValue: string): void\n}>()\nconst data = useVModel($props, 'modelValue', $emits)\n</script>\n\n<template>\n  <el-select class=\"trigger-container\" v-model=\"data\" filterable placeholder=\"选择字段\">\n    <el-option v-for=\"item in $props.options\" :key=\"item.id\" :label=\"item.label\" :value=\"item.id\" />\n  </el-select>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/components/AdvancedFilter/index.vue",
    "content": "<script setup lang=\"ts\" name=\"AdvancedFilter\">\nimport type { FilterRules } from './type'\nimport type { Field } from '@/components/Render/type'\nimport { useVModel } from '@vueuse/core'\nimport Trigger from './Trigger.vue'\nimport Operator from './Operator.vue'\n\nconst $props = defineProps<{\n  filterFields: Field[]\n  modelValue: FilterRules\n}>()\nconst $emits = defineEmits<{\n  (e: 'update:modelValue', modelValue: FilterRules): void\n  (e: 'addCondition', index: number): void\n  (e: 'delCondition', index: number): void\n  (e: 'delGroup'): void\n}>()\nconst filterRules = useVModel($props, 'modelValue', $emits)\n/**\n * 添加条件\n */\nconst addRule = () => {\n  filterRules.value.conditions.push({\n    field: null,\n    operator: 'eq',\n    value: null\n  })\n}\n/**\n * 删除条件\n * @param index\n */\nconst handleDel = (index: number) => {\n  filterRules.value.conditions.splice(index, 1)\n  if (filterRules.value.conditions.length <= 0) {\n    $emits('delGroup')\n  }\n  $emits('delCondition', index)\n}\n/**\n * 条件条件组\n */\nconst addGroup = () => {\n  filterRules.value.groups.push({\n    operator: 'and',\n    conditions: [\n      {\n        field: null,\n        operator: '',\n        value: null\n      }\n    ],\n    groups: []\n  })\n}\n/**\n * 删除条件组\n * @param index\n */\nconst delGroup = (index: number) => {\n  filterRules.value.groups.splice(index, 1)\n}\n</script>\n\n<template>\n  <div class=\"filter-container\">\n    <div class=\"logical-operator\">\n      <div class=\"logical-operator__line\"></div>\n      <el-switch\n        v-model=\"filterRules.operator\"\n        inline-prompt\n        style=\"--el-switch-on-color: #409eff; --el-switch-off-color: #67c23a\"\n        active-value=\"and\"\n        inactive-value=\"or\"\n        active-text=\"且\"\n        inactive-text=\"或\"\n      />\n    </div>\n    <div class=\"filter-option-content\">\n      <el-form :label-width=\"0\" :inline=\"true\" :model=\"filterRules\">\n        <el-row\n          v-for=\"(item, index) in filterRules.conditions\"\n          :key=\"`${item.field}-${index}`\"\n          :gutter=\"5\"\n          class=\"filter-item-rule\"\n        >\n          <el-col :xs=\"24\" :sm=\"7\">\n            <el-form-item :prop=\"'conditions.' + index + '.field'\" style=\"width: 100%\">\n              <trigger\n                ref=\"triggerRef\"\n                :options=\"$props.filterFields.filter((e) => e.value !== undefined)\"\n                :filter-rules=\"filterRules\"\n                v-model=\"item.field\"\n                @update:model-value=\"item.value = null\"\n              />\n            </el-form-item>\n          </el-col>\n          <el-col :xs=\"24\" :sm=\"5\" v-if=\"item.field\">\n            <el-form-item :prop=\"'conditions.' + index + '.operator'\" style=\"width: 100%\">\n              <operator ref=\"operatorRef\" v-model=\"item.operator\" />\n            </el-form-item>\n          </el-col>\n          <el-col :xs=\"24\" :sm=\"10\" v-if=\"item.field\">\n            <el-form-item :prop=\"'conditions.' + index + '.value'\" style=\"width: 100%\">\n              <Render\n                :field=\"$props.filterFields.find((e) => e.id === item.field) as Field\"\n                v-model=\"item.value\"\n              />\n            </el-form-item>\n          </el-col>\n          <el-col\n            :xs=\"24\"\n            :sm=\"2\"\n            style=\"display: flex; align-items: center; flex-direction: row-reverse\"\n          >\n            <el-button plain circle type=\"danger\" icon=\"Delete\" @click=\"handleDel(index)\" />\n          </el-col>\n        </el-row>\n        <AdvancedFilter\n          v-for=\"(item, index) in filterRules.groups\"\n          :key=\"index\"\n          @delGroup=\"delGroup(index)\"\n          v-model=\"filterRules.groups[index]\"\n          :filterFields=\"filterFields\"\n        >\n          <el-button @click=\"delGroup(index)\" icon=\"CircleClose\" class=\"filter-filter-item__add\">\n            删除条件组\n          </el-button>\n        </AdvancedFilter>\n        <div\n          v-if=\"filterRules.groups.length === 0 && filterRules.conditions.length === 0\"\n          class=\"filter-item-rule\"\n        />\n      </el-form>\n      <div class=\"filter-item-rule\">\n        <el-button @click=\"addRule\" icon=\"CirclePlus\" class=\"filter-filter-item__add\">\n          添加条件\n        </el-button>\n        <el-button @click=\"addGroup\" icon=\"CirclePlus\" class=\"filter-filter-item__add\">\n          添加条件组\n        </el-button>\n        <slot />\n      </div>\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n:deep(.el-form-item) {\n  margin-right: 0;\n  margin-bottom: 0;\n}\n\n.filter-container {\n  background-color: var(--el-fill-color-blank);\n  border-radius: 3px;\n  display: flex;\n\n  .logical-operator {\n    position: relative;\n    display: flex;\n    align-items: center;\n    overflow: hidden;\n    min-width: 60px;\n    padding-right: 5px;\n\n    .logical-operator__line {\n      position: absolute;\n      left: calc(32% - 1px);\n      width: 30px;\n      border-width: 1px 0 1px 1px;\n      border-top-style: solid;\n      border-bottom-style: solid;\n      border-left-style: solid;\n      border-top-color: var(--el-border-color);\n      border-bottom-color: var(--el-border-color);\n      border-left-color: var(--el-border-color);\n      border-image: initial;\n      border-right-style: initial;\n      border-right-color: initial;\n      border-radius: 5px 0 0 5px;\n      height: calc(100% - 48px);\n\n      &::before {\n        content: '';\n        position: absolute;\n        top: 0;\n        right: 0;\n        transform: translateX(100%) translateY(-50%);\n        width: 6px;\n        height: 6px;\n        border: var(--el-border);\n        border-radius: 50%;\n      }\n\n      &::after {\n        content: '';\n        position: absolute;\n        bottom: 0;\n        right: 0;\n        transform: translateX(100%) translateY(50%);\n        width: 6px;\n        height: 6px;\n        border: var(--el-border);\n        border-radius: 50%;\n      }\n    }\n  }\n\n  .filter-option-content {\n    position: relative;\n    width: 100%;\n\n    .filter-item-rule {\n      display: flex;\n      align-items: center;\n      min-height: 48px;\n    }\n\n    .filter-filter-item__add {\n      border-style: dashed;\n      width: 100%;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/AdvancedFilter/type.ts",
    "content": "/**\n * 字段筛选结果\n */\nexport interface Condition {\n  // 筛选字段\n  field: string | null\n  // 条件运算符\n  operator: string\n  // 筛选值\n  value: any | null\n}\n\n/**\n * 筛选规则\n */\nexport interface FilterRules {\n  operator: 'or' | 'and'\n  conditions: Condition[]\n  groups: FilterRules[]\n}\n"
  },
  {
    "path": "src/components/Render/index.tsx",
    "content": "import { cloneDeep } from 'lodash-es'\nimport type { Field } from './type'\nimport type { PropType } from 'vue'\n\nexport default defineComponent({\n  props: {\n    modelValue: {\n      type: [String, Number, Boolean, Array, Object] as PropType<any>,\n      default: undefined,\n      required: false\n    },\n    field: {\n      type: Object as PropType<Field>,\n      required: true\n    }\n  },\n  emits: ['update:modelValue'],\n  components: {\n    ElInput: defineAsyncComponent(() => import('element-plus/es').then(({ ElInput }) => ElInput)),\n    ElInputNumber: defineAsyncComponent(() =>\n      import('element-plus/es').then(({ ElInputNumber }) => ElInputNumber)\n    ),\n    ElSelect: defineAsyncComponent(() =>\n      import('element-plus/es').then(({ ElSelect }) => ElSelect)\n    ),\n    ElRadio: defineAsyncComponent(() => import('element-plus/es').then(({ ElRadio }) => ElRadio)),\n    ElCheckbox: defineAsyncComponent(() =>\n      import('element-plus/es').then(({ ElCheckbox }) => ElCheckbox)\n    ),\n    UserSelector: defineAsyncComponent(() => import('@/components/UserSelector/index.vue')),\n    RoleSelector: defineAsyncComponent(() => import('@/components/RoleSelector/index.vue'))\n  },\n  setup(props, { emit }) {\n    /**\n     * 构建属性参数\n     * @param fieldClone\n     */\n    const buildProps = (fieldClone: Field) => {\n      const dataObject: Record<string, any> = {}\n      const _props = fieldClone.props || {}\n      Object.keys(_props).forEach((key) => {\n        dataObject[key] = _props[key]\n      })\n      if (props.modelValue !== undefined) {\n        dataObject.modelValue = props.modelValue\n      } else {\n        dataObject.modelValue = fieldClone.value\n      }\n      dataObject['onUpdate:modelValue'] = (value: any) => {\n        emit('update:modelValue', value)\n      }\n      delete dataObject.options\n      return dataObject\n    }\n    /**\n     * 构建插槽\n     * @param fieldClone\n     */\n    const buildSlots = (fieldClone: Field) => {\n      const children: Record<string, any> = {}\n      const slotFunctions: Record<string, any> = {\n        ElSelect: (conf: Field) => {\n          return conf.props.options.map((item: any) => {\n            return <el-option label={item.label} value={item.value}></el-option>\n          })\n        },\n        ElRadio: (conf: Field) => {\n          return conf.props.options.map((item: any) => {\n            return <el-radio label={item.value}>{item.label}</el-radio>\n          })\n        },\n        ElCheckbox: (conf: Field) => {\n          return conf.props.options.map((item: any) => {\n            return <el-checkbox label={item.value}>{item.label}</el-checkbox>\n          })\n        }\n      }\n      const slotFunction = slotFunctions[fieldClone.name]\n      if (slotFunction) {\n        children.default = () => {\n          return slotFunction(fieldClone)\n        }\n      }\n      return children\n    }\n    return {\n      buildProps,\n      buildSlots\n    }\n  },\n  render() {\n    const fieldClone: Field = cloneDeep(this.field)\n    const slots = this.buildSlots(fieldClone)\n    const props = this.buildProps(fieldClone)\n    const eleComponent = resolveComponent(fieldClone.name)\n    if (typeof eleComponent === 'string') {\n      return h(eleComponent, props, slots)\n    }\n    return h(eleComponent, props, slots)\n  }\n})\n"
  },
  {
    "path": "src/components/Render/type.ts",
    "content": "export interface Field {\n  id: string\n  type: 'formItem' | 'container'\n  label: string\n  name: string\n  value: any\n  readonly?: boolean\n  required?: boolean\n  hidden: boolean\n  props: Recordable\n  children?: Field[]\n}\n"
  },
  {
    "path": "src/components/RoleSelector/RolePicker.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport type { TreeNodeData } from 'element-plus/es/components/tree/src/tree.type'\nimport { type ElTree } from 'element-plus'\nimport { getList } from '@/api/modules/role'\n\nexport type ModelValueType = string | string[] | null | undefined\n\nexport interface RoleDropdownProps {\n  modelValue: ModelValueType\n  multiple?: boolean\n}\n\nconst treeProps = {\n  label: 'name',\n  children: 'children',\n  isLeaf: 'leaf',\n  class: (role: TreeNodeData) => renderClass(role)\n}\n\nexport interface Role {\n  id: string\n  name: string\n}\n\nconst $props = withDefaults(defineProps<RoleDropdownProps>(), {\n  multiple: false\n})\n\nconst $emits = defineEmits<{\n  (e: 'update:modelValue', modelValue: ModelValueType): void\n}>()\n\nconst value = useVModel($props, 'modelValue', $emits)\n\nconst roleOptions = ref<Role[]>([])\nconst roleOrgOptions = ref<Role[]>([])\nconst orgTreeRef = ref<InstanceType<typeof ElTree>>()\nconst expandedKeys = ref<string[]>([])\n\nconst renderClass = (role: TreeNodeData): string | { [key: string]: boolean } => {\n  const val = roleOptions.value.find((e) => e.id === role.id)\n  if (val) {\n    return 'is-active'\n  } else {\n    return ''\n  }\n}\n\nconst onNodeClick = (data: Role) => {\n  if ($props.multiple) {\n    const index = roleOptions.value.findIndex((e) => e.id === data.id)\n    if (index === -1) {\n      roleOptions.value.push(data)\n      roleOptions.value.sort((a, b) => a.id.localeCompare(b.id))\n    } else {\n      roleOptions.value.splice(index, 1)\n    }\n  } else {\n    const index = roleOptions.value.findIndex((e) => e.id === data.id)\n    if (index === -1) {\n      roleOptions.value = [data]\n    } else {\n      roleOptions.value.splice(index, 1)\n    }\n  }\n}\nconst dialogVisible = ref(false)\nconst queryForm = reactive({\n  name: null\n})\n\nwatch(\n  () => queryForm.name,\n  (val) => {\n    orgTreeRef.value?.filter(val)\n  }\n)\nconst filterNode = (value: string, data: TreeNodeData): boolean => {\n  if (!value) return true\n  return data.name.includes(value)\n}\nconst open = () => {\n  dialogVisible.value = true\n}\nconst onOpen = () => {\n  getList().then((res) => {\n    if (res.success) {\n      roleOrgOptions.value = res.data.map((e) => {\n        return {\n          id: e.id,\n          name: e.name\n        } as Role\n      })\n    }\n  })\n\n  let roleIds: string[] = []\n  if (Array.isArray(value.value)) {\n    roleIds.push(...value.value)\n  } else if (value.value) {\n    roleIds.push(value.value)\n  }\n  if (roleIds.length > 0) {\n    getList(roleIds).then((res) => {\n      if (res.success) {\n        roleOptions.value = res.data.map((role) => {\n          return {\n            id: role.id,\n            name: role.name\n          } as Role\n        })\n        roleOptions.value.sort((a, b) => a.id.localeCompare(b.id))\n      }\n    })\n  } else {\n    roleOptions.value = []\n  }\n}\nconst handelConfirm = () => {\n  if ($props.multiple) {\n    value.value = roleOptions.value.map((e) => e.id)\n  } else {\n    if (roleOptions.value.length > 0) {\n      value.value = roleOptions.value[0].id\n    } else {\n      value.value = null\n    }\n  }\n  dialogVisible.value = false\n}\ndefineExpose({\n  open\n})\n</script>\n\n<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    @open=\"onOpen\"\n    :lock-scroll=\"false\"\n    align-center\n    draggable\n    title=\"选择角色\"\n    width=\"30%\"\n  >\n    <el-card shadow=\"never\" class=\"org-card\">\n      <template #header>\n        <el-input\n          v-model=\"queryForm.name\"\n          placeholder=\"输入关键字进行查询\"\n          :style=\"{ width: '100%' }\"\n          suffix-icon=\"search\"\n          clearable\n        >\n        </el-input>\n      </template>\n      <el-scrollbar tag=\"div\" class=\"org-tree\">\n        <el-tree\n          ref=\"orgTreeRef\"\n          node-key=\"id\"\n          :data=\"roleOrgOptions\"\n          :default-expanded-keys=\"expandedKeys\"\n          :props=\"treeProps\"\n          :filter-node-method=\"filterNode\"\n          @node-click=\"onNodeClick\"\n        >\n          <template #default=\"{ data }\">\n            <div class=\"flex flex-1 flex-items-center flex-justify-between\">\n              <div class=\"flex-center\">\n                <el-icon :size=\"16\">\n                  <School />\n                </el-icon>\n                &nbsp;{{ data.name }}\n              </div>\n              <el-icon class=\"is-selected\">\n                <Check />\n              </el-icon>\n            </div>\n          </template>\n        </el-tree>\n      </el-scrollbar>\n    </el-card>\n    <template #footer>\n      <el-button @click=\"dialogVisible = false\">取消</el-button>\n      <el-button type=\"primary\" @click=\"handelConfirm\">确认</el-button>\n    </template>\n  </el-dialog>\n</template>\n\n<style scoped lang=\"scss\">\n:deep {\n  .el-tree {\n    --el-tree-node-content-height: 40px;\n\n    .el-tree-node__content {\n      border-radius: 8px;\n      margin: 2px 0 2px 0;\n    }\n\n    .is-active {\n      color: var(--el-color-primary);\n\n      .is-selected {\n        display: block;\n      }\n    }\n  }\n}\n\n.el-card {\n  background-color: transparent;\n\n  :deep(.el-card__header) {\n    padding: 10px !important;\n  }\n\n  :deep(.el-card__body) {\n    padding: 0 !important;\n  }\n}\n\n.org-tree {\n  height: 270px;\n  padding: 5px;\n}\n\n.is-selected {\n  display: none;\n  padding-right: 15px;\n}\n</style>\n"
  },
  {
    "path": "src/components/RoleSelector/RoleTag.vue",
    "content": "<script setup lang=\"ts\">\nimport { getById } from '@/api/modules/role'\n\nexport interface RoleTagProps {\n  id: string\n  type?: 'success' | 'info' | 'warning' | 'danger'\n  closable?: boolean\n}\n\nconst $props = withDefaults(defineProps<RoleTagProps>(), {\n  closable: false,\n  type: 'info'\n})\nconst $emits = defineEmits<{\n  (e: 'close', id: string): void\n}>()\n\nexport interface RoleInfo {\n  id?: string\n  name?: string\n}\n\nlet roleInfo = reactive<RoleInfo>({\n  id: undefined,\n  name: undefined\n})\nonMounted(() => {\n  if (!$props.id) {\n    throw new Error('username is required')\n  }\n  getById($props.id).then((res) => {\n    if (res.success) {\n      roleInfo.id = res.data.id\n      roleInfo.name = res.data.name\n    }\n  })\n})\nconst onClose = () => {\n  $emits('close', $props.id)\n}\n</script>\n<template>\n  <el-tag round :closable=\"$props.closable\" :type=\"type\" effect=\"light\" @close=\"onClose\">\n    <div class=\"flex-center\" style=\"gap: 4px; grid-gap: 4px\">\n      <span>{{ roleInfo.name || id }}</span>\n    </div>\n  </el-tag>\n</template>\n\n<style scoped lang=\"scss\">\n:deep {\n  .el-tag__content:only-child {\n    margin-right: 4px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/RoleSelector/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport RoleTag from './RoleTag.vue'\nimport RolePicker, { type ModelValueType } from './RolePicker.vue'\nimport { useFormDisabled, useFormSize } from 'element-plus'\nimport type { CSSProperties } from 'vue'\n\nexport interface RoleSelectorProps {\n  modelValue: ModelValueType\n  placeholder?: string\n  multiple?: boolean\n  disabled?: boolean\n  style?: CSSProperties\n}\n\nconst $props = withDefaults(defineProps<RoleSelectorProps>(), {\n  multiple: false,\n  disabled: false,\n  placeholder: '请选择角色'\n})\nconst $emits = defineEmits<{\n  (e: 'update:modelValue', modelValue: ModelValueType): void\n}>()\nconst value = useVModel($props, 'modelValue', $emits)\nconst valueArr = computed<string[]>(() => {\n  if (!value.value) return []\n  return Array.isArray(value.value) ? value.value : [value.value]\n})\nconst RolePickerRef = ref<InstanceType<typeof RolePicker>>()\nconst formDisabled = useFormDisabled()\nconst formSize = useFormSize()\nconst disabled = computed<boolean>(() => {\n  return formDisabled.value || $props.disabled\n})\nconst openRolePicker = () => {\n  RolePickerRef.value?.open()\n}\nconst onClose = (username: string) => {\n  if (!value.value) return\n  if ($props.multiple && Array.isArray(value.value)) {\n    value.value.splice(value.value.indexOf(username), 1)\n  } else {\n    value.value = null\n  }\n}\n</script>\n\n<template>\n  <role-picker ref=\"RolePickerRef\" :multiple=\"multiple\" v-model=\"value\" />\n  <div class=\"role-wrapper\">\n    <el-button\n      class=\"role-but-item\"\n      :size=\"formSize\"\n      :disabled=\"disabled\"\n      @click=\"openRolePicker\"\n      circle\n    >\n      <el-icon>\n        <Avatar />\n      </el-icon>\n    </el-button>\n    <RoleTag\n      v-for=\"item in valueArr\"\n      :closable=\"!disabled\"\n      :key=\"item\"\n      :id=\"item\"\n      @close=\"onClose\"\n    />\n    <el-text v-show=\"!value || value.length === 0\" class=\"placeholder\">\n      {{ placeholder }}\n    </el-text>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.el-tag {\n  padding: 0 3px;\n}\n\n.role-wrapper {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  grid-gap: 7px;\n  gap: 7px;\n\n  .placeholder {\n    color: var(--el-text-color-placeholder);\n  }\n\n  .role-but-item {\n    border-style: dashed;\n\n    &:hover {\n      border-style: solid;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/SvgIcon/index.scss",
    "content": ".svg-icon {\n  width: 1em;\n  height: 1em;\n  vertical-align: -0.15em;\n  fill: currentColor;\n  overflow: hidden;\n}\n"
  },
  {
    "path": "src/components/SvgIcon/index.tsx",
    "content": "import './index.scss'\nimport type { CSSProperties, PropType } from 'vue'\n\nexport default defineComponent({\n  name: 'SvgIcon',\n  props: {\n    name: {\n      type: String as PropType<string>,\n      required: true\n    },\n    prefix: {\n      type: String as PropType<string>,\n      default: 'icon'\n    },\n    color: {\n      type: String as PropType<string>\n    },\n    size: {\n      type: Number as PropType<number>\n    },\n    className: {\n      type: String as PropType<string>\n    }\n  },\n  setup(props) {\n    const symbolId = computed(() => `#${props.prefix}-${props.name}`)\n    const svgClass = computed(() => [\n      'svg-icon',\n      props.name && props.name.replace('el:', ''),\n      props.className\n    ])\n    const fill = computed(() => (props.color ? props.color : 'currentColor'))\n    const style = computed<CSSProperties>(() => {\n      const { size } = props\n      if (!size) return {}\n      return {\n        fontSize: `${size}px`\n      }\n    })\n    return {\n      symbolId,\n      svgClass,\n      fill,\n      style\n    }\n  },\n  render() {\n    const { $attrs, symbolId, svgClass, fill } = this\n    if (this.name) {\n      if (this.name.startsWith('el:')) {\n        return (\n          <el-icon class={svgClass} color={this.color} size={this.size} {...$attrs}>\n            {h(resolveComponent(this.name.slice(3)))}\n          </el-icon>\n        )\n      } else {\n        return (\n          <svg class={svgClass} style={this.style} aria-hidden=\"true\" {...$attrs}>\n            <use xlinkHref={symbolId} fill={fill}></use>\n          </svg>\n        )\n      }\n    }\n    return null\n  }\n})\n"
  },
  {
    "path": "src/components/UserSelector/UserPicker.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport type { TreeNodeData } from 'element-plus/es/components/tree/src/tree.type'\nimport { getList } from '@/api/modules/user'\nimport type { TreeInstance } from 'element-plus'\n\nexport type ModelValueType = string | string[] | null | undefined\n\nexport interface UserDropdownProps {\n  modelValue: ModelValueType\n  multiple?: boolean\n}\n\nconst treeProps = {\n  label: 'name',\n  children: 'children',\n  isLeaf: 'leaf',\n  class: (org: TreeNodeData) => renderClass(org)\n}\n\nexport interface Org {\n  id: string\n  type: 'user' | 'dept'\n  avatar?: string\n  name: string\n  leaf: boolean\n}\n\nconst $props = withDefaults(defineProps<UserDropdownProps>(), {\n  multiple: false\n})\n\nconst $emits = defineEmits<{\n  (e: 'update:modelValue', modelValue: ModelValueType): void\n}>()\n\nconst value = useVModel($props, 'modelValue', $emits)\n\nconst userOptions = ref<Org[]>([])\nconst userOrgOptions = ref<Org[]>([])\nconst orgTreeRef = ref<TreeInstance>()\nconst expandedKeys = ref<string[]>([])\n\nconst renderClass = (org: TreeNodeData): string | { [key: string]: boolean } => {\n  const val = userOptions.value.find((e) => e.id === org.id)\n  if (val) {\n    return 'is-active'\n  } else {\n    return ''\n  }\n}\n\nconst onNodeClick = (data: Org) => {\n  if (data.type !== 'user') return\n  if ($props.multiple) {\n    const index = userOptions.value.findIndex((e) => e.id === data.id)\n    if (index === -1) {\n      userOptions.value.push(data)\n      userOptions.value.sort((a, b) => a.id.localeCompare(b.id))\n    } else {\n      userOptions.value.splice(index, 1)\n    }\n  } else {\n    const index = userOptions.value.findIndex((e) => e.id === data.id)\n    if (index === -1) {\n      userOptions.value = [data]\n    } else {\n      userOptions.value.splice(index, 1)\n    }\n  }\n}\nconst dialogVisible = ref(false)\nconst queryForm = reactive({\n  name: null\n})\n\nwatch(\n  () => queryForm.name,\n  (val) => {\n    orgTreeRef.value?.filter(val)\n  }\n)\nconst filterNode = (value: string, data: TreeNodeData): boolean => {\n  if (!value) return true\n  return data.name.includes(value)\n}\nconst open = () => {\n  dialogVisible.value = true\n}\nconst onOpen = () => {\n  getList().then((res) => {\n    if (res.success) {\n      userOrgOptions.value = res.data.map((e) => {\n        return {\n          id: e.username,\n          name: e.name,\n          type: 'user',\n          leaf: true,\n          avatar: e.avatar\n        } as Org\n      })\n    }\n  })\n\n  let userIds: string[] = []\n  if (Array.isArray(value.value)) {\n    userIds.push(...value.value)\n  } else if (value.value) {\n    userIds.push(value.value)\n  }\n  if (userIds.length > 0) {\n    getList(userIds).then((res) => {\n      if (res.success) {\n        userOptions.value = res.data.map((user) => {\n          return {\n            id: user.username,\n            name: user.name,\n            avatar: user.avatar,\n            type: 'user',\n            leaf: true\n          } as Org\n        })\n        userOptions.value.sort((a, b) => a.id.localeCompare(b.id))\n      }\n    })\n  } else {\n    userOptions.value = []\n  }\n}\nconst handelConfirm = () => {\n  if ($props.multiple) {\n    value.value = userOptions.value.map((e) => e.id)\n  } else {\n    if (userOptions.value.length > 0) {\n      value.value = userOptions.value[0].id\n    } else {\n      value.value = null\n    }\n  }\n  dialogVisible.value = false\n}\ndefineExpose({\n  open\n})\n</script>\n\n<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    @open=\"onOpen\"\n    :lock-scroll=\"false\"\n    align-center\n    draggable\n    title=\"选择用户\"\n    width=\"30%\"\n  >\n    <el-card shadow=\"never\" class=\"org-card\">\n      <template #header>\n        <el-input\n          v-model=\"queryForm.name\"\n          placeholder=\"输入关键字进行查询\"\n          :style=\"{ width: '100%' }\"\n          suffix-icon=\"search\"\n          clearable\n        >\n        </el-input>\n      </template>\n      <el-scrollbar tag=\"div\" class=\"org-tree\">\n        <el-tree\n          ref=\"orgTreeRef\"\n          node-key=\"id\"\n          :data=\"userOrgOptions\"\n          :default-expanded-keys=\"expandedKeys\"\n          :props=\"treeProps\"\n          :filter-node-method=\"filterNode\"\n          @node-click=\"onNodeClick\"\n        >\n          <template #default=\"{ data }\">\n            <div class=\"flex flex-1 flex-items-center flex-justify-between\">\n              <div class=\"flex-center\">\n                <el-avatar v-if=\"data.type === 'user'\" :size=\"25\" :src=\"data.avatar\">\n                  {{ data.name.charAt(0) }}\n                </el-avatar>\n                <el-icon v-else :size=\"16\">\n                  <School />\n                </el-icon>\n                &nbsp;{{ data.name }}\n              </div>\n              <el-icon class=\"is-selected\">\n                <Check />\n              </el-icon>\n            </div>\n          </template>\n        </el-tree>\n      </el-scrollbar>\n    </el-card>\n    <template #footer>\n      <el-button @click=\"dialogVisible = false\">取消</el-button>\n      <el-button type=\"primary\" @click=\"handelConfirm\">确认</el-button>\n    </template>\n  </el-dialog>\n</template>\n\n<style scoped lang=\"scss\">\n:deep {\n  .el-tree {\n    --el-tree-node-content-height: 40px;\n\n    .el-tree-node__content {\n      border-radius: 8px;\n      margin: 2px 0 2px 0;\n    }\n\n    .is-active {\n      color: var(--el-color-primary);\n\n      .is-selected {\n        display: block;\n      }\n    }\n  }\n}\n\n.el-card {\n  background-color: transparent;\n\n  :deep(.el-card__header) {\n    padding: 10px !important;\n  }\n\n  :deep(.el-card__body) {\n    padding: 0 !important;\n  }\n}\n\n.org-tree {\n  height: 270px;\n  padding: 5px;\n}\n\n.is-selected {\n  display: none;\n  padding-right: 15px;\n}\n</style>\n"
  },
  {
    "path": "src/components/UserSelector/UserTag.vue",
    "content": "<script setup lang=\"ts\">\nimport { getByUsername } from '@/api/modules/user'\nimport { componentSizeMap, useFormSize } from 'element-plus'\n\nexport interface UserTagProps {\n  username: string\n  type?: 'success' | 'info' | 'warning' | 'danger'\n  closable?: boolean\n}\n\nconst $props = withDefaults(defineProps<UserTagProps>(), {\n  closable: false,\n  type: 'info'\n})\nconst $emits = defineEmits<{\n  (e: 'close', username: string): void\n}>()\n\nexport interface UserInfo {\n  username?: string\n  avatar?: string\n  name?: string\n}\n\nlet userInfo = reactive<UserInfo>({\n  username: undefined,\n  avatar: undefined,\n  name: undefined\n})\nonMounted(() => {\n  if (!$props.username) {\n    throw new Error('username is required')\n  }\n  getByUsername($props.username).then((res) => {\n    if (res.success) {\n      userInfo.username = res.data.username\n      userInfo.avatar = res.data.avatar\n      userInfo.name = res.data.name\n    }\n  })\n})\nconst formSize = useFormSize()\nconst getComponentSize = computed(() => {\n  return componentSizeMap[formSize.value || 'default'] - 12\n})\nconst onClose = () => {\n  $emits('close', $props.username)\n}\n</script>\n<template>\n  <el-tag round :closable=\"$props.closable\" :type=\"type\" effect=\"light\" @close=\"onClose\">\n    <div class=\"flex-center\" style=\"gap: 4px; grid-gap: 4px\">\n      <el-avatar :size=\"getComponentSize\" :src=\"userInfo.avatar\">\n        {{ (userInfo.name || username).charAt(0) }}\n      </el-avatar>\n      <span>{{ userInfo.name || username }}</span>\n    </div>\n  </el-tag>\n</template>\n\n<style scoped lang=\"scss\">\n:deep {\n  .el-tag__content:only-child {\n    margin-right: 4px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/UserSelector/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { useVModel } from '@vueuse/core'\nimport UserTag from './UserTag.vue'\nimport UserPicker, { type ModelValueType } from './UserPicker.vue'\nimport { useFormDisabled, useFormSize } from 'element-plus'\nimport { type CSSProperties } from 'vue'\n\nexport interface UserSelectorProps {\n  modelValue: ModelValueType\n  placeholder?: string\n  multiple?: boolean\n  disabled?: boolean\n  style?: CSSProperties\n}\n\nconst $props = withDefaults(defineProps<UserSelectorProps>(), {\n  multiple: false,\n  disabled: false,\n  placeholder: '请选择用户'\n})\nconst $emits = defineEmits<{\n  (e: 'update:modelValue', modelValue: ModelValueType): void\n}>()\nconst value = useVModel($props, 'modelValue', $emits)\nconst valueArr = computed<string[]>(() => {\n  if (!value.value) return []\n  return Array.isArray(value.value) ? value.value : [value.value]\n})\nconst userPickerRef = ref<InstanceType<typeof UserPicker>>()\nconst formDisabled = useFormDisabled()\nconst formSize = useFormSize()\nconst disabled = computed<boolean>(() => {\n  return formDisabled.value || $props.disabled\n})\nconst openUserPicker = () => {\n  userPickerRef.value?.open()\n}\nconst onClose = (username: string) => {\n  if (!value.value) return\n  if ($props.multiple && Array.isArray(value.value)) {\n    value.value.splice(value.value.indexOf(username), 1)\n  } else {\n    value.value = null\n  }\n}\n</script>\n\n<template>\n  <user-picker ref=\"userPickerRef\" :multiple=\"multiple\" v-model=\"value\" />\n  <div class=\"user-wrapper\">\n    <el-button\n      class=\"user-but-item\"\n      :size=\"formSize\"\n      :disabled=\"disabled\"\n      @click=\"openUserPicker\"\n      circle\n    >\n      <svg-icon name=\"add-user\" />\n    </el-button>\n    <user-tag\n      v-for=\"item in valueArr\"\n      :closable=\"!disabled\"\n      :key=\"item\"\n      :username=\"item\"\n      @close=\"onClose\"\n    />\n    <el-text v-show=\"!value || value.length === 0\" class=\"placeholder\">\n      {{ placeholder }}\n    </el-text>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.el-tag {\n  padding: 0 3px;\n}\n\n.user-wrapper {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  grid-gap: 7px;\n  gap: 7px;\n\n  .placeholder {\n    color: var(--el-text-color-placeholder);\n  }\n\n  .user-but-item {\n    border-style: dashed;\n\n    &:hover {\n      border-style: solid;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/hooks/useDraggableScroll.ts",
    "content": "import { ref, onMounted, onBeforeUnmount, type Ref } from 'vue'\n\nexport function useDraggableScroll(containerRef: Ref<HTMLElement | null>) {\n  const isDragging = ref(false)\n  let startX: number, startY: number\n  let scrollLeft: number, scrollTop: number\n\n  const onMouseDown = (e: MouseEvent) => {\n    if (!containerRef.value) return\n    isDragging.value = true\n    startX = e.pageX\n    startY = e.pageY\n    scrollLeft = containerRef.value.scrollLeft\n    scrollTop = containerRef.value.scrollTop\n    document.addEventListener('mousemove', onMouseMove)\n    document.addEventListener('mouseup', onMouseUp)\n  }\n\n  const onMouseMove = (e: MouseEvent) => {\n    if (!isDragging.value || !containerRef.value) return\n    const deltaX = e.pageX - startX\n    const deltaY = e.pageY - startY\n    containerRef.value.scrollLeft = scrollLeft - deltaX\n    containerRef.value.scrollTop = scrollTop - deltaY\n  }\n\n  const onMouseUp = () => {\n    isDragging.value = false\n    document.removeEventListener('mousemove', onMouseMove)\n    document.removeEventListener('mouseup', onMouseUp)\n  }\n\n  onMounted(() => {\n    containerRef.value?.addEventListener('mousedown', onMouseDown)\n  })\n\n  onBeforeUnmount(() => {\n    containerRef.value?.removeEventListener('mousedown', onMouseDown)\n  })\n\n  return {\n    isDragging\n  }\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport router from './router'\nimport 'virtual:svg-icons-register'\nimport 'uno.css'\nimport '@/styles/index.scss'\n\n// If you want to use ElMessage, import it.\nimport 'element-plus/theme-chalk/src/message.scss'\nimport 'element-plus/theme-chalk/src/notification.scss'\nimport 'element-plus/theme-chalk/el-input-number.css'\n\nconst app = createApp(App)\nimport * as Icons from '@element-plus/icons-vue'\nfor (const [key, component] of Object.entries(Icons)) {\n  app.component(key, component)\n}\napp.use(router).use(createPinia())\napp.mount('#app')\n"
  },
  {
    "path": "src/mock/index.ts",
    "content": "import user from './user'\nimport role from './role'\nimport type { MockMethod } from 'vite-plugin-mock'\n\nconst mockModules: MockMethod[] = [...user, ...role]\nexport default mockModules\n"
  },
  {
    "path": "src/mock/role.ts",
    "content": "import type { MockMethod } from 'vite-plugin-mock'\nimport type { ResultData } from '@/api'\n\nconst roleList = [\n  {\n    id: '1',\n    name: '项目经理'\n  },\n  {\n    id: '2',\n    name: '产品经理'\n  },\n  {\n    id: '3',\n    name: '高级开发工程师'\n  },\n  {\n    id: '4',\n    name: '中级开发工程师'\n  },\n  {\n    id: '5',\n    name: '项目总监'\n  },\n  {\n    id: '6',\n    name: '产品策划'\n  },\n  {\n    id: '7',\n    name: '客服'\n  },\n  {\n    id: '8',\n    name: '销售经理'\n  }\n]\n\nconst role = [\n  {\n    url: '/api/role/info',\n    method: 'get',\n    response: (req: any) => {\n      const id = req.query.id\n      return {\n        code: 200,\n        success: true,\n        message: '操作成功',\n        data: roleList.find((item) => item.id === id)\n      } as ResultData\n    }\n  },\n  {\n    url: '/api/role/list',\n    method: 'post',\n    response: (req: any) => {\n      const roleIds = req.body.roleIds\n      return {\n        code: 200,\n        success: true,\n        message: '操作成功',\n        data: Array.isArray(roleIds)\n          ? roleList.filter((item) => roleIds.includes(item.id))\n          : roleList\n      } as ResultData\n    }\n  }\n] as MockMethod[]\n\nexport default role\n"
  },
  {
    "path": "src/mock/user.ts",
    "content": "import type { MockMethod } from 'vite-plugin-mock'\nimport type { ResultData } from '@/api'\n\nconst userList = [\n  {\n    id: 1,\n    name: '张三',\n    username: 'admin',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  },\n  {\n    id: 2,\n    name: '李四',\n    username: 'lisi',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  },\n  {\n    id: 3,\n    name: '王五',\n    username: 'wangwu',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  },\n  {\n    id: 4,\n    name: '赵六',\n    username: 'zhaoliu',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  },\n  {\n    id: 5,\n    name: '孙七',\n    username: 'sunqi',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  },\n  {\n    id: 6,\n    name: '周八',\n    username: 'zhouba',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  },\n  {\n    id: 7,\n    name: '吴九',\n    username: 'wujui',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  },\n  {\n    id: 8,\n    name: '郑十',\n    username: 'zhengshi',\n    avatar: 'https://avatars.githubusercontent.com/u/44080404?v=4'\n  }\n]\n\nconst user = [\n  {\n    url: '/api/user/info',\n    method: 'get',\n    response: (req: any) => {\n      const username = req.query.username\n      return {\n        code: 200,\n        success: true,\n        message: '操作成功',\n        data: userList.find((item) => item.username === username)\n      } as ResultData\n    }\n  },\n  {\n    url: '/api/user/list',\n    method: 'post',\n    response: (req: any) => {\n      const userIds = req.body.userIds\n      return {\n        code: 200,\n        success: true,\n        message: '操作成功',\n        data: Array.isArray(userIds)\n          ? userList.filter((item) => userIds.includes(item.username))\n          : userList\n      } as ResultData\n    }\n  }\n] as MockMethod[]\n\nexport default user\n"
  },
  {
    "path": "src/mockProdServer.ts",
    "content": "import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'\nimport mock from './mock'\n\nexport function setupProdMockServer() {\n  createProdMockServer(mock)\n}\n"
  },
  {
    "path": "src/router/index.ts",
    "content": "import { createRouter, createWebHistory } from 'vue-router'\nimport Home from '@/views/home/index.vue'\n\nconst router = createRouter({\n  history: createWebHistory(import.meta.env.BASE_URL),\n  routes: [\n    {\n      path: '/',\n      name: 'Home',\n      component: Home\n    }\n  ]\n})\n\nexport default router\n"
  },
  {
    "path": "src/stores/counter.ts",
    "content": "import { ref, computed } from 'vue'\nimport { defineStore } from 'pinia'\n\nexport const useCounterStore = defineStore('counter', () => {\n  const count = ref(0)\n  const doubleCount = computed(() => count.value * 2)\n  function increment() {\n    count.value++\n  }\n\n  return { count, doubleCount, increment }\n})\n"
  },
  {
    "path": "src/styles/el-segmented.scss",
    "content": ".el-segmented {\n  --el-segmented-radius: var(--el-border-radius-base);\n  --el-segmented-padding: 3px;\n  --el-segmented-bg: var(--el-fill-color-light);\n  --el-segmented-height: 28px;\n  --el-segmented-font-size: 14px;\n  --el-segmented-item-padding: 12px;\n  --el-segmented-color: var(--el-text-color-secondary);\n  --el-segmented-active-color: var(--el-text-color-primary);\n  --el-segmented-active-bg: var(--el-bg-color-overlay);\n  --el-segmented-active-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.08);\n  --el-segmented-hover-bg: rgba(0, 0, 0, 0.04);\n  --el-segmented-disabled-color: var(--el-text-color-placeholder);\n\n  :deep {\n    &.is-block {\n      .el-tabs__header {\n        display: inline-block;\n      }\n    }\n\n    .el-tabs__header {\n      margin: 0;\n      box-sizing: border-box;\n      background: var(--el-segmented-bg);\n      border-radius: var(--el-segmented-radius);\n      padding: var(--el-segmented-padding);\n    }\n\n    .el-tabs__nav-scroll,\n    .el-tabs__nav-wrap {\n      margin: 0;\n      overflow: visible;\n    }\n\n    .el-tabs__nav-wrap {\n      &:after {\n        display: none;\n      }\n    }\n\n    .el-tabs__nav {\n      float: none;\n\n      &:not(:has(.is-active)) {\n        .el-tabs__active-bar {\n          padding: 0;\n        }\n      }\n\n      .el-tabs__item {\n        padding: 0 var(--el-segmented-item-padding);\n        color: var(--el-segmented-color);\n        height: var(--el-segmented-height);\n        line-height: var(--el-segmented-height);\n        font-size: var(--el-segmented-font-size);\n        border-radius: var(--el-segmented-radius);\n        transition:\n          color 0.2s,\n          background-color 0.2s;\n        background: none;\n        z-index: 2;\n\n        &:not(.is-disabled) {\n          &.is-active {\n            color: var(--el-segmented-active-color) !important;\n            background: none !important;\n          }\n\n          &:hover {\n            color: var(--el-segmented-active-color);\n            background: var(--el-segmented-hover-bg);\n          }\n        }\n      }\n    }\n\n    .el-tabs__active-bar {\n      padding: 0 var(--el-segmented-item-padding);\n      margin-left: calc(0px - var(--el-segmented-item-padding));\n      background: var(--el-segmented-active-bg);\n      border-radius: var(--el-segmented-radius);\n      box-shadow: var(--el-segmented-active-shadow);\n      transform: translate(var(--el-segmented-item-padding));\n      box-sizing: content-box;\n      height: auto;\n      bottom: 0;\n      top: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/styles/element/dark.scss",
    "content": "// only scss variables\n\n$--colors: (\n  'primary': (\n    'base': #589ef8\n  )\n);\n\n@forward 'element-plus/theme-chalk/src/dark/var.scss' with (\n  $colors: $--colors\n);\n"
  },
  {
    "path": "src/styles/element/index.scss",
    "content": "$--colors: (\n  'primary': (\n    'base': #589ef8\n  ),\n  'success': (\n    'base': #21ba45\n  ),\n  'warning': (\n    'base': #f2711c\n  ),\n  'danger': (\n    'base': #db2828\n  ),\n  'error': (\n    'base': #db2828\n  ),\n  'info': (\n    'base': #42b8dd\n  )\n);\n\n// we can add this to custom namespace, default is 'el'\n@forward 'element-plus/theme-chalk/src/mixins/config.scss' with (\n  $namespace: 'el'\n);\n\n// You should use them in scss, because we calculate it by sass.\n// comment next lines to use default color\n@forward 'element-plus/theme-chalk/src/common/var.scss' with (\n    // do not use same name, it will override.\n    $colors: $--colors,\n    // $button-padding-horizontal: (\"default\": 50px)\n  );\n\n// if you want to import all\n// @use \"element-plus/theme-chalk/src/index.scss\" as *;\n\n// You can comment it to hide debug info.\n// @debug $--colors;\n\n// custom dark variables\n@use './dark.scss';\n"
  },
  {
    "path": "src/styles/index.scss",
    "content": "// import dark theme\n@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;\n\n:root {\n  .el-segmented {\n    --el-segmented-radius: var(--el-border-radius-base);\n    --el-segmented-padding: 3px;\n    --el-segmented-bg: var(--el-fill-color-light);\n    --el-segmented-height: 28px;\n    --el-segmented-font-size: 14px;\n    --el-segmented-item-padding: 12px;\n    --el-segmented-color: var(--el-text-color-secondary);\n    --el-segmented-active-color: var(--el-text-color-primary);\n    --el-segmented-active-bg: var(--el-bg-color-overlay);\n    --el-segmented-active-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.08);\n    --el-segmented-hover-bg: rgba(0, 0, 0, 0.04);\n    --el-segmented-disabled-color: var(--el-text-color-placeholder);\n  }\n}\n@media (max-width: 1200px) {\n  .el-drawer.rtl {\n    width: 90% !important;\n  }\n  .el-dialog {\n    width: 90% !important;\n  }\n  .el-dialog.is-fullscreen {\n    width: 100% !important;\n  }\n}\n\n// 自定义抽屉样式\n.el-drawer {\n  // 抽屉头部\n  .el-drawer__header {\n    margin-bottom: 0;\n    padding: calc(var(--el-drawer-padding-primary) - 5px) var(--el-drawer-padding-primary)\n      calc(var(--el-drawer-padding-primary) - 6px);\n    border-bottom: 1px var(--el-border-style) var(--el-border-color);\n    justify-content: space-between;\n    // 抽屉标题\n    .el-drawer__title {\n      border-left: 3px solid var(--el-color-primary);\n      padding-left: 5px;\n    }\n  }\n\n  .el-drawer__footer {\n    border-top: var(--el-border);\n    padding: calc(var(--el-drawer-padding-primary) - 5px);\n  }\n}\n\nbody {\n  font-family: Inter, system-ui, Avenir, 'Helvetica Neue', Helvetica, 'PingFang SC',\n    'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  margin: 0;\n}\n\na {\n  color: var(--el-color-primary);\n}\n\nhtml,\nbody,\n#app {\n  width: 100%;\n  height: 100%;\n  padding: 0;\n  margin: 0;\n}\n\ncode {\n  border-radius: 2px;\n  padding: 2px 4px;\n  background-color: var(--el-color-primary-light-9);\n  color: var(--el-color-primary);\n}\n"
  },
  {
    "path": "src/typings/auto-imports.d.ts",
    "content": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by unplugin-auto-import\nexport {}\ndeclare global {\n  const EffectScope: (typeof import('vue'))['EffectScope']\n  const ElCheckbox: (typeof import('element-plus/es'))['ElCheckbox']\n  const ElDivider: (typeof import('element-plus/es'))['ElDivider']\n  const ElInput: (typeof import('element-plus/es'))['ElInput']\n  const ElInputNumber: (typeof import('element-plus/es'))['ElInputNumber']\n  const ElRadio: (typeof import('element-plus/es'))['ElRadio']\n  const ElSelect: (typeof import('element-plus/es'))['ElSelect']\n  const computed: (typeof import('vue'))['computed']\n  const createApp: (typeof import('vue'))['createApp']\n  const customRef: (typeof import('vue'))['customRef']\n  const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent']\n  const defineComponent: (typeof import('vue'))['defineComponent']\n  const effectScope: (typeof import('vue'))['effectScope']\n  const getCurrentInstance: (typeof import('vue'))['getCurrentInstance']\n  const getCurrentScope: (typeof import('vue'))['getCurrentScope']\n  const h: (typeof import('vue'))['h']\n  const inject: (typeof import('vue'))['inject']\n  const isProxy: (typeof import('vue'))['isProxy']\n  const isReactive: (typeof import('vue'))['isReactive']\n  const isReadonly: (typeof import('vue'))['isReadonly']\n  const isRef: (typeof import('vue'))['isRef']\n  const markRaw: (typeof import('vue'))['markRaw']\n  const nextTick: (typeof import('vue'))['nextTick']\n  const onActivated: (typeof import('vue'))['onActivated']\n  const onBeforeMount: (typeof import('vue'))['onBeforeMount']\n  const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave']\n  const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate']\n  const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount']\n  const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate']\n  const onDeactivated: (typeof import('vue'))['onDeactivated']\n  const onErrorCaptured: (typeof import('vue'))['onErrorCaptured']\n  const onMounted: (typeof import('vue'))['onMounted']\n  const onRenderTracked: (typeof import('vue'))['onRenderTracked']\n  const onRenderTriggered: (typeof import('vue'))['onRenderTriggered']\n  const onScopeDispose: (typeof import('vue'))['onScopeDispose']\n  const onServerPrefetch: (typeof import('vue'))['onServerPrefetch']\n  const onUnmounted: (typeof import('vue'))['onUnmounted']\n  const onUpdated: (typeof import('vue'))['onUpdated']\n  const provide: (typeof import('vue'))['provide']\n  const reactive: (typeof import('vue'))['reactive']\n  const readonly: (typeof import('vue'))['readonly']\n  const ref: (typeof import('vue'))['ref']\n  const resolveComponent: (typeof import('vue'))['resolveComponent']\n  const shallowReactive: (typeof import('vue'))['shallowReactive']\n  const shallowReadonly: (typeof import('vue'))['shallowReadonly']\n  const shallowRef: (typeof import('vue'))['shallowRef']\n  const toRaw: (typeof import('vue'))['toRaw']\n  const toRef: (typeof import('vue'))['toRef']\n  const toRefs: (typeof import('vue'))['toRefs']\n  const toValue: (typeof import('vue'))['toValue']\n  const triggerRef: (typeof import('vue'))['triggerRef']\n  const unref: (typeof import('vue'))['unref']\n  const useAttrs: (typeof import('vue'))['useAttrs']\n  const useCssModule: (typeof import('vue'))['useCssModule']\n  const useCssVars: (typeof import('vue'))['useCssVars']\n  const useLink: (typeof import('vue-router'))['useLink']\n  const useRoute: (typeof import('vue-router'))['useRoute']\n  const useRouter: (typeof import('vue-router'))['useRouter']\n  const useSlots: (typeof import('vue'))['useSlots']\n  const watch: (typeof import('vue'))['watch']\n  const watchEffect: (typeof import('vue'))['watchEffect']\n  const watchPostEffect: (typeof import('vue'))['watchPostEffect']\n  const watchSyncEffect: (typeof import('vue'))['watchSyncEffect']\n}\n// for type re-export\ndeclare global {\n  // @ts-ignore\n  export type {\n    Component,\n    ComponentPublicInstance,\n    ComputedRef,\n    ExtractDefaultPropTypes,\n    ExtractPropTypes,\n    ExtractPublicPropTypes,\n    InjectionKey,\n    PropType,\n    Ref,\n    VNode,\n    WritableComputedRef\n  } from 'vue'\n  import('vue')\n}\n"
  },
  {
    "path": "src/typings/components.d.ts",
    "content": "/* eslint-disable */\n// @ts-nocheck\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/pull/3399\nexport {}\n\n/* prettier-ignore */\ndeclare module 'vue' {\n  export interface GlobalComponents {\n    AdvancedFilter: typeof import('./../components/AdvancedFilter/index.vue')['default']\n    ElAvatar: typeof import('element-plus/es')['ElAvatar']\n    ElBadge: typeof import('element-plus/es')['ElBadge']\n    ElButton: typeof import('element-plus/es')['ElButton']\n    ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']\n    ElCard: typeof import('element-plus/es')['ElCard']\n    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']\n    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']\n    ElCol: typeof import('element-plus/es')['ElCol']\n    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']\n    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']\n    ElDialog: typeof import('element-plus/es')['ElDialog']\n    ElDrawer: typeof import('element-plus/es')['ElDrawer']\n    ElDropdown: typeof import('element-plus/es')['ElDropdown']\n    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']\n    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']\n    ElForm: typeof import('element-plus/es')['ElForm']\n    ElFormItem: typeof import('element-plus/es')['ElFormItem']\n    ElIcon: typeof import('element-plus/es')['ElIcon']\n    ElInput: typeof import('element-plus/es')['ElInput']\n    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']\n    ElLink: typeof import('element-plus/es')['ElLink']\n    ElOption: typeof import('element-plus/es')['ElOption']\n    ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']\n    ElPopover: typeof import('element-plus/es')['ElPopover']\n    ElRadio: typeof import('element-plus/es')['ElRadio']\n    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']\n    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']\n    ElRow: typeof import('element-plus/es')['ElRow']\n    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']\n    ElSelect: typeof import('element-plus/es')['ElSelect']\n    ElSpace: typeof import('element-plus/es')['ElSpace']\n    ElSwitch: typeof import('element-plus/es')['ElSwitch']\n    ElTable: typeof import('element-plus/es')['ElTable']\n    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']\n    ElTabPane: typeof import('element-plus/es')['ElTabPane']\n    ElTabs: typeof import('element-plus/es')['ElTabs']\n    ElTag: typeof import('element-plus/es')['ElTag']\n    ElText: typeof import('element-plus/es')['ElText']\n    ElTooltip: typeof import('element-plus/es')['ElTooltip']\n    ElTree: typeof import('element-plus/es')['ElTree']\n    Operator: typeof import('./../components/AdvancedFilter/Operator.vue')['default']\n    Render: typeof import('./../components/Render/index.tsx')['default']\n    RolePicker: typeof import('./../components/RoleSelector/RolePicker.vue')['default']\n    RoleSelector: typeof import('./../components/RoleSelector/index.vue')['default']\n    RoleTag: typeof import('./../components/RoleSelector/RoleTag.vue')['default']\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n    SvgIcon: typeof import('./../components/SvgIcon/index.tsx')['default']\n    Trigger: typeof import('./../components/AdvancedFilter/Trigger.vue')['default']\n    UserPicker: typeof import('./../components/UserSelector/UserPicker.vue')['default']\n    UserSelector: typeof import('./../components/UserSelector/index.vue')['default']\n    UserTag: typeof import('./../components/UserSelector/UserTag.vue')['default']\n  }\n}\n"
  },
  {
    "path": "src/typings/index.d.ts",
    "content": "type Recordable<T = any> = Record<string, T>\n"
  },
  {
    "path": "src/views/flowDesign/index.vue",
    "content": "<script setup lang=\"ts\">\nimport TreeNode from './nodes/TreeNode.vue'\nimport Panel from './panels/index.vue'\nimport type { ErrorInfo, FlowNode, ServiceNode, TimerNode } from './nodes/type'\nimport type {\n  ApprovalNode,\n  BranchNode,\n  CcNode,\n  NotifyNode,\n  ConditionNode,\n  ExclusiveNode,\n  NodeType\n} from './nodes/type'\nimport type { FilterRules } from '@/components/AdvancedFilter/type'\nimport type { Field } from '@/components/Render/type'\nimport { useDraggableScroll } from '@/hooks/useDraggableScroll'\n\nconst props = withDefaults(\n  defineProps<{\n    process: FlowNode\n    fields: Field[]\n    readOnly?: boolean\n    defaultZoom?: number\n    bgColor?: string\n  }>(),\n  {\n    readOnly: false,\n    defaultZoom: 100,\n    bgColor: 'var(--el-bg-color-page)'\n  }\n)\n\nconst flatFields = computed(() => {\n  const all: Field[] = []\n  const loop = (children: Field[]) => {\n    children.forEach((field) => {\n      if (field.type === 'formItem') {\n        all.push(field)\n      }\n      if (Array.isArray(field.children)) {\n        loop(field.children)\n      }\n    })\n  }\n  loop(props.fields)\n  return all\n})\nconst getScale = computed(() => zoom.value / 100)\nconst zoom = ref(props.defaultZoom)\nconst readOnly = computed(() => props.readOnly)\nconst activeData = ref<FlowNode>({\n  id: '',\n  name: '',\n  type: 'start'\n})\nconst penalVisible = ref(false)\nconst nodesError = ref<Recordable<ErrorInfo[]>>({})\n\nconst designerContainerRef = ref<HTMLElement | null>(null)\nuseDraggableScroll(designerContainerRef)\nprovide('flowDesign', {\n  readOnly: readOnly,\n  fields: flatFields,\n  nodesError: nodesError\n})\nconst openPenal = (node: FlowNode) => {\n  activeData.value = node\n  penalVisible.value = true\n}\nconst nextId = (): string => {\n  let id = `node_${Math.random().toString(36).substring(2, 7)}`\n  const findId = (node: FlowNode, id: string): boolean => {\n    if (node.id === id) {\n      return true\n    }\n    if (node.next) {\n      return findId(node.next, id)\n    }\n    if ('branches' in node) {\n      const branchNode = node as BranchNode\n      if (branchNode.branches && branchNode.branches.length > 0) {\n        return branchNode.branches.some((item) => {\n          return findId(item, id)\n        })\n      }\n    }\n    return false\n  }\n  if (findId(props.process, id)) {\n    return nextId()\n  }\n  return id\n}\nconst addExclusive = (node: FlowNode) => {\n  const next = node.next\n  const id = nextId()\n  const exclusiveNode = {\n    id: id,\n    pid: node.id,\n    type: 'exclusive',\n    name: '独占网关',\n    next: next,\n    branches: []\n  } as ExclusiveNode\n  if (next) {\n    next.pid = id\n  }\n  addCondition(exclusiveNode)\n  addCondition(exclusiveNode)\n  node.next = exclusiveNode\n  if (exclusiveNode.branches.length > 0) {\n    const condition = exclusiveNode.branches[exclusiveNode.branches.length - 1] as ConditionNode\n    condition.def = true\n    condition.name = '默认条件'\n  }\n}\nconst addCondition = (node: FlowNode) => {\n  const exclusive = node as ExclusiveNode\n  exclusive.branches.splice(exclusive.branches.length - 1, 0, {\n    id: nextId(),\n    pid: exclusive.id,\n    type: 'condition',\n    def: false,\n    name: `条件${exclusive.branches.length + 1}`,\n    conditions: {\n      operator: 'and',\n      conditions: [],\n      groups: []\n    } as FilterRules,\n    next: undefined\n  })\n}\nconst addCc = (node: FlowNode) => {\n  const next = node.next\n  const id = nextId()\n  node.next = {\n    id: id,\n    pid: node.id,\n    type: 'cc',\n    name: '抄送人',\n    next: next,\n    assigneeType: 'user',\n    formUser: '',\n    formRole: '',\n    users: [],\n    roles: [],\n    leader: 1,\n    orgLeader: 1,\n    choice: false,\n    self: false,\n    formProperties: []\n  } as CcNode\n  if (next) {\n    next.pid = id\n  }\n}\nconst addTimer = (node: FlowNode) => {\n  const next = node.next\n  const id = nextId()\n  node.next = {\n    id: id,\n    pid: node.id,\n    name: '计时等待',\n    type: 'timer',\n    next: next,\n    waitType: 'duration',\n    unit: 'PT%sS',\n    duration: 0,\n    timeDate: undefined\n  } as TimerNode\n  if (next) {\n    next.pid = id\n  }\n}\n\nconst addNotify = (node: FlowNode) => {\n  const next = node.next\n  const id = nextId()\n  node.next = {\n    id: id,\n    pid: node.id,\n    name: '消息通知',\n    type: 'notify',\n    next: next,\n    assigneeType: 'user',\n    formUser: '',\n    formRole: '',\n    users: [],\n    roles: [],\n    leader: 1,\n    orgLeader: 1,\n    choice: false,\n    self: false,\n    types: ['site'],\n    subject: '',\n    content: ''\n  } as NotifyNode\n  if (next) {\n    next.pid = id\n  }\n}\nconst addService = (node: FlowNode) => {\n  const next = node.next\n  const id = nextId()\n  node.next = {\n    id: id,\n    pid: node.id,\n    type: 'service',\n    name: '服务节点',\n    next: next,\n    implementationType: '',\n    implementation: ''\n  } as ServiceNode\n  if (next) {\n    next.pid = id\n  }\n}\nconst addApproval = (node: FlowNode) => {\n  const next = node.next\n  const id = nextId()\n  node.next = {\n    id: id,\n    pid: node.id,\n    type: 'approval',\n    name: '审批人',\n    executionListeners: [],\n    next: next,\n    // 属性\n    assigneeType: 'user',\n    formUser: '',\n    formRole: '',\n    users: [],\n    roles: [],\n    leader: 1,\n    orgLeader: 1,\n    choice: false,\n    self: false,\n    multi: 'sequential',\n    multiPercent: 100,\n    nobody: 'pass',\n    nobodyUsers: [],\n    formProperties: [],\n    operations: {\n      complete: true,\n      refuse: true,\n      back: true,\n      transfer: true,\n      delegate: true,\n      addMulti: false,\n      minusMulti: false\n    }\n  } as ApprovalNode\n  if (next) {\n    next.pid = id\n  }\n}\nconst addNode = (type: NodeType, node: FlowNode) => {\n  const addMap: Recordable<(node: FlowNode) => void> = {\n    exclusive: addExclusive,\n    condition: addCondition,\n    cc: addCc,\n    timer: addTimer,\n    notify: addNotify,\n    service: addService,\n    approval: addApproval\n  }\n  const fun = addMap[type]\n  fun && fun(node)\n}\nconst delNode = (del: FlowNode) => {\n  delete nodesError.value[del.id]\n  delNodeNext(props.process, del)\n}\nconst delNodeNext = (next: FlowNode, del: FlowNode) => {\n  delete nodesError.value[del.id]\n  if (next.id === del.pid) {\n    if ('branches' in next && next.next?.id !== del.id) {\n      const branchNode = next as BranchNode\n      const index = branchNode.branches.findIndex((item) => item.id === del.id)\n      if (index !== -1) {\n        if (branchNode.branches.length <= 2) {\n          delError(branchNode)\n          delNode(branchNode)\n        } else {\n          delError(del)\n          branchNode.branches.splice(index, 1)\n        }\n      }\n    } else {\n      if (del.next && del.next.pid) {\n        del.next.pid = next.id\n      }\n      next.next = del.next\n    }\n  } else {\n    if (next.next) {\n      delNodeNext(next.next, del)\n    }\n    if ('branches' in next) {\n      const nextBranch = next as BranchNode\n      if (nextBranch.branches && nextBranch.branches.length > 0) {\n        nextBranch.branches.forEach((item) => {\n          delNodeNext(item, del)\n        })\n      }\n    }\n  }\n}\nconst delError = (node: FlowNode) => {\n  delete nodesError.value[node.id]\n  if (node.next) {\n    delError(node.next)\n  }\n  if ('branches' in node) {\n    const branchNode = node as BranchNode\n    if (branchNode.branches && branchNode.branches.length > 0) {\n      branchNode.branches.forEach((item) => {\n        delError(item)\n      })\n    }\n  }\n}\nconst validate = () => {\n  return new Promise((resolve, reject) => {\n    const errors = Object.values(nodesError.value).flat()\n    if (errors.length > 0) {\n      reject(errors)\n    } else {\n      resolve(true)\n    }\n  })\n}\ndefineExpose({\n  validate\n})\n</script>\n\n<template>\n  <div class=\"designer-container cursor-default active:cursor-grabbing\" ref=\"designerContainerRef\">\n    <div class=\"tool\">\n      <slot></slot>\n    </div>\n    <!--放大/缩小-->\n    <div class=\"zoom\">\n      <el-tooltip content=\"放大\" placement=\"bottom-start\">\n        <el-button icon=\"plus\" @click=\"zoom += 10\" :disabled=\"zoom >= 170\" circle></el-button>\n      </el-tooltip>\n      <span>{{ zoom }}%</span>\n      <el-tooltip content=\"缩小\" placement=\"bottom-start\">\n        <el-button icon=\"minus\" @click=\"zoom -= 10\" circle :disabled=\"zoom <= 50\"></el-button>\n      </el-tooltip>\n    </div>\n    <!--流程树-->\n    <div class=\"node-container\">\n      <TreeNode :node=\"process\" @addNode=\"addNode\" @delNode=\"delNode\" @activeNode=\"openPenal\" />\n    </div>\n    <!--属性面板-->\n    <Panel v-model=\"penalVisible\" :active-data=\"activeData\" />\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.designer-container {\n  --flow-bg-color: v-bind(bgColor);\n  position: relative;\n  display: flex;\n  flex-direction: row;\n  height: 100%;\n  width: 100%;\n  overflow: auto;\n  background-color: var(--flow-bg-color);\n\n  .zoom {\n    position: fixed;\n    z-index: 999;\n    top: 30px;\n    right: 40px;\n\n    span {\n      margin: 0 10px;\n    }\n  }\n\n  .tool {\n    position: fixed;\n    z-index: 999;\n    top: 5px;\n    left: 5px;\n    display: flex;\n    gap: 5px;\n  }\n\n  .node-container {\n    margin: 0 auto;\n    transform: scale(v-bind(getScale));\n    transform-origin: 50% 0 0;\n    display: flex;\n    align-items: center;\n    flex-direction: column;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/Add.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PopoverInstance } from 'element-plus'\nimport type { NodeType } from './type'\nimport type { Ref } from 'vue'\n\nconst { readOnly } = inject<{\n  readOnly?: Ref<boolean>\n}>('flowDesign', { readOnly: ref(false) })\nconst popoverRef = ref<PopoverInstance>()\nconst $emits = defineEmits<{\n  (e: 'addNode', type: NodeType): void\n}>()\nconst addApprovalNode = () => {\n  $emits('addNode', 'approval')\n  popoverRef.value?.hide()\n}\nconst addCcNode = () => {\n  $emits('addNode', 'cc')\n  popoverRef.value?.hide()\n}\nconst addExclusiveNode = () => {\n  $emits('addNode', 'exclusive')\n  popoverRef.value?.hide()\n}\nconst addTimerNode = () => {\n  $emits('addNode', 'timer')\n  popoverRef.value?.hide()\n}\nconst addNotifyNode = () => {\n  $emits('addNode', 'notify')\n  popoverRef.value?.hide()\n}\nconst addServiceNode = () => {\n  $emits('addNode', 'service')\n  popoverRef.value?.hide()\n}\n</script>\n\n<template>\n  <div class=\"add-but\">\n    <el-popover\n      placement=\"bottom-start\"\n      ref=\"popoverRef\"\n      trigger=\"click\"\n      title=\"添加节点\"\n      :width=\"336\"\n    >\n      <el-space wrap>\n        <div class=\"node-select\" @click=\"addApprovalNode\">\n          <svg-icon name=\"el:Stamp\" />\n          <el-text>审批人</el-text>\n        </div>\n        <div class=\"node-select\" @click=\"addCcNode\">\n          <svg-icon name=\"el:Promotion\" />\n          <el-text>抄送人</el-text>\n        </div>\n        <div class=\"node-select\" @click=\"addExclusiveNode\">\n          <svg-icon name=\"el:Share\" />\n          <el-text>互斥分支</el-text>\n        </div>\n        <div class=\"node-select\" @click=\"addTimerNode\">\n          <svg-icon name=\"el:Timer\" />\n          <el-text>计时等待</el-text>\n        </div>\n        <div class=\"node-select\" @click=\"addNotifyNode\">\n          <svg-icon name=\"el:BellFilled\" />\n          <el-text>消息通知</el-text>\n        </div>\n        <div class=\"node-select\" @click=\"addServiceNode\">\n          <svg-icon name=\"el:Tools\" />\n          <el-text>服务节点</el-text>\n        </div>\n      </el-space>\n      <template #reference>\n        <el-button\n          v-show=\"!readOnly\"\n          icon=\"Plus\"\n          type=\"primary\"\n          style=\"z-index: 1\"\n          circle\n        ></el-button>\n      </template>\n    </el-popover>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.node-select {\n  cursor: pointer;\n  display: flex;\n  padding: 8px;\n  width: 135px;\n  border-radius: 10px;\n  position: relative;\n  background-color: var(--el-fill-color-light);\n\n  &:hover {\n    background-color: var(--el-color-primary-light-9);\n    box-shadow: var(--el-box-shadow-light);\n    color: var(--el-color-primary);\n  }\n\n  .svg-icon {\n    font-size: 25px;\n    padding: 5px;\n    border-radius: 50%;\n    color: var(--el-color-white);\n\n    &.Stamp {\n      background-color: #ff943e;\n    }\n\n    &.Promotion {\n      background-color: #3296fa;\n    }\n\n    &.Share {\n      background-color: #45cf9b;\n    }\n\n    &.Timer {\n      background-color: #e872b7;\n    }\n\n    &.BellFilled {\n      background-color: #95d475;\n    }\n\n    &.Tools {\n      background-color: #ffc107;\n    }\n  }\n\n  .el-text {\n    margin-left: 10px;\n  }\n}\n\n.add-but {\n  display: flex;\n  justify-content: center;\n  width: 100%;\n  padding: 20px 0 32px;\n  position: relative;\n\n  &:before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    margin: auto;\n    width: 1px;\n    height: 100%;\n    background-color: var(--el-border-color);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/ApprovalNode.vue",
    "content": "<script setup lang=\"ts\">\nimport Node from './Node.vue'\nimport type { ApprovalNode } from './type'\nimport type { Ref } from 'vue'\nimport type { Field } from '@/components/Render/type'\nimport { getById } from '@/api/modules/role'\nimport type { ErrorInfo } from './type'\nimport { getByUsername } from '@/api/modules/user'\n\nconst { fields, nodesError } = inject<{\n  fields: Ref<Field[]>\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { fields: ref([]), nodesError: ref({}) })\nconst props = defineProps<{\n  node: ApprovalNode\n}>()\nconst content = ref<string>('')\nwatchEffect(() => {\n  const errors: ErrorInfo[] = []\n  const {\n    id,\n    name,\n    assigneeType,\n    nobody,\n    nobodyUsers,\n    choice,\n    formUser,\n    formRole,\n    leader,\n    orgLeader,\n    users,\n    roles\n  } = props.node\n  if (assigneeType === 'user') {\n    if (users.length > 0) {\n      const all = users.map((user) => getByUsername(user))\n      Promise.all(all).then((users) => {\n        content.value = users.map((user) => user.data.name).join('、')\n      })\n    } else {\n      errors.push({ id: id, name: name, message: '未指定人员' })\n      content.value = '未指定人员'\n    }\n  } else if (assigneeType === 'choice') {\n    content.value = `发起人自选（${choice ? '多选' : '单选'}）`\n  } else if (assigneeType === 'self') {\n    content.value = '发起人自己'\n  } else if (assigneeType === 'leader') {\n    content.value = leader === 1 ? '直属上级' : `${leader}级上级`\n  } else if (assigneeType === 'orgLeader') {\n    content.value = orgLeader === 1 ? '直属主管' : `${orgLeader}级主管`\n  } else if (assigneeType === 'formUser') {\n    if (!formUser) {\n      errors.push({ id: id, name: name, message: '未指定表单内人员' })\n    }\n    const title = fields.value.find((e) => e.id === formUser)?.label || formUser || '?'\n    content.value = `表单内（${title}）人员`\n  } else if (assigneeType === 'formRole') {\n    if (!formRole) {\n      errors.push({ id: id, name: name, message: '未指定表单内角色' })\n    }\n    const title = fields.value.find((e) => e.id === formRole)?.label || formRole || '?'\n    content.value = `表单内（${title}）角色`\n  } else if (assigneeType === 'role') {\n    if (roles.length > 0) {\n      const all = roles.map((id) => getById(id))\n      Promise.all(all).then((roles) => {\n        content.value = roles.map((res) => res.data.name).join('、')\n      })\n    } else {\n      errors.push({ id: id, name: name, message: '未指定角色' })\n      content.value = '未指定角色'\n    }\n  } else if (assigneeType === 'autoRefuse') {\n    content.value = '系统自动拒绝'\n  } else {\n    errors.push({ id: id, name: name, message: '未知错误' })\n    content.value = name\n  }\n  if (nobody === 'assign') {\n    if (!nobodyUsers || nobodyUsers.length === 0) {\n      errors.push({ id: id, name: name, message: '未指定审批人为空时的处理人' })\n    }\n  }\n\n  // 记录错误\n  if (errors.length > 0) {\n    nodesError.value[id] = errors\n  } else {\n    delete nodesError.value[id]\n  }\n})\n</script>\n\n<template>\n  <Node\n    v-bind=\"$attrs\"\n    icon=\"el:Stamp\"\n    color=\"linear-gradient(89.96deg, #FA6F32 .05%, #FB9337 79.83%)\"\n    :node=\"node\"\n  >\n    <el-text>{{ content }}</el-text>\n  </Node>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/CcNode.vue",
    "content": "<script setup lang=\"ts\">\nimport Node from './Node.vue'\nimport type { CcNode } from './type'\nimport type { Ref } from 'vue'\nimport type { ErrorInfo } from './type'\nimport type { Field } from '@/components/Render/type'\nimport { getById } from '@/api/modules/role'\nimport { getByUsername } from '@/api/modules/user'\n\nconst props = defineProps<{\n  node: CcNode\n}>()\nconst { fields, nodesError } = inject<{\n  fields: Ref<Field[]>\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { fields: ref([]), nodesError: ref({}) })\nconst content = ref<string>('')\nwatchEffect(() => {\n  const errors: ErrorInfo[] = []\n  const { id, assigneeType, name, users, roles, leader, choice, formUser, formRole, orgLeader } =\n    props.node\n  if (assigneeType === 'user') {\n    if (users.length > 0) {\n      const all = users.map((user) => getByUsername(user))\n      Promise.all(all).then((users) => {\n        content.value = users.map((user) => user.data.name).join('、')\n      })\n    } else {\n      errors.push({ id: id, name: name, message: '未指定人员' })\n      content.value = '未指定人员'\n    }\n  } else if (assigneeType === 'choice') {\n    content.value = `发起人自选（${choice ? '多选' : '单选'}）`\n  } else if (assigneeType === 'self') {\n    content.value = '发起人自己'\n  } else if (assigneeType === 'leader') {\n    content.value = leader === 1 ? '直属上级' : `${leader}级上级`\n  } else if (assigneeType === 'orgLeader') {\n    content.value = orgLeader === 1 ? '直属主管' : `${orgLeader}级主管`\n  } else if (assigneeType === 'formUser') {\n    if (!formUser) {\n      errors.push({ id: id, name: name, message: '未指定表单内人员' })\n    }\n    const title = fields.value.find((e) => e.id === formUser)?.label || formUser || '?'\n    content.value = `表单内（${title}）人员`\n  } else if (assigneeType === 'formRole') {\n    if (!formRole) {\n      errors.push({ id: id, name: name, message: '未指定表单内角色' })\n    }\n    const title = fields.value.find((e) => e.id === formRole)?.label || formRole || '?'\n    content.value = `表单内（${title}）角色`\n  } else if (assigneeType === 'role') {\n    if (roles.length > 0) {\n      const all = roles.map((id) => getById(id))\n      Promise.all(all).then((roles) => {\n        content.value = roles.map((res) => res.data.name).join('、')\n      })\n    } else {\n      errors.push({ id: id, name: name, message: '未指定角色' })\n      content.value = '未指定角色'\n    }\n  } else {\n    errors.push({ id: id, name: name, message: '未知错误' })\n    content.value = name\n  }\n\n  // 记录错误\n  if (errors.length > 0) {\n    nodesError.value[id] = errors\n  } else {\n    delete nodesError.value[id]\n  }\n})\n</script>\n\n<template>\n  <Node v-bind=\"$attrs\" icon=\"el:Promotion\" color=\"rgb(50, 150, 250)\" :node=\"node\">\n    <el-text>{{ content }}</el-text>\n  </Node>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/ConditionNode.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ConditionNode } from './type'\nimport type { Ref } from 'vue'\nimport type { ErrorInfo } from './type'\nimport Node from './Node.vue'\n\nconst props = defineProps<{\n  node: ConditionNode\n}>()\nconst { nodesError } = inject<{\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { nodesError: ref({}) })\nconst content = ref<string>('')\nwatchEffect(() => {\n  const errors: ErrorInfo[] = []\n  const { id, name, def, conditions, next } = props.node\n  if (def) {\n    content.value = '不满足其他条件，进入此分支'\n  } else if (conditions.conditions.length > 0 || (conditions.groups?.length || 0) > 0) {\n    const count = conditions.conditions.length + (conditions.groups?.length || 0)\n    content.value = `已设置（${count}）个条件`\n    if (!next) {\n      errors.push({ id: id, name: name, message: '分支下节点为空' })\n    }\n  } else {\n    errors.push({ id: id, name: name, message: '未设置条件' })\n    content.value = '未设置条件'\n  }\n  // 记录错误\n  if (errors.length > 0) {\n    nodesError.value[id] = errors\n  } else {\n    delete nodesError.value[id]\n  }\n})\n</script>\n\n<template>\n  <div class=\"branch-node\">\n    <Node v-bind=\"$attrs\" icon=\"el:Share\" :node=\"node\" :readOnly=\"node.def\">\n      <el-text>{{ content }}</el-text>\n      <slot name=\"append\" />\n    </Node>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.branch-node {\n  :deep(.node-box) {\n    margin: 60px 40px 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/EndNode.vue",
    "content": "<script setup lang=\"ts\">\nimport type { FlowNode } from '@/views/flowDesign/nodes/type'\nimport type { Ref } from 'vue'\n\nconst _inject = inject<{\n  readOnly?: Ref<boolean>\n}>('flowDesign', { readOnly: ref(false) })\nconst $emits = defineEmits<{\n  (e: 'activeNode', node: FlowNode): void\n}>()\nconst $props = withDefaults(\n  defineProps<{\n    node: FlowNode\n    readOnly?: boolean\n  }>(),\n  {\n    readOnly: false\n  }\n)\nconst _readOnly = computed(() => _inject.readOnly?.value || $props.readOnly)\nconst activeNode = () => {\n  if (_readOnly.value) return\n  $emits('activeNode', $props.node)\n}\n</script>\n\n<template>\n  <div class=\"node-box\">\n    <div class=\"end-node-circle\"></div>\n    <div class=\"end-node\" @click=\"activeNode\">\n      <el-text>{{ node.name }}</el-text>\n    </div>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.node-box {\n  position: relative;\n  padding-bottom: 50px;\n\n  .end-node-circle {\n    width: 10px;\n    height: 10px;\n    border-radius: 50%;\n    background-color: var(--el-border-color);\n    margin: auto auto 5px;\n  }\n\n  .end-node {\n    position: relative;\n    background: var(--el-border-color-lighter);\n    padding: 7px 20px;\n    border-radius: 24px;\n    cursor: pointer;\n    overflow: visible;\n    z-index: 10;\n    box-shadow: var(--el-box-shadow-light);\n\n    &:hover {\n      box-shadow: 0 0 5px 0 var(--el-color-primary);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/ExclusiveNode.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ExclusiveNode } from './type'\nimport GatewayNode from './GatewayNode.vue'\n\ndefineProps<{\n  node: ExclusiveNode\n}>()\n</script>\n\n<template>\n  <GatewayNode v-bind=\"$attrs\" :node=\"node\">\n    <template #default=\"{ addNode, readOnly }\">\n      <el-button type=\"primary\" :disabled=\"readOnly\" @click=\"addNode('condition', node)\" plain round\n        >添加条件</el-button\n      >\n    </template>\n  </GatewayNode>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/GatewayNode.vue",
    "content": "<script setup lang=\"ts\">\nimport TreeNode from './TreeNode.vue'\nimport type { BranchNode, FlowNode, NodeType } from './type'\nimport Add from './Add.vue'\nimport type { Ref } from 'vue'\n\nconst $emits = defineEmits<{\n  (e: 'addNode', type: NodeType, node: FlowNode): void\n}>()\nconst props = defineProps<{\n  node: BranchNode\n}>()\nconst { readOnly } = inject<{\n  readOnly?: Ref<boolean>\n}>('flowDesign', { readOnly: ref(false) })\nconst addNode = (type: NodeType, node?: FlowNode) => {\n  $emits('addNode', type, node || props.node)\n}\nconst moveRight = (index: number) => {\n  const node = props.node.branches[index]\n  props.node.branches.splice(index, 1)\n  props.node.branches.splice(index + 1, 0, node)\n}\nconst moveLeft = (index: number) => {\n  const node = props.node.branches[index]\n  props.node.branches.splice(index, 1)\n  props.node.branches.splice(index - 1, 0, node)\n}\n</script>\n\n<template>\n  <div class=\"gateway-node\">\n    <div class=\"add-branch\">\n      <slot :addNode=\"addNode\" :readOnly=\"readOnly\"></slot>\n    </div>\n    <div v-for=\"(item, index) in node.branches\" :key=\"item.id\" class=\"col-box\">\n      <template v-if=\"index === 0\">\n        <div class=\"top-left-border\"></div>\n        <div class=\"bottom-left-border\" />\n      </template>\n      <template v-else-if=\"node.branches.length === index + 1\">\n        <div class=\"top-right-border\"></div>\n        <div class=\"bottom-right-border\" />\n      </template>\n      <TreeNode :node=\"item\" v-bind=\"$attrs\" @addNode=\"addNode\" class=\"col-node\">\n        <template #append>\n          <div\n            class=\"move-left\"\n            @click.stop=\"moveLeft(index)\"\n            v-show=\"index !== 0 && node.branches.length !== index + 1 && !readOnly\"\n          >\n            <svg-icon name=\"el:ArrowLeft\" />\n          </div>\n          <div\n            class=\"move-right\"\n            @click.stop=\"moveRight(index)\"\n            v-show=\"![index + 1, index + 2].includes(node.branches.length) && !readOnly\"\n          >\n            <svg-icon name=\"el:ArrowRight\" />\n          </div>\n        </template>\n      </TreeNode>\n    </div>\n  </div>\n  <Add @addNode=\"addNode\" class=\"branch-but\" />\n</template>\n\n<style scoped lang=\"scss\">\n.gateway-node {\n  display: flex;\n  border-top: var(--el-border);\n  border-bottom: var(--el-border);\n  overflow: visible;\n  position: relative;\n\n  .add-branch {\n    position: absolute;\n    left: 50%;\n    top: -15px;\n    z-index: 2;\n    transform: translateX(-50%);\n  }\n\n  .col-box {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    background-color: var(--flow-bg-color);\n\n    &:before {\n      content: '';\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      margin: auto;\n      width: 1px;\n      height: 100%;\n      background-color: var(--el-border-color);\n    }\n\n    .top-left-border {\n      position: absolute;\n      left: 0;\n      top: -3px;\n      height: 3px;\n      width: 50%;\n      background-color: var(--flow-bg-color);\n    }\n\n    .bottom-left-border {\n      position: absolute;\n      left: 0;\n      bottom: -3px;\n      height: 3px;\n      width: 50%;\n      background-color: var(--flow-bg-color);\n    }\n\n    .top-right-border {\n      position: absolute;\n      right: 0;\n      top: -3px;\n      height: 3px;\n      width: 50%;\n      background-color: var(--flow-bg-color);\n    }\n\n    .bottom-right-border {\n      position: absolute;\n      right: 0;\n      bottom: -3px;\n      height: 3px;\n      width: 50%;\n      background-color: var(--flow-bg-color);\n    }\n  }\n}\n\n.col-node {\n  &:hover {\n    .move-right,\n    .move-left {\n      opacity: 1;\n    }\n  }\n}\n\n.move-right,\n.move-left {\n  position: absolute;\n  top: 0;\n  height: 100%;\n  display: flex;\n  align-items: center;\n  opacity: 0;\n\n  &:hover {\n    background-color: var(--el-color-primary-light-9);\n  }\n}\n\n.move-left {\n  left: 0;\n}\n\n.move-right {\n  right: 0;\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/Node.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ErrorInfo, FlowNode, NodeType } from './type'\nimport { ClickOutside as vClickOutside, type InputInstance } from 'element-plus'\nimport Add from './Add.vue'\nimport type { Ref } from 'vue'\n\nconst _inject = inject<{\n  readOnly?: Ref<boolean>\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { readOnly: ref(false), nodesError: ref({}) })\nconst $emits = defineEmits<{\n  (e: 'addNode', type: NodeType, node: FlowNode): void\n  (e: 'delNode', node: FlowNode): void\n  (e: 'activeNode', node: FlowNode): void\n}>()\nconst $props = withDefaults(\n  defineProps<{\n    icon?: string\n    node: FlowNode\n    color?: string\n    readOnly?: boolean\n    close?: boolean\n  }>(),\n  {\n    readOnly: false,\n    close: true\n  }\n)\nconst errorInfo = computed<ErrorInfo[] | undefined>(() => _inject.nodesError.value[$props.node.id])\nconst _readOnly = computed(() => _inject.readOnly?.value || $props.readOnly)\nconst showInput = ref(false)\nconst inputRef = ref<InputInstance>()\nconst onShowInput = () => {\n  if (_readOnly.value) return\n  showInput.value = true\n  nextTick(() => {\n    inputRef.value?.focus()\n  })\n}\nconst onClickOutside = () => {\n  if (showInput.value) {\n    showInput.value = false\n  }\n}\nconst activeNode = () => {\n  if (_readOnly.value) return\n  $emits('activeNode', $props.node)\n}\nconst addNode = (type: NodeType) => {\n  $emits('addNode', type, $props.node)\n}\nconst delNode = () => {\n  $emits('delNode', $props.node)\n}\n</script>\n\n<template>\n  <div class=\"node-box\">\n    <div @click=\"activeNode\" :class=\"['node', { 'error-node': errorInfo?.length && !_readOnly }]\">\n      <!--头部-->\n      <div class=\"node-header\">\n        <!--删除按钮-->\n        <span @click.stop>\n          <el-popconfirm\n            title=\"您确定要删除该节点吗？\"\n            width=\"200\"\n            :hide-after=\"0\"\n            placement=\"right-start\"\n            @confirm=\"delNode\"\n          >\n            <template #reference>\n              <el-button\n                class=\"node-close\"\n                v-show=\"close && !_readOnly\"\n                plain\n                circle\n                icon=\"CircleClose\"\n                size=\"small\"\n                type=\"danger\"\n              />\n            </template>\n          </el-popconfirm>\n        </span>\n        <div class=\"head\">\n          <div @click.stop v-if=\"showInput\">\n            <el-input\n              ref=\"inputRef\"\n              v-click-outside=\"onClickOutside\"\n              @blur=\"onClickOutside\"\n              maxlength=\"30\"\n              v-model=\"node.name\"\n            />\n          </div>\n          <el-text tag=\"b\" truncated v-else @click.stop=\"onShowInput\">\n            {{ node.name }}\n            <el-icon>\n              <EditPen />\n            </el-icon>\n          </el-text>\n          <slot name=\"icon\">\n            <svg-icon :size=\"30\" color=\"node-icon\" v-if=\"icon\" :name=\"icon\" />\n          </slot>\n        </div>\n        <!--错误提示-->\n        <el-tooltip placement=\"top-start\">\n          <template #content>\n            <div v-for=\"err in errorInfo\" :key=\"err.id\">\n              {{ err.message }}\n            </div>\n          </template>\n          <el-icon class=\"warn-icon\" :size=\"20\" v-show=\"errorInfo?.length && !_readOnly\">\n            <WarnTriangleFilled @click.stop />\n          </el-icon>\n        </el-tooltip>\n      </div>\n      <!--插槽内容-->\n      <div class=\"node-content\">\n        <slot></slot>\n      </div>\n    </div>\n    <Add @add-node=\"addNode\" />\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.node-box {\n  position: relative;\n\n  /* &:before {\n    content: '';\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    margin: auto;\n    width: 1px;\n    height: 100%;\n    background-color: var(--el-border-color);\n  }*/\n\n  &:after {\n    content: '';\n    position: absolute;\n    top: -12px;\n    left: 50%;\n    transform: translate(-50%);\n    border-style: solid;\n    width: 0;\n    border-width: 8px 6px 4px;\n    border-color: var(--el-border-color) transparent transparent;\n    background-color: var(--flow-bg-color);\n  }\n\n  .warn-icon {\n    cursor: pointer;\n    position: absolute;\n    right: -30px;\n    top: 50%;\n    transform: translateY(-50%);\n    color: var(--el-color-error);\n  }\n\n  .node {\n    border-radius: 7px;\n    cursor: pointer;\n    position: relative;\n    // overflow: visible;\n    min-height: 90px;\n    width: 230px;\n    z-index: 10;\n    color: var(--el-text-color-primary);\n    border: 1px solid var(--el-border-color-light);\n    background-color: var(--el-bg-color-overlay);\n    box-shadow: var(--el-box-shadow-light);\n\n    &.error-node {\n      box-shadow: 0 0 5px 0 var(--el-color-error-light-3);\n    }\n\n    .node-header {\n      padding: 2px 7px;\n      border-radius: 7px 7px 0 0;\n      background: v-bind(color);\n      box-sizing: border-box;\n      border-bottom: 1px solid var(--el-border-color-light);\n\n      .head {\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n\n        :deep(.el-input__wrapper) {\n          background-color: var(--el-bg-color-overlay);\n        }\n      }\n\n      .node-close {\n        position: absolute;\n        top: -10px;\n        right: -10px;\n        z-index: 10;\n        display: none;\n      }\n    }\n\n    .node-content {\n      position: relative;\n      padding: 20px;\n    }\n\n    &:hover {\n      &:not(.error-node) {\n        box-shadow: 0 0 5px 0 var(--el-color-primary);\n      }\n\n      .node-close {\n        display: block;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/NotifyNode.vue",
    "content": "<script setup lang=\"ts\">\nimport Node from './Node.vue'\nimport type { NotifyNode } from './type'\nimport type { Ref } from 'vue'\nimport type { ErrorInfo } from './type'\nimport { getById } from '@/api/modules/role'\nimport type { Field } from '@/components/Render/type'\nimport { getByUsername } from '@/api/modules/user'\n\nconst props = defineProps<{\n  node: NotifyNode\n}>()\nconst { fields, nodesError } = inject<{\n  fields: Ref<Field[]>\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { fields: ref([]), nodesError: ref({}) })\nconst content = ref<string>('')\nwatchEffect(() => {\n  const errors: ErrorInfo[] = []\n  const {\n    id,\n    assigneeType,\n    name,\n    users,\n    roles,\n    leader,\n    choice,\n    formUser,\n    formRole,\n    orgLeader,\n    subject,\n    types\n  } = props.node\n  if (assigneeType === 'user') {\n    if (users.length > 0) {\n      const all = users.map((user) => getByUsername(user))\n      Promise.all(all).then((users) => {\n        content.value = users.map((user) => user.data.name).join('、')\n      })\n    } else {\n      errors.push({ id: id, name: name, message: '未指定人员' })\n      content.value = '未指定人员'\n    }\n  } else if (assigneeType === 'choice') {\n    content.value = `发起人自选（${choice ? '多选' : '单选'}）`\n  } else if (assigneeType === 'self') {\n    content.value = '发起人自己'\n  } else if (assigneeType === 'leader') {\n    content.value = leader === 1 ? '直属上级' : `${leader}级上级`\n  } else if (assigneeType === 'orgLeader') {\n    content.value = orgLeader === 1 ? '直属主管' : `${orgLeader}级主管`\n  } else if (assigneeType === 'formUser') {\n    if (!formUser) {\n      errors.push({ id: id, name: name, message: '未指定表单内人员' })\n    }\n    const title = fields.value.find((e) => e.id === formUser)?.label || formUser || '?'\n    content.value = `表单内（${title}）人员`\n  } else if (assigneeType === 'formRole') {\n    if (!formRole) {\n      errors.push({ id: id, name: name, message: '未指定表单内角色' })\n    }\n    const title = fields.value.find((e) => e.id === formRole)?.label || formRole || '?'\n    content.value = `表单内（${title}）角色`\n  } else if (assigneeType === 'role') {\n    if (roles.length > 0) {\n      const all = roles.map((id) => getById(id))\n      Promise.all(all).then((roles) => {\n        content.value = roles.map((res) => res.data.name).join('、')\n      })\n    } else {\n      errors.push({ id: id, name: name, message: '未指定角色' })\n      content.value = '未指定角色'\n    }\n  } else {\n    errors.push({ id: id, name: name, message: '未知错误' })\n    content.value = name\n  }\n\n  if (types.length === 0) {\n    errors.push({ id: id, name: name, message: '未指定通知类型' })\n  }\n  if (!subject) {\n    errors.push({ id: id, name: name, message: '消息主题为空' })\n  }\n  if (!props.node.content) {\n    errors.push({ id: id, name: name, message: '消息内容为空' })\n  }\n\n  // 记录错误\n  if (errors.length > 0) {\n    nodesError.value[id] = errors\n  } else {\n    delete nodesError.value[id]\n  }\n})\n</script>\n\n<template>\n  <Node v-bind=\"$attrs\" icon=\"el:BellFilled\" color=\"#95d475\" :node=\"node\">\n    <el-text>{{ content }}</el-text>\n  </Node>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/ServiceNode.vue",
    "content": "<script setup lang=\"ts\">\nimport Node from './Node.vue'\nimport type { ErrorInfo, ServiceNode } from './type'\nimport type { Ref } from 'vue'\n\nconst props = defineProps<{\n  node: ServiceNode\n}>()\nconst { nodesError } = inject<{\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { nodesError: ref({}) })\nconst content = ref<string>('')\nwatchEffect(() => {\n  const errors: ErrorInfo[] = []\n  const { id, name, implementationType, implementation } = props.node\n  if (!implementationType) {\n    errors.push({ id: id, name: name, message: '执行类型为空' })\n    content.value = '执行类型为空'\n  } else if (!implementation) {\n    errors.push({ id: id, name: name, message: '执行值为空' })\n    content.value = '执行值为空'\n  } else {\n    content.value = `执行服务`\n  }\n  // 记录错误\n  if (errors.length > 0) {\n    nodesError.value[id] = errors\n  } else {\n    delete nodesError.value[id]\n  }\n})\n</script>\n\n<template>\n  <Node v-bind=\"$attrs\" icon=\"el:Tools\" color=\"#ffc107\" :node=\"node\">\n    <el-text>{{ content }}</el-text>\n  </Node>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/StartNode.vue",
    "content": "<script setup lang=\"ts\">\nimport Node from './Node.vue'\nimport type { StartNode } from './type'\nimport type { ErrorInfo } from './type'\nimport type { Ref } from 'vue'\n\nconst props = defineProps<{\n  node: StartNode\n}>()\nconst { nodesError } = inject<{\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { nodesError: ref({}) })\nwatchEffect(() => {\n  const errors: ErrorInfo[] = []\n  const { id, name, next } = props.node\n  if (next?.type === 'end') {\n    errors.push({ id: id, name: name, message: '发起下节点为空' })\n  }\n  // 记录错误\n  if (errors.length > 0) {\n    nodesError.value[id] = errors\n  } else {\n    delete nodesError.value[id]\n  }\n})\n</script>\n\n<template>\n  <div class=\"start-node\">\n    <Node v-bind=\"$attrs\" icon=\"el:List\" :close=\"false\" color=\"#8c7cf3\" :node=\"node\">\n      <el-text>发起人</el-text>\n    </Node>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.start-node {\n  padding-top: 50px;\n  :deep(.node-box) {\n    &:after {\n      display: none;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/TimerNode.vue",
    "content": "<script setup lang=\"ts\">\nimport Node from './Node.vue'\nimport type { TimerNode } from './type'\nimport type { Ref } from 'vue'\nimport type { ErrorInfo } from './type'\n\nconst props = defineProps<{\n  node: TimerNode\n}>()\nconst { nodesError } = inject<{\n  nodesError: Ref<Recordable<ErrorInfo[]>>\n}>('flowDesign', { nodesError: ref({}) })\nconst content = ref<string>('')\nconst unitMap: Recordable = {\n  'PT%sS': '秒',\n  'PT%sM': '分钟',\n  'PT%sH': '小时',\n  'P%sD': '天',\n  'P%sW': '周',\n  'P%sM': '月'\n}\nwatchEffect(() => {\n  const errors: ErrorInfo[] = []\n  const { id, name, waitType, unit, duration, timeDate } = props.node\n  if (waitType === 'date') {\n    content.value = `等至 ${timeDate || '?'}`\n    if (!timeDate) {\n      errors.push({ id: id, name: name, message: '未设置等待时间' })\n    }\n  } else if (waitType === 'duration') {\n    content.value = `等待 ${duration} ${unitMap[unit]}`\n    if (duration <= 0) {\n      errors.push({ id: id, name: name, message: '未设置等待时长' })\n    }\n  }\n  // 记录错误\n  if (errors.length > 0) {\n    nodesError.value[id] = errors\n  } else {\n    delete nodesError.value[id]\n  }\n})\n</script>\n\n<template>\n  <Node v-bind=\"$attrs\" icon=\"el:Timer\" color=\"#E872B7\" :node=\"node\">\n    <el-text>{{ content }}</el-text>\n  </Node>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/TreeNode.vue",
    "content": "<script setup lang=\"ts\" name=\"TreeNode\">\nimport type { FlowNode } from './type'\nimport type { Component } from 'vue'\nimport Start from './StartNode.vue'\nimport End from './EndNode.vue'\nimport Approval from './ApprovalNode.vue'\nimport Cc from './CcNode.vue'\nimport Timer from './TimerNode.vue'\nimport Notify from './NotifyNode.vue'\nimport Service from './ServiceNode.vue'\nimport Exclusive from './ExclusiveNode.vue'\nimport Condition from './ConditionNode.vue'\n\ndefineProps<{\n  node: FlowNode\n}>()\nconst nodes: Recordable<Component> = {\n  start: Start,\n  approval: Approval,\n  cc: Cc,\n  timer: Timer,\n  notify: Notify,\n  service: Service,\n  exclusive: Exclusive,\n  condition: Condition,\n  end: End\n}\n</script>\n\n<template>\n  <slot />\n  <component :is=\"nodes[node.type]\" :node=\"node\" v-bind=\"$attrs\">\n    <template v-for=\"(value, name) in $slots\" #[name]=\"scope\">\n      <slot :name=\"name\" v-bind=\"scope || {}\"></slot>\n    </template>\n  </component>\n  <TreeNode v-if=\"node.next\" :node=\"node.next\" v-bind=\"$attrs\" />\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/nodes/type.ts",
    "content": "import type { FilterRules } from '@/components/AdvancedFilter/type'\n\nexport type NodeType =\n  | 'start'\n  | 'approval'\n  | 'cc'\n  | 'exclusive'\n  | 'timer'\n  | 'notify'\n  | 'service'\n  | 'condition'\n  | 'end'\n\nexport interface FlowNode {\n  id: string\n  pid?: string\n  name: string\n  type: NodeType\n  executionListeners?: NodeListener[]\n  next?: FlowNode\n}\n\nexport interface NodeListener {\n  event: string\n  implementationType: 'class' | 'expression' | 'delegateExpression'\n  implementation: string\n}\n\nexport interface StartNode extends FlowNode {\n  formProperties: FormProperty[]\n}\n\nexport interface EndNode extends FlowNode {}\n\nexport interface AssigneeNode extends FlowNode {\n  // 审批方式\n  assigneeType:\n    | 'user'\n    | 'role'\n    | 'choice'\n    | 'self'\n    | 'leader'\n    | 'orgLeader'\n    | 'formUser'\n    | 'formRole'\n    | 'autoRefuse'\n  // 审批人\n  users: string[]\n  // 审批角色\n  roles: string[]\n  // 表单内人员\n  formUser: string\n  // 表单内角色\n  formRole: string\n  // 主管\n  leader: number\n  // 组织主管\n  orgLeader: number\n  // 自选：true-多选，false-单选\n  choice: boolean\n  // 发起人自己\n  self: boolean\n}\n\nexport interface CcNode extends AssigneeNode {\n  formProperties: FormProperty[]\n}\n\nexport interface NotifyNode extends AssigneeNode {\n  types: ('site' | 'email' | 'sms' | 'wechat' | 'dingtalk' | 'feishu')[]\n  subject: string\n  content: string\n}\n\nexport interface ApprovalNode extends AssigneeNode {\n  // 多人审批方式\n  multi: 'sequential' | 'joint' | 'single'\n  // 审批人为空时处理方式：reject-拒绝，pass-通过，admin-管理员，assign-指定人员\n  nobody: 'refuse' | 'pass' | 'admin' | 'assign'\n  // 多人审批通过比例\n  multiPercent: number\n  // 审批人为空时,指定人员\n  nobodyUsers: string[]\n  // 表单字段\n  formProperties: FormProperty[]\n  // 操作权限\n  operations: OperationPermissions\n  // 任务监听器\n  taskListeners?: NodeListener[]\n}\n\nexport interface ServiceNode extends FlowNode {\n  implementationType: string\n  implementation: string\n}\n\nexport interface TimerNode extends FlowNode {\n  waitType: 'duration' | 'date'\n  unit: 'PT%sS' | 'PT%sM' | 'PT%sH' | 'P%sD' | 'P%sW' | 'P%sM'\n  duration: number\n  timeDate?: string\n}\n\nexport interface ConditionNode extends FlowNode {\n  def: boolean\n  conditions: FilterRules\n}\n\nexport interface BranchNode extends FlowNode {\n  branches: FlowNode[]\n}\n\nexport interface ExclusiveNode extends BranchNode {\n  branches: ConditionNode[]\n}\n\nexport interface ErrorInfo {\n  id: string\n  name: string\n  message: string\n}\n\nexport interface FormProperty {\n  // 字段ID\n  id: string\n  // 字段名称\n  name: string\n  // 只读\n  readonly: boolean\n  // 必填\n  required: boolean\n  // 隐藏\n  hidden: boolean\n}\n\nexport interface OperationPermissions {\n  // 同意\n  complete: boolean\n  // 拒绝\n  refuse: boolean\n  // 回退\n  back: boolean\n  // 转交\n  transfer: boolean\n  // 委派\n  delegate: boolean\n  // 加签\n  addMulti: boolean\n  // 减签\n  minusMulti: boolean\n}\n"
  },
  {
    "path": "src/views/flowDesign/panels/ApprovalPanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ApprovalNode, FormProperty } from '../nodes/type'\nimport type { Field } from '@/components/Render/type'\nimport UserSelector from '@/components/UserSelector/index.vue'\nimport AssigneePanel from './AssigneePanel.vue'\nimport type { Ref } from 'vue'\nimport TaskListeners from './TaskListeners.vue'\n\nconst { fields } = inject<{ fields: Ref<Field[]>; admin: string[] }>('flowDesign', {\n  fields: ref([]),\n  admin: []\n})\nconst props = defineProps<{\n  activeData: ApprovalNode\n}>()\nconst spacer = h(ElDivider)\nconst activeName = ref('properties')\nconst allReadonly = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.readonly)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.readonly = val))\n    if (val) {\n      allHidden.value = false\n      allRequired.value = false\n    }\n  }\n})\nconst allHidden = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.hidden)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.hidden = val))\n    if (val) {\n      allRequired.value = false\n      allReadonly.value = false\n    }\n  }\n})\nconst allRequired = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.required)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.required = val))\n    if (val) {\n      allReadonly.value = false\n      allHidden.value = false\n    }\n  }\n})\n\nconst changeReadonly = (row: FormProperty) => {\n  if (row.readonly) {\n    row.required = false\n    row.hidden = false\n  }\n}\nconst changeRequired = (row: FormProperty) => {\n  if (row.required) {\n    row.readonly = false\n    row.hidden = false\n  }\n}\nconst changeHidden = (row: FormProperty) => {\n  if (row.hidden) {\n    row.readonly = false\n    row.required = false\n  }\n}\n\nwatchEffect(() => {\n  const formProperties = props.activeData.formProperties\n  props.activeData.formProperties = fields.value.map((field) => ({\n    id: field.id,\n    name: field.label,\n    readonly: field.readonly || false,\n    hidden: field.hidden,\n    required: field.required || false\n  }))\n  props.activeData.formProperties.forEach((item) => {\n    const properties = formProperties.find((f) => f.id === item.id)\n    if (properties) {\n      item.readonly = properties.readonly\n      item.hidden = properties.hidden\n      item.required = properties.required\n    }\n  })\n})\n</script>\n\n<template>\n  <el-tabs v-model=\"activeName\" stretch class=\"el-segmented\">\n    <el-tab-pane label=\"审批人\" name=\"properties\">\n      <el-form label-position=\"top\" label-width=\"90px\">\n        <AssigneePanel :active-data=\"activeData\" :fields=\"fields\" type=\"审批\">\n          <el-col :span=\"8\">\n            <el-radio value=\"autoRefuse\">自动拒绝</el-radio>\n          </el-col>\n        </AssigneePanel>\n        <el-form-item prop=\"method\" label=\"多人审批方式\">\n          <el-radio-group v-model=\"activeData.multi\" class=\"flex-col important-items-start\">\n            <el-radio value=\"sequential\">依次审批（按顺序审批）</el-radio>\n            <el-radio value=\"joint\">会签（需要所有审批人都通过）</el-radio>\n            <el-radio value=\"single\">或签（其中一名审批人通过即可）</el-radio>\n          </el-radio-group>\n          <el-text v-if=\"activeData.multi === 'joint'\">\n            需要 <el-input-number v-model=\"activeData.multiPercent\" :min=\"1\" :max=\"100\" /> %人员通过\n          </el-text>\n        </el-form-item>\n        <el-form-item prop=\"nobody\" label=\"审批人为空\">\n          <el-radio-group v-model=\"activeData.nobody\" class=\"w-full\">\n            <el-row>\n              <el-col :span=\"12\">\n                <el-radio value=\"pass\">自动通过</el-radio>\n              </el-col>\n              <el-col :span=\"12\">\n                <el-radio value=\"assign\">指定人员</el-radio>\n              </el-col>\n              <el-col :span=\"12\">\n                <el-radio value=\"reject\">自动拒绝</el-radio>\n              </el-col>\n              <el-col :span=\"12\">\n                <el-radio value=\"admin\">转交流程管理员</el-radio>\n              </el-col>\n            </el-row>\n          </el-radio-group>\n          <user-selector\n            v-if=\"activeData.nobody === 'assign'\"\n            multiple\n            v-model=\"activeData.nobodyUsers\"\n            placeholder=\"指定人员\"\n          />\n        </el-form-item>\n        <el-form-item prop=\"taskListeners\" label=\"任务监听器\">\n          <TaskListeners :node=\"activeData\" />\n        </el-form-item>\n      </el-form>\n    </el-tab-pane>\n    <el-tab-pane label=\"表单权限\" name=\"formPermissions\">\n      <el-table :data=\"activeData.formProperties\">\n        <el-table-column prop=\"name\" label=\"字段\" />\n        <el-table-column prop=\"readonly\">\n          <template #header>\n            <el-checkbox v-model=\"allReadonly\" label=\"只读\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.readonly\" @change=\"changeReadonly(row)\" />\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"required\">\n          <template #header>\n            <el-checkbox v-model=\"allRequired\" label=\"必填\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.required\" @change=\"changeRequired(row)\" />\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"hidden\">\n          <template #header>\n            <el-checkbox v-model=\"allHidden\" label=\"隐藏\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.hidden\" @change=\"changeHidden(row)\" />\n          </template>\n        </el-table-column>\n      </el-table>\n    </el-tab-pane>\n    <el-tab-pane label=\"操作权限\" name=\"operationPermissions\">\n      <el-space fill :size=\"0\" direction=\"horizontal\" :spacer=\"spacer\">\n        <div class=\"opt-item\">\n          <el-icon :size=\"32\" class=\"opt-item__icon\">\n            <CircleCheck />\n          </el-icon>\n          <div class=\"opt-item__content\">\n            <el-text tag=\"b\"> 同意</el-text>\n            <div class=\"opt-item__second\">审批通过，流转到下一个节点</div>\n          </div>\n          <el-switch\n            v-model=\"activeData.operations.complete\"\n            inline-prompt\n            active-text=\"开\"\n            inactive-text=\"关\"\n          />\n        </div>\n        <div class=\"opt-item\">\n          <el-icon :size=\"32\" class=\"opt-item__icon\">\n            <CircleClose />\n          </el-icon>\n          <div class=\"opt-item__content\">\n            <el-text tag=\"b\"> 拒绝</el-text>\n            <div class=\"opt-item__second\">当拒绝任务时，当前任务被终止，并结束整个流程</div>\n          </div>\n          <el-switch\n            v-model=\"activeData.operations.refuse\"\n            inline-prompt\n            active-text=\"开\"\n            inactive-text=\"关\"\n          />\n        </div>\n        <div class=\"opt-item\">\n          <el-icon :size=\"32\" class=\"opt-item__icon\">\n            <Back />\n          </el-icon>\n          <div class=\"opt-item__content\">\n            <el-text tag=\"b\"> 退回</el-text>\n            <div class=\"opt-item__second\">\n              若审批内容存在问题，当前任务将中止并回退至特定历史任务节点\n            </div>\n          </div>\n          <el-switch\n            v-model=\"activeData.operations.back\"\n            inline-prompt\n            active-text=\"开\"\n            inactive-text=\"关\"\n          />\n        </div>\n        <div class=\"opt-item\">\n          <el-icon :size=\"32\" class=\"opt-item__icon\">\n            <Switch />\n          </el-icon>\n          <div class=\"opt-item__content\">\n            <el-text tag=\"b\"> 转交</el-text>\n            <div class=\"opt-item__second\">\n              将当前任务移交给其他人处理，以便他们继续执行所需的操作\n            </div>\n          </div>\n          <el-switch\n            v-model=\"activeData.operations.transfer\"\n            inline-prompt\n            active-text=\"开\"\n            inactive-text=\"关\"\n          />\n        </div>\n        <div class=\"opt-item\">\n          <el-icon :size=\"32\" class=\"opt-item__icon\">\n            <UserFilled />\n          </el-icon>\n          <div class=\"opt-item__content\">\n            <el-text tag=\"b\"> 委派</el-text>\n            <div class=\"opt-item__second\">将当前任务暂时交由他人处理，待其完成后再交回自己处理</div>\n          </div>\n          <el-switch\n            v-model=\"activeData.operations.delegate\"\n            inline-prompt\n            active-text=\"开\"\n            inactive-text=\"关\"\n          />\n        </div>\n        <div class=\"opt-item\">\n          <svg-icon class-name=\"opt-item__icon\" name=\"add-user\" :size=\"32\" />\n          <div class=\"opt-item__content\">\n            <el-text tag=\"b\"> 加签</el-text>\n            <div class=\"opt-item__second\">\n              在当前任务上额外添加新人员，以处理相关事项或提供必要的审批或意见\n            </div>\n          </div>\n          <el-switch\n            v-model=\"activeData.operations.addMulti\"\n            inline-prompt\n            active-text=\"开\"\n            inactive-text=\"关\"\n          />\n        </div>\n        <div class=\"opt-item\">\n          <svg-icon class-name=\"opt-item__icon\" name=\"reduce-user\" :size=\"32\" />\n          <div class=\"opt-item__content\">\n            <el-text tag=\"b\"> 减签</el-text>\n            <div class=\"opt-item__second\">\n              在当前任务中减少处理人员数量，以简化流程或重新分配责任\n            </div>\n          </div>\n          <el-switch\n            v-model=\"activeData.operations.minusMulti\"\n            inline-prompt\n            active-text=\"开\"\n            inactive-text=\"关\"\n          />\n        </div>\n      </el-space>\n    </el-tab-pane>\n  </el-tabs>\n</template>\n\n<style scoped lang=\"scss\">\n@import '@/styles/el-segmented.scss';\n\n.opt-item {\n  display: flex;\n  align-items: center;\n\n  .opt-item__icon {\n    background: var(--el-color-primary);\n    color: var(--el-color-white);\n    border-radius: 7px;\n    padding: 3px;\n  }\n\n  .opt-item__content {\n    box-sizing: border-box;\n    flex: 1;\n    margin-left: 20px;\n    font-size: 14px;\n\n    .opt-item__second {\n      margin-top: 3px;\n      font-size: 12px;\n      color: var(--el-text-color-placeholder);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/AssigneePanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AssigneeNode } from '../nodes/type'\nimport type { Field } from '@/components/Render/type'\n\ndefineProps<{\n  activeData: AssigneeNode\n  fields: Field[]\n  type: '审批' | '抄送' | '办理' | '通知'\n}>()\n</script>\n\n<template>\n  <el-form label-position=\"top\" label-width=\"90px\">\n    <el-form-item prop=\"assigneeType\" :label=\"`${type}对象`\">\n      <el-radio-group v-model=\"activeData.assigneeType\">\n        <el-row>\n          <el-col :span=\"8\">\n            <el-radio value=\"user\">指定人员</el-radio>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-radio value=\"role\">指定角色</el-radio>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-radio value=\"choice\">发起人自选</el-radio>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-radio value=\"self\">发起人自己</el-radio>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-radio value=\"leader\">直属上级</el-radio>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-radio value=\"orgLeader\">组织主管</el-radio>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-radio value=\"formUser\">表单内人员</el-radio>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-radio value=\"formRole\">表单内角色</el-radio>\n          </el-col>\n          <slot></slot>\n        </el-row>\n      </el-radio-group>\n    </el-form-item>\n    <el-form-item prop=\"users\" label=\"指定人员\" v-if=\"activeData.assigneeType === 'user'\">\n      <user-selector v-model=\"activeData.users\" multiple :placeholder=\"`请选择${type}人`\" />\n    </el-form-item>\n    <el-form-item prop=\"choice\" label=\"发起人自选择\" v-if=\"activeData.assigneeType === 'choice'\">\n      <el-radio-group v-model=\"activeData.choice\">\n        <el-radio-button :value=\"false\">单选</el-radio-button>\n        <el-radio-button :value=\"true\">多选</el-radio-button>\n      </el-radio-group>\n    </el-form-item>\n    <el-form-item prop=\"leader\" label=\"多级上级\" v-if=\"activeData.assigneeType === 'leader'\">\n      <el-select v-model=\"activeData.leader\" style=\"width: 220px\" placeholder=\"请选择多级上级\">\n        <el-option label=\"直属上级\" :value=\"1\"></el-option>\n        <el-option label=\"2级上级\" :value=\"2\"></el-option>\n        <el-option label=\"3级上级\" :value=\"3\"></el-option>\n        <el-option label=\"4级上级\" :value=\"4\"></el-option>\n        <el-option label=\"5级上级\" :value=\"5\"></el-option>\n        <el-option label=\"6级上级\" :value=\"6\"></el-option>\n        <el-option label=\"7级上级\" :value=\"7\"></el-option>\n        <el-option label=\"8级上级\" :value=\"8\"></el-option>\n        <el-option label=\"9级上级\" :value=\"9\"></el-option>\n        <el-option label=\"10级上级\" :value=\"10\"></el-option>\n        <el-option label=\"11级上级\" :value=\"11\"></el-option>\n      </el-select>\n    </el-form-item>\n    <el-form-item prop=\"orgLeader\" label=\"组织主管\" v-if=\"activeData.assigneeType === 'orgLeader'\">\n      <el-select v-model=\"activeData.orgLeader\" style=\"width: 220px\" placeholder=\"请选择组织主管\">\n        <el-option label=\"直属主管\" :value=\"1\"></el-option>\n        <el-option label=\"2级主管\" :value=\"2\"></el-option>\n        <el-option label=\"3级主管\" :value=\"3\"></el-option>\n        <el-option label=\"4级主管\" :value=\"4\"></el-option>\n        <el-option label=\"5级主管\" :value=\"5\"></el-option>\n        <el-option label=\"6级主管\" :value=\"6\"></el-option>\n        <el-option label=\"7级主管\" :value=\"7\"></el-option>\n        <el-option label=\"8级主管\" :value=\"8\"></el-option>\n        <el-option label=\"9级主管\" :value=\"9\"></el-option>\n        <el-option label=\"10级主管\" :value=\"10\"></el-option>\n        <el-option label=\"11级主管\" :value=\"11\"></el-option>\n      </el-select>\n    </el-form-item>\n    <el-form-item prop=\"roles\" label=\"指定角色\" v-if=\"activeData.assigneeType === 'role'\">\n      <RoleSelector\n        v-model=\"activeData.roles\"\n        style=\"width: 220px\"\n        collapse-tags\n        :max-collapse-tags=\"1\"\n        multiple\n        clearable\n        placeholder=\"请选择角色\"\n      />\n    </el-form-item>\n    <el-form-item prop=\"formUser\" label=\"表单内人员\" v-if=\"activeData.assigneeType === 'formUser'\">\n      <el-select placeholder=\"选择表单内人员\" style=\"width: 220px\" v-model=\"activeData.formUser\">\n        <el-option\n          v-for=\"item in fields.filter((e) => e.name === 'UserSelector')\"\n          :key=\"item.id\"\n          :label=\"item.label\"\n          :value=\"item.id\"\n        />\n      </el-select>\n    </el-form-item>\n    <el-form-item prop=\"formRole\" label=\"表单内角色\" v-if=\"activeData.assigneeType === 'formRole'\">\n      <el-select placeholder=\"选择表单内角色\" style=\"width: 220px\" v-model=\"activeData.formRole\">\n        <el-option\n          v-for=\"item in fields.filter((e) => e.name === 'RoleSelector')\"\n          :key=\"item.id\"\n          :label=\"item.label\"\n          :value=\"item.id\"\n        />\n      </el-select>\n    </el-form-item>\n  </el-form>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/CcPanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CcNode } from '../nodes/type'\nimport type { Ref } from 'vue'\nimport type { Field } from '@/components/Render/type'\nimport type { FormProperty } from '../nodes/type'\nimport AssigneePanel from './AssigneePanel.vue'\n\nconst { fields } = inject<{ fields: Ref<Field[]> }>('flowDesign', { fields: ref([]) })\nconst props = defineProps<{\n  activeData: CcNode\n}>()\nconst activeName = ref('properties')\nconst allReadonly = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.readonly)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.readonly = val))\n    if (val) {\n      allHidden.value = false\n    }\n  }\n})\nconst allHidden = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.hidden)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.hidden = val))\n    if (val) {\n      allReadonly.value = false\n    }\n  }\n})\n\nconst changeReadonly = (row: FormProperty) => {\n  if (row.readonly) {\n    row.hidden = false\n  }\n}\nconst changeHidden = (row: FormProperty) => {\n  if (row.hidden) {\n    row.readonly = false\n  }\n}\n\nwatchEffect(() => {\n  const formProperties = props.activeData.formProperties\n  props.activeData.formProperties = fields.value.map((field) => ({\n    id: field.id,\n    name: field.label,\n    readonly: field.readonly || false,\n    hidden: field.hidden,\n    required: field.required || false\n  }))\n  props.activeData.formProperties.forEach((item) => {\n    const properties = formProperties.find((f) => f.id === item.id)\n    if (properties) {\n      item.readonly = properties.readonly\n      item.hidden = properties.hidden\n      item.required = properties.required\n    }\n  })\n})\n</script>\n\n<template>\n  <el-tabs v-model=\"activeName\" stretch class=\"el-segmented\">\n    <el-tab-pane label=\"抄送人\" name=\"properties\">\n      <el-form label-position=\"top\" label-width=\"90px\">\n        <AssigneePanel :active-data=\"activeData\" :fields=\"fields\" type=\"抄送\" />\n      </el-form>\n    </el-tab-pane>\n    <el-tab-pane label=\"表单权限\" name=\"formPermissions\">\n      <el-table :data=\"activeData.formProperties\">\n        <el-table-column prop=\"name\" label=\"字段\" />\n        <el-table-column prop=\"readonly\">\n          <template #header>\n            <el-checkbox v-model=\"allReadonly\" label=\"只读\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.readonly\" @change=\"changeReadonly(row)\" />\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"hidden\">\n          <template #header>\n            <el-checkbox v-model=\"allHidden\" label=\"隐藏\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.hidden\" @change=\"changeHidden(row)\" />\n          </template>\n        </el-table-column>\n      </el-table>\n    </el-tab-pane>\n  </el-tabs>\n</template>\n\n<style scoped lang=\"scss\">\n@import '@/styles/el-segmented.scss';\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/ConditionPanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ConditionNode } from '../nodes/type'\nimport type { Ref } from 'vue'\nimport type { Field } from '@/components/Render/type'\n\nconst { fields } = inject<{ fields: Ref<Field[]> }>('flowDesign', { fields: ref([]) })\ndefineProps<{\n  activeData: ConditionNode\n}>()\nconst initialFormFields = ref<Field[]>([\n  {\n    id: 'initiator',\n    name: 'UserSelector',\n    type: 'formItem',\n    label: '发起人',\n    value: null,\n    readonly: false,\n    required: true,\n    hidden: false,\n    props: {\n      key: undefined,\n      multiple: false,\n      placeholder: '请选择发起人',\n      class: [],\n      style: {\n        width: '100%'\n      }\n    }\n  }\n])\n</script>\n\n<template>\n  <AdvancedFilter\n    v-model=\"activeData.conditions\"\n    :filter-fields=\"[...initialFormFields, ...fields]\"\n  />\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/EndPanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { EndNode } from '../nodes/type'\nimport ExecutionListeners from './ExecutionListeners.vue'\n\ndefineProps<{\n  activeData: EndNode\n}>()\n</script>\n\n<template>\n  <el-form label-position=\"top\" label-width=\"90px\">\n    <el-form-item prop=\"executionListeners\" label=\"执行监听器\">\n      <ExecutionListeners :node=\"activeData\" />\n    </el-form-item>\n  </el-form>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/ExecutionListeners.vue",
    "content": "<script setup lang=\"ts\">\nimport type { FlowNode } from '@/views/flowDesign/nodes/type'\n\nconst props = defineProps<{\n  node: FlowNode\n}>()\nconst drawer = ref(false)\nconst addListener = () => {\n  if (!props.node.executionListeners) {\n    props.node.executionListeners = []\n  }\n  props.node.executionListeners?.push({\n    event: 'start',\n    implementationType: 'delegateExpression',\n    implementation: ''\n  })\n}\nconst delListener = (index: number) => {\n  props.node.executionListeners?.splice(index, 1)\n}\n</script>\n\n<template>\n  <div>\n    <slot>\n      <el-badge :value=\"node.executionListeners?.length || 0\" class=\"item\" type=\"primary\">\n        <el-button icon=\"Setting\" @click=\"drawer = true\"> 配置</el-button>\n      </el-badge>\n    </slot>\n    <el-drawer v-model=\"drawer\" :lock-scroll=\"false\" title=\"执行监听器\">\n      <div class=\"flex-col\">\n        <el-button @click=\"addListener\" type=\"primary\" icon=\"Plus\">添加监听器</el-button>\n        <div v-for=\"(item, index) in node.executionListeners\" :key=\"index\" class=\"listener-box\">\n          <el-button\n            class=\"listener-close\"\n            @click=\"delListener(index)\"\n            plain\n            circle\n            icon=\"CircleClose\"\n            size=\"small\"\n            type=\"danger\"\n          />\n          <el-form-item label=\"事件\" :prop=\"`executionListeners.${index}.event`\">\n            <el-radio-group v-model=\"item.event\">\n              <el-radio-button label=\"开始\" value=\"start\" />\n              <el-radio-button label=\"结束\" value=\"end\" />\n              <el-radio-button label=\"迁移\" value=\"take\" />\n            </el-radio-group>\n          </el-form-item>\n          <el-form-item label=\"类型\" :prop=\"`executionListeners.${index}.implementationType`\">\n            <el-radio-group v-model=\"item.implementationType\">\n              <el-radio-button label=\"委托表达式\" value=\"delegateExpression\" />\n              <el-radio-button label=\"java类\" value=\"class\" />\n              <el-radio-button label=\"表达式\" value=\"expression\" />\n            </el-radio-group>\n          </el-form-item>\n          <el-form-item label=\"监听器\" :prop=\"`executionListeners.${index}.implementation`\">\n            <template #label>\n              <div class=\"flex-items-center gap3px\">\n                <span>监听器</span>\n                <el-tooltip placement=\"top-start\">\n                  <template #content>\n                    实现 ExecutionListener 接口 <br />\n                    委托表达式：${myExecutionListener} <br />\n                    表达式: ${myExecutionListener.notify(execution)} <br />\n                    java类：${com.example.listener.MyExecutionListener}\n                  </template>\n                  <el-icon>\n                    <QuestionFilled />\n                  </el-icon>\n                </el-tooltip>\n              </div>\n            </template>\n            <el-input v-model=\"item.implementation\" placeholder=\"请输入监听器\" clearable>\n            </el-input>\n          </el-form-item>\n        </div>\n      </div>\n    </el-drawer>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.listener-box {\n  border: 1px dashed var(--el-border-color);\n  border-radius: var(--el-border-radius-base);\n  margin-top: 10px;\n  padding: 7px;\n  position: relative;\n\n  &:hover {\n    border-color: var(--el-color-primary);\n\n    .listener-close {\n      display: block;\n    }\n  }\n\n  .listener-close {\n    position: absolute;\n    top: -7px;\n    right: -7px;\n    z-index: 1;\n    display: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/NotifyPanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { NotifyNode } from '../nodes/type'\nimport type { Ref } from 'vue'\nimport type { Field } from '@/components/Render/type'\nimport AssigneePanel from './AssigneePanel.vue'\n\nconst { fields } = inject<{ fields: Ref<Field[]> }>('flowDesign', { fields: ref([]) })\ndefineProps<{\n  activeData: NotifyNode\n}>()\n</script>\n\n<template>\n  <el-form label-position=\"top\" label-width=\"90px\">\n    <AssigneePanel :active-data=\"activeData\" :fields=\"fields\" type=\"通知\" />\n    <el-form-item prop=\"types\" label=\"通知类型\">\n      <el-checkbox-group v-model=\"activeData.types\">\n        <el-checkbox label=\"站内\" value=\"site\" />\n        <el-checkbox label=\"邮件\" value=\"email\" />\n        <el-checkbox label=\"短信\" value=\"sms\" />\n        <el-checkbox label=\"企业微信\" value=\"wechat\" />\n        <el-checkbox label=\"钉钉\" value=\"dingtalk\" />\n        <el-checkbox label=\"飞书\" value=\"feishu\" />\n      </el-checkbox-group>\n    </el-form-item>\n    <el-form-item prop=\"subject\" label=\"消息主题\">\n      <template #label>\n        <div class=\"flex-items-center gap3px\">\n          <el-tooltip content=\"可以使用 ${字段名} 字段名填充内容\" placement=\"top\">\n            <el-icon>\n              <QuestionFilled />\n            </el-icon>\n          </el-tooltip>\n          <span>消息主题</span>\n        </div>\n      </template>\n      <el-input\n        v-model=\"activeData.subject\"\n        :maxlength=\"255\"\n        clearable\n        placeholder=\"请输入消息主题\"\n      />\n    </el-form-item>\n    <el-form-item prop=\"content\" label=\"消息内容\">\n      <template #label>\n        <div class=\"flex-items-center gap3px\">\n          <el-tooltip content=\"可以使用 ${字段名} 字段名填充内容\" placement=\"top\">\n            <el-icon>\n              <QuestionFilled />\n            </el-icon>\n          </el-tooltip>\n          <span>消息内容</span>\n        </div>\n      </template>\n      <el-input\n        v-model=\"activeData.content\"\n        :autosize=\"{ minRows: 6, maxRows: 8 }\"\n        type=\"textarea\"\n        :maxlength=\"1000\"\n        show-word-limit\n        placeholder=\"请输入消息内容\"\n      >\n      </el-input>\n    </el-form-item>\n  </el-form>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/ServicePanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ServiceNode } from '../nodes/type'\n\ndefineProps<{\n  activeData: ServiceNode\n}>()\n</script>\n\n<template>\n  <el-form label-position=\"top\">\n    <el-form-item prop=\"implementationType\" label=\"执行类型\">\n      <el-select v-model=\"activeData.implementationType\" placeholder=\"请选择执行类型\">\n        <el-option label=\"类\" value=\"class\" />\n        <el-option label=\"表达式\" value=\"expression\" />\n        <el-option label=\"委托表达式\" value=\"delegateExpression\" />\n      </el-select>\n    </el-form-item>\n    <el-form-item prop=\"implementation\" label=\"执行值\">\n      <template #label>\n        <div class=\"flex-items-center gap3px\">\n          <span>执行值</span>\n          <el-tooltip placement=\"top-start\">\n            <template #content>\n              实现 JavaDelegate 接口 <br />\n              类：${com.example.delegate.MyServiceDelegate} <br />\n              表达式: ${myServiceDelegate.execute(execution)} <br />\n              委托表达式：${myServiceDelegate}\n            </template>\n            <el-icon>\n              <QuestionFilled />\n            </el-icon>\n          </el-tooltip>\n        </div>\n      </template>\n      <el-input v-model=\"activeData.implementation\" placeholder=\"请输入执行值\" clearable />\n    </el-form-item>\n  </el-form>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/StartPanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { FormProperty, StartNode } from '../nodes/type'\nimport type { Field } from '@/components/Render/type'\nimport type { Ref } from 'vue'\nimport ExecutionListeners from './ExecutionListeners.vue'\n\nconst { fields } = inject<{ fields: Ref<Field[]> }>('flowDesign', { fields: ref([]) })\nconst props = defineProps<{\n  activeData: StartNode\n}>()\nconst activeName = ref('basicSettings')\nconst allReadonly = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.readonly)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.readonly = val))\n    if (val) {\n      allHidden.value = false\n      allRequired.value = false\n    }\n  }\n})\nconst allHidden = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.hidden)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.hidden = val))\n    if (val) {\n      allRequired.value = false\n      allReadonly.value = false\n    }\n  }\n})\nconst allRequired = computed({\n  get() {\n    return props.activeData.formProperties.every((e) => e.required)\n  },\n  set(val) {\n    props.activeData.formProperties.forEach((e) => (e.required = val))\n    if (val) {\n      allReadonly.value = false\n      allHidden.value = false\n    }\n  }\n})\n\nconst changeReadonly = (row: FormProperty) => {\n  if (row.readonly) {\n    row.required = false\n    row.hidden = false\n  }\n}\nconst changeRequired = (row: FormProperty) => {\n  if (row.required) {\n    row.readonly = false\n    row.hidden = false\n  }\n}\nconst changeHidden = (row: FormProperty) => {\n  if (row.hidden) {\n    row.readonly = false\n    row.required = false\n  }\n}\n\nwatchEffect(() => {\n  const formProperties = props.activeData.formProperties\n  props.activeData.formProperties = fields.value.map((field) => ({\n    id: field.id,\n    name: field.label,\n    readonly: field.readonly || false,\n    hidden: field.hidden,\n    required: field.required || false\n  }))\n  props.activeData.formProperties.forEach((item) => {\n    const properties = formProperties.find((f) => f.id === item.id)\n    if (properties) {\n      item.readonly = properties.readonly\n      item.hidden = properties.hidden\n      item.required = properties.required\n    }\n  })\n})\n</script>\n\n<template>\n  <el-tabs v-model=\"activeName\" stretch class=\"el-segmented\">\n    <el-tab-pane label=\"基础设置\" name=\"basicSettings\">\n      <el-form label-position=\"top\" label-width=\"90px\">\n        <el-form-item prop=\"executionListeners\" label=\"执行监听器\">\n          <ExecutionListeners :node=\"activeData\" />\n        </el-form-item>\n      </el-form>\n    </el-tab-pane>\n    <el-tab-pane label=\"表单权限\" name=\"formPermissions\">\n      <el-table :data=\"activeData.formProperties\">\n        <el-table-column prop=\"name\" label=\"字段\" />\n        <el-table-column prop=\"readonly\">\n          <template #header>\n            <el-checkbox v-model=\"allReadonly\" label=\"只读\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.readonly\" @change=\"changeReadonly(row)\" />\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"required\">\n          <template #header>\n            <el-checkbox v-model=\"allRequired\" label=\"必填\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.required\" @change=\"changeRequired(row)\" />\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"hidden\">\n          <template #header>\n            <el-checkbox v-model=\"allHidden\" label=\"隐藏\" />\n          </template>\n          <template #default=\"{ row }\">\n            <el-checkbox v-model=\"row.hidden\" @change=\"changeHidden(row)\" />\n          </template>\n        </el-table-column>\n      </el-table>\n    </el-tab-pane>\n  </el-tabs>\n</template>\n\n<style scoped lang=\"scss\">\n@import '@/styles/el-segmented.scss';\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/TaskListeners.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ApprovalNode } from '@/views/flowDesign/nodes/type'\n\nconst props = defineProps<{\n  node: ApprovalNode\n}>()\nconst drawer = ref(false)\nconst addListener = () => {\n  if (!props.node.taskListeners) {\n    props.node.taskListeners = []\n  }\n  props.node.taskListeners?.push({\n    event: 'create',\n    implementationType: 'delegateExpression',\n    implementation: ''\n  })\n}\nconst delListener = (index: number) => {\n  props.node.taskListeners?.splice(index, 1)\n}\n</script>\n\n<template>\n  <div>\n    <slot>\n      <el-badge :value=\"node.taskListeners?.length || 0\" class=\"item\" type=\"primary\">\n        <el-button icon=\"Setting\" @click=\"drawer = true\"> 配置</el-button>\n      </el-badge>\n    </slot>\n    <el-drawer v-model=\"drawer\" :lock-scroll=\"false\" title=\"任务监听器\">\n      <div class=\"flex-col\">\n        <el-button @click=\"addListener\" type=\"primary\" icon=\"Plus\">添加监听器</el-button>\n        <div v-for=\"(item, index) in node.taskListeners\" :key=\"index\" class=\"listener-box\">\n          <el-button\n            class=\"listener-close\"\n            @click=\"delListener(index)\"\n            plain\n            circle\n            icon=\"CircleClose\"\n            size=\"small\"\n            type=\"danger\"\n          />\n          <el-form-item label=\"事件\" :prop=\"`taskListeners.${index}.event`\">\n            <el-radio-group v-model=\"item.event\">\n              <el-radio-button label=\"创建\" value=\"create\" />\n              <el-radio-button label=\"指派\" value=\"assignment\" />\n              <el-radio-button label=\"完成\" value=\"complete\" />\n              <el-radio-button label=\"删除\" value=\"delete\" />\n            </el-radio-group>\n          </el-form-item>\n          <el-form-item label=\"类型\" :prop=\"`taskListeners.${index}.implementationType`\">\n            <el-radio-group v-model=\"item.implementationType\">\n              <el-radio-button label=\"委托表达式\" value=\"delegateExpression\" />\n              <el-radio-button label=\"java类\" value=\"class\" />\n              <el-radio-button label=\"表达式\" value=\"expression\" />\n            </el-radio-group>\n          </el-form-item>\n          <el-form-item label=\"监听器\" :prop=\"`taskListeners.${index}.implementation`\">\n            <template #label>\n              <div class=\"flex-items-center gap3px\">\n                <span>监听器</span>\n                <el-tooltip placement=\"top-start\">\n                  <template #content>\n                    委托表达式：${myCreateTaskListener} <br />\n                    表达式: ${myCreateTaskListener.notify(execution)} <br />\n                    java类：${com.example.listener.MyCreateTaskListener}\n                  </template>\n                  <el-icon>\n                    <QuestionFilled />\n                  </el-icon>\n                </el-tooltip>\n              </div>\n            </template>\n            <el-input v-model=\"item.implementation\" placeholder=\"请输入监听器\" clearable>\n            </el-input>\n          </el-form-item>\n        </div>\n      </div>\n    </el-drawer>\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.listener-box {\n  border: 1px dashed var(--el-border-color);\n  border-radius: var(--el-border-radius-base);\n  margin-top: 10px;\n  padding: 7px;\n  position: relative;\n\n  &:hover {\n    border-color: var(--el-color-primary);\n\n    .listener-close {\n      display: block;\n    }\n  }\n\n  .listener-close {\n    position: absolute;\n    top: -7px;\n    right: -7px;\n    z-index: 1;\n    display: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/TimerPanel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TimerNode } from '../nodes/type'\ndefineProps<{\n  activeData: TimerNode\n}>()\n</script>\n\n<template>\n  <el-form label-position=\"top\">\n    <el-form-item prop=\"waitType\" label=\"等待方式\">\n      <el-radio-group v-model=\"activeData.waitType\">\n        <el-radio-button label=\"固定时长\" value=\"duration\" />\n        <el-radio-button label=\"固定时间\" value=\"date\" />\n      </el-radio-group>\n    </el-form-item>\n    <el-form-item prop=\"duration\" label=\"等待时长\" v-if=\"activeData.waitType === 'duration'\">\n      <el-input\n        v-model.number=\"activeData.duration\"\n        :min=\"0\"\n        :max=\"9999999\"\n        type=\"number\"\n        style=\"max-width: 230px\"\n        class=\"input-with-select\"\n      >\n        <template #append>\n          <el-select v-model=\"activeData.unit\" placeholder=\"Select\" style=\"width: 80px\">\n            <el-option label=\"秒\" value=\"PT%sS\"></el-option>\n            <el-option label=\"分钟\" value=\"PT%sM\"></el-option>\n            <el-option label=\"小时\" value=\"PT%sH\"></el-option>\n            <el-option label=\"天\" value=\"P%sD\"></el-option>\n            <el-option label=\"周\" value=\"P%sW\"></el-option>\n            <el-option label=\"月\" value=\"P%sM\"></el-option>\n          </el-select>\n        </template>\n      </el-input>\n    </el-form-item>\n    <el-form-item prop=\"duration\" label=\"指定时间\" v-if=\"activeData.waitType === 'date'\">\n      <el-date-picker\n        v-model=\"activeData.timeDate\"\n        type=\"datetime\"\n        format=\"YYYY-MM-DD HH:mm:ss\"\n        value-format=\"YYYY-MM-DD HH:mm:ss\"\n        placeholder=\"请选择等待时间\"\n      />\n    </el-form-item>\n  </el-form>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "src/views/flowDesign/panels/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ClickOutside as vClickOutside } from 'element-plus'\nimport type { Component } from 'vue'\nimport Start from './StartPanel.vue'\nimport Approval from './ApprovalPanel.vue'\nimport Cc from './CcPanel.vue'\nimport Timer from './TimerPanel.vue'\nimport Notify from './NotifyPanel.vue'\nimport Service from './ServicePanel.vue'\nimport Condition from './ConditionPanel.vue'\nimport End from './EndPanel.vue'\nimport type { FlowNode } from '../nodes/type'\n\ndefineProps<{\n  activeData: FlowNode\n}>()\nconst penalVisible = defineModel<boolean>({ required: true })\nconst panels: Recordable<Component> = {\n  start: Start,\n  approval: Approval,\n  cc: Cc,\n  timer: Timer,\n  notify: Notify,\n  service: Service,\n  condition: Condition,\n  end: End\n}\nconst showInput = ref(false)\nconst onClickOutside = () => {\n  if (showInput.value) {\n    showInput.value = false\n  }\n}\n</script>\n\n<template>\n  <el-drawer v-model=\"penalVisible\" :lock-scroll=\"false\" size=\"35%\">\n    <template #header=\"{ titleId, titleClass }\">\n      <span :id=\"titleId\" :class=\"titleClass\">\n        <el-input\n          v-click-outside=\"onClickOutside\"\n          @blur=\"onClickOutside\"\n          maxlength=\"30\"\n          v-model=\"activeData.name\"\n          v-show=\"showInput\"\n        ></el-input>\n        <el-link icon=\"EditPen\" v-show=\"!showInput\" @click=\"showInput = true\">\n          {{ activeData?.name || '节点配置' }}\n        </el-link>\n      </span>\n    </template>\n    <component :is=\"panels[activeData.type]\" :activeData=\"activeData\" />\n  </el-drawer>\n</template>\n\n<style scoped lang=\"scss\">\n:deep(.el-tabs__content) {\n  margin-top: 10px;\n}\n</style>\n"
  },
  {
    "path": "src/views/home/index.vue",
    "content": "<script setup lang=\"ts\">\nimport FlowDesign from '@/views/flowDesign/index.vue'\nimport type { Field } from '@/components/Render/type'\nimport type { EndNode, FlowNode, StartNode } from '@/views/flowDesign/nodes/type'\nimport { downloadXml } from '@/api/modules/model'\n\n// 流程节点\nconst process = ref<FlowNode>({\n  id: 'root',\n  pid: undefined,\n  type: 'start',\n  name: '发起人',\n  executionListeners: [],\n  formProperties: [],\n  next: {\n    id: 'end',\n    pid: 'root',\n    type: 'end',\n    name: '流程结束',\n    executionListeners: [],\n    next: undefined\n  } as EndNode\n} as StartNode)\n// 表单字段\nconst fields = ref<Field[]>([\n  {\n    id: 'field_da2w55',\n    type: 'formItem',\n    label: '请假人',\n    name: 'UserSelector',\n    value: null,\n    readonly: false,\n    required: true,\n    hidden: false,\n    props: {\n      multiple: false,\n      disabled: false,\n      placeholder: '请选择用户',\n      style: {\n        width: '100%'\n      }\n    }\n  },\n  {\n    id: 'field_fa2w40',\n    type: 'formItem',\n    label: '请假天数',\n    name: 'ElInputNumber',\n    value: null,\n    readonly: false,\n    required: true,\n    hidden: false,\n    props: {\n      disabled: false,\n      placeholder: '请假天数',\n      style: {\n        width: '100%'\n      },\n      min: 0,\n      max: 100,\n      step: 1,\n      precision: 0\n    }\n  },\n  {\n    id: 'field_d42t45',\n    type: 'formItem',\n    label: '请假事由',\n    name: 'ElSelect',\n    value: null,\n    readonly: false,\n    required: true,\n    hidden: false,\n    props: {\n      disabled: false,\n      multiple: false,\n      placeholder: '请选择请假事由',\n      options: [\n        {\n          label: '事假',\n          value: '事假'\n        },\n        {\n          label: '病假',\n          value: '病假'\n        },\n        {\n          label: '婚假',\n          value: '婚假'\n        },\n        {\n          label: '产假',\n          value: '产假'\n        },\n        {\n          label: '丧假',\n          value: '丧假'\n        },\n        {\n          label: '其他',\n          value: '其他'\n        }\n      ],\n      style: {\n        width: '100%'\n      }\n    }\n  },\n  {\n    id: 'field_522g58',\n    type: 'formItem',\n    label: '请假原因',\n    name: 'ElInput',\n    value: null,\n    readonly: false,\n    required: true,\n    hidden: false,\n    props: {\n      type: 'textarea',\n      placeholder: '请输入请假原因',\n      autosize: {\n        minRows: 3,\n        maxRows: 3\n      },\n      disabled: false,\n      style: {\n        width: '100%'\n      }\n    }\n  }\n])\n// 是否只读\nconst readOnly = ref(false)\n// 是否暗黑模式\nconst isDark = ref(false)\nconst converterBpmn = () => {\n  const processModel = {\n    code: 'test',\n    name: '测试',\n    icon: {\n      name: 'el:HomeFilled',\n      color: '#409EFF'\n    },\n    process: process.value,\n    enable: true,\n    version: 1,\n    sort: 0,\n    groupId: '',\n    remark: ''\n  }\n  downloadXml(processModel)\n}\nconst handleToggleDark = () => {\n  if (isDark.value) {\n    document.documentElement.classList.add('dark')\n  } else {\n    document.documentElement.classList.remove('dark')\n  }\n}\nconst gitee = () => {\n  window.open('https://gitee.com/cai_xiao_feng/lowflow-design')\n}\nconst github = () => {\n  window.open('https://github.com/tsai996/lowflow-design')\n}\n</script>\n\n<template>\n  <FlowDesign :process=\"process\" :fields=\"fields\" :readOnly=\"readOnly\">\n    <el-switch\n      inline-prompt\n      active-text=\"正常模式\"\n      inactive-text=\"暗黑模式\"\n      @change=\"handleToggleDark\"\n      v-model=\"isDark\"\n    />\n    <el-switch\n      v-model=\"readOnly\"\n      active-text=\"只读模式\"\n      inactive-text=\"编辑模式\"\n      inline-prompt\n      :active-value=\"true\"\n      :inactive-value=\"false\"\n    />\n    <el-button-group>\n      <el-button @click=\"converterBpmn\" type=\"primary\" icon=\"Download\"> 转bpmn </el-button>\n      <!--开源地址-->\n      <el-dropdown>\n        <el-button type=\"primary\">\n          开源地址\n          <el-icon class=\"el-icon--right\">\n            <arrow-down />\n          </el-icon>\n        </el-button>\n        <template #dropdown>\n          <el-dropdown-menu>\n            <el-dropdown-item @click.stop=\"gitee\">Gitee</el-dropdown-item>\n            <el-dropdown-item @click.stop=\"github\">Github</el-dropdown-item>\n          </el-dropdown-menu>\n        </template>\n      </el-dropdown>\n    </el-button-group>\n  </FlowDesign>\n</template>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "tsconfig.app.json",
    "content": "{\n  \"extends\": \"@vue/tsconfig/tsconfig.dom.json\",\n  \"include\": [\"env.d.ts\", \"src/**/*\", \"src/**/*.vue\"],\n  \"exclude\": [\"src/**/__tests__/*\"],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.node.json\"\n    },\n    {\n      \"path\": \"./tsconfig.app.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"extends\": \"@tsconfig/node20/tsconfig.json\",\n  \"include\": [\n    \"vite.config.*\",\n    \"vitest.config.*\",\n    \"cypress.config.*\",\n    \"nightwatch.conf.*\",\n    \"playwright.config.*\"\n  ],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"noEmit\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"types\": [\"node\"]\n  }\n}\n"
  },
  {
    "path": "unocss.config.ts",
    "content": "import {\n  defineConfig,\n  presetAttributify,\n  presetIcons,\n  presetUno,\n  transformerDirectives,\n  transformerVariantGroup,\n} from 'unocss'\n\nexport default defineConfig({\n  presets: [\n    presetUno({ dark: 'class' }),\n    presetAttributify(),\n    presetIcons({\n      scale: 1.2,\n      warn: true,\n    }),\n  ],\n  // 自定义组合样式\n  shortcuts: {\n    bgc: 'flex red',\n    'flex-col-center': 'flex-center flex-col',\n    'flex-col': 'flex flex-col',\n    'flex-items-center': 'flex items-center',\n    'flex-center': 'flex-items-center justify-center',\n    'flex-between': 'flex-items-center justify-between',\n    'flex-space': 'flex-items-center flex-justify-between',\n    'wh-full': 'w-full h-full',\n  },\n  transformers: [\n    transformerDirectives(),\n    transformerVariantGroup(),\n  ]\n})\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import {fileURLToPath, URL} from 'node:url'\n\nimport {defineConfig} from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport vueJsx from '@vitejs/plugin-vue-jsx'\nimport VueSetupExtend from 'vite-plugin-vue-setup-extend'\nimport Components from 'unplugin-vue-components/vite'\nimport AutoImport from 'unplugin-auto-import/vite'\nimport {viteMockServe} from \"vite-plugin-mock\";\nimport { createSvgIconsPlugin } from 'vite-plugin-svg-icons'\nimport {ElementPlusResolver} from \"unplugin-vue-components/resolvers\";\nimport Unocss from 'unocss/vite'\nimport {resolve} from \"path\";\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n    base: '/lowflow-design',\n    resolve: {\n        alias: {\n            '@': fileURLToPath(new URL('./src', import.meta.url))\n        }\n    },\n    server: {\n        host: '0.0.0.0',\n        port: 3200,\n        open: true,\n        proxy: {\n            '/api': {\n                target: 'http://localhost:8084',\n                changeOrigin: true,\n                ws: true,\n                rewrite: (path) => path.replace(/^\\/api/, ''),\n                secure: false\n            }\n        }\n    },\n    plugins: [\n        vue(),\n        vueJsx(),\n        Unocss(),\n        VueSetupExtend(),\n        viteMockServe({\n            mockPath: './src/mock',\n            localEnabled: true,\n            prodEnabled: true,\n            injectCode: ` import { setupProdMockServer } from './mockProdServer'; setupProdMockServer(); `,\n        }),\n        Components({\n            extensions: ['vue', 'tsx', 'md'],\n            globs: ['src/components/*/*.vue', 'src/components/*/*.tsx'],\n            include: [/\\.vue$/, /\\.vue\\?vue/, /\\.md$/, /\\.[tj]sx?$/],\n            resolvers: [\n                ElementPlusResolver({\n                    importStyle: 'sass',\n                }),\n            ],\n            dts: 'src/typings/components.d.ts'\n        }),\n        AutoImport({\n            imports: ['vue', 'vue-router'],\n            resolvers: [ElementPlusResolver()],\n            dts: 'src/typings/auto-imports.d.ts',\n            eslintrc: {\n                enabled: true,\n                filepath: './.eslintrc-auto-import.json'\n            }\n        }),\n        // svg 图标\n        createSvgIconsPlugin({\n            iconDirs: [resolve(process.cwd(), 'src/assets/icons')],\n            symbolId: 'icon-[dir]-[name]'\n        }),\n    ],\n    build: {\n        rollupOptions: {\n            output: {\n                // 打包分类\n                chunkFileNames: 'assets/js/[name]-[hash].js',\n                entryFileNames: 'assets/js/[name]-[hash].js',\n                assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',\n                // 打包后的文件夹名称生成规则-->解决部分静态服务器无法正常返回_plugin-vue_export-helper文件\n                sanitizeFileName(name) {\n                    const match = /^[a-z]:/i.exec(name)\n                    const driveLetter = match ? match[0] : ''\n                    return (\n                        driveLetter +\n                        // eslint-disable-next-line no-control-regex\n                        name.substring(driveLetter.length).replace(/[\\x00-\\x1F\\x7F<>*#\"{}|^[\\]`;?:&=+$,]/g, '')\n                    )\n                }\n            }\n        }\n    }\n})\n"
  }
]