1
0

57 Commits

Author SHA1 Message Date
6eacbfddd4 feat(web): 适配jdk17 2024-12-01 20:27:18 +08:00
34e2f340c0 feat(web): 优化级联查询中的逻辑删除 2024-12-01 17:44:27 +08:00
0e57f6856a feat(web): 实体级联软删除实现 2024-12-01 16:53:03 +08:00
37267b4c7c feat(web): 实体软删除实现 2024-12-01 15:20:39 +08:00
e134006794 feat(web): 增加审核详情查看 2024-12-01 11:59:37 +08:00
be2971765e feat(web): 优化折行的使用 2024-12-01 11:47:23 +08:00
6742565f97 feat(web): 用户管理的用户名取消折行 2024-12-01 11:36:59 +08:00
b6418e1e3a feat(web): 审查页面增加操作人展示 2024-12-01 11:35:54 +08:00
b072b83f80 feat(web): 增加一些amis结构的工具函数 2024-12-01 11:17:44 +08:00
4dcc10f2f7 feat(web): 增加工单审批流结构 2024-11-29 19:30:53 +08:00
4d1119997e feat(web): 增加数据授权结构 2024-11-29 10:45:41 +08:00
1740b7bea2 feat(web): 增加审查状态类型的展示 2024-11-29 10:34:12 +08:00
51bca63ec6 feat(web): 增加数据资源确权状态显示 2024-11-29 10:28:28 +08:00
5aef7728b2 feat(web): 增加debug模式下amis生成结构打印 2024-11-29 10:28:04 +08:00
18add92466 feat(web): 增加数据资源名称长度限制 2024-11-29 10:27:30 +08:00
841f1af49a feat(web): 增加tab切换刷新内容 2024-11-29 10:07:00 +08:00
58f2173fb0 feat(web): 增加确权状态审查 2024-11-28 17:40:12 +08:00
00e88078a2 fix(web): Set改List统一接口逻辑 2024-11-28 14:46:24 +08:00
3bda73badf feat(web): 优化hql语句 2024-11-28 14:22:28 +08:00
499b7dc597 feat(web): 优化统一接口和类的定义 2024-11-28 10:39:53 +08:00
66bf1b9242 fix(web): 修复当前用户过滤无效 2024-11-27 19:51:03 +08:00
efe393835c feat(web): 新增根据当前登陆用户查询记录 2024-11-27 19:02:08 +08:00
aba9a1716c fix(web): 优化文件上传适配 2024-11-27 18:05:15 +08:00
f15bcc2f53 feat(web): 关联数据资源和确权状态 2024-11-27 14:01:36 +08:00
70d5a4549f fix(web): 修复文件无法移除 2024-11-27 11:11:00 +08:00
d72ebfd1d1 feat(web): 优化静态文件展示 2024-11-27 09:59:06 +08:00
82a00d0313 fix(web): 区分windows和macOS的文件路径 2024-11-27 09:41:02 +08:00
d781e137d8 feat(web): 增加确权信息新增、查看接口和适配 2024-11-27 00:11:16 +08:00
7584ca3736 feat(web): 优化统一的Controller和Service接口 2024-11-26 19:44:35 +08:00
851fc02ce5 feat(web): 增加权属管理界面 2024-11-26 14:02:15 +08:00
e76236be62 feat(web): 统一页面显示和权限 2024-11-26 10:40:58 +08:00
246e27677e feat(web): 完成数据资源编辑接口 2024-11-25 18:34:07 +08:00
1c0c32ddcb feat(web): 增加删除数据资源接口 2024-11-25 18:33:37 +08:00
c820ef2a39 fix(web): 优化外键关联 2024-11-25 18:32:55 +08:00
c7c56bb53d feat(web): 优化统一表名格式 2024-11-25 18:32:19 +08:00
dcae3adfd8 feat(web): 增加操作实体的方便方法 2024-11-25 18:31:44 +08:00
cb6c0256b9 fix(web): 增加相等判断 2024-11-25 18:31:09 +08:00
dd3ac59bee feat(web): 增加数据资源更新接口 2024-11-24 23:13:36 +08:00
d354cb9733 fix(web): 修复看不懂的前端错误展示 2024-11-21 19:18:34 +08:00
aeb89e2367 feat(frontend): 增加数据资源查看界面 2024-11-21 19:18:04 +08:00
c51228bf42 feat(web): 使用统一的超类和子图查询来优化查询性能 2024-11-21 19:17:14 +08:00
ccbefb9bdf feat(web): 增加type类型用于区分不同的子类 2024-11-21 19:15:49 +08:00
a273f93f7a fix(web): Repository父类被错误初始化的问题 2024-11-21 15:07:33 +08:00
fef98d69e1 feat(web): 整合一下Repository的接口,方便使用 2024-11-21 14:48:36 +08:00
d68f8a27ee feat(web): 增加文件上传接口 2024-11-21 12:25:19 +08:00
6d63c20b7f feat(web): 增加数据资源创建接口 2024-11-21 00:11:53 +08:00
ba19775764 feat(web): 增加数据资源接口 2024-11-20 18:35:31 +08:00
5ce66e1470 feat(web): 调整界面类 2024-11-20 18:32:59 +08:00
9f0c122bae feat(frontend): 只在build的时候混淆 2024-11-20 12:54:32 +08:00
94db7b510f feat(frontend): 增加一点混淆 2024-11-20 12:47:50 +08:00
50068873a4 fix(frontend): 使用正确的submit写法 2024-11-20 12:47:21 +08:00
c45cdf03a9 feat(web): 增加操作人 2024-11-20 11:58:25 +08:00
eb49fce4ae feat(web): 增加数据资源定义 2024-11-20 00:50:22 +08:00
5e4264fb86 fix(all): 修复maven版本指向 2024-11-20 00:08:56 +08:00
323d326e8d feat(web): 增加账号最后登陆时间 2024-11-19 19:14:32 +08:00
9a01d74d78 feat(web): 使用vite重新组织前端代码 2024-11-19 17:52:34 +08:00
7ccc602406 fix(web): 修复js文件引用错误 2024-11-19 11:14:10 +08:00
212 changed files with 5886 additions and 739 deletions

View File

@@ -1,6 +1,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="47" name="Java" />
</Languages>
</inspection_tool>
<inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="mavenCentral" />
<option name="name" value="mavenCentral" />

2
.idea/jpa.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<component name="JpaBuddyIdeaProjectConfig" newRepositoryParent="com.blinkfox.fenix.jpa.FenixJpaRepository">
<option name="defaultUnitInitialized" value="true" />
<option name="renamerInitialized" value="true" />
</component>

2
.idea/misc.xml generated
View File

@@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="corretto-11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectType">

9
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/gringotts.iml" filepath="$PROJECT_DIR$/gringotts.iml" />
<module fileurl="file://$PROJECT_DIR$/gringotts-web/gringotts-web.iml" filepath="$PROJECT_DIR$/gringotts-web/gringotts-web.iml" />
</modules>
</component>
</project>

View File

@@ -43,7 +43,7 @@
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>

View File

@@ -1,8 +1,12 @@
package com.eshore.gringotts.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* web配置
@@ -11,14 +15,25 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* @date 2024-11-14
*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 避免跨域影响调试
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("*")
.maxAge(3600);
public class WebConfiguration {
private static final Logger logger = LoggerFactory.getLogger(WebConfiguration.class);
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}

View File

@@ -18,7 +18,7 @@
</dependency>
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<artifactId>forest-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>

24
gringotts-frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,409 @@
const information = {
debug: true,
// baseUrl: '',
baseUrl: 'http://127.0.0.1:20080',
title: '可信供给中心',
}
export function useAmis(amisObject) {
document.title = information.title
let amis = amisRequire('amis/embed')
let struct = amisObject(information)
if (information.debug) {
console.log(struct)
}
amis.embed(
'#app',
struct,
{
data: {
base: information.baseUrl,
},
},
{
theme: 'antd',
enableAMISDebug: information.debug,
}
)
}
export function crudCommonOptions() {
return {
affixHeader: false,
stopAutoRefreshWhenModalIsOpen: true,
resizable: false,
syncLocation: false,
silentPolling: true,
}
}
export function readOnlyDialogOptions() {
return {
actions: [],
showCloseButton: false,
closeOnEsc: true,
closeOnOutside: true,
disabled: true,
}
}
export function horizontalFormOptions() {
return {
mode: 'horizontal',
canAccessSuperData: false,
horizontal: {
left: 3,
},
}
}
export function paginationCommonOptions(perPage = true, maxButtons = 5) {
let option = {
type: 'pagination',
layout: [
'pager'
],
maxButtons: maxButtons,
showPageInput: false,
perPageAvailable: [10, 15, 20, 50, 100, 200],
}
if (perPage) {
option.layout.push('perPage')
}
return option
}
export function paginationTemplate(perPage = 20, maxButtons = 5) {
return {
perPage: perPage,
headerToolbar: [
"reload",
paginationCommonOptions(true, maxButtons),
],
footerToolbar: [
"statistics",
paginationCommonOptions(true, maxButtons),
],
}
}
export const size5MB = 5242880
export const size100MB = 104857600
export const size500MB = 524288000
export const size1GB = 1073741824
export function inputFileFormItemCommonOptions(accept = '*', maxSize = size5MB) {
return {
useChunk: true,
accept: accept,
maxSize: maxSize,
autoUpload: false,
startChunkApi: apiPost('${base}/upload/start'),
chunkApi: apiPost('${base}/upload/slice'),
finishChunkApi: apiPost('${base}/upload/finish'),
}
}
/**
* @param {string} field
* @param {string} label
* @param {string} tips
* @param {number} ignoreLength
* @param {{}} extra
*/
export function copyField(field, label, tips = '复制', ignoreLength = 0, extra = undefined) {
let tpl = ignoreLength === 0 ? `\${${field}}` : `\${TRUNCATE(${field}, ${ignoreLength})}`
let content = extra
? {
type: 'action',
level: 'link',
label: `\${${field}}`,
className: 'text-current underline',
size: 'xs',
...extra,
}
: {
type: 'tpl',
className: 'mr-1',
tpl: tpl,
}
return {
name: field,
type: 'wrapper',
size: 'none',
className: 'nowrap',
body: [
content,
{
type: 'action',
level: 'link',
label: '',
icon: 'fa fa-copy',
size: 'xs',
actionType: 'copy',
content: `\$${field}`,
tooltip: `${tips}`,
},
]
}
}
/**
* @param {string} label
* @param {number} width
* @param {Array} buttons
*/
export function operationField(label, width = 100, buttons = []) {
return {
label: label,
width: width,
type: 'operation',
fixed: 'right',
className: 'nowrap',
buttons: buttons,
}
}
/**
* @param {string} field
* @param {string} label
* @param {number} width
*/
export function timeField(field, label, width = 150) {
return {
label: label,
width: width,
align: 'center',
type: 'tpl',
tpl: `\${IF(${field}, DATETOSTR(${field}), '-')}`
}
}
export function stringWrapField(field, label, width = undefined) {
return stringField(field, label, width, true)
}
/**
* @param {string} field
* @param {string} label
* @param {number} width
* @param {boolean} wrap
*/
export function stringField(field, label, width = undefined, wrap = false) {
let data = {
name: field,
label: label,
}
if (width) {
data['width'] = width
}
if (wrap) {
data['className'] = 'nowrap'
}
return data
}
/**
* @param {string} field
* @param {string} label
* @param {[{label: *, value: *, color: string}]} mapping
* @param {number} width
*/
export function mappingField(field, label, mapping, width = 80) {
let mapData = {
'*': `<span class='label bg-gray-300'>\${${field}}</span>`,
}
mapping.forEach(item => {
mapData[item['value']] = `<span class='label ${item['color']}'>${item['label']}</span>`
})
return {
label: label,
type: 'mapping',
width: width,
value: `\${${field}}`,
align: 'center',
map: mapData,
}
}
function mappingItem(label, value, color = 'bg-info') {
return {
label: label,
value: value,
color: color,
}
}
export const userRoleMapping = [
mappingItem('数据提供方', 'PROVIDER', 'bg-blue-500'),
mappingItem('数据使用方', 'CUSTOMER', 'bg-purple-500'),
mappingItem('审核监管方', 'CHECKER', 'bg-cyan-500'),
mappingItem('系统管理员', 'ADMINISTRATOR', 'bg-green-500'),
]
export const userStateMapping = [
mappingItem('审查中', 'CHECKING', 'bg-warning'),
mappingItem('正常', 'NORMAL', 'bg-success'),
mappingItem('禁用', 'DISABLED', 'bg-danger'),
]
export const confirmationStateMapping = [
mappingItem('未确权', 'NONE'),
mappingItem('草稿', 'DRAFT', 'bg-primary'),
mappingItem('审查中', 'CHECKING', 'bg-warning'),
mappingItem('通过', 'NORMAL', 'bg-success'),
mappingItem('驳回', 'REJECT', 'bg-danger'),
]
export const checkTypeMapping = [
mappingItem('确权审查', 'CONFIRMATION', 'bg-blue-500'),
mappingItem('授权审查', 'AUTHENTICATION', 'bg-purple-500'),
]
export const checkOverMapping = [
mappingItem('完结', 'true', 'bg-success'),
mappingItem('进行中', 'false', 'bg-primary'),
]
function api(method, url) {
return {
method: method,
url: url,
headers: {
token: '${token|default:undefined}',
}
}
}
export function apiGet(url) {
return api('get', url)
}
export function apiPost(url) {
return api('post', url)
}
export function arrayInCheck(array, field) {
return `\${ARRAYINCLUDES(['${array.join("','")}'], ${field})}`
}
export function arrayOutCheck(array, field) {
return `\${!ARRAYINCLUDES(['${array.join("','")}'], ${field})}`
}
export const confirmationState = {
none: 'NONE',
draft: 'DRAFT',
checking: 'CHECKING',
normal: 'NORMAL',
}
export function roleCheck(roles) {
return `\${ARRAYINCLUDES(['${roles.join("','")}'], role)}`
}
export const role = {
administrator: 'ADMINISTRATOR',
checker: 'CHECKER',
provider: 'PROVIDER',
customer: 'CUSTOMER',
}
export const administratorOnly = roleCheck([role.administrator])
export const checkerOnly = roleCheck([role.administrator, role.checker])
export const userOnly = roleCheck([role.administrator, role.provider, role.customer])
export const providerOnly = roleCheck([role.administrator, role.provider])
export const customerOnly = roleCheck([role.administrator, role.customer])
export const formInputClearable = {
clearable: true,
clearValueOnEmpty: true,
}
const formInputFileStaticColumns = [
{
name: 'filename',
label: '文件名',
},
{
type: 'operation',
label: '操作',
width: 140,
buttons: [
{
type: 'action',
label: '预览',
level: 'link',
icon: 'fas fa-eye'
},
{
type: 'action',
label: '下载',
level: 'link',
icon: 'fa fa-download'
}
]
}
]
export function formInputSingleFileStatic(field, label) {
return {
visibleOn: '${detail}',
type: 'control',
label: label,
required: true,
body: {
type: 'table',
source: `\${${field}|asArray}`,
columns: formInputFileStaticColumns,
}
}
}
export function formInputMultiFileStatic(field, label) {
return {
visibleOn: '${detail}',
type: 'input-table',
label: label,
name: field,
required: true,
resizable: false,
columns: formInputFileStaticColumns,
}
}
export function formCreatedUserAndModifiedUser() {
return [
{
type: 'group',
body: [
{
type: 'input-text',
name: 'createdUsername',
label: '创建人',
},
{
type: 'input-datetime',
name: 'createdTime',
label: '创建时间',
},
]
},
{
type: 'group',
body: [
{
type: 'input-text',
name: 'modifiedUsername',
label: '最后修改人',
},
{
type: 'input-datetime',
name: 'modifiedTime',
label: '最后修改时间',
},
]
},
]
}

View File

@@ -0,0 +1,3 @@
textarea {
resize: none !important;
}

View File

@@ -0,0 +1,152 @@
import './dialog-permission.css'
import {
apiGet,
apiPost,
formCreatedUserAndModifiedUser,
formInputClearable,
formInputMultiFileStatic,
horizontalFormOptions,
inputFileFormItemCommonOptions,
size100MB
} from "../constants.js";
import {resourceList} from "../../pages/index/tab-data.js";
import {resourceDetailDialog} from "../resource/dialog-resource.js";
function detailForm(showCreatedUserAndModifiedUser = false) {
return {
id: 'permission_form',
type: 'form',
...horizontalFormOptions(),
horizontal: {
left: 2,
},
body: [
{
type: 'hidden',
name: 'id',
},
{
type: 'picker',
name: 'targetId',
label: '数据资源',
required: true,
multiple: false,
size: 'md',
valueField: 'id',
labelField: 'name',
source: apiGet('${base}/data_resource/list'),
pickerSchema: {
...resourceList(),
},
staticSchema: {
type: 'tpl',
tpl: "<span class='text-primary' style='cursor: pointer'>${targetName}</span>",
onEvent: {
click: {
actions: [
{
actionType: 'dialog',
...resourceDetailDialog('targetId'),
}
]
}
}
}
},
{
type: 'textarea',
label: '确权说明',
name: 'description',
...formInputClearable,
},
formInputMultiFileStatic('evidenceFiles', '相关材料'),
{
visibleOn: '${!detail}',
type: 'input-file',
label: '相关材料',
name: 'evidenceFiles',
multiple: true,
required: true,
joinValues: false,
...inputFileFormItemCommonOptions(undefined, size100MB),
},
...(showCreatedUserAndModifiedUser ? formCreatedUserAndModifiedUser() : [])
]
}
}
export function permissionAddDialog() {
return {
actionType: 'dialog',
dialog: {
title: '新增确权申请',
size: 'md',
actions: [
{
type: 'reset',
label: '重置',
},
{
type: 'submit',
label: '确定',
level: 'primary',
}
],
body: {
...detailForm(),
api: apiPost('${base}/confirmation/save'),
data: {
add: true,
},
}
}
}
}
export function permissionDetailDialog(field = 'id', actions = []) {
return {
actionType: 'dialog',
dialog: {
title: '确权申请详情',
size: 'md',
actions: actions,
body: {
...detailForm(true),
initApi: apiGet(`\${base}/confirmation/detail/\${${field}}`),
static: true,
data: {
detail: true,
},
}
}
}
}
export function permissionEditeDialog(field = 'id') {
return {
actionType: 'dialog',
dialog: {
title: '确权申请详情',
size: 'md',
actions: [
{
type: 'reset',
label: '重置',
},
{
type: 'submit',
label: '确定',
level: 'primary',
}
],
body: {
...detailForm(),
api: apiPost('${base}/confirmation/save'),
initApi: apiGet(`\${base}/confirmation/detail/\${${field}}`),
data: {
edit: true,
}
},
}
}
}

View File

@@ -0,0 +1,3 @@
textarea {
resize: none !important;
}

View File

@@ -0,0 +1,378 @@
import './dialog-resource.css'
import {
apiGet,
apiPost,
formCreatedUserAndModifiedUser,
formInputClearable,
formInputSingleFileStatic,
horizontalFormOptions,
inputFileFormItemCommonOptions,
size1GB,
size500MB
} from "../constants.js";
function detailForm(showCreatedUserAndModifiedUser = false) {
return {
type: 'form',
...horizontalFormOptions(),
horizontal: {
left: 2,
},
body: [
{
type: 'hidden',
name: 'id',
},
{
type: 'input-text',
name: 'name',
label: '资源名称',
maxLength: 10,
showCounter: true,
required: true,
...formInputClearable,
},
{
type: 'textarea',
name: 'description',
label: '资源描述',
...formInputClearable,
showCounter: true,
trimContents: true,
minRows: 2,
maxRows: 2,
},
{
type: 'fieldSet',
title: '资源类型定义',
body: [
{
name: 'resourceType',
type: 'select',
label: '资源类型',
selectFirst: true,
required: true,
options: [
{label: 'API', value: 'API'},
{label: '文件', value: 'FILE'},
{label: '数据库', value: 'DATABASE'},
{label: 'HDFS', value: 'HDFS'},
{label: 'FTP', value: 'FTP'},
]
},
{
visibleOn: "${resourceType === 'API'}",
type: 'fieldSet',
body: [
{
type: 'input-text',
label: 'API地址',
name: 'apiUrl',
required: true,
...formInputClearable,
},
{
type: 'input-text',
label: '用户名',
name: 'apiUsername',
...formInputClearable,
},
{
type: 'input-password',
label: '密码',
name: 'apiPassword',
...formInputClearable,
},
]
},
{
visibleOn: "${resourceType === 'FILE'}",
type: 'fieldSet',
body: [
formInputSingleFileStatic('file', '数据文件'),
{
visibleOn: "${!detail}",
type: 'input-file',
label: '数据文件',
description: '只适合小于1GB的资源文件使用大文件请使用其他资源类型',
name: 'file',
multiple: false,
required: true,
joinValues: false,
...inputFileFormItemCommonOptions('.zip', size1GB),
},
]
},
{
visibleOn: "${resourceType === 'DATABASE'}",
type: 'fieldSet',
body: [
{
type: 'select',
label: '数据库类型',
name: 'databaseType',
required: true,
options: [
{label: 'MySQL', value: 'MYSQL'},
{label: 'Oracle', value: 'ORACLE'},
{label: 'PostgreSQL', value: 'POSTGRESQL'},
]
},
{
type: 'input-text',
label: 'JDBC',
name: 'databaseJdbc',
required: true,
...formInputClearable,
},
{
type: 'input-text',
label: '用户名',
name: 'databaseUsername',
...formInputClearable,
},
{
type: 'input-password',
label: '密码',
name: 'databasePassword',
...formInputClearable,
},
]
},
{
visibleOn: "${resourceType === 'HDFS'}",
type: 'fieldSet',
body: [
formInputSingleFileStatic('coreSiteFile', 'core-site.xml'),
{
visibleOn: "${!detail}",
type: 'input-file',
label: 'core-site.xml',
name: 'coreSiteFile',
multiple: false,
required: true,
joinValues: false,
...inputFileFormItemCommonOptions('.xml'),
},
formInputSingleFileStatic('hdfsSiteFile', 'hdfs-site.xml'),
{
visibleOn: "${!detail}",
type: 'input-file',
label: 'hdfs-site.xml',
name: 'hdfsSiteFile',
multiple: false,
required: true,
joinValues: false,
...inputFileFormItemCommonOptions('.xml'),
},
]
},
{
visibleOn: "${resourceType === 'FTP'}",
type: 'fieldSet',
body: [
{
type: 'input-text',
label: 'FTP地址',
name: 'ftpUrl',
required: true,
...formInputClearable,
},
{
type: 'input-text',
label: 'FTP账号',
name: 'ftpUsername',
...formInputClearable,
},
{
type: 'input-password',
label: 'FTP密码',
name: 'ftpPassword',
...formInputClearable,
},
{
type: 'input-text',
label: '相对路径',
name: 'ftpPath',
description: '若为空,则使用用户根目录',
...formInputClearable,
},
{
type: 'input-text',
label: '文件筛选',
name: 'ftpRegexFilter',
description: '正则表达式,用于匹配文件的路径,只有符合筛选条件的文件才会被采集;若为空则默认采集全部文件',
...formInputClearable,
},
]
},
]
},
{
type: 'fieldSet',
title: '资源格式定义',
className: 'mt-5',
body: [
{
name: 'formatType',
type: 'select',
label: '资源格式',
selectFirst: true,
required: true,
options: [
{label: '无', value: 'NONE'},
{label: 'Line', value: 'LINE'},
{label: 'JSON', value: 'JSON'},
{label: 'JSON Line', value: 'JSON_LINE'},
{label: 'CSV', value: 'CSV'},
]
},
{
visibleOn: "${formatType === 'JSON' && !detail}",
type: 'json-schema-editor',
name: 'jsonSchema',
label: 'JSON格式',
description: '使用JSON Schema格式',
required: true,
enableAdvancedSetting: true,
mini: true,
},
{
visibleOn: "${formatType === 'JSON' && detail}",
type: 'editor',
name: 'jsonSchemaText',
label: 'JSON格式',
language: 'json',
},
{
visibleOn: "${formatType === 'JSON_LINE' && !detail}",
type: 'json-schema-editor',
name: 'jsonLineSchema',
label: 'JSON格式',
description: 'JSON Line类型请定义单行JSON数据格式使用JSON Schema格式',
required: true,
enableAdvancedSetting: true,
mini: true,
},
{
visibleOn: "${formatType === 'JSON_LINE' && detail}",
type: 'editor',
name: 'jsonLineSchemaText',
label: 'JSON格式',
language: 'json',
},
{
visibleOn: "${formatType === 'CSV' && !detail}",
type: 'json-schema-editor',
name: 'csvSchema',
label: 'CSV格式',
description: '请定义单行数据中各个字段的格式使用JSON Schema格式',
required: true,
enableAdvancedSetting: true,
mini: true,
disabledTypes: [
'object',
'array',
'null',
]
},
{
visibleOn: "${formatType === 'CSV' && detail}",
type: 'editor',
name: 'csvSchemaText',
label: 'JSON格式',
language: 'json',
},
formInputSingleFileStatic('exampleFile', '资源示例'),
{
visibleOn: "${!detail}",
type: 'input-file',
label: '资源示例',
name: 'exampleFile',
description: '可以上传用于作为格式示范的样例数据',
multiple: false,
joinValues: false,
...inputFileFormItemCommonOptions(undefined, size500MB),
},
]
},
...(showCreatedUserAndModifiedUser ? formCreatedUserAndModifiedUser() : [])
]
}
}
export function resourceAddDialog() {
return {
actionType: 'dialog',
dialog: {
title: '新增数据资源',
size: 'md',
actions: [
{
type: 'reset',
label: '重置',
},
{
type: 'submit',
label: '确定',
level: 'primary',
}
],
body: {
...detailForm(),
api: apiPost('${base}/data_resource/save'),
data: {
add: true,
}
}
}
}
}
export function resourceDetailDialog(field = 'id') {
return {
actionType: 'dialog',
dialog: {
title: '账号详情',
actions: [],
size: 'md',
body: {
...detailForm(true),
static: true,
initApi: apiGet(`\${base}/data_resource/detail/\${${field}}`),
data: {
detail: true,
}
},
}
}
}
export function resourceEditeDialog() {
return {
actionType: 'dialog',
dialog: {
title: '账号详情',
size: 'md',
actions: [
{
type: 'reset',
label: '重置',
},
{
type: 'submit',
label: '确定',
level: 'primary',
}
],
body: {
...detailForm(),
api: apiPost('${base}/data_resource/save'),
initApi: apiGet('${base}/data_resource/detail/${id}'),
data: {
edit: true,
}
},
}
}
}

View File

@@ -0,0 +1,38 @@
import {horizontalFormOptions, apiPost} from '../constants.js'
import {confirmPasswordFormItem, passwordFormItem} from "./dialog-user-item.js";
export function userChangePasswordDialog() {
return {
actionType: 'dialog',
dialog: {
title: '修改密码',
actions: [
{
type: 'reset',
label: '清空',
},
{
type: 'submit',
label: '修改',
level: 'primary',
}
],
body: {
type: 'form',
api: apiPost('${base}/user_management/change_password'),
...horizontalFormOptions(),
body: [
{
type: 'input-password',
name: 'oldPassword',
label: '旧密码',
placeholder: '请输入旧密码',
required: true,
},
passwordFormItem('newPassword', '新密码'),
confirmPasswordFormItem('confirmNewPassword', 'newPassword', '新密码'),
]
}
}
}
}

View File

@@ -0,0 +1,98 @@
import {
apiGet,
formCreatedUserAndModifiedUser,
horizontalFormOptions,
mappingField,
userRoleMapping,
userStateMapping
} from "../constants.js";
const dialogBody = [
{
type: 'input-email',
name: 'username',
label: '邮箱',
},
{
type: 'group',
body: [
mappingField('role', '角色', userRoleMapping),
mappingField('state', '账号状态', userStateMapping),
]
},
...formCreatedUserAndModifiedUser(),
{
type: 'group',
body: [
{
type: 'input-text',
name: 'checkedUsername',
label: '审核人',
},
{
type: 'input-datetime',
name: 'checkedTime',
label: '审核时间',
},
]
},
]
export function userDetailDialog() {
return {
actionType: 'dialog',
dialog: {
title: '账号详情',
actions: [],
size: 'md',
body: {
type: 'form',
initApi: apiGet('${base}/user_management/detail/${username}'),
...horizontalFormOptions(),
horizontal: {
left: 2,
},
static: true,
body: [
...dialogBody,
{
type: 'input-datetime',
name: 'lastLoginTime',
label: '上次登陆时间',
},
],
}
}
}
}
export function userCheckDialog() {
return {
actionType: 'dialog',
dialog: {
title: '账号审核',
actions: [
{
type: 'action',
label: '同意',
level: 'primary',
actionType: 'submit',
confirmTitle: '审核通过',
confirmText: '确认通过账号为${username}通过审核吗?审核通过后将无法撤销。',
},
],
size: 'md',
body: {
type: 'form',
api: apiGet('${base}/user_management/check/${username}'),
initApi: apiGet('${base}/user_management/detail/${username}'),
...horizontalFormOptions(),
horizontal: {
left: 2,
},
static: true,
body: dialogBody,
}
}
}
}

View File

@@ -0,0 +1,49 @@
export function emailFormItem(name) {
return {
type: 'input-email',
name: name,
label: '邮箱',
placeholder: '请输入邮箱',
required: true,
clearable: true,
clearValueOnEmpty: true,
validateApi: '${base}/user/exists_username/${username}',
}
}
export function passwordFormItem(name, label = '密码') {
return {
type: 'input-password',
name: name,
label: label,
placeholder: `请输入${label}`,
required: true,
clearable: true,
clearValueOnEmpty: true,
validations: {
matchRegexp:
/^(?=.*\d)(?!.*(\d)\1{2})(?!.*(012|123|234|345|456|567|678|789|987|876|765|654|543|432|321|210))(?=.*[a-zA-Z])(?=.*[^\da-zA-Z\s]).{8,16}$/,
},
validationErrors: {
matchRegexp:
`${label}至少包含字母、数字、特殊字符8-16位并且不能连续出现3个大小连续或相同的数字`,
},
}
}
export function confirmPasswordFormItem(name, target, label = '密码') {
return {
type: 'input-password',
name: name,
label: `确认${label}`,
placeholder: `请再次输入${label}`,
required: true,
clearable: true,
validations: {
equalsField: target,
},
validationErrors: {
equalsField: `两次输入${label}不一致`,
},
}
}

View File

@@ -0,0 +1,88 @@
import {apiPost, horizontalFormOptions} from '../constants.js'
import {confirmPasswordFormItem, emailFormItem, passwordFormItem} from "./dialog-user-item.js";
export function userRegisterDialog() {
return {
actionType: 'dialog',
dialog: {
title: '用户注册',
actions: [
{
type: 'reset',
label: '清空',
},
{
type: 'submit',
label: '注册',
level: 'primary',
},
],
body: {
type: 'form',
api: apiPost('${base}/user/register'),
...horizontalFormOptions(),
body: [
emailFormItem('username'),
passwordFormItem('password'),
confirmPasswordFormItem('confirm-password', 'password'),
{
type: 'radios',
name: 'role',
label: '角色',
required: true,
selectFirst: true,
options: [
{ label: '数据提供方', value: 'PROVIDER' },
{ label: '数据使用方', value: 'CUSTOMER' },
{ label: '审查监管方', value: 'CHECKER' },
],
},
],
},
},
}
}
export function userAdministratorRegisterDialog() {
return {
actionType: 'dialog',
dialog: {
title: '用户注册',
actions: [
{
type: 'reset',
label: '清空',
},
{
type: 'submit',
label: '注册',
level: 'primary',
},
],
body: {
type: 'form',
api: apiPost('${base}/user_management/register'),
...horizontalFormOptions(),
body: [
emailFormItem('username'),
passwordFormItem('password'),
confirmPasswordFormItem('confirm-password', 'password'),
{
type: 'radios',
name: 'role',
label: '角色',
required: true,
selectFirst: true,
inline: false,
columnsCount: 2,
options: [
{ label: '数据提供方', value: 'PROVIDER' },
{ label: '数据使用方', value: 'CUSTOMER' },
{ label: '审查监管方', value: 'CHECKER' },
],
},
],
},
},
}
}

View File

@@ -0,0 +1,10 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<script>
window.location.href = '/pages/index/index.html'
</script>
</html>

View File

@@ -0,0 +1,15 @@
{
"name": "vite-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^5.4.10",
"vite-plugin-javascript-obfuscator": "^3.1.0"
}
}

View File

@@ -0,0 +1,25 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title></title>
<link href="/assets/sdk/antd.css" rel="stylesheet"/>
<link href="/assets/sdk/helper.css" rel="stylesheet"/>
<link href="/assets/sdk/iconfont.css" rel="stylesheet"/>
<style>
html, body, #app {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="/assets/sdk/sdk.js"></script>
<script type="module" src="./main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,72 @@
import {apiGet, useAmis} from '../../components/constants.js'
import {userChangePasswordDialog} from "../../components/user/dialog-user-change-password.js";
import {tabUser} from "./tab-user.js";
import {tabData} from "./tab-data.js";
import {tabSettings} from "./tab-settings.js";
import {tabCheck} from "./tab-check.js";
import {tabPermissions} from "./tab-permissions.js";
useAmis(information => {
return {
id: 'header-service',
className: 'h-full',
type: 'service',
api: apiGet('${base}/user/state'),
onEvent: {
fetchInited: {
actions: [
{
expression: '${!event.data.responseData.token}',
actionType: 'url',
args: {
url: '/pages/login/index.html',
blank: false,
}
}
]
}
},
body: [
{
type: 'page',
title: information.title,
subTitle: '提供合法合规的数据要素可信供给工具',
toolbar: [
{
type: 'dropdown-button',
label: '${username}',
align: 'right',
trigger: 'hover',
buttons: [
{
label: '修改密码',
...userChangePasswordDialog(),
},
{
label: '退出登陆',
actionType: 'ajax',
api: apiGet('${base}/user/logout'),
reload: 'header-service',
}
]
}
],
bodyClassName: 'p-0',
body: {
className: 'h-full border-0',
type: 'tabs',
tabsMode: 'vertical',
tabs: [
// tabOverview(),
// tabMarket(),
tabCheck(),
tabData(),
tabPermissions(),
tabUser(),
tabSettings(),
]
},
}
]
}
})

View File

@@ -0,0 +1,77 @@
import {
apiGet,
checkerOnly,
checkOverMapping,
checkTypeMapping,
crudCommonOptions,
mappingField,
operationField,
stringField,
timeField,
} from "../../components/constants.js";
import {permissionDetailDialog} from "../../components/permission/dialog-permission.js";
export function tabCheck() {
return {
visibleOn: checkerOnly,
title: '审核审查',
icon: 'fa fa-shield-halved',
reload: true,
body: [
{
name: 'check_order_list',
type: 'crud',
api: apiGet('${base}/check_order/list'),
...crudCommonOptions(),
headerToolbar: [
'reload',
],
columns: [
stringField('description', '描述'),
mappingField('type', '类型', checkTypeMapping),
mappingField('over', '状态', checkOverMapping),
timeField('createdTime', '创建时间'),
stringField('createdUsername', '创建人', 100),
timeField('modifiedTime', '最后修改时间'),
stringField('modifiedUsername', '最后操作人', 100),
operationField('操作', undefined, [
{
visibleOn: '${!over}',
type: 'action',
label: '处理',
level: 'link',
...permissionDetailDialog(
'parameters.confirmationId',
[
{
type: 'action',
label: '同意',
actionType: 'ajax',
close: true,
api: apiGet('${base}/check_order/operation/${checkOrderId}/APPLY'),
reload: 'check_order_list',
},
{
type: 'action',
label: '拒绝',
actionType: 'ajax',
close: true,
api: apiGet('${base}/check_order/operation/${checkOrderId}/REJECT'),
reload: 'check_order_list',
},
],
),
},
{
visibleOn: '${over}',
type: 'action',
label: '查看',
level: 'link',
...permissionDetailDialog('parameters.confirmationId'),
},
]),
],
},
],
}
}

View File

@@ -0,0 +1,94 @@
import {
resourceAddDialog,
resourceDetailDialog,
resourceEditeDialog
} from "../../components/resource/dialog-resource.js";
import {
apiGet,
arrayInCheck,
arrayOutCheck,
confirmationState,
confirmationStateMapping,
crudCommonOptions,
mappingField,
operationField,
timeField,
userOnly
} from "../../components/constants.js";
export function resourceList() {
return {
type: 'crud',
api: apiGet('${base}/data_resource/list'),
...crudCommonOptions(),
headerToolbar: [
'reload',
{
type: 'action',
label: '',
icon: 'fa fa-plus',
...resourceAddDialog()
},
],
columns: [
{
label: '名称',
name: 'name',
width: 150,
},
{
label: '描述',
name: 'description',
},
mappingField('confirmationState', '确权状态', confirmationStateMapping),
timeField('createdTime', '创建时间'),
operationField('操作', undefined, [
{
type: 'action',
label: '详情',
level: 'link',
...resourceDetailDialog(),
},
{
type: 'dropdown-button',
level: 'link',
icon: 'fa fa-ellipsis-h',
hideCaret: true,
trigger: 'hover',
buttons: [
{
disabledOn: arrayOutCheck([confirmationState.none, confirmationState.draft], 'confirmationState'),
disabledTip: '审查或确权成功后无法编辑',
tooltipPlacement: 'top',
type: 'action',
label: '编辑',
level: 'link',
...resourceEditeDialog(),
},
{
disabledOn: arrayInCheck([confirmationState.checking], 'confirmationState'),
disabledTip: '审查中无法删除',
tooltipPlacement: 'bottom',
type: 'action',
label: "删除",
confirmTitle: '确认删除',
confirmText: '确认删除名称为「${name}」的数据资源吗?',
actionType: 'ajax',
api: apiGet('${base}/data_resource/remove/${id}')
},
]
},
]),
]
}
}
export function tabData() {
return {
visibleOn: userOnly,
title: '数据资源',
icon: 'fa fa-database',
reload: true,
body: resourceList(),
}
}

View File

@@ -0,0 +1,9 @@
export function tabMarket() {
return {
title: '数据市场',
icon: 'fa fa-store',
body: [
'hello world'
]
}
}

View File

@@ -0,0 +1,114 @@
import {
apiGet,
confirmationStateMapping,
crudCommonOptions,
mappingField,
operationField,
providerOnly,
stringField,
stringWrapField,
timeField,
userOnly,
} from "../../components/constants.js";
import {
permissionAddDialog,
permissionDetailDialog,
permissionEditeDialog,
} from "../../components/permission/dialog-permission.js";
export function tabPermissions() {
return {
visibleOn: userOnly,
title: '权属管理',
icon: 'fa fa-user-shield',
reload: true,
body: {
type: 'tabs',
tabs: [
{
visibleOn: providerOnly,
title: '确权管理',
body: {
type: 'crud',
api: {
...apiGet('${base}/confirmation/list')
},
...crudCommonOptions(),
headerToolbar: [
'reload',
{
type: 'action',
label: '',
icon: 'fa fa-plus',
...permissionAddDialog()
},
],
columns: [
stringField('name', '名称', 200),
stringWrapField('description', '描述'),
mappingField('state', '状态', confirmationStateMapping),
timeField('createdTime', '创建时间'),
operationField('操作', undefined, [
{
type: 'action',
label: '查看',
level: 'link',
...permissionDetailDialog(),
},
{
visibleOn: "${state === 'CHECKING'}",
type: 'action',
label: '撤销',
level: 'link',
confirmTitle: '确认撤销',
confirmText: '确认撤销名称为「${name}」的确权申请吗?',
actionType: 'ajax',
api: apiGet('${base}/confirmation/retract/${id}'),
},
{
visibleOn: "${state === 'DRAFT' || state === 'REJECT'}",
type: 'action',
label: '提交',
level: 'link',
confirmTitle: '确认提交',
confirmText: '确认提交名称为「${name}」的确权申请吗?',
actionType: 'ajax',
api: apiGet('${base}/confirmation/submit/${id}'),
},
{
type: 'dropdown-button',
level: 'link',
icon: 'fa fa-ellipsis-h',
hideCaret: true,
trigger: 'hover',
buttons: [
{
disabledOn: "${state !== 'DRAFT'}",
type: 'action',
label: '编辑',
level: 'link',
...permissionEditeDialog(),
},
{
disabledOn: "${state === 'CHECKING'}",
type: 'action',
label: "删除",
confirmTitle: '确认删除',
confirmText: '确认删除名称为「${name}」的确权申请吗?删除后对应的数据资源处于未确权状态。',
actionType: 'ajax',
api: apiGet('${base}/confirmation/remove/${id}'),
},
]
},
]),
]
}
},
{
title: '授权管理',
body: []
}
]
}
}
}

View File

@@ -0,0 +1,12 @@
import {administratorOnly} from "../../components/constants.js";
export function tabSettings() {
return {
visibleOn: administratorOnly,
title: '系统管理',
icon: 'fa fa-gear',
body: [
'hello world'
]
}
}

View File

@@ -0,0 +1,83 @@
import {
administratorOnly,
apiGet,
copyField,
crudCommonOptions,
mappingField,
operationField,
timeField,
userRoleMapping,
userStateMapping
} from '../../components/constants.js'
import {userCheckDialog, userDetailDialog} from "../../components/user/dialog-user-check.js";
import {userAdministratorRegisterDialog} from "../../components/user/dialog-user-register.js";
export function tabUser() {
return {
visibleOn: administratorOnly,
title: '用户管理',
icon: 'fa fa-user',
reload: true,
body: [
{
type: 'crud',
api: apiGet('${base}/user_management/list'),
...crudCommonOptions(),
headerToolbar: [
'reload',
{
type: 'action',
icon: 'fa fa-plus',
tooltip: '新增账号',
...userAdministratorRegisterDialog(),
},
],
columns: [
copyField(
'username',
'邮箱',
undefined,
undefined,
userDetailDialog(),
),
mappingField('role', '角色', userRoleMapping, 120),
mappingField('state', '账号状态', userStateMapping),
timeField('createdTime', '创建时间'),
timeField('lastLoginTime', '上次登陆时间'),
operationField('操作', undefined, [
{
visibleOn: "${state === 'CHECKING'}",
label: '审核',
icon: 'fa fa-fingerprint',
level: 'primary',
size: 'xs',
...userCheckDialog(),
},
{
visibleOn: "${state === 'NORMAL' && role !== 'ADMINISTRATOR'}",
label: '禁用',
icon: 'fa fa-ban',
level: 'danger',
size: 'xs',
confirmText: '确认禁用账号${username}',
confirmTitle: '禁用账号',
actionType: 'ajax',
api: apiGet('${base}/user_management/disable/${username}'),
},
{
visibleOn: "${state === 'DISABLED' && role !== 'ADMINISTRATOR'}",
label: '启用',
icon: 'fa fa-check',
level: 'success',
size: 'xs',
confirmText: '确认启用账号${username}',
confirmTitle: '启用账号',
actionType: 'ajax',
api: apiGet('${base}/user_management/enable/${username}'),
}
]),
]
}
]
}
}

View File

@@ -0,0 +1,25 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title></title>
<link href="/assets/sdk/antd.css" rel="stylesheet"/>
<link href="/assets/sdk/helper.css" rel="stylesheet"/>
<link href="/assets/sdk/iconfont.css" rel="stylesheet"/>
<style>
html, body, #app {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="/assets/sdk/sdk.js"></script>
<script type="module" src="./main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,77 @@
import {useAmis} from '../../components/constants.js'
import {userRegisterDialog} from '../../components/user/dialog-user-register.js'
useAmis(information => {
return {
type: 'page',
title: information.title,
subTitle: '统一登陆入口',
body: [
{
type: 'grid',
columns: [
{
xs: 0,
sm: 0,
md: 4,
body: [],
},
{
xs: 12,
sm: 12,
md: 4,
body: [
{
type: 'form',
title: '用户登陆',
api: '${base}/user/login',
redirect: '/pages/index/index.html?token=${token}',
mode: 'horizontal',
horizontal: {
left: 2,
},
actions: [
{
type: 'action',
label: '注册',
...userRegisterDialog(),
},
{
type: 'action',
actionType: 'submit',
label: '登陆',
level: 'primary',
},
],
body: [
{
type: 'input-email',
name: 'username',
label: '邮箱',
placeholder: '请输入邮箱',
required: true,
clearable: true,
clearValueOnEmpty: true,
},
{
type: 'input-password',
name: 'password',
label: '密码',
placeholder: '请再次输入密码',
required: true,
},
],
},
],
},
{
xs: 0,
sm: 0,
md: 4,
body: [],
},
],
},
],
}
})

1174
gringotts-frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 694 KiB

After

Width:  |  Height:  |  Size: 694 KiB

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