init: 初始化
This commit is contained in:
commit
332a3fbeac
|
@ -0,0 +1,22 @@
|
||||||
|
NODE_ENV=development
|
||||||
|
# 指定站点名称
|
||||||
|
# 本地开发状态:localhost
|
||||||
|
# 部署时:大多数情况下不需要填写
|
||||||
|
VITE_SITEHOST=localhost
|
||||||
|
# 启动站点API接口地址
|
||||||
|
VITE_SITEHOST_API=http://47.103.207.8:8132
|
||||||
|
# 路由根地址
|
||||||
|
VITE_ROUTER_BASE=/
|
||||||
|
|
||||||
|
# 应用码
|
||||||
|
VITE_APPCODE=hospital
|
||||||
|
VITE_TOOL_ICONS=//at.alicdn.com/t/c/font_4255235_cqom7h3serb.js
|
||||||
|
VITE_MICROLAYOUT_ICONS=//at.alicdn.com/t/c/font_4255235_cqom7h3serb.js
|
||||||
|
|
||||||
|
|
||||||
|
# 是否开启登录测试
|
||||||
|
VITE_LOGIN_TEST=false
|
||||||
|
# 登录测试账号
|
||||||
|
VITE_LOGIN_TEST_USERNAME=admin
|
||||||
|
# 登录测试账号密码
|
||||||
|
VITE_LOGIN_TEST_PASSWORD="234@#$"
|
|
@ -0,0 +1,5 @@
|
||||||
|
# 路由根地址
|
||||||
|
VITE_ROUTER_BASE=/
|
||||||
|
|
||||||
|
# 应用码
|
||||||
|
VITE_APPCODE=hospital
|
|
@ -0,0 +1,23 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
},
|
||||||
|
plugins: ['vue', '@typescript-eslint', 'prettier'],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'error',
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,12 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
source
|
||||||
|
|
||||||
|
pnpm-lock.yaml
|
||||||
|
visualizer.html
|
||||||
|
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
yaml-tempfile
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
**/.DS_Store
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"tabWidth": 3,
|
||||||
|
"useTabs": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"printWidth": 120,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"vueIndentScriptAndStyle": false
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
// 控制相关文件嵌套展示
|
||||||
|
"explorer.fileNesting.enabled": true,
|
||||||
|
"explorer.fileNesting.expand": false,
|
||||||
|
"explorer.fileNesting.patterns": {
|
||||||
|
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
|
||||||
|
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
|
||||||
|
"package.json": "index.html,pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitignore,.gitattributes,.gitpod.yml,CNAME,README*,.npmrc,.browserslistrc,.env.*,.eslintrc.js,.prettierrc,env.d.ts",
|
||||||
|
"vite.config.ts": "tsconfig.*.json,postcss.config.ts,tailwind.config.ts,tsconfig.json"
|
||||||
|
},
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "always"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
# 502404_ManageDb
|
||||||
|
|
||||||
|
### 系统管理平台
|
||||||
|
|
||||||
|
基础后台管理,无组织管理,包含功能:
|
||||||
|
|
||||||
|
#### 基础配置
|
||||||
|
- 1、启动配置
|
||||||
|
- 2、类别管理
|
||||||
|
- 3、行政管理
|
||||||
|
|
||||||
|
#### 客户管理
|
||||||
|
- 4、项目管理
|
||||||
|
- 5、用户管理
|
||||||
|
- 6、用户组管理 TODO
|
||||||
|
- 7、权限管理(基于用户组) TODO
|
||||||
|
|
||||||
|
#### 公共管理
|
||||||
|
- 9、任务管理
|
||||||
|
- 10、任务日志
|
||||||
|
- 11、天气查询
|
||||||
|
- 12、登录日志
|
||||||
|
|
||||||
|
|
||||||
|
### 安装使用步骤
|
||||||
|
|
||||||
|
- **Install:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Run:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Build:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
- **preview:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
pnpm preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件资源目录 📚
|
||||||
|
|
||||||
|
```text
|
||||||
|
502402_BasicManage
|
||||||
|
├─ .vscode # VSCode 推荐配置
|
||||||
|
├─ mock # Mock模拟数据
|
||||||
|
├─ public # 静态资源文件
|
||||||
|
│ ├─ assets # 静态资源文件
|
||||||
|
├─ src
|
||||||
|
│ ├─ api # API 接口管理
|
||||||
|
│ │ └─ typings # 接口 ts 声明
|
||||||
|
│ ├─ assets # 静态资源文件
|
||||||
|
│ │ └─ styles # 全局样式文件
|
||||||
|
│ ├─ const # 全局 常量 声明(颜色/长宽)
|
||||||
|
│ ├─ components # 全局组件
|
||||||
|
│ ├─ router # 路由管理
|
||||||
|
│ ├─ stores # pinia store
|
||||||
|
│ ├─ utils # 常用工具库
|
||||||
|
│ ├─ views # 项目所有页面
|
||||||
|
│ ├─ App.vue # 项目主组件
|
||||||
|
│ ├─ main.ts # 项目入口文件
|
||||||
|
│ └─ vite-env.d.ts # 指定 ts 识别 vue
|
||||||
|
├─ .env # vite 常用配置
|
||||||
|
├─ .env.development # 开发环境配置
|
||||||
|
├─ .env.production # 生产环境配置
|
||||||
|
├─ .env.test # 测试环境配置
|
||||||
|
├─ .prettierrc # vs代码规范文件
|
||||||
|
├─ index.html # 入口 html
|
||||||
|
├─ pnpm-lock.yaml # 依赖包包版本锁
|
||||||
|
├─ package.json # 依赖包管理
|
||||||
|
├─ README.md # README 介绍
|
||||||
|
├─ tsconfig.json # typescript 全局配置
|
||||||
|
└─ vite.config.ts # vite 全局配置文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 开发环境 🔨
|
||||||
|
|
||||||
|
- 使用 Vue3.3 + TypeScript + Vite4 + Pinia + VueRouter开发
|
||||||
|
- UI组件: ant-design-vue
|
||||||
|
- CSS预处理器: sass
|
||||||
|
- vite插件: axios
|
||||||
|
- Node环境: v18.16.0
|
||||||
|
|
||||||
|
### Git commit ⻛格指南
|
||||||
|
|
||||||
|
- feat: 增加新功能
|
||||||
|
- fix: 修复问题
|
||||||
|
- style: 代码⻛格相关⽆影响运⾏结果的
|
||||||
|
- perf: 优化/性能提升
|
||||||
|
- refactor: 重构
|
||||||
|
- revert: 撤销修改
|
||||||
|
- test: 测试相关
|
||||||
|
- docs: ⽂档/注释
|
||||||
|
- chore: 依赖更新/脚⼿架配置修改等
|
||||||
|
- ci: 持续集成
|
|
@ -0,0 +1,11 @@
|
||||||
|
declare module '*.vue' {
|
||||||
|
import { ComponentOptions } from 'vue';
|
||||||
|
const componentOptions: ComponentOptions;
|
||||||
|
export default componentOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMetaEnv {}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="null">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>后台管理系统</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"name": "basic-manager",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "main.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host",
|
||||||
|
"gen": "yaml-tempfile",
|
||||||
|
"build": "vue-tsc -b && vite build --mode production",
|
||||||
|
"build:test": "vue-tsc && vite build --mode test",
|
||||||
|
"build:pro": "vue-tsc && vite build --mode production",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ace-builds": "^1.37.1",
|
||||||
|
"ant-design-vue": "^4.2.6",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"pinia": "^2.3.0",
|
||||||
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-draggable-next": "^2.2.1",
|
||||||
|
"vue-m-message": "^4.0.2",
|
||||||
|
"vue-router": "^4.5.0",
|
||||||
|
"vue3-ace-editor": "^2.2.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@skyfox2000/vite-plugin-auto-import-dts": "^1.0.3",
|
||||||
|
"@types/mockjs": "1.0.7",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"esbuild": "^0.19.12",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"rollup": "^4.29.1",
|
||||||
|
"rollup-plugin-gzip": "^4.0.1",
|
||||||
|
"rollup-plugin-visualizer": "^5.13.1",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.7",
|
||||||
|
"vite-plugin-mock": "3.0.1",
|
||||||
|
"vite-svg-loader": "^5.1.0",
|
||||||
|
"vue-tsc": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { Plugin } from 'vite';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首字母大写的工具函数
|
||||||
|
*/
|
||||||
|
function capitalize(str: string): string {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归扫描目录中的 .vue 文件并生成导出语句
|
||||||
|
*/
|
||||||
|
function scanDir(dir: string, baseDir: string): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
const files = fs.readdirSync(dir);
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
const filePath = path.join(dir, file);
|
||||||
|
const stat = fs.statSync(filePath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
// 递归扫描子目录
|
||||||
|
result.push(...scanDir(filePath, baseDir));
|
||||||
|
} else if (file.endsWith('.vue')) {
|
||||||
|
const relativePath = path.relative(baseDir, filePath);
|
||||||
|
const componentName = generateComponentName(filePath, baseDir);
|
||||||
|
result.push(
|
||||||
|
`export { default as ${componentName} } from './${relativePath.replace(
|
||||||
|
/\\/g,
|
||||||
|
'/',
|
||||||
|
)}';`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件路径生成组件名
|
||||||
|
*/
|
||||||
|
function generateComponentName(filePath: string, baseDir: string): string {
|
||||||
|
const dirName = path.basename(path.dirname(filePath));
|
||||||
|
const fileName = path.basename(filePath, '.vue');
|
||||||
|
|
||||||
|
if (fileName === 'index') {
|
||||||
|
return capitalize(dirName);
|
||||||
|
} else {
|
||||||
|
return capitalize(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件主逻辑
|
||||||
|
*/
|
||||||
|
export default function AutoGenerateVue(options: {
|
||||||
|
dir: string;
|
||||||
|
output: string;
|
||||||
|
}): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-auto-generate-vue',
|
||||||
|
config() {
|
||||||
|
const { dir, output } = options;
|
||||||
|
|
||||||
|
const componentsDir = path.resolve(process.cwd(), dir);
|
||||||
|
const outputPath = path.resolve(process.cwd(), output);
|
||||||
|
|
||||||
|
if (!fs.existsSync(componentsDir)) {
|
||||||
|
throw new Error(
|
||||||
|
`The specified directory "${componentsDir}" does not exist.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportStatements = scanDir(componentsDir, componentsDir).join('\n');
|
||||||
|
|
||||||
|
// 写入到目标文件
|
||||||
|
fs.writeFileSync(outputPath, exportStatements, 'utf-8');
|
||||||
|
console.log(`[autoGenerateVue] Global components written to ${output}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,5 @@
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<router-view></router-view>
|
||||||
|
</template>
|
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
|
@ -0,0 +1,23 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 按钮组件
|
||||||
|
* @component
|
||||||
|
* @name Button
|
||||||
|
* @summary 提供一个可点击的图标按钮,支持自动切换图标,发送点击事件等功能。
|
||||||
|
*/
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
import Tooltip from '../tooltip/index.vue';
|
||||||
|
import LayoutIcon from '../icon/layoutIcon.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* 提示标题
|
||||||
|
* @props
|
||||||
|
* @name content
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
tiptext: String,
|
||||||
|
/**
|
||||||
|
* 提示显示位置
|
||||||
|
* @props
|
||||||
|
* @name placement
|
||||||
|
* @type {string}
|
||||||
|
* @default 'top'
|
||||||
|
*/
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 内置图标属性设置
|
||||||
|
* @props
|
||||||
|
* @name iconProps
|
||||||
|
* @type {object}
|
||||||
|
* @summary 内置图标复杂属性设置,仅支持前置图标
|
||||||
|
*/
|
||||||
|
iconProps: Object,
|
||||||
|
/**
|
||||||
|
* 默认使用框架图标
|
||||||
|
* 其它图标请自定义
|
||||||
|
* @props
|
||||||
|
* @name icon
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
icon: String,
|
||||||
|
// /**
|
||||||
|
// * 点击事件
|
||||||
|
// * @props
|
||||||
|
// * @name clickEvent
|
||||||
|
// * @summary 格式 "空间名#事件名",空间名和事件名用#分隔,事件名用.分隔
|
||||||
|
// * @type {string}
|
||||||
|
// */
|
||||||
|
// clickEvent: {
|
||||||
|
// type: String
|
||||||
|
// },
|
||||||
|
// /**
|
||||||
|
// * 点击传输数据
|
||||||
|
// * @props
|
||||||
|
// * @name data
|
||||||
|
// * @summary 点击事件传输的默认数据
|
||||||
|
// * @type {object|string}
|
||||||
|
// */
|
||||||
|
// data: {
|
||||||
|
// type: [Object, String]
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义组件发出的事件
|
||||||
|
const emits = defineEmits([
|
||||||
|
/**
|
||||||
|
* 点击事件
|
||||||
|
* @emits
|
||||||
|
* @name click
|
||||||
|
* @summary 图标按钮点击时触发的事件
|
||||||
|
*/
|
||||||
|
'click',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onClicked = () => {
|
||||||
|
// if (props.clickEvent) {
|
||||||
|
// const eventNames = props.clickEvent.split("#");
|
||||||
|
// if (eventNames.length === 2) {
|
||||||
|
// const $Bus = inject("$" + eventNames[0]) as any;
|
||||||
|
// $Bus.$emit(eventNames[1], props.data);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (props.iconProps && props.iconProps.icons) {
|
||||||
|
props.iconProps.iconIndex = (props.iconProps.iconIndex + 1) % props.iconProps.icons.length;
|
||||||
|
}
|
||||||
|
emits('click');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Tooltip :title="props.tiptext" :disabled="props.tiptext ? undefined : 'disabled'" :placement="placement">
|
||||||
|
<Button class="px-[10px] py-[4px] flex items-center gap-1" v-bind="$attrs" @click="onClicked">
|
||||||
|
<template #icon>
|
||||||
|
<slot name="icon">
|
||||||
|
<LayoutIcon
|
||||||
|
:icon="props.icon"
|
||||||
|
:class="[$attrs.type === 'primary' ? 'ant-btn-primary' : '', 'cursor-pointer w-[17px] h-[17px] mx-auto']"
|
||||||
|
v-bind="props.iconProps"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</template>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { SERVER_HOST } from '@skyfox2000/fapi';
|
||||||
|
import { createFromIconfont } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用图标加载
|
||||||
|
* @returns 应用图标集
|
||||||
|
*/
|
||||||
|
const AppIcon = () => {
|
||||||
|
return createFromIconfont({
|
||||||
|
iconUrl: `${SERVER_HOST.APP_ICONS}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载全部应用图标
|
||||||
|
*/
|
||||||
|
const AppIcons = AppIcon()
|
||||||
|
defineProps({
|
||||||
|
icon: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<AppIcons v-if="icon" :icon="icon" :class="['w-6', 'h-6', 'text-2xl', 'align-middle', 'mx-auto']" v-bind="$attrs"/>
|
||||||
|
</template>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onFullscreenClick, useSettingInfo } from '@skyfox2000/webbase';
|
||||||
|
import LayoutIcon from './layoutIcon.vue';
|
||||||
|
const settingStore = useSettingInfo();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutIcon
|
||||||
|
@click.stop="onFullscreenClick"
|
||||||
|
:icon="settingStore.fullscreen ? 'icon-exitscreen' : 'icon-fullscreen'"
|
||||||
|
class="w-[17px] h-[17px]"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Popover } from 'ant-design-vue';
|
||||||
|
import LayoutIcon from './layoutIcon.vue';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
text?: string,
|
||||||
|
maxWidth?: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Popover placement="topRight" v-bind="$attrs">
|
||||||
|
<template #content>
|
||||||
|
<slot>
|
||||||
|
<div class="text-[14px]" :style="{ width: maxWidth ? maxWidth : '250px' }">
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<span class="ml-2">
|
||||||
|
<LayoutIcon icon="icon-question-circle" class="text-[#888]" v-bind="$attrs"/>
|
||||||
|
</span>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
|
@ -0,0 +1,421 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 图标组件
|
||||||
|
* @component
|
||||||
|
* @name Icon
|
||||||
|
* @summary 提供一个可点击的图标按钮,支持自动切换图标,发送点击事件等功能。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ref, computed, inject, watch } from 'vue';
|
||||||
|
import Tooltip from '../tooltip/index.vue';
|
||||||
|
import { getIconTransform } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* 点击后自动切换
|
||||||
|
* @props
|
||||||
|
* @name autoSwitch
|
||||||
|
* @type {boolean}
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
autoSwitch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 提示标题
|
||||||
|
* @props
|
||||||
|
* @name tiptext
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
tiptext: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 提示尺寸
|
||||||
|
* @props
|
||||||
|
* @name tipsize
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
tipsize: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 提示背景色
|
||||||
|
* @props
|
||||||
|
* @name tipcolor
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
tipcolor: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 提示显示位置
|
||||||
|
* @props
|
||||||
|
* @name placement
|
||||||
|
* @type {string}
|
||||||
|
* @default 'top'
|
||||||
|
*/
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* IconFont图标
|
||||||
|
* @props
|
||||||
|
* @name icon
|
||||||
|
* @type {string}
|
||||||
|
* @summary IconFont图标显示,使用sym-开头的使用svg模式显示
|
||||||
|
*/
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* IconFont图标组
|
||||||
|
* @props
|
||||||
|
* @name icons
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @summary IconFont图标组显示,使用sym-开头的使用svg模式显示
|
||||||
|
* @default []
|
||||||
|
*/
|
||||||
|
icons: {
|
||||||
|
type: Array<string>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 图标索引
|
||||||
|
* @props
|
||||||
|
* @name iconIndex
|
||||||
|
* @type {number}
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
iconIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 可点击,鼠标手
|
||||||
|
* @props
|
||||||
|
* @name clickable
|
||||||
|
* @type {boolean}
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
clickable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 点击事件
|
||||||
|
* @props
|
||||||
|
* @name clickEvent
|
||||||
|
* @summary 格式 "空间名#事件名",空间名和事件名用#分隔,事件名用.分隔
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
clickEvent: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 点击传输数据
|
||||||
|
* @props
|
||||||
|
* @name data
|
||||||
|
* @summary 点击事件传输的默认数据
|
||||||
|
* @type {object|string}
|
||||||
|
*/
|
||||||
|
data: {
|
||||||
|
type: [Object, String],
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 字体大小
|
||||||
|
* @props
|
||||||
|
* @name size
|
||||||
|
* @summary 字体大小
|
||||||
|
* @type {string}
|
||||||
|
* @default 20px
|
||||||
|
*/
|
||||||
|
fontsize: {
|
||||||
|
type: String, // 大小
|
||||||
|
default: '20px',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 空间大小
|
||||||
|
* @props
|
||||||
|
* @name size
|
||||||
|
* @summary 空间大小
|
||||||
|
* @type {string, [string, string]}
|
||||||
|
* @default 20px
|
||||||
|
*/
|
||||||
|
size: {
|
||||||
|
type: [String, Array<String>], // 大小
|
||||||
|
default: () => ['20px', '20px'],
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 图标位置
|
||||||
|
* @props
|
||||||
|
* @name position
|
||||||
|
* @summary 图标位置
|
||||||
|
* @type {[string|number, string|number]}
|
||||||
|
*/
|
||||||
|
position: {
|
||||||
|
type: Array<string | number>, // 图标位置
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 旋转中心
|
||||||
|
* @props
|
||||||
|
* @name center
|
||||||
|
* @summary 旋转中心
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
center: {
|
||||||
|
type: String, // 旋转中心
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 指定角度
|
||||||
|
* @props
|
||||||
|
* @name angle
|
||||||
|
* @summary 指定角度
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
angle: {
|
||||||
|
type: Number, // 指定角度
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 颜色
|
||||||
|
* @props
|
||||||
|
* @name color
|
||||||
|
* @summary 颜色
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
color: {
|
||||||
|
type: String, // 颜色
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 样式
|
||||||
|
* @props
|
||||||
|
* @name hovercolor
|
||||||
|
* @summary 样式
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
className: {
|
||||||
|
type: String, // 样式
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 水平翻转
|
||||||
|
* @props
|
||||||
|
* @name flip
|
||||||
|
* @summary 水平翻转
|
||||||
|
* @type {boolean}
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
flip: {
|
||||||
|
type: Boolean, // 水平翻转
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 自动旋转
|
||||||
|
* @props
|
||||||
|
* @name spin
|
||||||
|
* @summary 自动旋转
|
||||||
|
* @type {boolean}
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
spin: {
|
||||||
|
type: Boolean, // 自动旋转
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义组件发出的事件
|
||||||
|
const emits = defineEmits([
|
||||||
|
/**
|
||||||
|
* 点击事件
|
||||||
|
* @emits
|
||||||
|
* @name click
|
||||||
|
* @summary 图标按钮点击时触发的事件
|
||||||
|
*/
|
||||||
|
'click',
|
||||||
|
/**
|
||||||
|
* 图标索引更新
|
||||||
|
* @emits
|
||||||
|
* @name update:iconIndex
|
||||||
|
* @summary 图标索引发生变化时触发的事件
|
||||||
|
* @param {number} index - 新的图标索引
|
||||||
|
*/
|
||||||
|
'update:iconIndex',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const boxSize = computed(() => {
|
||||||
|
if (Array.isArray(props.size)) return props.size;
|
||||||
|
else {
|
||||||
|
let size = props.size;
|
||||||
|
return [size, size];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const curIcon = ref(props.icon);
|
||||||
|
const curIndex = ref(props.iconIndex);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.icon,
|
||||||
|
(newVal) => {
|
||||||
|
curIcon.value = newVal;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.iconIndex,
|
||||||
|
(newVal) => {
|
||||||
|
curIndex.value = newVal;
|
||||||
|
curIcon.value = props.icons[newVal];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const iconIndex = computed<number>({
|
||||||
|
get() {
|
||||||
|
return curIndex.value;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
curIndex.value = val;
|
||||||
|
emits('update:iconIndex', curIndex.value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (props.icons.length > 0) {
|
||||||
|
iconIndex.value = iconIndex.value >= props.icons.length ? 0 : iconIndex.value;
|
||||||
|
curIcon.value = props.icons[curIndex.value];
|
||||||
|
} else {
|
||||||
|
curIcon.value = props.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClicked = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (props.clickEvent) {
|
||||||
|
const eventNames = props.clickEvent.split('#');
|
||||||
|
if (eventNames.length === 2) {
|
||||||
|
const $Bus = inject('$' + eventNames[0]) as any;
|
||||||
|
$Bus.$emit(eventNames[1], props.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (props.autoSwitch && props.icons.length > 0) {
|
||||||
|
iconIndex.value = (iconIndex.value + 1) % props.icons.length;
|
||||||
|
curIcon.value = props.icons[curIndex.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
emits('click');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIconClass = (): string => {
|
||||||
|
let className = '';
|
||||||
|
if (props.spin) {
|
||||||
|
className += 'rotate';
|
||||||
|
}
|
||||||
|
if (props.flip) {
|
||||||
|
className += ' flip';
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Tooltip
|
||||||
|
:title="props.tiptext"
|
||||||
|
:disabled="props.tiptext ? undefined : 'disabled'"
|
||||||
|
:color="tipcolor"
|
||||||
|
:placement="placement"
|
||||||
|
:size="tipsize"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="re-icon-container"
|
||||||
|
v-if="curIcon"
|
||||||
|
:style="{
|
||||||
|
width: boxSize[0].toString(),
|
||||||
|
height: boxSize[1].toString(),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!--
|
||||||
|
使用动态 class 绑定,通过 icon-class 控制图标样式,curIcon 是计算属性,
|
||||||
|
可以根据 iconIndex 控制图标显示,点击图标时执行 onClicked 方法
|
||||||
|
-->
|
||||||
|
<i
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-if="!curIcon?.startsWith('sym-')"
|
||||||
|
class="re-icon iconfont fontclass"
|
||||||
|
:class="[props.clickable ? 'clickable' : '', 'icon-' + curIcon, getIconClass(), props.className]"
|
||||||
|
:style="{
|
||||||
|
top: props.position ? props.position[1] : 1,
|
||||||
|
left: props.position ? props.position[0] : 0,
|
||||||
|
fontSize: props.fontsize,
|
||||||
|
transformOrigin: props.center ?? 'center center',
|
||||||
|
transform: getIconTransform(props.angle, props.flip),
|
||||||
|
color: props.color,
|
||||||
|
}"
|
||||||
|
aria-hidden="true"
|
||||||
|
@click="onClicked"
|
||||||
|
></i>
|
||||||
|
<svg
|
||||||
|
v-else
|
||||||
|
v-bind="$attrs"
|
||||||
|
class="re-icon symbol"
|
||||||
|
aria-hidden="true"
|
||||||
|
:class="[props.clickable ? 'clickable' : '', getIconClass(), props.className]"
|
||||||
|
:style="{
|
||||||
|
top: props.position ? props.position[1] : 0,
|
||||||
|
left: props.position ? props.position[0] : 0,
|
||||||
|
fontSize: props.fontsize,
|
||||||
|
transformOrigin: props.center ?? 'center center',
|
||||||
|
transform: getIconTransform(),
|
||||||
|
color: props.color,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<use :xlink:href="'#icon-' + curIcon.replace('sym-', '')"></use>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.re-icon-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
// margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.re-icon.symbol {
|
||||||
|
position: relative;
|
||||||
|
margin: 5px 0;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.re-icon.fontclass {
|
||||||
|
position: relative;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-stroke-width: 0.2px;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.re-icon[disabled] {
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rotate {
|
||||||
|
animation: rotate 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { SERVER_HOST } from '@skyfox2000/fapi';
|
||||||
|
import { createFromIconfont } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基座图标加载
|
||||||
|
* @returns 基座图标集
|
||||||
|
*/
|
||||||
|
const LayoutIcon = () => {
|
||||||
|
return createFromIconfont({
|
||||||
|
iconUrl: `${SERVER_HOST.MICROLAYOUT_ICONS}`,
|
||||||
|
monoColor: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基座图标集
|
||||||
|
*/
|
||||||
|
const LayoutIcons = LayoutIcon();
|
||||||
|
defineProps({
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
type: Array<string>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<LayoutIcons v-if="icon || icons" :icon="icon" :icons="icons" v-bind="$attrs" />
|
||||||
|
</template>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { SERVER_HOST } from '@skyfox2000/fapi';
|
||||||
|
import { createFromIconfont } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格工具图标加载
|
||||||
|
* @returns 表格工具图标加载
|
||||||
|
*/
|
||||||
|
const ToolIcon = () => {
|
||||||
|
return createFromIconfont({
|
||||||
|
iconUrl: `${SERVER_HOST.TOOL_ICONS}`,
|
||||||
|
monoColor: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格工具图标集
|
||||||
|
*/
|
||||||
|
const ToolIcons = ToolIcon();
|
||||||
|
defineProps({
|
||||||
|
icon: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
type: Array<string>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ToolIcons v-if="icon || icons" :icon="icon" :icons="icons" v-bind="$attrs" />
|
||||||
|
</template>
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Tooltip from './tooltip/index.vue';
|
||||||
|
export { Tooltip };
|
||||||
|
import Icon from './icon/index.vue';
|
||||||
|
export { Icon };
|
||||||
|
import Fullscreen from './icon/fullscreen.vue';
|
||||||
|
export { Fullscreen };
|
||||||
|
import Helper from './icon/helper.vue';
|
||||||
|
export { Helper };
|
||||||
|
import ToolIcon from './icon/toolIcon.vue';
|
||||||
|
export { ToolIcon };
|
||||||
|
import Button from './button/index.vue';
|
||||||
|
export { Button };
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CSSProperties } from 'vue';
|
||||||
|
import { Tooltip } from 'ant-design-vue';
|
||||||
|
const props = defineProps<{
|
||||||
|
size?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const overlayStyle: CSSProperties = {
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlayInnerStyle: CSSProperties = {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.size === 'small') {
|
||||||
|
overlayInnerStyle.fontSize = '12px';
|
||||||
|
overlayInnerStyle.padding = '4px 6px';
|
||||||
|
overlayInnerStyle.minHeight = '26px';
|
||||||
|
overlayStyle.height = '30px';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Tooltip :overlayInnerStyle="overlayInnerStyle" :overlayStyle="overlayStyle">
|
||||||
|
<slot />
|
||||||
|
</Tooltip>
|
||||||
|
</template>
|
|
@ -0,0 +1,119 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watch, ref, onMounted } from 'vue';
|
||||||
|
import { Button } from '../../common';
|
||||||
|
import { Modal, Space } from 'ant-design-vue';
|
||||||
|
import { PageData, onFormSave, onFormSaveAs, onFormClose } from '@skyfox2000/webbase';
|
||||||
|
import { AnyData } from '@skyfox2000/fapi';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 确认按钮文字,空字符串则不显示
|
||||||
|
*/
|
||||||
|
saveText?: string;
|
||||||
|
/**
|
||||||
|
* 另存为按钮文字,空字符串则不显示
|
||||||
|
*/
|
||||||
|
saveAsText?: string;
|
||||||
|
/**
|
||||||
|
* 取消按钮文字,空字符则不显示
|
||||||
|
*/
|
||||||
|
cancelText?: string;
|
||||||
|
/**
|
||||||
|
* 保存数据请求配置
|
||||||
|
*/
|
||||||
|
pageData?: PageData<AnyData>;
|
||||||
|
/**
|
||||||
|
* 全屏模式
|
||||||
|
*/
|
||||||
|
full?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const editorData = props.pageData?.editor!;
|
||||||
|
const open = ref<boolean>(false);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => editorData.visible,
|
||||||
|
(newVal) => {
|
||||||
|
open.value = newVal;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
open.value = editorData.visible;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogSave = () => {
|
||||||
|
if (props.pageData) onFormSave(props.pageData);
|
||||||
|
};
|
||||||
|
const dialogSaveAs = () => {
|
||||||
|
if (props.pageData) onFormSaveAs(props.pageData);
|
||||||
|
};
|
||||||
|
const dialogClose = () => {
|
||||||
|
if (props.pageData) onFormClose(props.pageData);
|
||||||
|
else open.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
v-model:open="open"
|
||||||
|
:wrapClassName="
|
||||||
|
'modal mx-auto ' +
|
||||||
|
($attrs.width ? 'w-[' + $attrs.width + ']' : 'w-[430px]') +
|
||||||
|
' ' +
|
||||||
|
(full ? 'full-modal w-full' : '')
|
||||||
|
"
|
||||||
|
@close="dialogClose"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
<template #footer>
|
||||||
|
<Space>
|
||||||
|
<Button @click="dialogClose" v-if="cancelText !== ''">
|
||||||
|
{{ cancelText ?? '取消' }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
@click="dialogSaveAs"
|
||||||
|
v-if="saveAsText !== '' && editorData.saveAsBtnVisible !== false"
|
||||||
|
type="primary"
|
||||||
|
:loading="editorData.isFormSaving"
|
||||||
|
>
|
||||||
|
{{ saveAsText ?? '另存为' }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
@click="dialogSave"
|
||||||
|
v-if="saveText !== '' && editorData.saveBtnVisible !== false"
|
||||||
|
type="primary"
|
||||||
|
:loading="editorData.isFormSaving"
|
||||||
|
>
|
||||||
|
{{ saveText ?? '保存' }}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-modal {
|
||||||
|
.ant-modal {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100%;
|
||||||
|
top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,144 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watch, ref, onMounted } from 'vue';
|
||||||
|
import { Button } from '../../common';
|
||||||
|
import { Modal, Space } from 'ant-design-vue';
|
||||||
|
import { AsyncUploader, EditorData, PageData } from '@skyfox2000/webbase';
|
||||||
|
import { AnyData, IUrlInfo } from '@skyfox2000/fapi';
|
||||||
|
import UploadFileList from './uploadList.vue';
|
||||||
|
import { UploadFile } from '@skyfox2000/webbase';
|
||||||
|
import message from 'vue-m-message';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 文件后缀限制
|
||||||
|
*/
|
||||||
|
fileExt?: string[];
|
||||||
|
/**
|
||||||
|
* 保存数据请求配置
|
||||||
|
*/
|
||||||
|
pageData: PageData<AnyData>;
|
||||||
|
/**
|
||||||
|
* 最大文件数
|
||||||
|
* 默认1个
|
||||||
|
*/
|
||||||
|
maxCount?: number;
|
||||||
|
/**
|
||||||
|
* 并发上传文件数
|
||||||
|
* 默认3个
|
||||||
|
*/
|
||||||
|
maxConcurrent?: number;
|
||||||
|
/**
|
||||||
|
* 控制弹窗
|
||||||
|
*/
|
||||||
|
uploadForm: EditorData<any>;
|
||||||
|
/**
|
||||||
|
* 上传地址和参数
|
||||||
|
*/
|
||||||
|
url?: IUrlInfo;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const uploaderForm = ref(props.uploadForm);
|
||||||
|
const open = ref<boolean>(false);
|
||||||
|
|
||||||
|
const maxCount = props.maxCount ?? 1;
|
||||||
|
const maxConcurrent = props.maxConcurrent ?? 3;
|
||||||
|
|
||||||
|
const fileList = ref<UploadFile[]>([]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => uploaderForm.value.visible,
|
||||||
|
() => {
|
||||||
|
open.value = uploaderForm.value.visible;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const dialogUpload = async () => {
|
||||||
|
const url = props.url ?? props.pageData?.urls.upload;
|
||||||
|
if (!url) {
|
||||||
|
message.error('未配置文件上传地址!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url.api) url.api = props.pageData.api;
|
||||||
|
if (url.authorize === undefined) url.authorize = props.pageData.authorize;
|
||||||
|
|
||||||
|
const uploader = new AsyncUploader(url, maxConcurrent);
|
||||||
|
|
||||||
|
uploaderForm.value.isFormLoading = true;
|
||||||
|
try {
|
||||||
|
if (fileList.value.length === 0) {
|
||||||
|
message.warning('请选择上传的文件!');
|
||||||
|
setTimeout(() => {
|
||||||
|
uploaderForm.value.isFormLoading = false;
|
||||||
|
}, 10000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始上传文件
|
||||||
|
await uploader.upload(
|
||||||
|
fileList.value,
|
||||||
|
(file) => {
|
||||||
|
console.log(`${file.name} 上传进度:${file.percent}% (${file.status})`);
|
||||||
|
},
|
||||||
|
(files) => {
|
||||||
|
uploaderForm.value.isFormLoading = false;
|
||||||
|
console.log('所有文件上传结束:', files);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
uploaderForm.value.isFormLoading = false;
|
||||||
|
console.error('上传错误:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
open.value = uploaderForm.value.visible;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogClose = () => {
|
||||||
|
uploaderForm.value.visible = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
title="文件上传"
|
||||||
|
v-model:open="open"
|
||||||
|
:wrapClassName="'modal mx-auto ' + ($attrs.width ? 'w-[' + $attrs.width + ']' : 'w-[430px]')"
|
||||||
|
@close="dialogClose"
|
||||||
|
>
|
||||||
|
<UploadFileList v-model:file-list="fileList" :max-count="maxCount" :file-ext="fileExt" />
|
||||||
|
<template #footer>
|
||||||
|
<Space>
|
||||||
|
<Button @click="dialogClose">取消</Button>
|
||||||
|
<Button @click="dialogUpload" type="primary" :loading="uploaderForm.isFormSaving"> 上传文件 </Button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-modal {
|
||||||
|
.ant-modal {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100%;
|
||||||
|
top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/components';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import message from 'vue-m-message';
|
||||||
|
import type { UploadProps } from 'ant-design-vue';
|
||||||
|
import { Upload, Progress, Tag } from 'ant-design-vue';
|
||||||
|
import { UploadFile, UploadFileStatus } from '@skyfox2000/webbase';
|
||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fileList: UploadFile<any>[];
|
||||||
|
placeholder?: string;
|
||||||
|
fileExt?: string[];
|
||||||
|
maxFileSize?: number;
|
||||||
|
maxCount?: number;
|
||||||
|
autoUpload?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
fileList: () => [],
|
||||||
|
placeholder: '',
|
||||||
|
maxFileSize: 20,
|
||||||
|
maxCount: 5,
|
||||||
|
autoUpload: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileList = ref<UploadFile[]>([]);
|
||||||
|
const fileUploader = ref();
|
||||||
|
const emit = defineEmits(['update:file-list']);
|
||||||
|
|
||||||
|
const acceptString = computed(() => (props.fileExt?.length ? props.fileExt.map((ext) => `.${ext}`).join(',') : ''));
|
||||||
|
|
||||||
|
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||||
|
if (props.fileExt && props.fileExt.length > 0) {
|
||||||
|
const extension = file.name.split('.').pop()?.toLowerCase() || '';
|
||||||
|
if (!props.fileExt.includes(extension)) {
|
||||||
|
message.error('文件类型不支持');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size / 1024 / 1024 > props.maxFileSize) {
|
||||||
|
message.error(`文件大小超过 ${props.maxFileSize}MB 限制`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileList.value.length >= props.maxCount) {
|
||||||
|
message.error(`最多上传 ${props.maxCount} 个文件`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.autoUpload;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadProps = computed<UploadProps>(() => ({
|
||||||
|
accept: acceptString.value,
|
||||||
|
multiple: true,
|
||||||
|
fileList: fileList.value as UploadProps['fileList'],
|
||||||
|
beforeUpload: beforeUpload,
|
||||||
|
listType: 'text',
|
||||||
|
maxCount: props.maxCount,
|
||||||
|
showUploadList: false,
|
||||||
|
onChange: (info) => {
|
||||||
|
if (!props.autoUpload) {
|
||||||
|
fileList.value = info.fileList as unknown as UploadFile[];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => fileList.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit('update:file-list', newVal);
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPlaceholder = (): string => {
|
||||||
|
const typeMsg = props.fileExt && props.fileExt.length ? `文件必须为 ${props.fileExt.join('/')}` : '';
|
||||||
|
const sizeMsg = props.maxFileSize !== 0 ? `单文件最大 ${props.maxFileSize}MB` : '';
|
||||||
|
const countMsg = props.maxCount !== 0 ? `最多 ${props.maxCount} 个文件` : '';
|
||||||
|
|
||||||
|
return [sizeMsg, typeMsg, countMsg].filter(Boolean).join(',');
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFile = (index: number) => {
|
||||||
|
fileList.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status?: UploadFileStatus) => {
|
||||||
|
switch (status) {
|
||||||
|
case UploadFileStatus.Uploading:
|
||||||
|
return 'blue';
|
||||||
|
case UploadFileStatus.Success:
|
||||||
|
return 'green';
|
||||||
|
case UploadFileStatus.Error:
|
||||||
|
return 'red';
|
||||||
|
default:
|
||||||
|
return 'cyan';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getStatus = (status?: UploadFileStatus) => {
|
||||||
|
switch (status) {
|
||||||
|
case UploadFileStatus.Uploading:
|
||||||
|
return '上传中';
|
||||||
|
case UploadFileStatus.Success:
|
||||||
|
return '已完成';
|
||||||
|
case UploadFileStatus.Error:
|
||||||
|
return '上传失败';
|
||||||
|
default:
|
||||||
|
return '待上传';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full border border-solid border-gray-100 mt-1 rounded-md py-5">
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="w-35 mx-3">
|
||||||
|
<Upload ref="fileUploader" v-bind="uploadProps">
|
||||||
|
<Button>选择文件</Button>
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-sm text-gray-500">{{ getPlaceholder() }}</div>
|
||||||
|
<!-- <Button v-if="!autoUpload" @click="manualUpload" class="mr-3">开始上传</Button> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 px-3">
|
||||||
|
<div v-for="(file, index) in fileList" :key="index" class="mb-2 pb-1">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-gray-700 mr-2">{{ file.name }}</span>
|
||||||
|
<span>
|
||||||
|
<Tag :color="getStatusColor(file.status)">{{ getStatus(file.status) }}</Tag>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<!-- <span class="text-blue-500 hover:text-blue-700 mr-4">预览</span> -->
|
||||||
|
<span class="text-red-500 hover:text-red-700 cursor-pointer" @click="removeFile(index)">删除</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 上传进度条 -->
|
||||||
|
<div>
|
||||||
|
<Progress :percent="file.percent" :stroke-width="2" :show-info="false" style="height: 2px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,110 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted } from 'vue';
|
||||||
|
import { Button } from '../../common';
|
||||||
|
import { Drawer, Space, theme } from 'ant-design-vue';
|
||||||
|
const { useToken } = theme;
|
||||||
|
const { token } = useToken();
|
||||||
|
import { PageData, onFormSave, onFormSaveAs, onFormClose } from '@skyfox2000/webbase';
|
||||||
|
import { AnyData } from '@skyfox2000/fapi';
|
||||||
|
import LayoutIcon from '@/components/common/icon/layoutIcon.vue';
|
||||||
|
|
||||||
|
const open = ref<boolean>(false);
|
||||||
|
|
||||||
|
// interface RuleItem {
|
||||||
|
// key: string[]; // 使用字符串数组来表示嵌套属性的路径
|
||||||
|
// validate: (value: any) => boolean; //
|
||||||
|
// message: string
|
||||||
|
// }
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 确认按钮文字,空字符串则不显示
|
||||||
|
*/
|
||||||
|
saveText?: string;
|
||||||
|
/**
|
||||||
|
* 另存为按钮文字,空字符串则不显示
|
||||||
|
*/
|
||||||
|
saveAsText?: string;
|
||||||
|
/**
|
||||||
|
* 取消按钮文字,空字符则不显示
|
||||||
|
*/
|
||||||
|
cancelText?: string;
|
||||||
|
/**
|
||||||
|
* 保存数据请求配置
|
||||||
|
*/
|
||||||
|
pageData: PageData<AnyData>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const editorData = props.pageData.editor!;
|
||||||
|
watch(
|
||||||
|
() => editorData.visible,
|
||||||
|
() => {
|
||||||
|
open.value = editorData.visible;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
open.value = props.pageData.editor!.visible;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Drawer
|
||||||
|
v-model:open="open"
|
||||||
|
:get-container="false"
|
||||||
|
:closable="false"
|
||||||
|
:header-style="{
|
||||||
|
height: '40px',
|
||||||
|
padding: '10px 6px 10px 16px',
|
||||||
|
backgroundColor: token.colorBgLayout,
|
||||||
|
}"
|
||||||
|
:body-style="{
|
||||||
|
padding: '10px 16px',
|
||||||
|
}"
|
||||||
|
:footer-style="{
|
||||||
|
textAlign: 'right',
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
boxShadow: 'rgba(0, 0, 0, 0.3) -2px 0px 8px',
|
||||||
|
}"
|
||||||
|
width="412px"
|
||||||
|
@close="() => onFormClose(pageData)"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<div class="hover:bg-gray-200 w-[24px] h-[24px] rounded-md">
|
||||||
|
<LayoutIcon
|
||||||
|
class="top-[-2px] left-[2px]"
|
||||||
|
icon="add"
|
||||||
|
:angle="45"
|
||||||
|
fontsize="24px"
|
||||||
|
clickable
|
||||||
|
color="#666"
|
||||||
|
:position="[0, 0]"
|
||||||
|
@click="() => onFormClose(pageData)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Space>
|
||||||
|
<Button @click="() => onFormClose(pageData)" v-if="cancelText !== ''">{{ cancelText ?? '取消' }}</Button>
|
||||||
|
<Button
|
||||||
|
@click="() => onFormSaveAs(pageData)"
|
||||||
|
v-if="saveAsText !== '' && editorData.saveAsBtnVisible !== false"
|
||||||
|
type="primary"
|
||||||
|
:loading="editorData.isFormSaving"
|
||||||
|
>{{ saveAsText ?? '另存为' }}</Button
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
@click="() => onFormSave(pageData)"
|
||||||
|
v-if="saveText !== '' && editorData.saveBtnVisible !== false"
|
||||||
|
type="primary"
|
||||||
|
:loading="editorData.isFormSaving"
|
||||||
|
>{{ saveText ?? '保存' }}</Button
|
||||||
|
>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useFormItemFactory } from '@skyfox2000/webbase';
|
||||||
|
import { FormItem } from 'ant-design-vue';
|
||||||
|
import { Helper } from '@/components/common';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 标签文字
|
||||||
|
*/
|
||||||
|
label?: string;
|
||||||
|
/**
|
||||||
|
* 验证规则
|
||||||
|
*/
|
||||||
|
rule?: string;
|
||||||
|
/**
|
||||||
|
* 帮助文字
|
||||||
|
*/
|
||||||
|
helper?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { msg } = useFormItemFactory({
|
||||||
|
label: props.label,
|
||||||
|
rule: props.rule,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 参考代码
|
||||||
|
// this.$parent.$el.scrollIntoView({
|
||||||
|
// //滚动到指定节点
|
||||||
|
// block: "center", //值有start,center,end,nearest,当前显示在视图区域中间
|
||||||
|
// behavior: "smooth" //值有auto、instant,smooth,缓动动画(当前是慢速的)
|
||||||
|
// });
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="w-full relative mb-1">
|
||||||
|
<FormItem :required="rule !== undefined" class="!w-[95%] relative" v-bind="$attrs" :class="[rule ? '' : 'mb-3']">
|
||||||
|
<template #label>
|
||||||
|
<span :class="[msg ? 'text-[#ff4d4f]' : '', 'w-full']"> {{ label }}</span>
|
||||||
|
</template>
|
||||||
|
<div class="w-full flex items-center">
|
||||||
|
<div class="flex-grow">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div class="w-8 mt-[-2px]">
|
||||||
|
<slot name="helper">
|
||||||
|
<Helper v-if="helper" :text="helper" />
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
<span class="absolute bottom-[3px] left-[80px] text-[12px] text-[#ff4d4fcc] block" v-if="msg">
|
||||||
|
{{ msg }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { EditorData, PageData, ProviderKeys } from '@skyfox2000/webbase';
|
||||||
|
import { AnyData } from '@skyfox2000/fapi';
|
||||||
|
import { Form } from 'ant-design-vue';
|
||||||
|
import { inject, provide, ref } from 'vue';
|
||||||
|
// 使用以下统一控制表单显示
|
||||||
|
// :label-col="{ flex: '60px' }"
|
||||||
|
// :wrapper-col="{ flex: '200px' }"
|
||||||
|
// 单独项使用FormItem控制
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 快速配置表单标签宽度
|
||||||
|
*/
|
||||||
|
labelWidth?: string;
|
||||||
|
/**
|
||||||
|
* 快速配置表单项宽度
|
||||||
|
*/
|
||||||
|
wrapperWidth?: string;
|
||||||
|
/**
|
||||||
|
* 表单配置信息
|
||||||
|
*/
|
||||||
|
editorData?: EditorData<AnyData>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const pageData = ref<PageData<AnyData> | undefined>(inject(ProviderKeys.PageData, undefined));
|
||||||
|
|
||||||
|
const editorData = props.editorData ?? (pageData.value ? pageData.value.editor : undefined);
|
||||||
|
provide(ProviderKeys.EditorData, editorData);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
:label-col="{ flex: props.labelWidth ?? '85px' }"
|
||||||
|
:wrapper-col="{
|
||||||
|
flex: props.wrapperWidth ?? '1',
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</Form>
|
||||||
|
</template>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, watch, useSlots, VNode } from 'vue';
|
||||||
|
import { Form, Space } from 'ant-design-vue';
|
||||||
|
import SearchItem from './searchItem.vue';
|
||||||
|
import { Button } from '../../common';
|
||||||
|
import { PageData } from '@skyfox2000/webbase';
|
||||||
|
import { AnyData } from '@skyfox2000/fapi';
|
||||||
|
// 使用以下统一控制表单显示
|
||||||
|
// :label-col="{ flex: '60px' }"
|
||||||
|
// :wrapper-col="{ flex: '200px' }"
|
||||||
|
// 单独项使用FormItem控制
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 搜索条件
|
||||||
|
*/
|
||||||
|
search: Record<string, any>;
|
||||||
|
/**
|
||||||
|
* 页面数据
|
||||||
|
*/
|
||||||
|
pageData: PageData<AnyData>;
|
||||||
|
}>();
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: 'update:search', val: Record<string, any>): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索按钮前的占位数量,1或者2
|
||||||
|
*/
|
||||||
|
const holderSize = ref(0);
|
||||||
|
const defaultSlots = ref(0);
|
||||||
|
const controlSlots = ref(0);
|
||||||
|
const getSlotLen = (items: VNode[]): number => {
|
||||||
|
let count = 0;
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (typeof items[i].type === 'object') count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
const slots = useSlots();
|
||||||
|
const updateHolderSize = () => {
|
||||||
|
defaultSlots.value = 0;
|
||||||
|
controlSlots.value = 0;
|
||||||
|
if (slots.default) defaultSlots.value = getSlotLen(slots.default({}));
|
||||||
|
if (props.pageData.searchBar && slots.control) controlSlots.value = getSlotLen(slots.control({}));
|
||||||
|
|
||||||
|
holderSize.value = 2 - ((defaultSlots.value + controlSlots.value) % 3);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.pageData.searchBar,
|
||||||
|
() => {
|
||||||
|
updateHolderSize();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultData: Record<string, any> = JSON.parse(JSON.stringify(props.search));
|
||||||
|
onMounted(() => {
|
||||||
|
updateHolderSize();
|
||||||
|
let search = updateSearchNull();
|
||||||
|
props.pageData.searchQuery = {
|
||||||
|
Query: { ...search },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSearch = () => {
|
||||||
|
let search = updateSearchNull();
|
||||||
|
props.pageData.searchQuery = {
|
||||||
|
Query: { ...search },
|
||||||
|
};
|
||||||
|
if (props.pageData.grid) props.pageData.grid.reload = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSearchNull = (): Record<string, any> => {
|
||||||
|
let search = { ...props.search };
|
||||||
|
for (const key in search) {
|
||||||
|
if (search[key] === null) {
|
||||||
|
search[key] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return search;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
const data = JSON.parse(JSON.stringify(defaultData));
|
||||||
|
emits('update:search', data);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
v-if="defaultSlots + controlSlots > 0"
|
||||||
|
:label-col="{ flex: '60px' }"
|
||||||
|
ref="searchForm"
|
||||||
|
:style="{
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
borderBottom: '1px solid #e9e9e9',
|
||||||
|
}"
|
||||||
|
class="flex mb-[10px]"
|
||||||
|
>
|
||||||
|
<!-- 默认插槽 -->
|
||||||
|
<slot></slot>
|
||||||
|
<!-- 受控插槽 -->
|
||||||
|
<slot name="control" v-if="pageData.searchBar"></slot>
|
||||||
|
<!-- 表单操作按钮 占位数量 -->
|
||||||
|
<SearchItem class="w-[33%]" v-if="holderSize >= 1"> </SearchItem>
|
||||||
|
<SearchItem class="w-[33%]" v-if="holderSize >= 2"> </SearchItem>
|
||||||
|
<SearchItem
|
||||||
|
v-if="defaultSlots || pageData.searchBar"
|
||||||
|
class="w-[33%] flex justify-end text-right pr-2"
|
||||||
|
:wrapper-col="{ flex: 'auto' }"
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" @click="onSearch">搜索</Button>
|
||||||
|
<Button @click="onReset">重置</Button>
|
||||||
|
</Space>
|
||||||
|
</SearchItem>
|
||||||
|
</Form>
|
||||||
|
</template>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useFormItemFactory } from '@skyfox2000/webbase';
|
||||||
|
import { FormItem } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 标签文字
|
||||||
|
*/
|
||||||
|
label?: string;
|
||||||
|
/**
|
||||||
|
* 验证规则
|
||||||
|
*/
|
||||||
|
rule?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { msg } = useFormItemFactory({
|
||||||
|
label: props.label,
|
||||||
|
rule: props.rule,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="w-1/3 relative mb-1">
|
||||||
|
<FormItem
|
||||||
|
:required="rule !== undefined"
|
||||||
|
class="!w-[90%] relative"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:class="[rule ? '' : 'mb-3']"
|
||||||
|
:labelCol="{ span: 6 }"
|
||||||
|
>
|
||||||
|
<template #label v-if="label">
|
||||||
|
<span :class="[msg ? 'text-[#ff4d4f]' : '', 'w-full']"> {{ label }}</span>
|
||||||
|
</template>
|
||||||
|
<div class="w-full flex items-center">
|
||||||
|
<div class="flex-grow">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
<span class="absolute bottom-[3px] left-[80px] text-[12px] text-[#ff4d4fcc] block" v-if="msg">
|
||||||
|
{{ msg }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,153 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { Table, TablePaginationConfig, TableProps } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
gridQueryFind,
|
||||||
|
gridStatusUpdate,
|
||||||
|
PageData,
|
||||||
|
PrimaryKey,
|
||||||
|
useSettingInfo,
|
||||||
|
OPTIONS,
|
||||||
|
gridQueryList,
|
||||||
|
} from '@skyfox2000/webbase';
|
||||||
|
import TableOperate from './tableOperate.vue';
|
||||||
|
import Toolbar from '../toolbar/index.vue';
|
||||||
|
import Switch from '../../form/switch/index.vue';
|
||||||
|
import { RowOperate } from '@skyfox2000/microbase';
|
||||||
|
import { AnyData } from '@skyfox2000/fapi';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 页面主数据
|
||||||
|
*/
|
||||||
|
pageData: PageData<AnyData>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const pageData = props.pageData;
|
||||||
|
const gridData = pageData.grid!;
|
||||||
|
|
||||||
|
gridData.pageNo = 1;
|
||||||
|
gridData.total = 0;
|
||||||
|
gridData.pageSize = gridData.pageSize ?? 10;
|
||||||
|
|
||||||
|
const dataList = ref<Record<string, AnyData>[]>([]);
|
||||||
|
const pagination = ref<TablePaginationConfig>({
|
||||||
|
total: 0,
|
||||||
|
current: 1,
|
||||||
|
pageSize: gridData.pageSize,
|
||||||
|
showTotal: (total: number) => {
|
||||||
|
return `共 ${total} 条记录`;
|
||||||
|
},
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
pagination.value.current = page;
|
||||||
|
pagination.value.pageSize = pageSize;
|
||||||
|
gridData.pageSize = pageSize;
|
||||||
|
gridData.pageNo = page;
|
||||||
|
gridQueryFind(pageData);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => gridData.tableData,
|
||||||
|
() => {
|
||||||
|
dataList.value = gridData.tableData!;
|
||||||
|
pagination.value.total = gridData.total ?? 0;
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => gridData.reload,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
gridQueryFind(pageData);
|
||||||
|
gridData.reload = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const columns = ref<Record<string, any>[]>();
|
||||||
|
|
||||||
|
const selection = {
|
||||||
|
onChange: (selectedRowKeys: PrimaryKey[], selectedRows: any[]) => {
|
||||||
|
gridData.selectKeys = selectedRowKeys;
|
||||||
|
gridData.selectRows = selectedRows;
|
||||||
|
},
|
||||||
|
columnWidth: '30px',
|
||||||
|
};
|
||||||
|
const rowSelection = ref<TableProps['rowSelection']>(selection);
|
||||||
|
watch(
|
||||||
|
() => gridData.selectable,
|
||||||
|
(newVal) => {
|
||||||
|
rowSelection.value = newVal ? selection : undefined;
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => gridData.columns,
|
||||||
|
() => {
|
||||||
|
columns.value = gridData.columns.filter((column) => column.visible !== false);
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true },
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
const settingInfoStore = useSettingInfo();
|
||||||
|
if (settingInfoStore.rowOperate === RowOperate.RIGHT) {
|
||||||
|
const operateColumns: Record<string, any>[] = [];
|
||||||
|
const dataColumns: Record<string, any>[] = [];
|
||||||
|
gridData.columns!.forEach((column) => {
|
||||||
|
if (column['key'] === 'operation') {
|
||||||
|
operateColumns.push(column);
|
||||||
|
} else dataColumns.push(column);
|
||||||
|
});
|
||||||
|
gridData.columns.splice(0, gridData.columns.length);
|
||||||
|
gridData.columns.push(...dataColumns);
|
||||||
|
gridData.columns.push(...operateColumns.reverse());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gridData.tableData) {
|
||||||
|
dataList.value = gridData.tableData;
|
||||||
|
gridData.total = dataList.value.length;
|
||||||
|
pagination.value.total = gridData.total ?? 0;
|
||||||
|
} else if (gridData.autoload !== false) {
|
||||||
|
if (gridData.remotePage) gridQueryFind(pageData);
|
||||||
|
else gridQueryList(pageData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Toolbar :page-data="pageData" />
|
||||||
|
<Table
|
||||||
|
class="w-full"
|
||||||
|
:rowKey="pageData.primaryKey"
|
||||||
|
:data-source="dataList"
|
||||||
|
:loading="gridData.isGridLoading"
|
||||||
|
:columns="columns"
|
||||||
|
:pagination="pagination"
|
||||||
|
:row-selection="rowSelection"
|
||||||
|
:scroll="{ x: 700, y: 1000 }"
|
||||||
|
:size="gridData.tableSize"
|
||||||
|
bordered
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<template #bodyCell="bodyCell">
|
||||||
|
<slot name="bodyCell" :column="bodyCell?.column" :record="bodyCell?.record"></slot>
|
||||||
|
<template v-if="bodyCell?.column.dataIndex === 'enabled' || bodyCell?.column.dataIndex === 'disabled'">
|
||||||
|
<Switch
|
||||||
|
v-model:checked="bodyCell.record.Enabled"
|
||||||
|
:data="OPTIONS.EnableDisable"
|
||||||
|
@click="gridStatusUpdate(pageData, bodyCell.record)"
|
||||||
|
:disabled="bodyCell?.column.dataIndex === 'disabled'"
|
||||||
|
:class="['w-[58px]']"
|
||||||
|
:loading="bodyCell?.record.isLoading"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-if="bodyCell?.column.dataIndex === 'operation'">
|
||||||
|
<slot name="operate" :record="bodyCell?.record">
|
||||||
|
<TableOperate :record="bodyCell?.record" :tools="gridData.operates" :page-data="pageData">
|
||||||
|
</TableOperate>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</template>
|
|
@ -0,0 +1,134 @@
|
||||||
|
<script setup lang="ts" generic="T">
|
||||||
|
import { reactive, watch } from 'vue';
|
||||||
|
import {
|
||||||
|
ButtonTool,
|
||||||
|
PageData,
|
||||||
|
getToolGroup,
|
||||||
|
getToolVisible,
|
||||||
|
getToolStatus,
|
||||||
|
onToolClicked,
|
||||||
|
onGridRowEdit,
|
||||||
|
onGridRowDelete,
|
||||||
|
} from '@skyfox2000/webbase';
|
||||||
|
import { ConfigProvider, Button, Space, DropdownButton, Menu, MenuItem, Popconfirm } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 数据行记录
|
||||||
|
*/
|
||||||
|
record: Record<string, any>;
|
||||||
|
/**
|
||||||
|
* 页面数据
|
||||||
|
*/
|
||||||
|
pageData: PageData<T>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const pageData = props.pageData;
|
||||||
|
const gridData = pageData.grid!;
|
||||||
|
|
||||||
|
/// 兼容其它按钮操作参数
|
||||||
|
const defaultOperates: ButtonTool[] = [
|
||||||
|
{
|
||||||
|
key: 'Edit',
|
||||||
|
label: '编辑',
|
||||||
|
type: 'primary',
|
||||||
|
visible: true,
|
||||||
|
disabled: props.record.Enabled == 0,
|
||||||
|
click: () => onGridRowEdit<T>(pageData, props.record as T),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Delete',
|
||||||
|
label: '删除',
|
||||||
|
type: 'primary',
|
||||||
|
visible: true,
|
||||||
|
disabled: props.record.Enabled == 1,
|
||||||
|
danger: true,
|
||||||
|
confirm: true,
|
||||||
|
confirmText: '是否删除此记录?',
|
||||||
|
click: () => onGridRowDelete<T>(pageData, props.record as T),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { buttons, menus } = getToolGroup(defaultOperates, 0, gridData.operates);
|
||||||
|
|
||||||
|
const Menus = reactive(menus);
|
||||||
|
const Buttons = reactive(buttons);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.record.Enabled,
|
||||||
|
() => {
|
||||||
|
const { buttons, menus } = getToolGroup(defaultOperates, 0, gridData.operates);
|
||||||
|
Buttons.splice(0, Buttons.length, ...buttons);
|
||||||
|
Menus.splice(0, Menus.length, ...menus);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const disabled = (item: ButtonTool) => {
|
||||||
|
switch (item.key) {
|
||||||
|
case 'Edit':
|
||||||
|
item.disabled = props.record.Enabled ? false : true;
|
||||||
|
break;
|
||||||
|
case 'Delete':
|
||||||
|
item.disabled = props.record.Enabled ? true : false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return getToolStatus(item, props.record);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ConfigProvider
|
||||||
|
:theme="{
|
||||||
|
token: {
|
||||||
|
fontSize: 13,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<template v-for="item in Buttons" :key="item.key">
|
||||||
|
<Popconfirm
|
||||||
|
:disabled="!item.confirm"
|
||||||
|
cancelText="否"
|
||||||
|
okText="是"
|
||||||
|
:title="item.confirmText"
|
||||||
|
:okButtonProps="{ size: 'small' }"
|
||||||
|
:cancelButtonProps="{ size: 'small' }"
|
||||||
|
@confirm="onToolClicked(item, pageData, props.record, true)"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:key="item.key"
|
||||||
|
:type="item.type ?? 'text'"
|
||||||
|
:danger="item.danger"
|
||||||
|
v-if="getToolVisible(item)"
|
||||||
|
:disabled="disabled(item)"
|
||||||
|
@click="onToolClicked(item, pageData, props.record)"
|
||||||
|
size="small"
|
||||||
|
:style="{
|
||||||
|
padding: item.type ?? '0px 4px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ConfigProvider :autoInsertSpaceInButton="false" v-if="record.Enabled == 1">
|
||||||
|
<DropdownButton v-if="Menus.length > 0" size="small">
|
||||||
|
更多
|
||||||
|
<template #overlay>
|
||||||
|
<Menu>
|
||||||
|
<template v-for="item in Menus" :key="item.key">
|
||||||
|
<MenuItem
|
||||||
|
v-if="getToolVisible(item)"
|
||||||
|
:disabled="disabled(item)"
|
||||||
|
@click="onToolClicked(item, pageData, props.record)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</MenuItem>
|
||||||
|
</template>
|
||||||
|
</Menu>
|
||||||
|
</template>
|
||||||
|
</DropdownButton>
|
||||||
|
</ConfigProvider>
|
||||||
|
</Space>
|
||||||
|
</ConfigProvider>
|
||||||
|
</template>
|
|
@ -0,0 +1,143 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { onColumnVisibleChanged } from '@skyfox2000/webbase';
|
||||||
|
import { VueDraggableNext as draggable } from 'vue-draggable-next';
|
||||||
|
import { Checkbox } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
PageData,
|
||||||
|
getToolStatus,
|
||||||
|
getToolByKey,
|
||||||
|
getToolVisible,
|
||||||
|
useToolFactory,
|
||||||
|
onToolClicked,
|
||||||
|
} from '@skyfox2000/webbase';
|
||||||
|
import { Button, ToolIcon } from '@/components';
|
||||||
|
import { Dropdown, Menu, MenuItem } from 'ant-design-vue';
|
||||||
|
|
||||||
|
// 定义组件映射
|
||||||
|
// const componentMap: Record<string, any> = {
|
||||||
|
// headset: () => import('./headset.vue'),
|
||||||
|
// };
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 页面主数据
|
||||||
|
*/
|
||||||
|
pageData: PageData<any>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const gridData = props.pageData.grid!;
|
||||||
|
const { tools } = useToolFactory(props.pageData);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => gridData.selectable,
|
||||||
|
(newVal) => {
|
||||||
|
if (getToolByKey(tools.value, 'tool.export.excel.selected'))
|
||||||
|
getToolByKey(tools.value, 'tool.export.excel.selected')!.disabled = !newVal;
|
||||||
|
if (getToolByKey(tools.value, 'tool.export.pdf.selected'))
|
||||||
|
getToolByKey(tools.value, 'tool.export.pdf.selected')!.disabled = !newVal;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = gridData.columns;
|
||||||
|
|
||||||
|
// 添加拖拽相关的响应式数据
|
||||||
|
const dragColumns = ref(columns);
|
||||||
|
// 处理拖拽结束事件
|
||||||
|
const onDragEnd = () => {
|
||||||
|
// 更新列的顺序
|
||||||
|
columns.splice(0, columns.length, ...dragColumns.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="inline-flex [&>button]:ml-[-1px] first:[&>button]:ml-0">
|
||||||
|
<template v-for="(item, index) in tools" :key="item.key">
|
||||||
|
<Dropdown placement="bottomRight" class="p-0 rounded-none" v-if="getToolVisible(item) && item.dropdown">
|
||||||
|
<template #overlay>
|
||||||
|
<div class="min-w-[100px] bg-white rounded shadow-md p-4" :class="item.dropdownClass">
|
||||||
|
<draggable
|
||||||
|
v-if="item.dropdown === 'headset'"
|
||||||
|
v-model="dragColumns"
|
||||||
|
item-key="dataIndex"
|
||||||
|
@end="onDragEnd"
|
||||||
|
handle=".drag-handle"
|
||||||
|
>
|
||||||
|
<template v-for="element in dragColumns" :key="element.name">
|
||||||
|
<div @click.stop class="flex items-center mb-2 last:mb-0 select-none">
|
||||||
|
<span class="drag-handle mr-2 text-gray-400 hover:text-gray-600 cursor-move">⋮⋮</span>
|
||||||
|
<Checkbox
|
||||||
|
:checked="element.visible !== false"
|
||||||
|
@change.stop.prevent="
|
||||||
|
(e) => {
|
||||||
|
onColumnVisibleChanged(element, e.target.checked);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
class="text-gray-700 hover:text-gray-900 select-none"
|
||||||
|
>
|
||||||
|
{{ element.title }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Button
|
||||||
|
:class="[
|
||||||
|
'px-[8px] py-[2px] relative border-[#ccc] bg-[#fcfcfc] rounded-none text-[#666] hover:z-10',
|
||||||
|
index === 0 ? 'rounded-l-[5px]' : '',
|
||||||
|
index === tools.length - 1 ? 'rounded-r-[5px]' : '',
|
||||||
|
]"
|
||||||
|
:disabled="getToolStatus(item)"
|
||||||
|
:tiptext="item.label"
|
||||||
|
@click="onToolClicked(item, pageData)"
|
||||||
|
>
|
||||||
|
<ToolIcon :icon="item.icon" class="w-[18px] h-[18.5px]" />
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<Button
|
||||||
|
v-else-if="!item.children && getToolVisible(item)"
|
||||||
|
:class="[
|
||||||
|
'px-[8px] py-[2px] relative border-[#ccc] bg-[#fcfcfc] rounded-none text-[#666] hover:z-10',
|
||||||
|
index === 0 ? 'rounded-l-[5px]' : '',
|
||||||
|
index === tools.length - 1 ? 'rounded-r-[5px]' : '',
|
||||||
|
]"
|
||||||
|
:disabled="getToolStatus(item)"
|
||||||
|
:tiptext="item.label"
|
||||||
|
@click="onToolClicked(item, pageData)"
|
||||||
|
>
|
||||||
|
<ToolIcon :icon="item.icon" class="w-[18px] h-[18.5px]" />
|
||||||
|
</Button>
|
||||||
|
<Dropdown placement="bottomRight" class="p-0 rounded-none" v-else-if="getToolVisible(item)">
|
||||||
|
<template #overlay>
|
||||||
|
<Menu>
|
||||||
|
<MenuItem v-for="menu in item.children" :key="menu.key" :disabled="getToolStatus(menu)">
|
||||||
|
{{ menu.label }}
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</template>
|
||||||
|
<Button
|
||||||
|
:class="[
|
||||||
|
'!w-[46px] px-[5px] py-[2px] relative border-[#ccc] bg-[#fcfcfc] rounded-none text-[#666] hover:z-10',
|
||||||
|
index === 0 ? 'rounded-l-[5px]' : '',
|
||||||
|
]"
|
||||||
|
:disabled="getToolStatus(item)"
|
||||||
|
:tiptext="item.label"
|
||||||
|
:icon="item.icon"
|
||||||
|
:iconProps="{ class: 'w-[19px] h-[19px]' }"
|
||||||
|
@click="onToolClicked(item, pageData)"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<ToolIcon icon="icon-down-arrow" class="w-[12px] h-[12px]" />
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</template>
|
||||||
|
<!--
|
||||||
|
导出Excel 基于表头导出
|
||||||
|
导出PDF
|
||||||
|
TODO:支持多模板导出选择
|
||||||
|
表头设置
|
||||||
|
TODO:增加保存功能,可设置个性化表头和多模式表头
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script setup lang="ts" generic="T">
|
||||||
|
import { ButtonTool, PageData, getToolGroup, getToolStatus, onToolClicked, openNewForm } from '@skyfox2000/webbase';
|
||||||
|
import { Dropdown, Menu, MenuItem, Space } from 'ant-design-vue';
|
||||||
|
import { Button } from '@/components/common';
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
// 定义异步组件
|
||||||
|
const IconTool = defineAsyncComponent(() => import('./icontool.vue'));
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/**
|
||||||
|
* 页面主数据
|
||||||
|
*/
|
||||||
|
pageData: PageData<T>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const gridData = props.pageData.grid!;
|
||||||
|
|
||||||
|
const defaultButtons: ButtonTool[] = [
|
||||||
|
{
|
||||||
|
key: 'New',
|
||||||
|
label: '新增',
|
||||||
|
type: 'primary',
|
||||||
|
icon: 'icon-new',
|
||||||
|
danger: true,
|
||||||
|
click: openNewForm,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const MaxButtonCount = 3;
|
||||||
|
const { buttons, menus } = getToolGroup(
|
||||||
|
defaultButtons,
|
||||||
|
gridData.flat !== undefined ? gridData.flat : MaxButtonCount,
|
||||||
|
gridData.buttons,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex justify-between mb-[10px]">
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
v-for="item in buttons"
|
||||||
|
:key="item.key"
|
||||||
|
:type="item.type"
|
||||||
|
:danger="item.danger"
|
||||||
|
:disabled="getToolStatus(item)"
|
||||||
|
:icon="item.icon"
|
||||||
|
@click="onToolClicked(item, pageData)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</Button>
|
||||||
|
<Dropdown v-if="menus.length > 0">
|
||||||
|
<template #overlay>
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
v-for="item in menus"
|
||||||
|
:key="item.key"
|
||||||
|
:disabled="getToolStatus(item)"
|
||||||
|
@click="onToolClicked(item, pageData)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</template>
|
||||||
|
<Button> 更多操作 </Button>
|
||||||
|
</Dropdown>
|
||||||
|
<!-- 空占位元素 -->
|
||||||
|
<span v-if="buttons.length === 0 && menus.length === 0"></span>
|
||||||
|
</Space>
|
||||||
|
<Space class="mr-1">
|
||||||
|
<component :is="IconTool" :page-data="pageData" />
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Result, Button } from 'ant-design-vue';
|
||||||
|
import router from '@/router';
|
||||||
|
const onClicked = () => {
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Result status="403" title="403" sub-title="您没有权限访问当前页面!">
|
||||||
|
<template #extra>
|
||||||
|
<Button type="primary" @click="onClicked">点击返回</Button>
|
||||||
|
</template>
|
||||||
|
</Result>
|
||||||
|
</template>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Result, Button } from 'ant-design-vue';
|
||||||
|
import router from '@/router';
|
||||||
|
const onClicked = () => {
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Result status="404" title="404" sub-title="页面不存在或者没有权限访问页面!">
|
||||||
|
<template #extra>
|
||||||
|
<Button type="primary" @click="onClicked">点击返回</Button>
|
||||||
|
</template>
|
||||||
|
</Result>
|
||||||
|
</template>
|
|
@ -0,0 +1,108 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { AutoComplete, AutoCompleteOption } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
circleLoading,
|
||||||
|
useInputFactory,
|
||||||
|
OptionCommProps,
|
||||||
|
OptionItemProps,
|
||||||
|
onOptionChanged,
|
||||||
|
loadOption,
|
||||||
|
unloadOption,
|
||||||
|
} from '@skyfox2000/webbase';
|
||||||
|
import { ReqParams } from '@skyfox2000/fapi';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
...OptionCommProps,
|
||||||
|
autoload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
onsearch: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputFactory = useInputFactory();
|
||||||
|
const { errClass, labelText } = inputFactory;
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'update:label-info']);
|
||||||
|
inputFactory.inputEmit = emit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际的选择项
|
||||||
|
*/
|
||||||
|
const selectOptions = ref<OptionItemProps[]>([]);
|
||||||
|
|
||||||
|
const onSearch = (value: string) => {
|
||||||
|
selectOptions.value = [];
|
||||||
|
let search_value = value.trim();
|
||||||
|
let query: ReqParams = { Query: {} };
|
||||||
|
if (props.onsearch) {
|
||||||
|
props.onsearch(search_value, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOption(true, props, selectOptions, inputFactory, query);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelected = (value: any, _option: any) => {
|
||||||
|
onOptionChanged(props, value as number | string, selectOptions, inputFactory);
|
||||||
|
|
||||||
|
// const labelInfo: string[] = [];
|
||||||
|
// if (typeof selectedOptions === 'object') selectedOptions = [selectedOptions];
|
||||||
|
// selectedOptions.forEach((selectedOption: OptionItemProps) => {
|
||||||
|
// labelInfo.push(selectedOption.label);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// emit('update:label-info', labelInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.url && !props.url.fieldMap && !props.fieldMap) {
|
||||||
|
props.url.fieldMap = {
|
||||||
|
title: 'Name',
|
||||||
|
label: 'Name',
|
||||||
|
value: 'Id',
|
||||||
|
key: 'Id',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
unloadOption(props, inputFactory);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="props.url && props.url.loading === true"
|
||||||
|
class="absolute z-10 mt-[5px] mr-[10px] text-[#999] flex items-center"
|
||||||
|
>
|
||||||
|
<circleLoading class="text-[#555] mx-[5px] !ml-[10px] !w-4 !h-4" />
|
||||||
|
<span>数据加载中...</span>
|
||||||
|
</div>
|
||||||
|
<AutoComplete
|
||||||
|
:class="[errClass]"
|
||||||
|
@search="onSearch"
|
||||||
|
@select="onSelected"
|
||||||
|
:placeholder="props.url && !props.url.loading ? '请输入并选择' + labelText : ''"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:label-in-value="false"
|
||||||
|
>
|
||||||
|
<template v-for="item in selectOptions" :key="item.value">
|
||||||
|
<AutoCompleteOption :value="item.value">
|
||||||
|
{{ item.label }}
|
||||||
|
</AutoCompleteOption>
|
||||||
|
</template>
|
||||||
|
</AutoComplete>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error :deep(.ant-select-selector) {
|
||||||
|
border-color: #ef4444;
|
||||||
|
box-shadow: 0 0 3px 0 #ff4d4f;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,76 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import {
|
||||||
|
circleLoading,
|
||||||
|
useInputFactory,
|
||||||
|
OptionCommProps,
|
||||||
|
OptionItemProps,
|
||||||
|
onOptionChanged,
|
||||||
|
loadOption,
|
||||||
|
unloadOption,
|
||||||
|
} from '@skyfox2000/webbase';
|
||||||
|
import { Cascader } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const props = defineProps(OptionCommProps);
|
||||||
|
|
||||||
|
const inputFactory = useInputFactory();
|
||||||
|
const { errClass, labelText } = inputFactory;
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'update:label-info']);
|
||||||
|
inputFactory.inputEmit = emit;
|
||||||
|
/**
|
||||||
|
* 实际的选择项
|
||||||
|
*/
|
||||||
|
const selectOptions = ref<OptionItemProps[]>([]);
|
||||||
|
|
||||||
|
const onChanged = (value: any, selectedOptions: any) => {
|
||||||
|
onOptionChanged(props, value as number | string, selectOptions, inputFactory);
|
||||||
|
const labelInfo: string[] = [];
|
||||||
|
if (selectedOptions) {
|
||||||
|
selectedOptions.forEach((option: OptionItemProps) => {
|
||||||
|
labelInfo.push(option.label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:label-info', labelInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.url && !props.url.fieldMap && !props.fieldMap) {
|
||||||
|
props.url.fieldMap = {
|
||||||
|
title: 'Name',
|
||||||
|
label: 'Name',
|
||||||
|
value: 'Id',
|
||||||
|
key: 'Id',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
loadOption(props.autoload, props, selectOptions, inputFactory);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
unloadOption(props, inputFactory);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="!selectOptions.length" class="absolute z-10 mt-[5px] mr-[10px] text-[#999] flex items-center">
|
||||||
|
<circleLoading class="text-[#555] mx-[5px] !ml-[10px] !w-4 !h-4" />
|
||||||
|
<span>数据加载中...</span>
|
||||||
|
</div>
|
||||||
|
<Cascader
|
||||||
|
:options="selectOptions"
|
||||||
|
:class="[errClass]"
|
||||||
|
:allow-clear="true"
|
||||||
|
:placeholder="selectOptions.length > 0 ? '请选择' + labelText : ''"
|
||||||
|
@change="onChanged"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error :deep(.ant-select-selector) {
|
||||||
|
border-color: #ef444480;
|
||||||
|
box-shadow: 0 0 3px 0 #ff4d4f;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
import { Checkbox, CheckboxGroup } from 'ant-design-vue';
|
||||||
|
import { loadOption, onOptionChanged, OptionItemProps, unloadOption, useInputFactory } from "@skyfox2000/webbase";
|
||||||
|
import { CheckboxValueType } from "ant-design-vue/es/checkbox/interface";
|
||||||
|
import { OptionCommProps } from "@skyfox2000/webbase";
|
||||||
|
|
||||||
|
const props = defineProps(OptionCommProps);
|
||||||
|
|
||||||
|
const inputFactory = useInputFactory();
|
||||||
|
|
||||||
|
const checkboxOptions = ref<OptionItemProps[]>([]);
|
||||||
|
|
||||||
|
const onChanged = (e: CheckboxValueType[]) => {
|
||||||
|
const checkedValue = e;
|
||||||
|
onOptionChanged(props, checkedValue as (number | string)[], checkboxOptions, inputFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.url && !props.url.fieldMap && !props.fieldMap) {
|
||||||
|
props.url.fieldMap = {
|
||||||
|
title: 'Name',
|
||||||
|
label: 'Name',
|
||||||
|
value: 'Id',
|
||||||
|
key: 'Id',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
loadOption(props.autoload, props, checkboxOptions, inputFactory)
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
unloadOption(props, inputFactory);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<CheckboxGroup @change="onChanged" class="w-full" v-bind="$attrs">
|
||||||
|
<Checkbox v-for="item in checkboxOptions" :key="item.value" :value="item.value"">
|
||||||
|
{{ item.label }}
|
||||||
|
</Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</template>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { DatePicker } from 'ant-design-vue';
|
||||||
|
import locale from 'ant-design-vue/es/date-picker/locale/zh_CN';
|
||||||
|
import { useInputFactory } from '@skyfox2000/webbase';
|
||||||
|
const props = defineProps<{
|
||||||
|
valueFormat?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const inputFactory = useInputFactory();
|
||||||
|
const { labelText, errClass } = inputFactory;
|
||||||
|
|
||||||
|
const dateFormat = ref<string>(props.valueFormat ?? 'YYYY-MM-DD');
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<DatePicker
|
||||||
|
:class="
|
||||||
|
errClass === 'error'
|
||||||
|
? ['error', '!border-red-300', 'shadow-[0_0_3px_0px_#ff4d4f]']
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="'请选择' + labelText"
|
||||||
|
:locale="locale"
|
||||||
|
:valueFormat="dateFormat"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { formValidate, useInputFactory } from '@skyfox2000/webbase';
|
||||||
|
import { Input } from 'ant-design-vue';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const { editorData, labelText, errClass } = useInputFactory();
|
||||||
|
const onBlur = () => {
|
||||||
|
if (errClass.value && editorData.value) {
|
||||||
|
/// 重新开始验证
|
||||||
|
formValidate(editorData.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
value?: any;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
const defaultValue = props.value;
|
||||||
|
// 内部值,拦截外部传入的 value
|
||||||
|
const innerValue = ref(props.value);
|
||||||
|
|
||||||
|
// 监听外部 value 的变化,更新内部值
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(newValue) => {
|
||||||
|
innerValue.value = newValue;
|
||||||
|
},
|
||||||
|
{ immediate: true }, // 立即同步初始值
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => innerValue.value,
|
||||||
|
(newValue) => {
|
||||||
|
emit('update:value', newValue); // 更新外部的 value
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClear = () => {
|
||||||
|
if (innerValue.value === '') {
|
||||||
|
emit('update:value', defaultValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Input
|
||||||
|
:class="errClass === 'error' ? ['error', '!border-red-300', 'shadow-[0_0_3px_0px_#ff4d4f]'] : ''"
|
||||||
|
v-model:value="innerValue"
|
||||||
|
@change="onClear"
|
||||||
|
:allow-clear="true"
|
||||||
|
:placeholder="'请输入' + labelText"
|
||||||
|
@blur="onBlur"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { formValidate, useInputFactory } from '@skyfox2000/webbase';
|
||||||
|
import { InputNumber } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const { editorData, labelText, errClass } = useInputFactory();
|
||||||
|
const onBlur = () => {
|
||||||
|
if (errClass.value && editorData.value) {
|
||||||
|
/// 重新开始验证
|
||||||
|
formValidate(editorData.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<InputNumber
|
||||||
|
:class="errClass === 'error' ? ['error', '!border-red-300', 'shadow-[0_0_3px_0px_#ff4d4f]'] : ''"
|
||||||
|
@blur="onBlur"
|
||||||
|
:allow-clear="false"
|
||||||
|
:placeholder="'请输入' + labelText"
|
||||||
|
class="w-[50%]"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { formValidate, useInputFactory } from "@skyfox2000/webbase";
|
||||||
|
import { InputPassword } from "ant-design-vue";
|
||||||
|
|
||||||
|
const { editorData, labelText, errClass } = useInputFactory();
|
||||||
|
const onBlur = () => {
|
||||||
|
if (errClass.value && editorData.value) {
|
||||||
|
/// 重新开始验证
|
||||||
|
formValidate(editorData.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<InputPassword :class="errClass === 'error' ? ['error', '!border-red-300', 'shadow-[0_0_3px_0px_#ff4d4f]'] : ''"
|
||||||
|
:allow-clear="true" :placeholder="'请输入' + labelText" @blur="onBlur" v-bind="$attrs" />
|
||||||
|
</template>
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { Input, Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
interface ConfigItem {
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
value: Record<string, string>;
|
||||||
|
labelHolder?: string;
|
||||||
|
valueHolder: string;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits(['update:config']);
|
||||||
|
|
||||||
|
const configList = ref<ConfigItem[]>([]);
|
||||||
|
|
||||||
|
const initConfigList = () => {
|
||||||
|
configList.value = Object.entries(props.value).map(([field, value]) => ({
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.value, () => {
|
||||||
|
initConfigList();
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
const updateConfig = () => {
|
||||||
|
const newConfig = configList.value.reduce((acc, item) => {
|
||||||
|
if (item.field) {
|
||||||
|
acc[item.field] = item.value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, string>);
|
||||||
|
emit('update:config', newConfig);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addNewLine = () => {
|
||||||
|
configList.value.push({
|
||||||
|
field: '',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = () => {
|
||||||
|
updateConfig();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div v-for="item in configList" :key="item.field" class="flex items-center gap-2">
|
||||||
|
<div class="w-[33%]">
|
||||||
|
<Input v-model:value="item.field" class="w-full" :placeholder="labelHolder || '配置名'"
|
||||||
|
@change="handleInputChange" />
|
||||||
|
</div>
|
||||||
|
<div class="w-[3%]">
|
||||||
|
=
|
||||||
|
</div>
|
||||||
|
<div class="w-[64%]">
|
||||||
|
<Input v-model:value="item.value" :placeholder="valueHolder" @change="handleInputChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button @click="addNewLine"
|
||||||
|
class="mt-1 w-[80px] !text-[12px] text-[#666] bg-[#e6f7ff] border-[#b3e0ff] hover:bg-[#b3e0ff] hover:border-[#8abeff]"
|
||||||
|
size="small">
|
||||||
|
新增配置行
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { Radio, RadioChangeEvent, RadioGroup } from 'ant-design-vue';
|
||||||
|
import { loadOption, onOptionChanged, OptionItemProps, unloadOption, useInputFactory } from '@skyfox2000/webbase';
|
||||||
|
import { OptionCommProps } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const props = defineProps(OptionCommProps);
|
||||||
|
|
||||||
|
const inputFactory = useInputFactory();
|
||||||
|
const { errClass } = inputFactory;
|
||||||
|
const radioOptions = ref<OptionItemProps[]>([]);
|
||||||
|
|
||||||
|
const onChanged = (e: RadioChangeEvent) => {
|
||||||
|
const checkedValue = e.target.value;
|
||||||
|
onOptionChanged(props, checkedValue as number | string, radioOptions, inputFactory);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.url && !props.url.fieldMap && !props.fieldMap) {
|
||||||
|
props.url.fieldMap = {
|
||||||
|
title: 'Name',
|
||||||
|
label: 'Name',
|
||||||
|
value: 'Id',
|
||||||
|
key: 'Id',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
loadOption(props.autoload, props, radioOptions, inputFactory);
|
||||||
|
if (props.all) radioOptions.value.unshift({ label: '全部', value: undefined });
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
unloadOption(props, inputFactory);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<RadioGroup :autocheck="false" @change="onChanged" class="w-full flex align-items" v-bind="$attrs">
|
||||||
|
<template v-for="item in radioOptions" :key="item.value">
|
||||||
|
<Radio :value="item.value" :class="errClass === 'error' ? ['error', '!text-red-400'] : ''">
|
||||||
|
{{ item.label }}
|
||||||
|
</Radio>
|
||||||
|
</template>
|
||||||
|
</RadioGroup>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.error :deep(input + span) {
|
||||||
|
border-color: #ff7171;
|
||||||
|
box-shadow: 0 0 3px 0 #ff4d4f;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,73 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { RangePicker } from 'ant-design-vue';
|
||||||
|
import locale from 'ant-design-vue/es/date-picker/locale/zh_CN';
|
||||||
|
import { useInputFactory } from '@skyfox2000/webbase';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
startDate?: string | null;
|
||||||
|
endDate?: string | null;
|
||||||
|
valueFormat?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
valueFormat: 'YYYY-MM-DD',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:startDate', value: string | null): void;
|
||||||
|
(e: 'update:endDate', value: string | null): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const inputFactory = useInputFactory();
|
||||||
|
const { errClass } = inputFactory;
|
||||||
|
|
||||||
|
const dateFormat = computed(() => props.valueFormat);
|
||||||
|
const rangeValue = computed(
|
||||||
|
(): [Dayjs, Dayjs] | [string, string] | undefined => {
|
||||||
|
const start = props.startDate;
|
||||||
|
const end = props.endDate;
|
||||||
|
if (!start || !end) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startDayjs = dayjs(start);
|
||||||
|
const endDayjs = dayjs(end);
|
||||||
|
if (!startDayjs.isValid() || !endDayjs.isValid()) return undefined;
|
||||||
|
return [startDayjs, endDayjs];
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChange = (
|
||||||
|
dates: [Dayjs, Dayjs] | [string, string] | null,
|
||||||
|
dateStrings: [string, string],
|
||||||
|
) => {
|
||||||
|
if (!dates || !dateStrings || dateStrings.length !== 2) {
|
||||||
|
emit('update:startDate', null);
|
||||||
|
emit('update:endDate', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:startDate', dateStrings[0] || null);
|
||||||
|
emit('update:endDate', dateStrings[1] || null);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangePicker
|
||||||
|
:class="
|
||||||
|
errClass === 'error'
|
||||||
|
? ['error', '!border-red-300', 'shadow-[0_0_3px_0px_#ff4d4f]']
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
class="w-full"
|
||||||
|
:locale="locale"
|
||||||
|
:value-format="dateFormat"
|
||||||
|
:value="rangeValue"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { Select, SelectOption } from 'ant-design-vue';
|
||||||
|
import type { DefaultOptionType, SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
import {
|
||||||
|
circleLoading,
|
||||||
|
useInputFactory,
|
||||||
|
OptionCommProps,
|
||||||
|
OptionItemProps,
|
||||||
|
onOptionChanged,
|
||||||
|
loadOption,
|
||||||
|
unloadOption,
|
||||||
|
} from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const props = defineProps(OptionCommProps);
|
||||||
|
|
||||||
|
const inputFactory = useInputFactory();
|
||||||
|
const { errClass, labelText } = inputFactory;
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'update:label-info']);
|
||||||
|
inputFactory.inputEmit = emit;
|
||||||
|
/**
|
||||||
|
* 实际的选择项
|
||||||
|
*/
|
||||||
|
const selectOptions = ref<OptionItemProps[]>([]);
|
||||||
|
|
||||||
|
const onChanged = (value: SelectValue, selectedOptions: DefaultOptionType | DefaultOptionType[]) => {
|
||||||
|
onOptionChanged(props, value as number | string, selectOptions, inputFactory);
|
||||||
|
|
||||||
|
const labelInfo: string[] = [];
|
||||||
|
if (typeof selectedOptions === 'object') selectedOptions = [selectedOptions];
|
||||||
|
selectedOptions.forEach((selectedOption: OptionItemProps) => {
|
||||||
|
labelInfo.push(selectedOption.label);
|
||||||
|
});
|
||||||
|
|
||||||
|
emit('update:label-info', labelInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.url && !props.url.fieldMap && !props.fieldMap) {
|
||||||
|
props.url.fieldMap = {
|
||||||
|
title: 'Name',
|
||||||
|
label: 'Name',
|
||||||
|
value: 'Id',
|
||||||
|
key: 'Id',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
loadOption(props.autoload, props, selectOptions, inputFactory);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
unloadOption(props, inputFactory);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="props.url && props.url.loading === true"
|
||||||
|
class="absolute z-10 mt-[5px] mr-[10px] text-[#999] flex items-center"
|
||||||
|
>
|
||||||
|
<circleLoading class="text-[#555] mx-[5px] !ml-[10px] !w-4 !h-4" />
|
||||||
|
<span>数据加载中...</span>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
:class="[errClass]"
|
||||||
|
@change="onChanged"
|
||||||
|
:placeholder="props.url && !props.url.loading ? '请选择' + labelText : ''"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:label-in-value="false"
|
||||||
|
>
|
||||||
|
<template v-for="item in selectOptions" :key="item.value">
|
||||||
|
<SelectOption :value="item.value">
|
||||||
|
{{ item.label }}
|
||||||
|
</SelectOption>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error :deep(.ant-select-selector) {
|
||||||
|
border-color: #ef4444;
|
||||||
|
box-shadow: 0 0 3px 0 #ff4d4f;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import { formValidate, useInputFactory, OptionItemProps, loadOption, unloadOption } from '@skyfox2000/webbase';
|
||||||
|
import { Switch } from 'ant-design-vue';
|
||||||
|
import message from 'vue-m-message';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* 选择项数据
|
||||||
|
*/
|
||||||
|
data: {
|
||||||
|
type: Array as PropType<Record<string, any>[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际的选择项
|
||||||
|
*/
|
||||||
|
const switchOptions = ref<OptionItemProps[]>([]);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'change', checked: boolean | string | number): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { editorData, errClass } = useInputFactory();
|
||||||
|
const onChange = (checked: boolean | string | number) => {
|
||||||
|
if (errClass.value && editorData.value) {
|
||||||
|
/// 重新开始验证
|
||||||
|
formValidate(editorData.value);
|
||||||
|
}
|
||||||
|
emit('change', checked);
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.data || props.data.length != 2) {
|
||||||
|
console.error('Switch组件: ', props.data);
|
||||||
|
message.error('Switch组件必须有且只有两个选项');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadOption(false, props, switchOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
unloadOption(props);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Switch
|
||||||
|
v-if="switchOptions.length === 2"
|
||||||
|
:class="[errClass === 'error' ? 'error !border-red-300 shadow-[0_0_3px_0px_#ff4d4f]' : '', 'w-[58px]']"
|
||||||
|
:checkedChildren="switchOptions[0].label"
|
||||||
|
:checkedValue="switchOptions[0].value"
|
||||||
|
:unCheckedChildren="switchOptions[1].label"
|
||||||
|
:unCheckedValue="switchOptions[1].value"
|
||||||
|
@change="onChange"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { formValidate, useInputFactory } from '@skyfox2000/webbase';
|
||||||
|
import { Textarea } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const { editorData, labelText, errClass } = useInputFactory();
|
||||||
|
const onBlur = () => {
|
||||||
|
if (errClass.value && editorData.value) {
|
||||||
|
/// 重新开始验证
|
||||||
|
formValidate(editorData.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Textarea
|
||||||
|
:class="errClass === 'error' ? ['error', '!border-red-300', 'shadow-[0_0_3px_0px_#ff4d4f]'] : ''"
|
||||||
|
:allow-clear="true"
|
||||||
|
:placeholder="'请输入' + labelText"
|
||||||
|
@blur="onBlur"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,43 @@
|
||||||
|
export { default as Button } from './common/button/index.vue';
|
||||||
|
export { default as Appicon } from './common/icon/appicon.vue';
|
||||||
|
export { default as Fullscreen } from './common/icon/fullscreen.vue';
|
||||||
|
export { default as Helper } from './common/icon/helper.vue';
|
||||||
|
export { default as Icon } from './common/icon/index.vue';
|
||||||
|
export { default as LayoutIcon } from './common/icon/layoutIcon.vue';
|
||||||
|
export { default as ToolIcon } from './common/icon/toolIcon.vue';
|
||||||
|
export { default as Tooltip } from './common/tooltip/index.vue';
|
||||||
|
export { default as Dialog } from './content/dialog/index.vue';
|
||||||
|
export { default as UploadForm } from './content/dialog/uploadForm.vue';
|
||||||
|
export { default as UploadList } from './content/dialog/uploadList.vue';
|
||||||
|
export { default as Drawer } from './content/drawer/index.vue';
|
||||||
|
export { default as FormItem } from './content/form/formItem.vue';
|
||||||
|
export { default as Form } from './content/form/index.vue';
|
||||||
|
export { default as Search } from './content/search/index.vue';
|
||||||
|
export { default as SearchItem } from './content/search/searchItem.vue';
|
||||||
|
export { default as Table } from './content/table/index.vue';
|
||||||
|
export { default as TableOperate } from './content/table/tableOperate.vue';
|
||||||
|
export { default as Icontool } from './content/toolbar/icontool.vue';
|
||||||
|
export { default as Toolbar } from './content/toolbar/index.vue';
|
||||||
|
export { default as Error403 } from './error/error403.vue';
|
||||||
|
export { default as Error404 } from './error/error404.vue';
|
||||||
|
export { default as AutoComplete } from './form/autoComplete/index.vue';
|
||||||
|
export { default as Cascader } from './form/cascader/index.vue';
|
||||||
|
export { default as Checkbox } from './form/checkbox/index.vue';
|
||||||
|
export { default as DatePicker } from './form/datePicker/index.vue';
|
||||||
|
export { default as Input } from './form/input/index.vue';
|
||||||
|
export { default as InputNumber } from './form/input/inputNumber.vue';
|
||||||
|
export { default as InputPassword } from './form/input/inputPassword.vue';
|
||||||
|
export { default as PropEditor } from './form/propEditor/index.vue';
|
||||||
|
export { default as Radio } from './form/radio/index.vue';
|
||||||
|
export { default as RangePicker } from './form/rangePicker/index.vue';
|
||||||
|
export { default as Select } from './form/select/index.vue';
|
||||||
|
export { default as Switch } from './form/switch/index.vue';
|
||||||
|
export { default as Textarea } from './form/textarea/index.vue';
|
||||||
|
export { default as Breadcrumb } from './layout/breadcrumb/index.vue';
|
||||||
|
export { default as Content } from './layout/content/index.vue';
|
||||||
|
export { default as Datetime } from './layout/datetime/index.vue';
|
||||||
|
export { default as HeaderExits } from './layout/header/headerExits.vue';
|
||||||
|
export { default as Header } from './layout/header/index.vue';
|
||||||
|
export { default as Menu } from './layout/menu/index.vue';
|
||||||
|
export { default as MenuTabs } from './layout/menu/menuTabs.vue';
|
||||||
|
export { default as BasicLayout } from './layout/page/basicLayout.vue';
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, watch } from 'vue';
|
||||||
|
import { Breadcrumb, theme } from 'ant-design-vue';
|
||||||
|
import { LayoutIcon } from '@/components';
|
||||||
|
import { BreadcrumbRoute, showBreadcrumb, crumbs, usePageInfo } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const { useToken } = theme;
|
||||||
|
const { token } = useToken();
|
||||||
|
|
||||||
|
const pageInfoStore = usePageInfo();
|
||||||
|
watch(
|
||||||
|
() => pageInfoStore.TabActive,
|
||||||
|
() => showBreadcrumb(),
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
showBreadcrumb();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ml-5 h-fit p-0 flex items-center justify-between"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: token.colorBgContainer,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<LayoutIcon icon="icon-home" class="w-[15px]" />
|
||||||
|
<span class="leading-[2.5] mx-[6px] text-[rgba(0,0,0,0.45)]">></span>
|
||||||
|
<Breadcrumb :routes="crumbs" separator="">
|
||||||
|
<template #itemRender="{ route }: { route: BreadcrumbRoute }">
|
||||||
|
<span class="text-xs leading-[3]">{{ route.breadcrumbName }}</span>
|
||||||
|
<LayoutIcon :icon="route.icon" fontsize="15px" />
|
||||||
|
<span v-if="route.index! < crumbs.length - 1" class="leading-[2.5] mx-[6px] text-[rgba(0,0,0,0.45)]"
|
||||||
|
>></span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</Breadcrumb>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { LayoutContent, theme } from 'ant-design-vue';
|
||||||
|
const { useToken } = theme;
|
||||||
|
const { token } = useToken();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="relative h-[calc(100vh-80px)] overflow-y-auto">
|
||||||
|
<LayoutContent class="m-[10px] p-[10px] h-fit min-h-[calc(100vh-100px)]" :style="{
|
||||||
|
backgroundColor: token.colorBgContainer,
|
||||||
|
}">
|
||||||
|
<slot></slot>
|
||||||
|
</LayoutContent>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
const DateTime = ref("")
|
||||||
|
onMounted(() => {
|
||||||
|
setInterval(() => {
|
||||||
|
const curTime = new Date()
|
||||||
|
const options: any = { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' };
|
||||||
|
const formattedTime = curTime.toLocaleString(undefined, options).replace(/\//g, '-').replace(',', '');
|
||||||
|
DateTime.value = curTime.getFullYear() + "-" + formattedTime
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="font-['Courier'] text-[#666]">{{ DateTime }}</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Modal, Flex } from 'ant-design-vue';
|
||||||
|
import { useUserInfo } from '@skyfox2000/webbase';
|
||||||
|
import { LayoutIcon } from '@/components';
|
||||||
|
|
||||||
|
const userInfoStore = useUserInfo();
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const confirmExit = () => {
|
||||||
|
userInfoStore.logout();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<LayoutIcon icon="icon-logout" @click="open = true" class="cursor-pointer w-5 h-5" />
|
||||||
|
<Modal v-model:open="open" title="确定退出?" ok-text="确定" cancel-text="取消" :width="380" @ok="confirmExit">
|
||||||
|
<Flex align="center" justify="flex-start" :style="{ padding: '0 32px', margin: '20px 0' }">
|
||||||
|
<LayoutIcon icon="icon-question-circle" color="orange" class="w-[60px] h-[60px]" />
|
||||||
|
<div style="margin: 0 0 0 20px; font-weight: 400; font-size: 16px">是否退出系统,<br />清除用户缓存信息?</div>
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { LayoutHeader, Avatar, theme, Space } from 'ant-design-vue';
|
||||||
|
import { useSettingInfo } from '@skyfox2000/webbase';
|
||||||
|
import { LayoutIcon } from '@/components';
|
||||||
|
import Breadcrumb from '../breadcrumb/index.vue';
|
||||||
|
import HeaderExits from './headerExits.vue';
|
||||||
|
|
||||||
|
const { useToken } = theme;
|
||||||
|
const { token } = useToken();
|
||||||
|
|
||||||
|
const settingInfoStore = useSettingInfo();
|
||||||
|
const onCollapseClick = () => {
|
||||||
|
settingInfoStore.setMenuCollapse(!settingInfoStore.menuCollapse);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<LayoutHeader
|
||||||
|
class="w-full relative z-[1] shadow-[0_-3px_6px_#000] py-0 flex items-center justify-between"
|
||||||
|
:style="{
|
||||||
|
height: '40px',
|
||||||
|
lineHeight: '1',
|
||||||
|
paddingLeft: '10px',
|
||||||
|
paddingRight: '10px',
|
||||||
|
backgroundColor: token.colorBgContainer,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<LayoutIcon
|
||||||
|
icon="icon-menu"
|
||||||
|
class="w-[18px] h-[18px] cursor-pointer"
|
||||||
|
:angle="settingInfoStore.menuCollapse ? 90 : 0"
|
||||||
|
@click="onCollapseClick"
|
||||||
|
/>
|
||||||
|
<Breadcrumb></Breadcrumb>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Space size="middle" class="flex items-center">
|
||||||
|
<Avatar class="avatar" :style="{ backgroundColor: '#f56a00', fontSize: '14px' }" :size="24"> U </Avatar>
|
||||||
|
<HeaderExits />
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</LayoutHeader>
|
||||||
|
</template>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { nextTick, onMounted, reactive, ref, watch } from 'vue';
|
||||||
|
import { ItemType, Menu } from 'ant-design-vue';
|
||||||
|
import type { MenuProps } from 'ant-design-vue';
|
||||||
|
import { routes } from '@/router';
|
||||||
|
import { AppRouter, initMenu, useSettingInfo, useAppInfo, usePageInfo } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
import { LayoutIcon } from '@/components';
|
||||||
|
|
||||||
|
const openKeys = ref<string[]>([]);
|
||||||
|
const selectedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
// 转换为树形结构
|
||||||
|
const menuData: Array<ItemType> = reactive([]);
|
||||||
|
|
||||||
|
const pageInfoStore = usePageInfo();
|
||||||
|
const onMenuClick: MenuProps['onClick'] = (menuInfo) => {
|
||||||
|
useAppInfo().push(menuInfo.key.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
const settingInfoStore = useSettingInfo();
|
||||||
|
const showOpenKeys = ref<string[]>([]);
|
||||||
|
const activeMenu = () => {
|
||||||
|
let subPath = pageInfoStore.TabActive;
|
||||||
|
const paths = subPath.split('/');
|
||||||
|
paths.pop();
|
||||||
|
openKeys.value = [paths.join('/')];
|
||||||
|
if (!settingInfoStore.menuCollapse) {
|
||||||
|
showOpenKeys.value = [paths.join('/')];
|
||||||
|
}
|
||||||
|
selectedKeys.value = [subPath];
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => settingInfoStore.menuCollapse,
|
||||||
|
(newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
showOpenKeys.value = [];
|
||||||
|
nextTick(() => {
|
||||||
|
showOpenKeys.value = [...openKeys.value];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => pageInfoStore.TabActive,
|
||||||
|
() => {
|
||||||
|
activeMenu();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initMenu<ItemType>(routes, menuData, LayoutIcon, { class: '!w-5 !h-5' });
|
||||||
|
pageInfoStore.setTabActive(AppRouter.currentRoute.value.path);
|
||||||
|
|
||||||
|
activeMenu();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Menu
|
||||||
|
v-model:openKeys="showOpenKeys"
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
mode="inline"
|
||||||
|
theme="dark"
|
||||||
|
:items="menuData"
|
||||||
|
@click="onMenuClick"
|
||||||
|
>
|
||||||
|
</Menu>
|
||||||
|
</template>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Tabs, TabPane, theme } from 'ant-design-vue';
|
||||||
|
import { useAppInfo, usePageInfo } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
import { LayoutIcon, Tooltip } from '@/components';
|
||||||
|
|
||||||
|
const { useToken } = theme;
|
||||||
|
const { token } = useToken();
|
||||||
|
|
||||||
|
const pageInfoStore = usePageInfo();
|
||||||
|
|
||||||
|
const onTabClicked = (tabKey: any) => {
|
||||||
|
useAppInfo().push(tabKey as string);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeTab = (key: string) => {
|
||||||
|
pageInfoStore.removeTabPane(key);
|
||||||
|
useAppInfo().push(pageInfoStore.TabActive);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div :style="{ height: '38px', backgroundColor: token.colorBgBase }">
|
||||||
|
<Tabs
|
||||||
|
:activeKey="pageInfoStore.TabActive"
|
||||||
|
hide-add
|
||||||
|
size="small"
|
||||||
|
:tabBarStyle="{ padding: '0 20px' }"
|
||||||
|
@tabClick="onTabClicked"
|
||||||
|
>
|
||||||
|
<TabPane v-for="pane in pageInfoStore.TabPanes" :key="pane.key">
|
||||||
|
<template #tab>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="flex">
|
||||||
|
{{ pane.title }}
|
||||||
|
</span>
|
||||||
|
<Tooltip title="关闭" placement="top" size="small">
|
||||||
|
<div
|
||||||
|
class="inline-block mx-auto relative flex items-center"
|
||||||
|
v-if="pageInfoStore.TabPanes.length > 1 && pane.closable"
|
||||||
|
@click.stop="closeTab(pane.key)"
|
||||||
|
>
|
||||||
|
<LayoutIcon
|
||||||
|
icon="icon-add"
|
||||||
|
:angle="45"
|
||||||
|
class="w-[15px] h-[15px] ml-1"
|
||||||
|
:tipcolor="token.colorBgSpotlight"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { Layout, LayoutSider } from 'ant-design-vue';
|
||||||
|
import Tooltip from '../../common/tooltip/index.vue';
|
||||||
|
import Icon from '../../common/icon/index.vue';
|
||||||
|
import Menu from '../menu/index.vue';
|
||||||
|
import Header from '../header/index.vue';
|
||||||
|
import MenuTabs from '../menu/menuTabs.vue';
|
||||||
|
|
||||||
|
import { useAppInfo, usePageInfo, useSettingInfo } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const appInfoStore = useAppInfo();
|
||||||
|
const settingInfoStore = useSettingInfo();
|
||||||
|
const pageInfoStore = usePageInfo();
|
||||||
|
|
||||||
|
const bodyClass = ref('h-[calc(100vh-80px)]');
|
||||||
|
watch(
|
||||||
|
() => settingInfoStore.fullscreen,
|
||||||
|
(newVal) => {
|
||||||
|
bodyClass.value = newVal ? 'h-[calc(100vh-40px)]' : 'h-[calc(100vh-80px)]';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Layout class="h-screen">
|
||||||
|
<LayoutSider
|
||||||
|
class="overflow-auto h-screen left-0 top-0 bottom-0"
|
||||||
|
v-model:collapsed="settingInfoStore.menuCollapse"
|
||||||
|
collapsible
|
||||||
|
v-if="!settingInfoStore.fullscreen"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-[40px] max-h-[40px] bg-[rgba(240,240,240,0.2)] flex flex-nowrap items-center justify-center text-white font-bold text-lg overflow-hidden text-ellipsis"
|
||||||
|
>
|
||||||
|
<Tooltip :title="settingInfoStore.menuCollapse ? appInfoStore.appInfo.Name : ''" placement="right">
|
||||||
|
<Icon :icon="appInfoStore.appInfo.Icon" fontsize="30px" size="26px" />
|
||||||
|
</Tooltip>
|
||||||
|
<span v-if="!settingInfoStore.menuCollapse" class="ml-[10px]">{{ appInfoStore.appInfo.Name }}</span>
|
||||||
|
</div>
|
||||||
|
<Menu></Menu>
|
||||||
|
</LayoutSider>
|
||||||
|
<Layout class="overflow-y-auto block">
|
||||||
|
<Header v-if="!settingInfoStore.fullscreen"></Header>
|
||||||
|
<MenuTabs v-if="pageInfoStore.TabEnabled"></MenuTabs>
|
||||||
|
<div class="relative overflow-y-auto" :class="bodyClass">
|
||||||
|
<router-view v-slot="{ Component, route }">
|
||||||
|
<keep-alive :include="appInfoStore.CachedComponents" :exclude="appInfoStore.ExcludeComponents">
|
||||||
|
<component :is="appInfoStore.cacheComponent(Component, route)" />
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</template>
|
|
@ -0,0 +1,67 @@
|
||||||
|
import '@/assets/styles/global.css';
|
||||||
|
import 'vue-m-message/dist/style.css';
|
||||||
|
|
||||||
|
import Message from 'vue-m-message';
|
||||||
|
|
||||||
|
import { createApp } from 'vue';
|
||||||
|
//引入状态管理
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||||
|
|
||||||
|
import { initMainAppData, isMicroApp } from '@skyfox2000/microbase';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import 'dayjs/locale/zh-cn';
|
||||||
|
dayjs.locale('zh-cn');
|
||||||
|
|
||||||
|
import App from '@/App.vue';
|
||||||
|
|
||||||
|
//引入路由
|
||||||
|
import router from '@/router';
|
||||||
|
import { appList, appRoutes } from '@/router/app-routes';
|
||||||
|
|
||||||
|
import { initValidate, useUserInfo, useHostInfo, useAppInfo } from '@skyfox2000/webbase';
|
||||||
|
import { SERVER_HOST } from '@skyfox2000/fapi';
|
||||||
|
|
||||||
|
const initializeApp = async () => {
|
||||||
|
initValidate();
|
||||||
|
const app = createApp(App);
|
||||||
|
const pinia = createPinia();
|
||||||
|
pinia.use(piniaPluginPersistedstate);
|
||||||
|
app.use(pinia);
|
||||||
|
app.use(Message);
|
||||||
|
|
||||||
|
const userInfoStore = useUserInfo();
|
||||||
|
userInfoStore.init();
|
||||||
|
|
||||||
|
if (!isMicroApp()) {
|
||||||
|
// 本地应用,使用本地配置加载方式
|
||||||
|
const hostInfoStore = useHostInfo();
|
||||||
|
await hostInfoStore.loadHostInfo(import.meta.env.VITE_SITEHOST_API, import.meta.env.VITE_SITEHOST);
|
||||||
|
} else {
|
||||||
|
SERVER_HOST.TOOL_ICONS = import.meta.env.VITE_TOOL_ICONS;
|
||||||
|
SERVER_HOST.MICROLAYOUT_ICONS = import.meta.env.VITE_MICROLAYOUT_ICONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appInfoStore = useAppInfo();
|
||||||
|
await appInfoStore.loadAppList(appList);
|
||||||
|
await appInfoStore.setAppRoutes(appRoutes);
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
app.mount('#app');
|
||||||
|
|
||||||
|
if (import.meta.env.VITE_LOGIN_TEST == 'true') {
|
||||||
|
await userInfoStore.login({
|
||||||
|
UserName: import.meta.env.VITE_LOGIN_TEST_USERNAME,
|
||||||
|
UserPass: import.meta.env.VITE_LOGIN_TEST_PASSWORD,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isMicroApp()) {
|
||||||
|
initMainAppData();
|
||||||
|
initializeApp();
|
||||||
|
} else {
|
||||||
|
initializeApp();
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { AppAction, AppInfo, AppSource } from '@skyfox2000/microbase';
|
||||||
|
import '/public/layout/appicons.js';
|
||||||
|
import { parseIcons } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
parseIcons({
|
||||||
|
iconUrl: '',
|
||||||
|
monoColor: false,
|
||||||
|
});
|
||||||
|
const BasicLayout = () => import('@/components/layout/page/basicLayout.vue');
|
||||||
|
|
||||||
|
export const Application: AppInfo = {
|
||||||
|
Id: 'hospital',
|
||||||
|
Name: '后台管理',
|
||||||
|
AppCode: 'hospital',
|
||||||
|
Version: '1.0',
|
||||||
|
Host: '/',
|
||||||
|
Source: AppSource.Manual,
|
||||||
|
Action: AppAction.App,
|
||||||
|
Default: true,
|
||||||
|
Icon: 'sym-app-setting',
|
||||||
|
Enabled: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appList = [Application];
|
||||||
|
|
||||||
|
export const appRoutes = [
|
||||||
|
{
|
||||||
|
path: '/business',
|
||||||
|
name: '业务管理',
|
||||||
|
component: BasicLayout,
|
||||||
|
icon: 'icon-home',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'order',
|
||||||
|
name: '订单管理',
|
||||||
|
component: () => import('@/views/business/order/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'priceqr',
|
||||||
|
name: '价码管理',
|
||||||
|
component: () => import('@/views/business/priceqr/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'helper',
|
||||||
|
name: '取样帮手',
|
||||||
|
component: () => import('@/views/business/helper/index.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system',
|
||||||
|
name: '系统管理',
|
||||||
|
component: BasicLayout,
|
||||||
|
icon: 'icon-setting',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'member',
|
||||||
|
name: '会员管理',
|
||||||
|
component: () => import('@/views/system/member/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'hospital',
|
||||||
|
name: '医院管理',
|
||||||
|
component: () => import('@/views/system/hospital/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'doctor',
|
||||||
|
name: '医生管理',
|
||||||
|
component: () => import('@/views/system/doctor/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'account',
|
||||||
|
name: '用户管理',
|
||||||
|
component: () => import('@/views/system/account/index.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { AppRouter, routes, useUserInfo } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const BasicLayout = () => import('@/components/layout/page/basicLayout.vue');
|
||||||
|
|
||||||
|
const NotFound404 = () => import('@/components/error/error404.vue');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认路由
|
||||||
|
*/
|
||||||
|
const defaultRoutes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
redirect: '/business/order',
|
||||||
|
beforeEnter: () => {
|
||||||
|
const userInfoStore = useUserInfo();
|
||||||
|
if (!userInfoStore.isLogin) {
|
||||||
|
AppRouter.replace('/login');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('@/views/login/index.vue'),
|
||||||
|
beforeEnter: () => {
|
||||||
|
const userInfoStore = useUserInfo();
|
||||||
|
if (userInfoStore.isLogin) {
|
||||||
|
AppRouter.replace('/business/order');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 404 路由
|
||||||
|
{
|
||||||
|
path: '/error', // 使用通配符 :catchAll 匹配所有未匹配到的路由
|
||||||
|
name: 'NotFound',
|
||||||
|
component: BasicLayout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '404',
|
||||||
|
name: '404',
|
||||||
|
component: NotFound404,
|
||||||
|
meta: { keepAlive: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
defaultRoutes.forEach((route) => {
|
||||||
|
routes.push(route);
|
||||||
|
AppRouter.addRoute(route);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { routes };
|
||||||
|
|
||||||
|
export default AppRouter;
|
|
@ -0,0 +1,343 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/**
|
||||||
|
* 取样帮手配置
|
||||||
|
*/
|
||||||
|
interface SamplingHelperEntity extends import('src/views/business/helper/model')['SamplingHelperEntity'] {
|
||||||
|
/**
|
||||||
|
* 配置ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 医生
|
||||||
|
*/
|
||||||
|
DoctorId: string | null;
|
||||||
|
/**
|
||||||
|
* 地区信息
|
||||||
|
*/
|
||||||
|
LocationInfo?: { [key: string]: any };
|
||||||
|
/**
|
||||||
|
* 取样流程
|
||||||
|
*/
|
||||||
|
Process: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单信息
|
||||||
|
*/
|
||||||
|
interface OrderEntity extends import('src/views/business/order/model')['OrderEntity'] {
|
||||||
|
/**
|
||||||
|
* 订单ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 所属会员
|
||||||
|
*/
|
||||||
|
MemberId: string | null;
|
||||||
|
/**
|
||||||
|
* 所属会员
|
||||||
|
*/
|
||||||
|
MemberName: string | null;
|
||||||
|
/**
|
||||||
|
* 价码ID
|
||||||
|
*/
|
||||||
|
PriceId: string | null;
|
||||||
|
/**
|
||||||
|
* 价码
|
||||||
|
*/
|
||||||
|
PriceCode: string | null;
|
||||||
|
/**
|
||||||
|
* 产品名称
|
||||||
|
*/
|
||||||
|
ProductName: string | null;
|
||||||
|
/**
|
||||||
|
* 会员地址
|
||||||
|
*/
|
||||||
|
Address: string | null;
|
||||||
|
/**
|
||||||
|
* 订单快递号
|
||||||
|
*/
|
||||||
|
ExpressNumber?: string | null;
|
||||||
|
/**
|
||||||
|
* 快递地址信息
|
||||||
|
*/
|
||||||
|
ExpressInfo?: { [key: string]: any };
|
||||||
|
/**
|
||||||
|
* 检查报告
|
||||||
|
*/
|
||||||
|
ReportFileUrl?: string | null;
|
||||||
|
/**
|
||||||
|
* 订单状态
|
||||||
|
*/
|
||||||
|
Status: string | null;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
Remark?: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 价码信息
|
||||||
|
*/
|
||||||
|
interface PriceQREntity extends import('src/views/business/priceqr/model')['PriceQREntity'] {
|
||||||
|
/**
|
||||||
|
* 价格ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 价格码
|
||||||
|
*/
|
||||||
|
Code: string | null;
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 单价
|
||||||
|
*/
|
||||||
|
Price: number | null;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
Remark?: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号
|
||||||
|
*/
|
||||||
|
interface AccountEntity extends import('src/views/system/account/model')['AccountEntity'] {
|
||||||
|
/**
|
||||||
|
* 帐号类型(Admin管理员/User操作员)
|
||||||
|
*/
|
||||||
|
AccountType: string | null;
|
||||||
|
/**
|
||||||
|
* 所属租户
|
||||||
|
*/
|
||||||
|
ClientId?: string | null;
|
||||||
|
/**
|
||||||
|
* 主部门
|
||||||
|
*/
|
||||||
|
DepartId?: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 主键Id
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 主职位
|
||||||
|
*/
|
||||||
|
JobTitleId?: string | null;
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 密码时间
|
||||||
|
*/
|
||||||
|
PassTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
Remark?: string | null;
|
||||||
|
/**
|
||||||
|
* 审核状态
|
||||||
|
*/
|
||||||
|
State?: string | null;
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
Tags?: string[];
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新人
|
||||||
|
*/
|
||||||
|
UpdateUser?: string | null;
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
UserCode: string | null;
|
||||||
|
/**
|
||||||
|
* 用户级别(Platform平台/Client租户/Member会员)
|
||||||
|
*/
|
||||||
|
UserLevel: string | null;
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*/
|
||||||
|
UserProps?: { [key: string]: any };
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
UserPwd?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医生账号
|
||||||
|
*/
|
||||||
|
interface DoctorEntity extends import('src/views/system/doctor/model')['DoctorEntity'] {
|
||||||
|
/**
|
||||||
|
* 医生ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
Mobile: string | null;
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 所属医院
|
||||||
|
*/
|
||||||
|
HospitalId: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医院信息
|
||||||
|
*/
|
||||||
|
interface HospitalEntity extends import('src/views/system/hospital/model')['HospitalEntity'] {
|
||||||
|
/**
|
||||||
|
* 医院ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 医院名称
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 医院地址
|
||||||
|
*/
|
||||||
|
Address?: string | null;
|
||||||
|
/**
|
||||||
|
* 联系电话
|
||||||
|
*/
|
||||||
|
Contact?: string | null;
|
||||||
|
/**
|
||||||
|
* 位置信息
|
||||||
|
*/
|
||||||
|
LocationInfo?: { [key: string]: any };
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会员信息
|
||||||
|
*/
|
||||||
|
interface MemberEntity extends import('src/views/system/member/model')['MemberEntity'] {
|
||||||
|
/**
|
||||||
|
* 会员ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
Mobile: string | null;
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 性别
|
||||||
|
*/
|
||||||
|
Gender: '男' | '女' | null;
|
||||||
|
/**
|
||||||
|
* 出生年月日
|
||||||
|
*/
|
||||||
|
BirthDate: string | null;
|
||||||
|
/**
|
||||||
|
* 微信openid
|
||||||
|
*/
|
||||||
|
WeChatOpenId: string | null;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
Remark?: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Drawer, Form, FormItem, Textarea, Switch, Cascader } from '@/components';
|
||||||
|
import { pageData, editorData } from './page';
|
||||||
|
import { OPTIONS } from '@skyfox2000/webbase';
|
||||||
|
import { locationTreeUrl } from '../../system/location/page';
|
||||||
|
import DoctorSelect from '@/views/system/doctor/select.vue';
|
||||||
|
|
||||||
|
const formData = ref<SamplingHelperEntity>(editorData.formData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Drawer title="取样帮手配置" :page-data="pageData">
|
||||||
|
<Form>
|
||||||
|
<FormItem label="所在地区" rule="LocationInfo.LocationIds">
|
||||||
|
<Cascader
|
||||||
|
v-model:value="formData.LocationInfo!.LocationIds"
|
||||||
|
:url="locationTreeUrl"
|
||||||
|
v-model:label-info="formData.LocationInfo!.LocationNames"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="医生" rule="DoctorId">
|
||||||
|
<DoctorSelect v-model:value="formData.DoctorId" :autoload="true" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="取样流程" rule="Process">
|
||||||
|
<Textarea v-model:value="formData.Process" :auto-size="{ minRows: 3, maxRows: 10 }" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="启用状态">
|
||||||
|
<Switch v-model:checked="formData.Enabled" :data="OPTIONS.EnableDisable" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Table } from '@/components';
|
||||||
|
import { pageData, useGridInit } from './page';
|
||||||
|
useGridInit();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Table :page-data="pageData" />
|
||||||
|
</template>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import { Content } from '@/components';
|
||||||
|
import Grid from './grid.vue';
|
||||||
|
import Search from './search.vue';
|
||||||
|
import { pageData, usePageInit } from './page';
|
||||||
|
usePageInit();
|
||||||
|
|
||||||
|
// 定义异步组件
|
||||||
|
const Editor = defineAsyncComponent(() => import('./editor.vue'));
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Content>
|
||||||
|
<Search />
|
||||||
|
<Grid />
|
||||||
|
</Content>
|
||||||
|
<component :is="Editor" v-if="pageData.editor?.visible" />
|
||||||
|
</template>
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* 取样帮手配置
|
||||||
|
*/
|
||||||
|
export interface SamplingHelperEntity {
|
||||||
|
/**
|
||||||
|
* 配置ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 医生
|
||||||
|
*/
|
||||||
|
DoctorId: string | null;
|
||||||
|
/**
|
||||||
|
* 地区信息
|
||||||
|
*/
|
||||||
|
LocationInfo?: { [key: string]: any };
|
||||||
|
/**
|
||||||
|
* 取样流程
|
||||||
|
*/
|
||||||
|
Process: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* 启动配置
|
||||||
|
* API操作
|
||||||
|
*/
|
||||||
|
import { usePageFactory, ApiUrls, ValidateRule } from '@skyfox2000/webbase';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const SamplingHelperUrl: ApiUrls = {
|
||||||
|
/**
|
||||||
|
* api
|
||||||
|
*/
|
||||||
|
api: 'PLATFORM_API',
|
||||||
|
authorize: true,
|
||||||
|
urls: {
|
||||||
|
/**
|
||||||
|
* 查询地址
|
||||||
|
*/
|
||||||
|
list: {
|
||||||
|
url: '/api/RCSamplingHelperSrv/list',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 保存地址
|
||||||
|
*/
|
||||||
|
save: {
|
||||||
|
url: '/api/RCSamplingHelperSrv/save',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除地址
|
||||||
|
*/
|
||||||
|
delete: {
|
||||||
|
url: '/api/RCSamplingHelperSrv/remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单默认数据
|
||||||
|
*/
|
||||||
|
const defaultData: SamplingHelperEntity = {
|
||||||
|
Id: null,
|
||||||
|
DoctorId: null,
|
||||||
|
LocationInfo: {
|
||||||
|
LocationIds: [],
|
||||||
|
},
|
||||||
|
Process: null,
|
||||||
|
Enabled: 1,
|
||||||
|
CreateTime: null,
|
||||||
|
UpdateTime: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单验证条件
|
||||||
|
* # 适当验证条件
|
||||||
|
* # async-validator的语法规范
|
||||||
|
*/
|
||||||
|
const formRules: Record<string, ValidateRule> = {
|
||||||
|
DoctorId: {
|
||||||
|
required: true,
|
||||||
|
message: '医生不能为空',
|
||||||
|
},
|
||||||
|
Process: {
|
||||||
|
required: true,
|
||||||
|
message: '取样流程不能为空',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const { editorData, gridData, pageData, usePageInit, useEditorInit, useGridInit } =
|
||||||
|
usePageFactory<SamplingHelperEntity>(SamplingHelperUrl, defaultData, formRules);
|
||||||
|
|
||||||
|
gridData.gridQuery = {
|
||||||
|
Option: {},
|
||||||
|
Query: {
|
||||||
|
$order: [['UpdateTime', 'desc']], // # 合适的默认查询条件,比如更新时间
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
以下为数据列表头定义
|
||||||
|
# 不需要显示CreateTime/UpdateTime
|
||||||
|
*/
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
title: '医生',
|
||||||
|
dataIndex: 'DoctorName',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所在地区',
|
||||||
|
key: 'LocationInfo.LocationNames',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
customRender: ({ record }: { record: HospitalEntity }) => {
|
||||||
|
return record.LocationInfo?.LocationNames ? record.LocationInfo?.LocationNames.join('/') : '';
|
||||||
|
},
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '取样流程',
|
||||||
|
dataIndex: 'Process',
|
||||||
|
width: 200,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'enabled',
|
||||||
|
width: 80,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['sm'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
gridData.columns = columns.value;
|
||||||
|
gridData.tableSize = 'small';
|
||||||
|
gridData.remotePage = false;
|
||||||
|
gridData.tools = ['Reload', 'Fullscreen'];
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Search, SearchItem, Input } from '@/components';
|
||||||
|
import { pageData } from './page';
|
||||||
|
|
||||||
|
// #合适的1至2个查询条件,以及下面对应的修改,
|
||||||
|
// #合适的组件,所有组件从@/components获取
|
||||||
|
const searchData = ref<Record<string, any>>({
|
||||||
|
DoctorName: undefined,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Search v-model:search="searchData" :page-data="pageData">
|
||||||
|
<SearchItem label="医生名">
|
||||||
|
<Input v-model:value="searchData.DoctorName" />
|
||||||
|
</SearchItem>
|
||||||
|
</Search>
|
||||||
|
</template>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Drawer, Form, FormItem, Input, Textarea } from '@/components';
|
||||||
|
import { pageData, editorData } from './page';
|
||||||
|
import Status from './status.vue';
|
||||||
|
|
||||||
|
const formData = ref<OrderEntity>(editorData.formData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Drawer title="订单信息" :page-data="pageData">
|
||||||
|
<Form>
|
||||||
|
<FormItem label="会员名">
|
||||||
|
<Input v-model:value="formData.MemberName" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="产品名称">
|
||||||
|
<Input v-model:value="formData.ProductName" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="价码">
|
||||||
|
<Input v-model:value="formData.PriceCode" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="快递号">
|
||||||
|
<Input v-model:value="formData.ExpressNumber" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="订单状态" rule="Status">
|
||||||
|
<Status v-model:value="formData.Status" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="备注">
|
||||||
|
<Textarea v-model:value="formData.Remark" :auto-size="{ minRows: 3, maxRows: 6 }" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Table } from '@/components';
|
||||||
|
import { pageData, useGridInit } from './page';
|
||||||
|
useGridInit();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Table :page-data="pageData" />
|
||||||
|
</template>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import { Content } from '@/components';
|
||||||
|
import UploadForm from '@/components/content/dialog/uploadForm.vue';
|
||||||
|
import Grid from './grid.vue';
|
||||||
|
import Search from './search.vue';
|
||||||
|
import { pageData, usePageInit } from './page';
|
||||||
|
usePageInit();
|
||||||
|
|
||||||
|
// 定义异步组件
|
||||||
|
const Editor = defineAsyncComponent(() => import('./editor.vue'));
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Content>
|
||||||
|
<Search />
|
||||||
|
<Grid />
|
||||||
|
</Content>
|
||||||
|
<component :is="Editor" v-if="pageData.editor?.visible" />
|
||||||
|
<UploadForm
|
||||||
|
v-if="pageData.subEditor!.uploadForm.visible"
|
||||||
|
width="600px"
|
||||||
|
:upload-form="pageData.subEditor!.uploadForm"
|
||||||
|
:max-count="1"
|
||||||
|
:page-data="pageData"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* 订单信息
|
||||||
|
*/
|
||||||
|
export interface OrderEntity {
|
||||||
|
/**
|
||||||
|
* 订单ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 所属会员
|
||||||
|
*/
|
||||||
|
MemberId: string | null;
|
||||||
|
/**
|
||||||
|
* 所属会员
|
||||||
|
*/
|
||||||
|
MemberName: string | null;
|
||||||
|
/**
|
||||||
|
* 价码ID
|
||||||
|
*/
|
||||||
|
PriceId: string | null;
|
||||||
|
/**
|
||||||
|
* 价码
|
||||||
|
*/
|
||||||
|
PriceCode: string | null;
|
||||||
|
/**
|
||||||
|
* 产品名称
|
||||||
|
*/
|
||||||
|
ProductName: string | null;
|
||||||
|
/**
|
||||||
|
* 会员地址
|
||||||
|
*/
|
||||||
|
Address: string | null;
|
||||||
|
/**
|
||||||
|
* 订单快递号
|
||||||
|
*/
|
||||||
|
ExpressNumber?: string | null;
|
||||||
|
/**
|
||||||
|
* 快递地址信息
|
||||||
|
*/
|
||||||
|
ExpressInfo?: { [key: string]: any };
|
||||||
|
/**
|
||||||
|
* 检查报告
|
||||||
|
*/
|
||||||
|
ReportFileUrl?: string | null;
|
||||||
|
/**
|
||||||
|
* 订单状态
|
||||||
|
*/
|
||||||
|
Status: string | null;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
Remark?: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
/**
|
||||||
|
* 启动配置
|
||||||
|
* API操作
|
||||||
|
*/
|
||||||
|
import { usePageFactory, ApiUrls, ValidateRule, ButtonTool, exportSelectedRows, EditorData } from '@skyfox2000/webbase';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import message from 'vue-m-message';
|
||||||
|
|
||||||
|
export const OrderUrl: ApiUrls = {
|
||||||
|
/**
|
||||||
|
* api
|
||||||
|
*/
|
||||||
|
api: 'PLATFORM_API',
|
||||||
|
authorize: true,
|
||||||
|
urls: {
|
||||||
|
/**
|
||||||
|
* 查询地址
|
||||||
|
*/
|
||||||
|
list: {
|
||||||
|
url: '/api/RCOrderSrv/list',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 保存地址
|
||||||
|
*/
|
||||||
|
save: {
|
||||||
|
url: '/api/RCOrderSrv/save',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除地址
|
||||||
|
*/
|
||||||
|
delete: {
|
||||||
|
url: '/api/RCOrderSrv/remove',
|
||||||
|
},
|
||||||
|
|
||||||
|
upload: {
|
||||||
|
url: '',
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
authorize: true, // 需要授权
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单默认数据
|
||||||
|
*/
|
||||||
|
const defaultData: OrderEntity = {
|
||||||
|
Id: null,
|
||||||
|
MemberId: null,
|
||||||
|
MemberName: null,
|
||||||
|
PriceId: null,
|
||||||
|
PriceCode: null,
|
||||||
|
ProductName: null,
|
||||||
|
Address: null,
|
||||||
|
ExpressNumber: null,
|
||||||
|
ExpressInfo: {},
|
||||||
|
ReportFileUrl: null,
|
||||||
|
Status: null,
|
||||||
|
Remark: null,
|
||||||
|
Enabled: 1,
|
||||||
|
CreateTime: null,
|
||||||
|
UpdateTime: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单验证条件
|
||||||
|
* # 适当验证条件
|
||||||
|
* # async-validator的语法规范
|
||||||
|
*/
|
||||||
|
const formRules: Record<string, ValidateRule> = {
|
||||||
|
Status: {
|
||||||
|
required: true,
|
||||||
|
message: '订单状态不能为空',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const { editorData, gridData, pageData, usePageInit, useEditorInit, useGridInit } = usePageFactory<OrderEntity>(
|
||||||
|
OrderUrl,
|
||||||
|
defaultData,
|
||||||
|
formRules,
|
||||||
|
);
|
||||||
|
|
||||||
|
editorData.saveAsBtnVisible = false;
|
||||||
|
|
||||||
|
gridData.gridQuery = {
|
||||||
|
Option: {},
|
||||||
|
Query: {
|
||||||
|
$order: [['UpdateTime', 'desc']], // # 合适的默认查询条件,比如更新时间
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
以下为数据列表头定义
|
||||||
|
# 不需要显示CreateTime/UpdateTime
|
||||||
|
*/
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
title: '订单号',
|
||||||
|
dataIndex: 'OrderNo',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '会员名',
|
||||||
|
dataIndex: 'MemberName',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '手机号',
|
||||||
|
dataIndex: 'Mobile',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '价码',
|
||||||
|
dataIndex: 'PriceCode',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['lg'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单快递号',
|
||||||
|
dataIndex: 'ExpressNumber',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
responsive: ['lg'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单状态',
|
||||||
|
dataIndex: 'Status',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 150,
|
||||||
|
export: false,
|
||||||
|
responsive: ['sm'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
gridData.columns = columns.value;
|
||||||
|
gridData.tableSize = 'small';
|
||||||
|
gridData.remotePage = false;
|
||||||
|
gridData.selectKeys = [];
|
||||||
|
gridData.selectRows = [];
|
||||||
|
gridData.selectable = true;
|
||||||
|
let download: ButtonTool = {
|
||||||
|
label: '下载',
|
||||||
|
key: 'download',
|
||||||
|
type: 'primary',
|
||||||
|
icon: 'icon-download',
|
||||||
|
click: () => {
|
||||||
|
if (gridData.selectRows && gridData.selectRows.length > 0) {
|
||||||
|
exportSelectedRows<OrderEntity>('订单-{YYYY-MM-DD}.csv', columns.value, gridData.selectRows);
|
||||||
|
} else {
|
||||||
|
message.warning('请选择需要下载的订单!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
gridData.buttons = ['New', download];
|
||||||
|
|
||||||
|
// 文件上传弹窗
|
||||||
|
let uploadForm: EditorData<OrderEntity> = {
|
||||||
|
visible: false,
|
||||||
|
formData: JSON.parse(JSON.stringify(defaultData)),
|
||||||
|
isFormSaving: false,
|
||||||
|
isFormLoading: false,
|
||||||
|
};
|
||||||
|
pageData.value.subEditor = {
|
||||||
|
uploadForm,
|
||||||
|
};
|
||||||
|
|
||||||
|
gridData.tools = ['Reload', 'Fullscreen'];
|
||||||
|
let upload: ButtonTool = {
|
||||||
|
label: '上传报告',
|
||||||
|
key: 'upload',
|
||||||
|
type: 'default',
|
||||||
|
icon: 'icon-upload',
|
||||||
|
click: (_, row) => {
|
||||||
|
pageData.value.subEditor!.uploadForm.formData = row;
|
||||||
|
pageData.value.subEditor!.uploadForm.visible = !pageData.value.subEditor!.uploadForm.visible;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
gridData.operates = [upload, 'Edit', 'Delete'];
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Search, SearchItem, Input } from '@/components';
|
||||||
|
import { pageData } from './page';
|
||||||
|
import Status from './status.vue';
|
||||||
|
|
||||||
|
// #合适的1至2个查询条件,以及下面对应的修改,
|
||||||
|
// #合适的组件,所有组件从@/components获取
|
||||||
|
const searchData = ref<Record<string, any>>({
|
||||||
|
OrderNo: null,
|
||||||
|
Mobile: null,
|
||||||
|
MemberName: null,
|
||||||
|
ProductName: null,
|
||||||
|
Status: null,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Search v-model:search="searchData" :page-data="pageData">
|
||||||
|
<SearchItem label="订单号">
|
||||||
|
<Input v-model:value="searchData.OrderNo" />
|
||||||
|
</SearchItem>
|
||||||
|
<SearchItem label="手机号">
|
||||||
|
<Input v-model:value="searchData.Mobile" />
|
||||||
|
</SearchItem>
|
||||||
|
<SearchItem label="会员名">
|
||||||
|
<Input v-model:value="searchData.MemberName" />
|
||||||
|
</SearchItem>
|
||||||
|
<SearchItem label="产品名称">
|
||||||
|
<Input v-model:value="searchData.ProductName" />
|
||||||
|
</SearchItem>
|
||||||
|
<SearchItem label="订单状态">
|
||||||
|
<Status :all="true" v-model:value="searchData.Status" />
|
||||||
|
</SearchItem>
|
||||||
|
</Search>
|
||||||
|
</template>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Select } from '@/components';
|
||||||
|
const props = defineProps({
|
||||||
|
all: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const options: Array<{
|
||||||
|
label: string;
|
||||||
|
value: string | null;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
label: '待付款',
|
||||||
|
value: '待付款',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '待发货',
|
||||||
|
value: '待发货',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '待收货',
|
||||||
|
value: '待收货',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已完成',
|
||||||
|
value: '已完成',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已取消',
|
||||||
|
value: '已取消',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (props.all) {
|
||||||
|
options.unshift({
|
||||||
|
label: '全部',
|
||||||
|
value: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Select :data="options"></Select>
|
||||||
|
</template>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Drawer, Form, FormItem, Input, Textarea, InputNumber, Switch } from '@/components';
|
||||||
|
import { pageData, editorData } from './page';
|
||||||
|
import { OPTIONS } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const formData = ref<PriceQREntity>(editorData.formData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Drawer title="价码信息" :page-data="pageData">
|
||||||
|
<Form>
|
||||||
|
<FormItem label="名称" rule="Name">
|
||||||
|
<Input v-model:value="formData.Name" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="价格码" rule="Code">
|
||||||
|
<Input v-model:value="formData.Code" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="单价" rule="Price">
|
||||||
|
<InputNumber v-model:value="formData.Price" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="备注">
|
||||||
|
<Textarea v-model:value="formData.Remark" :auto-size="{ minRows: 2, maxRows: 5 }" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="启用状态">
|
||||||
|
<Switch v-model:checked="formData.Enabled" :data="OPTIONS.EnableDisable" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Table } from '@/components';
|
||||||
|
import { pageData, useGridInit } from './page';
|
||||||
|
useGridInit();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Table :page-data="pageData" />
|
||||||
|
</template>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import { Content } from '@/components';
|
||||||
|
import Grid from './grid.vue';
|
||||||
|
import Search from './search.vue';
|
||||||
|
import { pageData, usePageInit } from './page';
|
||||||
|
usePageInit();
|
||||||
|
|
||||||
|
// 定义异步组件
|
||||||
|
const Editor = defineAsyncComponent(() => import('./editor.vue'));
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Content>
|
||||||
|
<Search />
|
||||||
|
<Grid />
|
||||||
|
</Content>
|
||||||
|
<component :is="Editor" v-if="pageData.editor?.visible" />
|
||||||
|
</template>
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* 价码信息
|
||||||
|
*/
|
||||||
|
export interface PriceQREntity {
|
||||||
|
/**
|
||||||
|
* 价格ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 价格码
|
||||||
|
*/
|
||||||
|
Code: string | null;
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 单价
|
||||||
|
*/
|
||||||
|
Price: number | null;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
Remark?: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
/**
|
||||||
|
* 启动配置
|
||||||
|
* API操作
|
||||||
|
*/
|
||||||
|
import { usePageFactory, ApiUrls, ValidateRule } from '@skyfox2000/webbase';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const PriceQRUrl: ApiUrls = {
|
||||||
|
/**
|
||||||
|
* api
|
||||||
|
*/
|
||||||
|
api: 'PLATFORM_API',
|
||||||
|
authorize: true,
|
||||||
|
urls: {
|
||||||
|
/**
|
||||||
|
* 查询地址
|
||||||
|
*/
|
||||||
|
find: {
|
||||||
|
url: '/api/RCPriceQRSrv/find',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 保存地址
|
||||||
|
*/
|
||||||
|
save: {
|
||||||
|
url: '/api/RCPriceQRSrv/save',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除地址
|
||||||
|
*/
|
||||||
|
delete: {
|
||||||
|
url: '/api/RCPriceQRSrv/remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单默认数据
|
||||||
|
*/
|
||||||
|
const defaultData: PriceQREntity = {
|
||||||
|
Id: null,
|
||||||
|
Code: null,
|
||||||
|
Name: null,
|
||||||
|
Price: null,
|
||||||
|
Remark: null,
|
||||||
|
Enabled: 1,
|
||||||
|
CreateTime: null,
|
||||||
|
UpdateTime: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单验证条件
|
||||||
|
* # 适当验证条件
|
||||||
|
* # async-validator的语法规范
|
||||||
|
*/
|
||||||
|
const formRules: Record<string, ValidateRule> = {
|
||||||
|
Code: {
|
||||||
|
required: true,
|
||||||
|
message: '价格码不能为空',
|
||||||
|
},
|
||||||
|
Name: {
|
||||||
|
required: true,
|
||||||
|
message: '名称不能为空',
|
||||||
|
},
|
||||||
|
Price: {
|
||||||
|
required: true,
|
||||||
|
message: '单价不能为空',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const { editorData, gridData, pageData, usePageInit, useEditorInit, useGridInit } =
|
||||||
|
usePageFactory<PriceQREntity>(PriceQRUrl, defaultData, formRules);
|
||||||
|
|
||||||
|
gridData.gridQuery = {
|
||||||
|
Option: {},
|
||||||
|
Query: {
|
||||||
|
$order: [['UpdateTime', 'desc']], // # 合适的默认查询条件,比如更新时间
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
以下为数据列表头定义
|
||||||
|
# 不需要显示CreateTime/UpdateTime
|
||||||
|
*/
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'Name',
|
||||||
|
width: 150,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '价格码',
|
||||||
|
dataIndex: 'Code',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '单价',
|
||||||
|
dataIndex: 'Price',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
dataIndex: 'Remark',
|
||||||
|
width: 150,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'enabled',
|
||||||
|
width: 80,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['sm'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
gridData.columns = columns.value;
|
||||||
|
gridData.tableSize = 'small';
|
||||||
|
gridData.tools = ['Reload', 'Fullscreen'];
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { Search, SearchItem, Input } from "@/components";
|
||||||
|
import { pageData } from "./page";
|
||||||
|
|
||||||
|
// #合适的1至2个查询条件,以及下面对应的修改,
|
||||||
|
// #合适的组件,所有组件从@/components获取
|
||||||
|
const searchData = ref<Record<string, any>>({
|
||||||
|
Code: undefined,
|
||||||
|
Name: undefined,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Search v-model:search="searchData" :page-data="pageData">
|
||||||
|
<SearchItem label="价格码">
|
||||||
|
<Input v-model:value="searchData.Code" />
|
||||||
|
</SearchItem>
|
||||||
|
<SearchItem label="名称">
|
||||||
|
<Input v-model:value="searchData.Name" />
|
||||||
|
</SearchItem>
|
||||||
|
</Search>
|
||||||
|
</template>
|
|
@ -0,0 +1,49 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import LoginBox from './login.vue';
|
||||||
|
import { HostInfo } from '@skyfox2000/microbase';
|
||||||
|
import { getHostInfo } from '@skyfox2000/webbase';
|
||||||
|
const hostInfo = ref<HostInfo>({
|
||||||
|
Host: '',
|
||||||
|
Title: '管理系统',
|
||||||
|
FullTitle: '管理系统',
|
||||||
|
Provider: 'XXXX有限公司',
|
||||||
|
});
|
||||||
|
|
||||||
|
hostInfo.value = getHostInfo();
|
||||||
|
|
||||||
|
document.title = '用户登录-' + hostInfo.value.Title;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-white text-[#424242]">
|
||||||
|
<!-- Header -->
|
||||||
|
<header
|
||||||
|
class="w-full h-[100px] bg-gradient-to-r from-[#618dde] to-[#e3f1ff] flex justify-start items-center pl-[50px]"
|
||||||
|
>
|
||||||
|
<div class="text-[28px] text-white">{{ hostInfo.Title }}</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<main
|
||||||
|
class="w-full bg-[#e3f1ff] border-t-4 border-b-4 border-[#769BDA] flex justify-end items-center min-h-[calc(100vh-200px)] bg-no-repeat bg-[url('@/assets/images/login/banner.png')] bg-[25%_center] bg-[length:600px_auto]"
|
||||||
|
>
|
||||||
|
<div class="w-full pr-[9%]">
|
||||||
|
<div class="mx-auto max-w-[1280px] flex justify-end">
|
||||||
|
<LoginBox :title="hostInfo.Title" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-[#f5f5f5]/90 w-full min-h-[100px] flex justify-start items-center">
|
||||||
|
<div class="w-full flex justify-around items-center">
|
||||||
|
<div class="w-1/3"></div>
|
||||||
|
<div class="w-1/3 text-center text-gray-500">
|
||||||
|
© 2023 {{ hostInfo.FullTitle }} - {{ hostInfo.Provider }} 提供技术支持
|
||||||
|
</div>
|
||||||
|
<div class="w-1/3"></div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,151 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import Message from 'vue-m-message';
|
||||||
|
import { Input, InputPassword } from '@/components';
|
||||||
|
import { userLogin } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
title: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLoginClicked = ref(userLogin);
|
||||||
|
|
||||||
|
const name = ref('');
|
||||||
|
const password = ref('');
|
||||||
|
|
||||||
|
const nameTip = ref('请输入用户名');
|
||||||
|
const nameStatus = ref<'' | 'error' | 'warning' | undefined>('');
|
||||||
|
const passwordTip = ref('请输入密码');
|
||||||
|
const passwordStatus = ref<'' | 'error' | 'warning' | undefined>('');
|
||||||
|
|
||||||
|
watch(name, () => {
|
||||||
|
nameStatus.value = '';
|
||||||
|
nameTip.value = '请输入用户名';
|
||||||
|
});
|
||||||
|
watch(password, () => {
|
||||||
|
passwordStatus.value = '';
|
||||||
|
passwordTip.value = '请输入密码';
|
||||||
|
});
|
||||||
|
|
||||||
|
const logining = ref<boolean>(false);
|
||||||
|
const onClicked = async () => {
|
||||||
|
nameTip.value = '请输入用户名';
|
||||||
|
nameStatus.value = '';
|
||||||
|
passwordTip.value = '请输入密码';
|
||||||
|
passwordStatus.value = '';
|
||||||
|
if (name.value === '') {
|
||||||
|
nameTip.value = '用户名不能为空';
|
||||||
|
nameStatus.value = 'error';
|
||||||
|
}
|
||||||
|
if (password.value === '') {
|
||||||
|
passwordTip.value = '密码不能为空';
|
||||||
|
passwordStatus.value = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.value !== '' && password.value !== '') {
|
||||||
|
if (onLoginClicked.value) {
|
||||||
|
logining.value = true;
|
||||||
|
const result = await onLoginClicked.value({
|
||||||
|
UserName: name.value,
|
||||||
|
UserPass: password.value,
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
logining.value = false;
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
if (result && result.errno) {
|
||||||
|
nameTip.value = result.msg!;
|
||||||
|
nameStatus.value = 'error';
|
||||||
|
passwordTip.value = result.msg!;
|
||||||
|
passwordStatus.value = 'error';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Message.error('未配置登录接口!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="w-[300px] h-[343px] flex mt-[2px] border-[3px] border-blue-300 rounded-[10px] shadow-lg flex-col items-center bg-white px-[16px]"
|
||||||
|
>
|
||||||
|
<div class="w-[84%]">
|
||||||
|
<div class="text-center text-gray-500 font-medium mt-[30px] mb-[22px]">{{ title }} —— 用户登录</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-[84%] h-[74px]">
|
||||||
|
<Input placeholder="请输入用户名" v-model:value="name" :status="nameStatus" :maxlength="30">
|
||||||
|
<template #prefix>
|
||||||
|
<i class="iconfont icon-user text-gray-400" :class="[nameStatus === 'error' ? 'text-red-400' : '']"></i>
|
||||||
|
</template>
|
||||||
|
</Input>
|
||||||
|
<span class="mt-1 hidden" :class="[nameStatus]">{{ nameTip }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-[84%] h-[70px]">
|
||||||
|
<InputPassword placeholder="请输入密码" v-model:value="password" :status="passwordStatus" :maxlength="30">
|
||||||
|
<template #prefix>
|
||||||
|
<i
|
||||||
|
class="iconfont icon-mima text-gray-400"
|
||||||
|
:class="[passwordStatus === 'error' ? 'text-red-400' : '']"
|
||||||
|
></i>
|
||||||
|
</template>
|
||||||
|
</InputPassword>
|
||||||
|
<span class="mt-1 hidden" :class="[passwordStatus]">{{ passwordTip }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-[84%] h-[34px]">
|
||||||
|
<!-- <Col>忘记密码?</Col> -->
|
||||||
|
</div>
|
||||||
|
<div class="w-[84%]">
|
||||||
|
<button
|
||||||
|
:class="[
|
||||||
|
'w-full',
|
||||||
|
'border-[2px]',
|
||||||
|
'border-solid',
|
||||||
|
'border-[#90B0E3] hover:border-[#769BDA]',
|
||||||
|
'bg-[#769BDA]' /* 替换为接近 #4476D3 的 Tailwind 颜色 */,
|
||||||
|
'focus:ring-4',
|
||||||
|
'focus:ring-blue-300',
|
||||||
|
'text-base' /* 调整文字大小 */,
|
||||||
|
'font-normal' /* 调整文字粗细为正常 */,
|
||||||
|
'text-white',
|
||||||
|
'text-center',
|
||||||
|
'py-2',
|
||||||
|
'px-4',
|
||||||
|
'rounded-xl' /* 增加圆角 */,
|
||||||
|
'disabled:bg-gray-300',
|
||||||
|
'disabled:cursor-not-allowed',
|
||||||
|
logining ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-[#90B0E3]',
|
||||||
|
]"
|
||||||
|
@click="onClicked"
|
||||||
|
:disabled="logining"
|
||||||
|
>
|
||||||
|
<i class="anticon-spin iconfont icon-loading loading" v-if="logining"></i>
|
||||||
|
<span class="ml-[10px]">登 录</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.loading {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 17px;
|
||||||
|
|
||||||
|
animation: loading 2s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
display: block;
|
||||||
|
color: #ff0000;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Drawer, Form, FormItem, Input, Textarea, InputPassword, Switch } from '@/components';
|
||||||
|
import { pageData, editorData } from './page';
|
||||||
|
import { OPTIONS } from '@skyfox2000/webbase';
|
||||||
|
|
||||||
|
const formData = ref<AccountEntity>(editorData.formData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Drawer title="账号信息" :page-data="pageData">
|
||||||
|
<Form>
|
||||||
|
<FormItem label="登录账号" rule="UserCode">
|
||||||
|
<Input v-model:value="formData.UserCode" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="姓名" rule="Name">
|
||||||
|
<Input v-model:value="formData.Name" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="密码" rule="UserPwd">
|
||||||
|
<InputPassword v-model:value="formData.UserPwd" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="备注">
|
||||||
|
<Textarea v-model:value="formData.Remark" :auto-size="{ minRows: 2, maxRows: 5 }" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="启用状态">
|
||||||
|
<Switch v-model:checked="formData.Enabled" :data="OPTIONS.EnableDisable" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Table } from '@/components';
|
||||||
|
import { pageData, useGridInit } from './page';
|
||||||
|
useGridInit();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Table :page-data="pageData" />
|
||||||
|
</template>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import { Content } from '@/components';
|
||||||
|
import Grid from './grid.vue';
|
||||||
|
import Search from './search.vue';
|
||||||
|
import { pageData, usePageInit } from './page';
|
||||||
|
usePageInit();
|
||||||
|
|
||||||
|
// 定义异步组件
|
||||||
|
const Editor = defineAsyncComponent(() => import('./editor.vue'));
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Content>
|
||||||
|
<Search />
|
||||||
|
<Grid />
|
||||||
|
</Content>
|
||||||
|
<component :is="Editor" v-if="pageData.editor?.visible" />
|
||||||
|
</template>
|
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* 账号
|
||||||
|
*/
|
||||||
|
export interface AccountEntity {
|
||||||
|
/**
|
||||||
|
* 帐号类型(Admin管理员/User操作员)
|
||||||
|
*/
|
||||||
|
AccountType: string | null;
|
||||||
|
/**
|
||||||
|
* 所属租户
|
||||||
|
*/
|
||||||
|
ClientId?: string | null;
|
||||||
|
/**
|
||||||
|
* 主部门
|
||||||
|
*/
|
||||||
|
DepartId?: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 主键Id
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 主职位
|
||||||
|
*/
|
||||||
|
JobTitleId?: string | null;
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 密码时间
|
||||||
|
*/
|
||||||
|
PassTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
Remark?: string | null;
|
||||||
|
/**
|
||||||
|
* 审核状态
|
||||||
|
*/
|
||||||
|
State?: string | null;
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
Tags?: string[];
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新人
|
||||||
|
*/
|
||||||
|
UpdateUser?: string | null;
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
UserCode: string | null;
|
||||||
|
/**
|
||||||
|
* 用户级别(Platform平台/Client租户/Member会员)
|
||||||
|
*/
|
||||||
|
UserLevel: string | null;
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*/
|
||||||
|
UserProps?: { [key: string]: any };
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
UserPwd?: string | null;
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* 启动配置
|
||||||
|
* API操作
|
||||||
|
*/
|
||||||
|
import { usePageFactory, ApiUrls, ValidateRule } from '@skyfox2000/webbase';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const AccountUrl: ApiUrls = {
|
||||||
|
/**
|
||||||
|
* api
|
||||||
|
*/
|
||||||
|
api: 'PLATFORM_API',
|
||||||
|
authorize: true,
|
||||||
|
urls: {
|
||||||
|
/**
|
||||||
|
* 查询地址
|
||||||
|
*/
|
||||||
|
find: {
|
||||||
|
url: '/api/RCAccountOpSrv/find',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 保存地址
|
||||||
|
*/
|
||||||
|
save: {
|
||||||
|
url: '/api/RCAccountOpSrv/save',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除地址
|
||||||
|
*/
|
||||||
|
delete: {
|
||||||
|
url: '/api/RCAccountOpSrv/remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单默认数据
|
||||||
|
*/
|
||||||
|
const defaultData: AccountEntity = {
|
||||||
|
Id: null,
|
||||||
|
ClientId: null,
|
||||||
|
UserCode: null,
|
||||||
|
UserPwd: null,
|
||||||
|
Name: null,
|
||||||
|
AccountType: 'Admin',
|
||||||
|
UserLevel: 'Platform',
|
||||||
|
JobTitleId: null,
|
||||||
|
DepartId: null,
|
||||||
|
Tags: [],
|
||||||
|
UserProps: {},
|
||||||
|
PassTime: null,
|
||||||
|
Remark: null,
|
||||||
|
State: null,
|
||||||
|
Enabled: 1,
|
||||||
|
UpdateTime: null,
|
||||||
|
UpdateUser: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单验证条件
|
||||||
|
* # 适当验证条件
|
||||||
|
* # async-validator的语法规范
|
||||||
|
*/
|
||||||
|
const formRules: Record<string, ValidateRule> = {
|
||||||
|
UserCode: {
|
||||||
|
required: true,
|
||||||
|
message: '登录账号不能为空',
|
||||||
|
},
|
||||||
|
Name: {
|
||||||
|
required: true,
|
||||||
|
message: '姓名不能为空',
|
||||||
|
},
|
||||||
|
UserPwd: {
|
||||||
|
required: true,
|
||||||
|
message: '密码不能为空',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const { editorData, gridData, pageData, usePageInit, useEditorInit, useGridInit } =
|
||||||
|
usePageFactory<AccountEntity>(AccountUrl, defaultData, formRules);
|
||||||
|
|
||||||
|
gridData.gridQuery = {
|
||||||
|
Option: {},
|
||||||
|
Query: {
|
||||||
|
$order: [['UpdateTime', 'desc']], // # 合适的默认查询条件,比如更新时间
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
以下为数据列表头定义
|
||||||
|
# 不需要显示CreateTime/UpdateTime
|
||||||
|
*/
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
title: '登录账号',
|
||||||
|
dataIndex: 'UserCode',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'Name',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
dataIndex: 'Remark',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'enabled',
|
||||||
|
width: 80,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['sm'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
gridData.columns = columns.value;
|
||||||
|
gridData.tableSize = 'small';
|
||||||
|
gridData.tools = ['Reload', 'Fullscreen'];
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { Search, SearchItem, Input } from "@/components";
|
||||||
|
import { pageData } from "./page";
|
||||||
|
|
||||||
|
const searchData = ref<Record<string, any>>({
|
||||||
|
UserCode: undefined,
|
||||||
|
Name: undefined,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Search v-model:search="searchData" :page-data="pageData">
|
||||||
|
<SearchItem label="登录账号">
|
||||||
|
<Input v-model:value="searchData.UserCode" />
|
||||||
|
</SearchItem>
|
||||||
|
<SearchItem label="姓名">
|
||||||
|
<Input v-model:value="searchData.Name" />
|
||||||
|
</SearchItem>
|
||||||
|
</Search>
|
||||||
|
</template>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { AutoComplete } from '@/components';
|
||||||
|
import { DoctorUrl } from './page';
|
||||||
|
import { ReqParams } from '@skyfox2000/fapi';
|
||||||
|
|
||||||
|
const onSearch = (search_value: String, query: ReqParams) => {
|
||||||
|
query.Query = {
|
||||||
|
Name: '%' + search_value + '%',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AutoComplete
|
||||||
|
:onsearch="onSearch"
|
||||||
|
:url="DoctorUrl.urls.select"
|
||||||
|
:params="{
|
||||||
|
Option: {
|
||||||
|
SelectFields: ['S_Doctor.Id', 'S_Doctor.Name'],
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Drawer, Form, FormItem, Input, Switch } from '@/components';
|
||||||
|
import { pageData, editorData } from './page';
|
||||||
|
import { OPTIONS } from '@skyfox2000/webbase';
|
||||||
|
import HospitalSelect from '@/views/system/hospital/select.vue';
|
||||||
|
|
||||||
|
const formData = ref<DoctorEntity>(editorData.formData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Drawer title="医生信息" :page-data="pageData">
|
||||||
|
<Form>
|
||||||
|
<FormItem label="姓名" rule="Name">
|
||||||
|
<Input v-model:value="formData.Name" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="手机号" rule="Mobile">
|
||||||
|
<Input v-model:value="formData.Mobile" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="所属医院" rule="HospitalId">
|
||||||
|
<HospitalSelect v-model:value="formData.HospitalId" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="启用状态">
|
||||||
|
<Switch v-model:checked="formData.Enabled" :data="OPTIONS.EnableDisable" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Table } from '@/components';
|
||||||
|
import { pageData, useGridInit } from './page';
|
||||||
|
useGridInit();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Table :page-data="pageData" />
|
||||||
|
</template>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import { Content } from '@/components';
|
||||||
|
import Grid from './grid.vue';
|
||||||
|
import Search from './search.vue';
|
||||||
|
import { pageData, usePageInit } from './page';
|
||||||
|
usePageInit();
|
||||||
|
|
||||||
|
// 定义异步组件
|
||||||
|
const Editor = defineAsyncComponent(() => import('./editor.vue'));
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Content>
|
||||||
|
<Search />
|
||||||
|
<Grid />
|
||||||
|
</Content>
|
||||||
|
<component :is="Editor" v-if="pageData.editor?.visible" />
|
||||||
|
</template>
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* 医生账号
|
||||||
|
*/
|
||||||
|
export interface DoctorEntity {
|
||||||
|
/**
|
||||||
|
* 医生ID
|
||||||
|
*/
|
||||||
|
Id: string | null;
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
Mobile: string | null;
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
Name: string | null;
|
||||||
|
/**
|
||||||
|
* 所属医院
|
||||||
|
*/
|
||||||
|
HospitalId: string | null;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
Enabled: number;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
CreateTime?: string | null;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
UpdateTime?: string | null;
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/**
|
||||||
|
* 启动配置
|
||||||
|
* API操作
|
||||||
|
*/
|
||||||
|
import { usePageFactory, ApiUrls, ValidateRule } from '@skyfox2000/webbase';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const DoctorUrl: ApiUrls = {
|
||||||
|
/**
|
||||||
|
* api
|
||||||
|
*/
|
||||||
|
api: 'PLATFORM_API',
|
||||||
|
authorize: true,
|
||||||
|
urls: {
|
||||||
|
/**
|
||||||
|
* 查询地址
|
||||||
|
*/
|
||||||
|
list: {
|
||||||
|
url: '/api/RCDoctorSrv/list',
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
url: '/api/RCDoctorSrv/select',
|
||||||
|
cacheTime: 600, // 缓存600秒
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 保存地址
|
||||||
|
*/
|
||||||
|
save: {
|
||||||
|
url: '/api/RCDoctorSrv/save',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除地址
|
||||||
|
*/
|
||||||
|
delete: {
|
||||||
|
url: '/api/RCDoctorSrv/remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单默认数据
|
||||||
|
*/
|
||||||
|
const defaultData: DoctorEntity = {
|
||||||
|
Id: null,
|
||||||
|
Mobile: null,
|
||||||
|
Name: null,
|
||||||
|
HospitalId: null,
|
||||||
|
Enabled: 1,
|
||||||
|
CreateTime: null,
|
||||||
|
UpdateTime: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单验证条件
|
||||||
|
* # 适当验证条件
|
||||||
|
* # async-validator的语法规范
|
||||||
|
*/
|
||||||
|
const formRules: Record<string, ValidateRule> = {
|
||||||
|
Mobile: {
|
||||||
|
required: true,
|
||||||
|
message: '手机号不能为空',
|
||||||
|
},
|
||||||
|
Name: {
|
||||||
|
required: true,
|
||||||
|
message: '姓名不能为空',
|
||||||
|
},
|
||||||
|
HospitalId: {
|
||||||
|
required: true,
|
||||||
|
message: '所属医院不能为空',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const { editorData, gridData, pageData, usePageInit, useEditorInit, useGridInit } = usePageFactory<DoctorEntity>(
|
||||||
|
DoctorUrl,
|
||||||
|
defaultData,
|
||||||
|
formRules,
|
||||||
|
);
|
||||||
|
|
||||||
|
gridData.gridQuery = {
|
||||||
|
Option: {},
|
||||||
|
Query: {
|
||||||
|
$order: [['UpdateTime', 'desc']], // # 合适的默认查询条件,比如更新时间
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
以下为数据列表头定义
|
||||||
|
# 不需要显示CreateTime/UpdateTime
|
||||||
|
*/
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'Name',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '手机号',
|
||||||
|
dataIndex: 'Mobile',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属医院',
|
||||||
|
dataIndex: 'HospitalName',
|
||||||
|
width: 150,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'enabled',
|
||||||
|
width: 80,
|
||||||
|
responsive: ['md'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 100,
|
||||||
|
responsive: ['sm'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
gridData.columns = columns.value;
|
||||||
|
gridData.tableSize = 'small';
|
||||||
|
gridData.remotePage = false;
|
||||||
|
gridData.tools = ['Reload', 'Fullscreen'];
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Search, SearchItem, Input } from '@/components';
|
||||||
|
import { pageData } from './page';
|
||||||
|
|
||||||
|
// #合适的1至2个查询条件,以及下面对应的修改,
|
||||||
|
// #合适的组件,所有组件从@/components获取
|
||||||
|
const searchData = ref<Record<string, any>>({
|
||||||
|
Mobile: undefined,
|
||||||
|
Name: undefined,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Search v-model:search="searchData" :page-data="pageData">
|
||||||
|
<SearchItem label="手机号">
|
||||||
|
<Input v-model:value="searchData.Mobile" />
|
||||||
|
</SearchItem>
|
||||||
|
<SearchItem label="姓名">
|
||||||
|
<Input v-model:value="searchData.Name" />
|
||||||
|
</SearchItem>
|
||||||
|
</Search>
|
||||||
|
</template>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Select } from '@/components';
|
||||||
|
import { DoctorUrl } from './page';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Select
|
||||||
|
:url="DoctorUrl.urls.list"
|
||||||
|
:params="{
|
||||||
|
Option: {
|
||||||
|
SelectFields: ['Id', 'Name'],
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
></Select>
|
||||||
|
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue