feat: 实现发行申请提交

This commit is contained in:
2025-12-16 03:49:39 +07:00
parent 831bc78ec5
commit 2d1454d08e
21 changed files with 870 additions and 40 deletions

16
components.d.ts vendored
View File

@@ -20,6 +20,8 @@ declare module 'vue' {
IonAvatar: typeof import('@ionic/vue')['IonAvatar'] IonAvatar: typeof import('@ionic/vue')['IonAvatar']
IonBackButton: typeof import('@ionic/vue')['IonBackButton'] IonBackButton: typeof import('@ionic/vue')['IonBackButton']
IonBackdrop: typeof import('@ionic/vue')['IonBackdrop'] IonBackdrop: typeof import('@ionic/vue')['IonBackdrop']
IonBreadcrumb: typeof import('@ionic/vue')['IonBreadcrumb']
IonBreadcrumbs: typeof import('@ionic/vue')['IonBreadcrumbs']
IonButton: typeof import('@ionic/vue')['IonButton'] IonButton: typeof import('@ionic/vue')['IonButton']
IonButtons: typeof import('@ionic/vue')['IonButtons'] IonButtons: typeof import('@ionic/vue')['IonButtons']
IonCard: typeof import('@ionic/vue')['IonCard'] IonCard: typeof import('@ionic/vue')['IonCard']
@@ -58,10 +60,16 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
UiAvatar: typeof import('./src/components/ui/avatar/index.vue')['default'] UiAvatar: typeof import('./src/components/ui/avatar/index.vue')['default']
UiCollapse: typeof import('./src/components/ui/collapse/index.vue')['default']
UiCollapseExample: typeof import('./src/components/ui/collapse/example.vue')['default']
UiDatetime: typeof import('./src/components/ui/datetime/index.vue')['default']
UiDivider: typeof import('./src/components/ui/divider/index.vue')['default'] UiDivider: typeof import('./src/components/ui/divider/index.vue')['default']
UiForm: typeof import('./src/components/ui/form/index.vue')['default'] UiForm: typeof import('./src/components/ui/form/index.vue')['default']
UiFormFormItem: typeof import('./src/components/ui/form/form-item.vue')['default']
UiFormItem: typeof import('./src/components/ui/form/item.vue')['default']
UiInput: typeof import('./src/components/ui/input/index.vue')['default'] UiInput: typeof import('./src/components/ui/input/index.vue')['default']
UiInputLabel: typeof import('./src/components/ui/input-label/index.vue')['default'] UiInputLabel: typeof import('./src/components/ui/input-label/index.vue')['default']
UiResult: typeof import('./src/components/ui/result/index.vue')['default']
UiSelect: typeof import('./src/components/ui/select/index.vue')['default'] UiSelect: typeof import('./src/components/ui/select/index.vue')['default']
UiSelectLabel: typeof import('./src/components/ui/select-label/index.vue')['default'] UiSelectLabel: typeof import('./src/components/ui/select-label/index.vue')['default']
} }
@@ -77,6 +85,8 @@ declare global {
const IonAvatar: typeof import('@ionic/vue')['IonAvatar'] const IonAvatar: typeof import('@ionic/vue')['IonAvatar']
const IonBackButton: typeof import('@ionic/vue')['IonBackButton'] const IonBackButton: typeof import('@ionic/vue')['IonBackButton']
const IonBackdrop: typeof import('@ionic/vue')['IonBackdrop'] const IonBackdrop: typeof import('@ionic/vue')['IonBackdrop']
const IonBreadcrumb: typeof import('@ionic/vue')['IonBreadcrumb']
const IonBreadcrumbs: typeof import('@ionic/vue')['IonBreadcrumbs']
const IonButton: typeof import('@ionic/vue')['IonButton'] const IonButton: typeof import('@ionic/vue')['IonButton']
const IonButtons: typeof import('@ionic/vue')['IonButtons'] const IonButtons: typeof import('@ionic/vue')['IonButtons']
const IonCard: typeof import('@ionic/vue')['IonCard'] const IonCard: typeof import('@ionic/vue')['IonCard']
@@ -115,10 +125,16 @@ declare global {
const RouterLink: typeof import('vue-router')['RouterLink'] const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView'] const RouterView: typeof import('vue-router')['RouterView']
const UiAvatar: typeof import('./src/components/ui/avatar/index.vue')['default'] const UiAvatar: typeof import('./src/components/ui/avatar/index.vue')['default']
const UiCollapse: typeof import('./src/components/ui/collapse/index.vue')['default']
const UiCollapseExample: typeof import('./src/components/ui/collapse/example.vue')['default']
const UiDatetime: typeof import('./src/components/ui/datetime/index.vue')['default']
const UiDivider: typeof import('./src/components/ui/divider/index.vue')['default'] const UiDivider: typeof import('./src/components/ui/divider/index.vue')['default']
const UiForm: typeof import('./src/components/ui/form/index.vue')['default'] const UiForm: typeof import('./src/components/ui/form/index.vue')['default']
const UiFormFormItem: typeof import('./src/components/ui/form/form-item.vue')['default']
const UiFormItem: typeof import('./src/components/ui/form/item.vue')['default']
const UiInput: typeof import('./src/components/ui/input/index.vue')['default'] const UiInput: typeof import('./src/components/ui/input/index.vue')['default']
const UiInputLabel: typeof import('./src/components/ui/input-label/index.vue')['default'] const UiInputLabel: typeof import('./src/components/ui/input-label/index.vue')['default']
const UiResult: typeof import('./src/components/ui/result/index.vue')['default']
const UiSelect: typeof import('./src/components/ui/select/index.vue')['default'] const UiSelect: typeof import('./src/components/ui/select/index.vue')['default']
const UiSelectLabel: typeof import('./src/components/ui/select-label/index.vue')['default'] const UiSelectLabel: typeof import('./src/components/ui/select-label/index.vue')['default']
} }

View File

@@ -23,17 +23,21 @@
"@elysiajs/eden": "^1.4.5", "@elysiajs/eden": "^1.4.5",
"@ionic/vue": "^8.7.11", "@ionic/vue": "^8.7.11",
"@ionic/vue-router": "^8.7.11", "@ionic/vue-router": "^8.7.11",
"@riwa/api-types": "http://192.168.1.36:9527/api/riwa-api-types-0.0.12.tgz", "@riwa/api-types": "http://192.168.1.36:9527/api/riwa-api-types-0.0.16.tgz",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"@vee-validate/yup": "^4.15.1",
"@vueuse/core": "^14.1.0", "@vueuse/core": "^14.1.0",
"@vueuse/router": "^14.1.0",
"better-auth": "^1.4.6", "better-auth": "^1.4.6",
"ionicons": "^8.0.13", "ionicons": "^8.0.13",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"vee-validate": "^4.15.1",
"vue": "^3.5.25", "vue": "^3.5.25",
"vue-i18n": "^11.2.2", "vue-i18n": "^11.2.2",
"vue-router": "^4.6.3" "vue-router": "^4.6.3",
"yup": "^1.7.1"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^6.6.1", "@antfu/eslint-config": "^6.6.1",

97
pnpm-lock.yaml generated
View File

@@ -36,14 +36,20 @@ importers:
specifier: ^8.7.11 specifier: ^8.7.11
version: 8.7.11(@stencil/core@4.39.0)(vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3)) version: 8.7.11(@stencil/core@4.39.0)(vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3))
'@riwa/api-types': '@riwa/api-types':
specifier: http://192.168.1.36:9527/api/riwa-api-types-0.0.12.tgz specifier: http://192.168.1.36:9527/api/riwa-api-types-0.0.16.tgz
version: http://192.168.1.36:9527/api/riwa-api-types-0.0.12.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))) version: http://192.168.1.36:9527/api/riwa-api-types-0.0.16.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.1.18 specifier: ^4.1.18
version: 4.1.18(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) version: 4.1.18(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))
'@vee-validate/yup':
specifier: ^4.15.1
version: 4.15.1(vue@3.5.25(typescript@5.9.3))(yup@1.7.1)
'@vueuse/core': '@vueuse/core':
specifier: ^14.1.0 specifier: ^14.1.0
version: 14.1.0(vue@3.5.25(typescript@5.9.3)) version: 14.1.0(vue@3.5.25(typescript@5.9.3))
'@vueuse/router':
specifier: ^14.1.0
version: 14.1.0(vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3))
better-auth: better-auth:
specifier: ^1.4.6 specifier: ^1.4.6
version: 1.4.6(vue@3.5.25(typescript@5.9.3)) version: 1.4.6(vue@3.5.25(typescript@5.9.3))
@@ -59,6 +65,9 @@ importers:
tailwindcss: tailwindcss:
specifier: ^4.1.18 specifier: ^4.1.18
version: 4.1.18 version: 4.1.18
vee-validate:
specifier: ^4.15.1
version: 4.15.1(vue@3.5.25(typescript@5.9.3))
vue: vue:
specifier: ^3.5.25 specifier: ^3.5.25
version: 3.5.25(typescript@5.9.3) version: 3.5.25(typescript@5.9.3)
@@ -68,6 +77,9 @@ importers:
vue-router: vue-router:
specifier: ^4.6.3 specifier: ^4.6.3
version: 4.6.3(vue@3.5.25(typescript@5.9.3)) version: 4.6.3(vue@3.5.25(typescript@5.9.3))
yup:
specifier: ^1.7.1
version: 1.7.1
devDependencies: devDependencies:
'@antfu/eslint-config': '@antfu/eslint-config':
specifier: ^6.6.1 specifier: ^6.6.1
@@ -1251,9 +1263,9 @@ packages:
'@quansync/fs@1.0.0': '@quansync/fs@1.0.0':
resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
'@riwa/api-types@http://192.168.1.36:9527/api/riwa-api-types-0.0.12.tgz': '@riwa/api-types@http://192.168.1.36:9527/api/riwa-api-types-0.0.16.tgz':
resolution: {tarball: http://192.168.1.36:9527/api/riwa-api-types-0.0.12.tgz} resolution: {tarball: http://192.168.1.36:9527/api/riwa-api-types-0.0.16.tgz}
version: 0.0.12 version: 0.0.16
peerDependencies: peerDependencies:
'@elysiajs/eden': ^1.4.5 '@elysiajs/eden': ^1.4.5
@@ -1744,6 +1756,11 @@ packages:
peerDependencies: peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
'@vee-validate/yup@4.15.1':
resolution: {integrity: sha512-+u6lI1IZftjHphj+mTCPJRruwBBwv1IKKCI1EFm6ipQroAPibkS5M8UNX+yeVYG5++ix6m1rsv4/SJvJJQTWJg==}
peerDependencies:
yup: ^1.3.2
'@vitejs/plugin-legacy@7.2.1': '@vitejs/plugin-legacy@7.2.1':
resolution: {integrity: sha512-CaXb/y0mlfu7jQRELEJJc2/5w2bX2m1JraARgFnvSB2yfvnCNJVWWlqAo6WjnKoepOwKx8gs0ugJThPLKCOXIg==} resolution: {integrity: sha512-CaXb/y0mlfu7jQRELEJJc2/5w2bX2m1JraARgFnvSB2yfvnCNJVWWlqAo6WjnKoepOwKx8gs0ugJThPLKCOXIg==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
@@ -1903,6 +1920,12 @@ packages:
'@vueuse/metadata@14.1.0': '@vueuse/metadata@14.1.0':
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==} resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
'@vueuse/router@14.1.0':
resolution: {integrity: sha512-8h7g0PhjcMC2Vnu9zBkN1J038JFIzkS3/DP2L5ouzFEhY3YAM8zkIOZ0K+hzAWkYEFLGmWGcgBfuvCUD0U42Jw==}
peerDependencies:
vue: ^3.5.0
vue-router: ^4.0.0
'@vueuse/shared@14.1.0': '@vueuse/shared@14.1.0':
resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==} resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
peerDependencies: peerDependencies:
@@ -3875,6 +3898,9 @@ packages:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
property-expr@2.0.6:
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
proto-list@1.2.4: proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -4244,6 +4270,9 @@ packages:
through@2.3.8: through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tiny-case@1.0.3:
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
tinybench@2.9.0: tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -4293,6 +4322,9 @@ packages:
resolution: {integrity: sha512-9mjy3frhioGIVGcwamlVlUyJ9x+WHw/TXiz9R4YOlmsIuBN43r9Dp8HZ35SF9EKjHrn3BUZj04CF+YqZ2oJ+7w==} resolution: {integrity: sha512-9mjy3frhioGIVGcwamlVlUyJ9x+WHw/TXiz9R4YOlmsIuBN43r9Dp8HZ35SF9EKjHrn3BUZj04CF+YqZ2oJ+7w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
toposort@2.0.2:
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
totalist@3.0.1: totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -4345,6 +4377,14 @@ packages:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'} engines: {node: '>=8'}
type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
type-fest@4.41.0:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
typescript-eslint@8.49.0: typescript-eslint@8.49.0:
resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -4504,6 +4544,11 @@ packages:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true hasBin: true
vee-validate@4.15.1:
resolution: {integrity: sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==}
peerDependencies:
vue: ^3.4.26
verror@1.10.0: verror@1.10.0:
resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
engines: {'0': node >=0.6.0} engines: {'0': node >=0.6.0}
@@ -4735,6 +4780,9 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
yup@1.7.1:
resolution: {integrity: sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==}
zod@4.1.13: zod@4.1.13:
resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==}
@@ -6053,7 +6101,7 @@ snapshots:
dependencies: dependencies:
quansync: 1.0.0 quansync: 1.0.0
'@riwa/api-types@http://192.168.1.36:9527/api/riwa-api-types-0.0.12.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))': '@riwa/api-types@http://192.168.1.36:9527/api/riwa-api-types-0.0.16.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))':
dependencies: dependencies:
'@elysiajs/eden': 1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)) '@elysiajs/eden': 1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))
@@ -6561,6 +6609,14 @@ snapshots:
unplugin-utils: 0.3.1 unplugin-utils: 0.3.1
vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)
'@vee-validate/yup@4.15.1(vue@3.5.25(typescript@5.9.3))(yup@1.7.1)':
dependencies:
type-fest: 4.41.0
vee-validate: 4.15.1(vue@3.5.25(typescript@5.9.3))
yup: 1.7.1
transitivePeerDependencies:
- vue
'@vitejs/plugin-legacy@7.2.1(terser@5.44.1)(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': '@vitejs/plugin-legacy@7.2.1(terser@5.44.1)(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))':
dependencies: dependencies:
'@babel/core': 7.28.5 '@babel/core': 7.28.5
@@ -6802,6 +6858,12 @@ snapshots:
'@vueuse/metadata@14.1.0': {} '@vueuse/metadata@14.1.0': {}
'@vueuse/router@14.1.0(vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3))':
dependencies:
'@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3))
vue: 3.5.25(typescript@5.9.3)
vue-router: 4.6.3(vue@3.5.25(typescript@5.9.3))
'@vueuse/shared@14.1.0(vue@3.5.25(typescript@5.9.3))': '@vueuse/shared@14.1.0(vue@3.5.25(typescript@5.9.3))':
dependencies: dependencies:
vue: 3.5.25(typescript@5.9.3) vue: 3.5.25(typescript@5.9.3)
@@ -8962,6 +9024,8 @@ snapshots:
kleur: 3.0.3 kleur: 3.0.3
sisteransi: 1.0.5 sisteransi: 1.0.5
property-expr@2.0.6: {}
proto-list@1.2.4: {} proto-list@1.2.4: {}
proxy-from-env@1.0.0: {} proxy-from-env@1.0.0: {}
@@ -9344,6 +9408,8 @@ snapshots:
through@2.3.8: {} through@2.3.8: {}
tiny-case@1.0.3: {}
tinybench@2.9.0: {} tinybench@2.9.0: {}
tinyexec@1.0.2: {} tinyexec@1.0.2: {}
@@ -9388,6 +9454,8 @@ snapshots:
dependencies: dependencies:
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
toposort@2.0.2: {}
totalist@3.0.1: {} totalist@3.0.1: {}
tough-cookie@5.1.2: tough-cookie@5.1.2:
@@ -9429,6 +9497,10 @@ snapshots:
type-fest@0.8.1: {} type-fest@0.8.1: {}
type-fest@2.19.0: {}
type-fest@4.41.0: {}
typescript-eslint@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): typescript-eslint@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3):
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
@@ -9623,6 +9695,12 @@ snapshots:
uuid@8.3.2: {} uuid@8.3.2: {}
vee-validate@4.15.1(vue@3.5.25(typescript@5.9.3)):
dependencies:
'@vue/devtools-api': 7.7.9
type-fest: 4.41.0
vue: 3.5.25(typescript@5.9.3)
verror@1.10.0: verror@1.10.0:
dependencies: dependencies:
assert-plus: 1.0.0 assert-plus: 1.0.0
@@ -9820,6 +9898,13 @@ snapshots:
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
yup@1.7.1:
dependencies:
property-expr: 2.0.6
tiny-case: 1.0.3
toposort: 2.0.2
type-fest: 2.19.0
zod@4.1.13: {} zod@4.1.13: {}
zwitch@2.0.4: {} zwitch@2.0.4: {}

View File

@@ -9,7 +9,7 @@ const client = treaty<App>(window.location.origin, {
}); });
export async function safeClient<T, E>( export async function safeClient<T, E>(
requestPromise: Promise<{ data: T; error: E | null }>, requestPromise: Promise<{ data: T; error: E }>,
options: { silent?: boolean } = {}, options: { silent?: boolean } = {},
) { ) {
const { data, error } = await requestPromise; const { data, error } = await requestPromise;

View File

@@ -19,3 +19,9 @@ export type WithdrawBody = Omit<Parameters<typeof client.api.withdraw.post>[0],
export type UserProfileData = Treaty.Data<typeof client.api.user.profile.get>["userProfile"]; export type UserProfileData = Treaty.Data<typeof client.api.user.profile.get>["userProfile"];
export type UpdateUserProfileBody = NonNullable<Parameters<typeof client.api.user.profile.put>[0]>; export type UpdateUserProfileBody = NonNullable<Parameters<typeof client.api.user.profile.put>[0]>;
export type RwaIssuanceProductsData = Treaty.Data<typeof client.api.rwa.issuance.products.bundle.post>;
export type RwaIssuanceProductBody = NonNullable<Parameters<typeof client.api.rwa.issuance.products.bundle.post>[0]>;
export type RwaIssuanceCategoriesData = Treaty.Data<typeof client.api.rwa.issuance.categories.get>;

View File

@@ -0,0 +1,2 @@
export { default as Collapse } from './index.vue';
export type * from './types';

View File

@@ -0,0 +1,138 @@
<script lang='ts' setup>
import type { CollapseEmits, CollapseProps } from "./types";
import { IonIcon } from "@ionic/vue";
import { chevronDownOutline } from "ionicons/icons";
import { nextTick, ref } from "vue";
const props = withDefaults(defineProps<CollapseProps>(), {
disabled: false,
bordered: true,
size: "medium",
});
defineEmits<CollapseEmits>();
const active = defineModel<boolean>("active", { type: Boolean, default: true });
const contentRef = ref<HTMLElement>();
async function toggle() {
if (props.disabled)
return;
if (!active.value) {
// 展开
active.value = true;
await nextTick();
if (contentRef.value) {
const height = contentRef.value.scrollHeight;
contentRef.value.style.height = "0px";
requestAnimationFrame(() => {
contentRef.value!.style.height = `${height}px`;
});
}
}
else {
// 收起
if (contentRef.value) {
const height = contentRef.value.scrollHeight;
contentRef.value.style.height = `${height}px`;
requestAnimationFrame(() => {
contentRef.value!.style.height = "0px";
});
}
setTimeout(() => {
active.value = false;
}, 300);
}
setTimeout(() => {
if (contentRef.value && active.value) {
contentRef.value.style.height = "auto";
}
}, 300);
}
function onTransitionEnd() {
if (contentRef.value && !active.value) {
contentRef.value.style.height = "0px";
}
}
// 暴露给父组件的方法和属性
defineExpose({
toggle,
});
</script>
<template>
<div
class="w-full overflow-hidden"
:class="{
'border border-gray-200 dark:border-gray-600 rounded-lg': bordered,
'opacity-60': disabled,
}"
>
<!-- 头部触发区域 -->
<div
class="flex items-center justify-between cursor-pointer transition-colors duration-200 select-none"
:class="[
{
'border-b border-gray-200 dark:border-gray-600': bordered,
},
{
'p-3 text-sm': size === 'small',
'p-4 text-base': size === 'medium',
'p-5 text-lg': size === 'large',
},
{
'cursor-not-allowed': disabled,
},
]"
@click="toggle"
>
<div class="font-medium text-gray-900 dark:text-white flex-1">
<slot name="title">
{{ title }}
</slot>
</div>
<IonIcon
:icon="chevronDownOutline"
class="text-gray-500 dark:text-gray-400 transition-transform duration-300"
:class="[
{
'w-4 h-4': size === 'small',
'w-5 h-5': size === 'medium',
'w-6 h-6': size === 'large',
},
{
'rotate-180': active,
},
]"
/>
</div>
<!-- 内容区域 -->
<div
ref="contentRef"
class="overflow-hidden transition-all duration-300 ease-in-out"
:style="{ height: active ? 'auto' : '0' }"
@transitionend="onTransitionEnd"
>
<div
class="p-4 text-gray-600 dark:text-gray-300 space-y-5"
>
<slot />
</div>
</div>
</div>
</template>
<style lang='css' scoped>
.select-none {
user-select: none;
}
</style>

View File

@@ -0,0 +1,29 @@
export interface CollapseProps {
/** 面板标题 */
title?: string;
/** 是否禁用 */
disabled?: boolean;
/** 是否显示边框 */
bordered?: boolean;
/** 尺寸大小 */
size?: "small" | "medium" | "large";
/** 是否展开 (v-model:active) */
active?: boolean;
}
export interface CollapseEmits {
/** 展开状态变化时触发 */
"update:active": [value: boolean];
}
export interface CollapseSlots {
/** 默认插槽 - 面板内容 */
default: () => any;
/** 标题插槽 - 自定义标题内容 */
title: () => any;
}
export interface CollapseInstance {
/** 切换展开/收起状态 */
toggle: () => void;
}

View File

@@ -0,0 +1,39 @@
<script lang='ts' setup>
import type { FieldBindingObject } from "vee-validate";
interface Props extends FieldBindingObject {
label?: string;
}
const props = defineProps<Props>();
function handleChange(value: string) {
const formattedValue = useDateFormat(value, "YYYY/MM/DD").value;
props.onChange(formattedValue);
}
</script>
<template>
<div class="flex flex-col items-start">
<ion-label class="text-sm font-bold color-(--ion-text-color-secondary) mb-3.5">
{{ props.label }}
</ion-label>
<ion-datetime-button datetime="datetime" color="primary">
<div slot="date-target">
{{ props.value }}
</div>
</ion-datetime-button>
<ion-modal :keep-contents-mounted="true">
<ion-datetime
id="datetime"
class="ui-datetime"
done-text="Done"
presentation="date"
:show-default-buttons="true"
@ion-change="handleChange($event.detail.value as string)"
/>
</ion-modal>
</div>
</template>
<style lang='css' scoped></style>

View File

@@ -1,9 +0,0 @@
<script lang='ts' setup>
</script>
<template>
hello world
</template>
<style lang='css' scoped></style>

View File

@@ -0,0 +1,22 @@
<script lang='ts' setup>
defineProps<{
icon?: string;
title?: string;
description?: string;
}>();
</script>
<template>
<div class="flex-col-center space-y-3">
<ion-icon :icon="icon" class="text-5xl text-lime-500" />
<div class="text-xl font-semibold">
{{ title }}
</div>
<p class="text-center text-text-400 max-w-xs">
{{ description }}
</p>
<slot name="extra" />
</div>
</template>
<style lang='css' scoped></style>

View File

@@ -47,7 +47,63 @@
"myCompany": "My Company", "myCompany": "My Company",
"issuingApply": "Issuing Apply", "issuingApply": "Issuing Apply",
"audit": "Audit", "audit": "Audit",
"supervision": "Supervision" "supervision": "Supervision",
"apply": {
"title": "Issuing Apply",
"base": "Base",
"issuePeriod": "Issue Period",
"productName": "Product Name",
"enterProductName": "Please enter product name",
"productCode": "Product Code",
"enterProductCode": "Please enter product code",
"productType": "Product Type",
"chooseProductType": "Please choose product type",
"productValue": "Product Valuation",
"enterProductValue": "Please enter product valuation (USD)",
"assetProof": "Asset Proof",
"enterAssetProof": "Please enter asset proof",
"totalSupplyLimit": "Total Supply Limit",
"enterTotalSupplyLimit": "Please enter total supply limit",
"editionName": "Edition Name",
"enterEditionName": "Please enter edition name",
"launchDate": "Launch Date",
"enterLaunchDate": "Please enter launch date",
"perUserLimit": "Per User Limit",
"enterPerUserLimit": "Please enter per user limit",
"totalSupply": "Total Supply",
"enterTotalSupply": "Please enter total supply",
"subscriptionDeadline": "Subscription Deadline",
"enterSubscriptionDeadline": "Please enter subscription deadline",
"unitPrice": "Unit Price",
"enterUnitPrice": "Please enter unit price",
"dividendRate": "Dividend Rate",
"enterDividendRate": "Dividend rate (e.g., 0.01 for 1%)",
"issuePeriodIndex": "Issue Period {index}",
"realEstate": "Real Estate",
"snickers": "Snickers",
"next": "Next",
"back": "Back",
"submit": "Submit",
"addStep": "Add Step",
"removeItem": "Remove Item",
"validation": {
"nameRequired": "Name is required",
"codeRequired": "Code is required",
"categoryRequired": "Type is required",
"editionNameRequired": "Edition name is required",
"launchDateRequired": "Launch date is required",
"perUserLimitRequired": "Per user limit is required",
"totalSupplyRequired": "Total supply is required",
"subscriptionDeadlineRequired": "Subscription deadline is required",
"unitPriceRequired": "Unit price is required",
"dividendRateRequired": "Dividend rate is required"
},
"done": {
"title": "Application Submitted Successfully",
"description": "Your issuance application has been submitted successfully. We will review your application within business days. Please wait patiently for the review results.",
"viewProducts": "View My Products"
}
}
}, },
"purchase": { "purchase": {
"purchaseAsset": "Purchase Assets", "purchaseAsset": "Purchase Assets",

View File

@@ -47,7 +47,63 @@
"myCompany": "我的企业", "myCompany": "我的企业",
"issuingApply": "发行申请", "issuingApply": "发行申请",
"audit": "审核审计", "audit": "审核审计",
"supervision": "委托监管" "supervision": "委托监管",
"apply": {
"title": "发行申请",
"base": "基础信息",
"issuePeriod": "发行期",
"productName": "产品名称",
"enterProductName": "请输入产品名称",
"productCode": "产品编码",
"enterProductCode": "请输入产品编码",
"productType": "产品类型",
"chooseProductType": "请选择产品类型",
"productValue": "产品估值",
"enterProductValue": "请输入产品估值(美元)",
"assetProof": "资产证明",
"enterAssetProof": "请输入资产证明",
"totalSupplyLimit": "总发行量上限",
"enterTotalSupplyLimit": "请输入总发行量上限",
"editionName": "发行期名称",
"enterEditionName": "请输入发行期名称",
"launchDate": "发行日期",
"enterLaunchDate": "请输入发行日期",
"perUserLimit": "个人申购上限",
"enterPerUserLimit": "请输入个人申购上限",
"totalSupply": "发行总量",
"enterTotalSupply": "请输入发行总量",
"subscriptionDeadline": "申购截止日期",
"enterSubscriptionDeadline": "请输入申购截止日期",
"unitPrice": "单价",
"enterUnitPrice": "请输入单价",
"dividendRate": "分红率",
"enterDividendRate": "分红率(如0.01代表1%)",
"issuePeriodIndex": "发行期 {index}",
"realEstate": "房地产",
"snickers": "糖果",
"next": "下一步",
"back": "返回",
"submit": "提交",
"addStep": "添加发行期",
"removeItem": "移除项目",
"validation": {
"nameRequired": "产品名称是必填项",
"codeRequired": "产品编码是必填项",
"categoryRequired": "产品类型是必填项",
"editionNameRequired": "发行期名称是必填项",
"launchDateRequired": "发行日期是必填项",
"perUserLimitRequired": "个人申购上限是必填项",
"totalSupplyRequired": "发行总量是必填项",
"subscriptionDeadlineRequired": "申购截止日期是必填项",
"unitPriceRequired": "单价是必填项",
"dividendRateRequired": "分红率是必填项"
},
"done": {
"title": "申请提交成功",
"description": "您的发行申请已成功提交,我们将在工作日内对您的申请进行审核。请耐心等待审核结果。",
"viewProducts": "查看我的产品"
}
}
}, },
"purchase": { "purchase": {
"purchaseAsset": "购买资产", "purchaseAsset": "购买资产",

View File

@@ -54,6 +54,7 @@ const routes: Array<RouteRecordRaw> = [
}, },
{ {
path: "/issue/issuing-apply", path: "/issue/issuing-apply",
props: ({ query, params }) => ({ query, params }),
component: () => import("@/views/issue/issuing-apply/index.vue"), component: () => import("@/views/issue/issuing-apply/index.vue"),
}, },
]; ];

View File

@@ -2,3 +2,30 @@
--background: var(--ion-color-primary-contrast); --background: var(--ion-color-primary-contrast);
--min-height: 50px; --min-height: 50px;
} }
.ui-select::part(label){
font-size: 14px;
font-weight: 500;
}
.ui-button {
--border-radius: 8px;
}
ion-datetime.ui-datetime {
--background: rgb(255 255 255);
--background-rgb: 255, 255, 255;
--wheel-highlight-background: rgb(194 194 194);
--wheel-fade-background-rgb: 255, 255, 255;
border-radius: 16px;
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
ion-datetime.ui-datetime {
--background: rgb(15, 15, 15);
--background-rgb: 15, 15, 15;
--wheel-highlight-background: rgb(50, 50, 50);
--wheel-highlight-border-radius: 48px;
--wheel-fade-background-rgb: 15, 15, 15;
}
}

View File

@@ -0,0 +1,127 @@
<script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { RwaIssuanceCategoriesData, RwaIssuanceProductBody } from "@/api/types";
import { toTypedSchema } from "@vee-validate/yup";
import { ErrorMessage, Field, FieldArray, Form } from "vee-validate";
import { useI18n } from "vue-i18n";
import * as yup from "yup";
import { client, safeClient } from "@/api";
const props = defineProps<{
initialData: RwaIssuanceProductBody["product"];
categories: RwaIssuanceCategoriesData;
}>();
const emit = defineEmits<{
(e: "next", values: GenericObject): void;
}>();
const { t } = useI18n();
const schema = toTypedSchema(
yup.object({
name: yup.string().required(t("asset.issue.apply.validation.nameRequired")),
code: yup.string().required(t("asset.issue.apply.validation.codeRequired")),
categoryId: yup.string().required(t("asset.issue.apply.validation.categoryRequired")),
}),
);
function handleSubmit(values: GenericObject) {
emit("next", values);
}
</script>
<template>
<Form
:validation-schema="schema" :initial-values="initialData"
@submit="handleSubmit"
>
<div class="space-y-5">
<div>
<Field name="name" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.productName')"
:placeholder="t('asset.issue.apply.enterProductName')"
/>
</template>
</Field>
<ErrorMessage name="name" />
</div>
<div>
<Field name="code" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.productCode')"
:placeholder="t('asset.issue.apply.enterProductCode')"
/>
</template>
</Field>
<ErrorMessage name="code" />
</div>
<div>
<Field name="categoryId" type="text">
<template #default="{ field }">
<ion-select class="ui-select" interface="action-sheet" toggle-icon="" v-bind="field" :label="t('asset.issue.apply.productType')" :placeholder="t('asset.issue.apply.chooseProductType')">
<ion-select-option v-for="item in categories" :key="item.id" :value="item.id">
{{ item.name }}
</ion-select-option>
</ion-select>
</template>
</Field>
<ErrorMessage name="categoryId" />
</div>
<div>
<Field name="estimatedValue" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="text"
inputmode="numeric"
:label="t('asset.issue.apply.productValue')"
:placeholder="t('asset.issue.apply.enterProductValue')"
/>
</template>
</Field>
<ErrorMessage name="estimatedValue" />
</div>
<div>
<Field name="proofDocuments" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.assetProof')"
:placeholder="t('asset.issue.apply.enterAssetProof')"
/>
</template>
</Field>
<ErrorMessage name="proofDocuments" />
</div>
<div>
<Field name="totalSupplyLimit" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.totalSupplyLimit')"
:placeholder="t('asset.issue.apply.enterTotalSupplyLimit')"
/>
</template>
</Field>
<ErrorMessage name="totalSupplyLimit" />
</div>
<ion-button type="submit" expand="block">
{{ t('asset.issue.apply.next') }}
</ion-button>
</div>
</Form>
</template>
<style lang='css' scoped></style>

View File

@@ -0,0 +1,27 @@
<script lang='ts' setup>
import { ribbonOutline } from "ionicons/icons";
const { t } = useI18n();
</script>
<template>
<div>
<ui-result
:icon="ribbonOutline"
:title="t('asset.issue.apply.done.title')"
:description="t('asset.issue.apply.done.description')"
>
<template #extra>
<ion-button
expand="block"
color="primary"
router-link="/issue/issuance-products"
>
{{ t('asset.issue.apply.done.viewProducts') }}
</ion-button>
</template>
</ui-result>
</div>
</template>
<style lang='css' scoped></style>

View File

@@ -1,14 +1,62 @@
<script lang='ts' setup> <script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { RwaIssuanceProductBody } from "@/api/types";
import { useRouteQuery } from "@vueuse/router";
import { useI18n } from "vue-i18n";
import { client, safeClient } from "@/api";
import Base from "./base.vue";
import Done from "./done.vue";
import IssuePeriod from "./issue-period.vue";
const { t } = useI18n();
const now = useNow();
const { data: categories = [] } = await safeClient(client.api.rwa.issuance.categories.get());
const step = useRouteQuery<number>("step", 1, { transform: v => Number(v), mode: "push" });
const initialData: RwaIssuanceProductBody = {
product: {
name: "",
code: "",
categoryId: categories!.length > 0 ? categories![0].id : "",
},
editions: [
{
editionName: "",
launchDate: useDateFormat(now.value, "YYYY/MM/DD").value,
perUserLimit: "",
subscriptionDeadline: useDateFormat(now.value, "YYYY/MM/DD").value,
totalSupply: "",
unitPrice: "",
dividendRate: "",
},
],
};
const form = useStorage<RwaIssuanceProductBody>("issuing-apply-form", { ...initialData });
function handleNext(values: GenericObject) {
form.value.product = { ...values as any };
step.value = 2;
}
async function handleSubmit(editions: RwaIssuanceProductBody["editions"]) {
form.value.editions = editions;
await client.api.rwa.issuance.products.bundle.post(form.value);
form.value = { ...initialData };
step.value = 3;
}
</script> </script>
<template> <template>
<IonPage> <IonPage>
<ion-header> <ion-header>
<ion-toolbar class="ui-toolbar"> <ion-toolbar class="ui-toolbar">
<ion-title>Issuing Apply</ion-title> <ion-title>{{ t('asset.issue.apply.title') }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<IonContent :fullscreen="true" class="ion-padding" /> <IonContent :fullscreen="true" class="ion-padding">
<Base v-if="step === 1" :initial-data="form.product" :categories="categories || []" @next="handleNext" />
<IssuePeriod v-else-if="step === 2" :initial-data="form.editions" @submit="handleSubmit" />
<Done v-else />
</IonContent>
</IonPage> </IonPage>
</template> </template>

View File

@@ -0,0 +1,173 @@
<script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { RwaIssuanceProductBody } from "@/api/types";
import { toTypedSchema } from "@vee-validate/yup";
import { addOutline, removeOutline } from "ionicons/icons";
import { ErrorMessage, Field, FieldArray, Form } from "vee-validate";
import { useI18n } from "vue-i18n";
import * as yup from "yup";
const props = defineProps<{
initialData: RwaIssuanceProductBody["editions"];
}>();
const emit = defineEmits<{
(e: "submit", values: RwaIssuanceProductBody["editions"]): void;
}>();
const { t } = useI18n();
const now = useNow();
const initialIssuePeriod: RwaIssuanceProductBody["editions"][0] = {
editionName: "",
launchDate: useDateFormat(now.value, "YYYY/MM/DD").value,
perUserLimit: "",
subscriptionDeadline: useDateFormat(now.value, "YYYY/MM/DD").value,
totalSupply: "",
unitPrice: "",
dividendRate: "",
};
const initialValues = ref({
editions: props.initialData || [{ ...initialIssuePeriod }],
});
const schema = toTypedSchema(yup.object({
editions: yup.array().of(
yup.object({
editionName: yup.string().required(t("asset.issue.apply.validation.editionNameRequired")),
launchDate: yup.string().required(t("asset.issue.apply.validation.launchDateRequired")),
perUserLimit: yup.string().required(t("asset.issue.apply.validation.perUserLimitRequired")),
totalSupply: yup.string().required(t("asset.issue.apply.validation.totalSupplyRequired")),
subscriptionDeadline: yup.string().required(t("asset.issue.apply.validation.subscriptionDeadlineRequired")),
unitPrice: yup.string().required(t("asset.issue.apply.validation.unitPriceRequired")),
dividendRate: yup.string().required(t("asset.issue.apply.validation.dividendRateRequired")),
}),
),
}));
function handleSubmit(values: GenericObject) {
emit("submit", values.editions);
}
</script>
<template>
<Form :validation-schema="schema" :initial-values="initialValues" class="space-y-5" @submit="handleSubmit">
<FieldArray v-slot="{ fields, push, remove }" name="editions">
<ui-collapse v-for="(entry, idx) in fields" :key="entry.key" :title="t('asset.issue.apply.issuePeriodIndex', { index: idx + 1 })" size="small">
<div>
<Field :name="`editions[${idx}].editionName`">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.editionName')"
:placeholder="t('asset.issue.apply.enterEditionName')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].editionName`" />
</div>
<div>
<Field :name="`editions[${idx}].launchDate`">
<template #default="{ field }">
<ui-datetime
v-bind="field"
:label="t('asset.issue.apply.launchDate')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].launchDate`" />
</div>
<div>
<Field :name="`editions[${idx}].perUserLimit`" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.perUserLimit')"
:placeholder="t('asset.issue.apply.enterPerUserLimit')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].perUserLimit`" />
</div>
<div>
<Field :name="`editions[${idx}].totalSupply`" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.totalSupply')"
:placeholder="t('asset.issue.apply.enterTotalSupply')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].totalSupply`" />
</div>
<div>
<Field :name="`editions[${idx}].subscriptionDeadline`">
<template #default="{ field }">
<ui-datetime
v-bind="field"
:label="t('asset.issue.apply.subscriptionDeadline')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].subscriptionDeadline`" />
</div>
<div>
<Field :name="`editions[${idx}].unitPrice`" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.unitPrice')"
:placeholder="t('asset.issue.apply.enterUnitPrice')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].unitPrice`" />
</div>
<div>
<Field :name="`editions[${idx}].dividendRate`" type="number">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.dividendRate')"
:placeholder="t('asset.issue.apply.enterDividendRate')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].dividendRate`" />
</div>
<ion-button color="tertiary" size="small" class="ui-button" @click="remove(idx)">
<ion-icon slot="start" :icon="removeOutline" />
<span>{{ t('asset.issue.apply.removeItem') }}</span>
</ion-button>
</ui-collapse>
<ion-button expand="block" color="tertiary" fill="outline" @click="push({ ...initialIssuePeriod })">
<ion-icon slot="icon-only" :icon="addOutline" />
<span>{{ t('asset.issue.apply.addStep') }}</span>
</ion-button>
</FieldArray>
<ion-button expand="block" @click="$router.back()">
{{ t('asset.issue.apply.back') }}
</ion-button>
<ion-button expand="block" type="submit">
{{ t('asset.issue.apply.submit') }}
</ion-button>
</Form>
</template>
<style lang='css' scoped></style>

View File

@@ -30,7 +30,6 @@ import WalletCard from "./components/wallet-card.vue";
<WalletCard /> <WalletCard />
<IssuingAsset /> <IssuingAsset />
<PurchaseAsset /> <PurchaseAsset />
<AssetBalance />
<MyRevenue /> <MyRevenue />
</IonContent> </IonContent>
</IonPage> </IonPage>

View File

@@ -160,6 +160,7 @@ onMounted(() => {
<ion-modal :keep-contents-mounted="true"> <ion-modal :keep-contents-mounted="true">
<ion-datetime <ion-datetime
id="datetime" id="datetime"
class="ui-datetime"
done-text="Done" done-text="Done"
presentation="date" presentation="date"
:value="userProfile?.birthday" :value="userProfile?.birthday"
@@ -187,21 +188,4 @@ ion-avatar {
ion-item { ion-item {
--min-height: 60px; --min-height: 60px;
} }
ion-datetime {
--background: rgb(255 255 255);
--background-rgb: 255, 255, 255;
--wheel-highlight-background: rgb(194 194 194);
--wheel-fade-background-rgb: 255, 255, 255;
border-radius: 16px;
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
ion-datetime {
--background: rgb(15, 15, 15);
--background-rgb: 15, 15, 15;
--wheel-highlight-background: rgb(50, 50, 50);
--wheel-highlight-border-radius: 48px;
--wheel-fade-background-rgb: 15, 15, 15;
}
}
</style> </style>