max hai 7 meses
pai
achega
cd11b525e2
Modificáronse 100 ficheiros con 2326 adicións e 9530 borrados
  1. 4 2
      .env
  2. BIN=BIN
      dist.zip
  3. 1 0
      package.json
  4. 23 0
      pnpm-lock.yaml
  5. 2 2
      src/config/proxy.ts
  6. 766 0
      src/cool/utils/city.ts
  7. 1 1
      src/modules/base/pages/login/index.vue
  8. 0 255
      src/modules/chat/components/index.vue
  9. 0 354
      src/modules/chat/components/message.vue
  10. 0 209
      src/modules/chat/components/session.vue
  11. 0 15
      src/modules/chat/config.ts
  12. 0 10
      src/modules/chat/hooks/index.ts
  13. 0 56
      src/modules/chat/service/message.ts
  14. 0 43
      src/modules/chat/service/session.ts
  15. 0 12
      src/modules/chat/store/index.ts
  16. 0 46
      src/modules/chat/store/message.ts
  17. 0 46
      src/modules/chat/store/session.ts
  18. 0 36
      src/modules/chat/types/index.d.ts
  19. 0 27
      src/modules/demo/config.ts
  20. 0 5
      src/modules/demo/directives/color.ts
  21. 0 154
      src/modules/demo/service/test.ts
  22. 0 6
      src/modules/demo/service/user/follow.ts
  23. 0 33
      src/modules/demo/service/user/info.ts
  24. 0 138
      src/modules/demo/views/crud/components/adv-search/base.vue
  25. 0 152
      src/modules/demo/views/crud/components/adv-search/custom.vue
  26. 0 41
      src/modules/demo/views/crud/components/code.vue
  27. 0 574
      src/modules/demo/views/crud/components/crud/all.vue
  28. 0 145
      src/modules/demo/views/crud/components/crud/base.vue
  29. 0 164
      src/modules/demo/views/crud/components/crud/dict.vue
  30. 0 182
      src/modules/demo/views/crud/components/crud/event.vue
  31. 0 185
      src/modules/demo/views/crud/components/crud/service.vue
  32. 0 118
      src/modules/demo/views/crud/components/form/children.vue
  33. 0 124
      src/modules/demo/views/crud/components/form/component/index.vue
  34. 0 38
      src/modules/demo/views/crud/components/form/component/select-labels.vue
  35. 0 43
      src/modules/demo/views/crud/components/form/component/select-status.vue
  36. 0 59
      src/modules/demo/views/crud/components/form/component/select-work.vue
  37. 0 38
      src/modules/demo/views/crud/components/form/component/select-work2.vue
  38. 0 122
      src/modules/demo/views/crud/components/form/config.vue
  39. 0 154
      src/modules/demo/views/crud/components/form/crud.vue
  40. 0 64
      src/modules/demo/views/crud/components/form/disabled.vue
  41. 0 93
      src/modules/demo/views/crud/components/form/event.vue
  42. 0 105
      src/modules/demo/views/crud/components/form/group.vue
  43. 0 77
      src/modules/demo/views/crud/components/form/hidden.vue
  44. 0 98
      src/modules/demo/views/crud/components/form/layout.vue
  45. 0 83
      src/modules/demo/views/crud/components/form/open.vue
  46. 0 172
      src/modules/demo/views/crud/components/form/options.vue
  47. 0 109
      src/modules/demo/views/crud/components/form/plugin/index.vue
  48. 0 20
      src/modules/demo/views/crud/components/form/plugin/role.ts
  49. 0 75
      src/modules/demo/views/crud/components/form/required.vue
  50. 0 123
      src/modules/demo/views/crud/components/form/rules.vue
  51. 0 168
      src/modules/demo/views/crud/components/other/tips.vue
  52. 0 28
      src/modules/demo/views/crud/components/other/tsx/index.scss
  53. 0 109
      src/modules/demo/views/crud/components/other/tsx/index.tsx
  54. 0 147
      src/modules/demo/views/crud/components/search/base.vue
  55. 0 176
      src/modules/demo/views/crud/components/search/custom.vue
  56. 0 152
      src/modules/demo/views/crud/components/search/layout.vue
  57. 0 109
      src/modules/demo/views/crud/components/table/base.vue
  58. 0 98
      src/modules/demo/views/crud/components/table/children.vue
  59. 0 108
      src/modules/demo/views/crud/components/table/column-custom.vue
  60. 0 108
      src/modules/demo/views/crud/components/table/component/index.vue
  61. 0 30
      src/modules/demo/views/crud/components/table/component/user-info.vue
  62. 0 191
      src/modules/demo/views/crud/components/table/context-menu.vue
  63. 0 156
      src/modules/demo/views/crud/components/table/dict.vue
  64. 0 98
      src/modules/demo/views/crud/components/table/formatter.vue
  65. 0 127
      src/modules/demo/views/crud/components/table/hidden.vue
  66. 0 164
      src/modules/demo/views/crud/components/table/op.vue
  67. 0 48
      src/modules/demo/views/crud/components/table/plugin/column.tsx
  68. 0 86
      src/modules/demo/views/crud/components/table/plugin/index.vue
  69. 0 143
      src/modules/demo/views/crud/components/table/search.vue
  70. 0 110
      src/modules/demo/views/crud/components/table/selection.vue
  71. 0 97
      src/modules/demo/views/crud/components/table/slot.vue
  72. 0 116
      src/modules/demo/views/crud/components/table/span-method.vue
  73. 0 95
      src/modules/demo/views/crud/components/table/summary.vue
  74. 0 133
      src/modules/demo/views/crud/components/upsert/base.vue
  75. 0 210
      src/modules/demo/views/crud/components/upsert/event.vue
  76. 0 200
      src/modules/demo/views/crud/components/upsert/hook/index.vue
  77. 0 14
      src/modules/demo/views/crud/components/upsert/hook/reg-pca2.ts
  78. 0 146
      src/modules/demo/views/crud/components/upsert/mode.vue
  79. 0 294
      src/modules/demo/views/crud/index.vue
  80. 0 84
      src/modules/demo/views/home/components/category-ratio.vue
  81. 0 90
      src/modules/demo/views/home/components/count-effect.vue
  82. 0 89
      src/modules/demo/views/home/components/count-paid.vue
  83. 0 80
      src/modules/demo/views/home/components/count-user.vue
  84. 0 125
      src/modules/demo/views/home/components/count-views.vue
  85. 0 265
      src/modules/demo/views/home/components/hot-search.vue
  86. 0 201
      src/modules/demo/views/home/components/sales-rank.vue
  87. 0 151
      src/modules/demo/views/home/components/tab-chart.vue
  88. 0 122
      src/modules/demo/views/home/index.vue
  89. 0 52
      src/modules/demo/views/test/route.vue
  90. 3 2
      src/modules/helper/components/auto-menu/btn.vue
  91. 159 0
      src/modules/payment/views/auth.vue
  92. 236 0
      src/modules/payment/views/business.vue
  93. 135 0
      src/modules/payment/views/channel.vue
  94. 224 0
      src/modules/payment/views/components/business.vue
  95. 183 0
      src/modules/payment/views/components/individual.vue
  96. 83 0
      src/modules/payment/views/components/transfer.vue
  97. 85 0
      src/modules/payment/views/components/withdraw.vue
  98. 101 0
      src/modules/payment/views/currency.vue
  99. 172 0
      src/modules/payment/views/individual.vue
  100. 148 0
      src/modules/payment/views/merchant.vue

+ 4 - 2
.env

@@ -1,5 +1,7 @@
 # 应用名称
-VITE_NAME = "COOL-ADMIN"
+VITE_NAME = "FUSION-ADMIN"
 
 # 网络超时请求时间
-VITE_TIMEOUT = 30000
+VITE_TIMEOUT = 30000
+
+DEV=true

BIN=BIN
dist.zip


+ 1 - 0
package.json

@@ -26,6 +26,7 @@
 		"file-saver": "^2.0.5",
 		"lodash-es": "^4.17.21",
 		"marked": "^14.1.3",
+		"md5": "^2.3.0",
 		"mitt": "^3.0.1",
 		"mockjs": "^1.1.0",
 		"monaco-editor": "0.52.0",

+ 23 - 0
pnpm-lock.yaml

@@ -47,6 +47,9 @@ dependencies:
   marked:
     specifier: ^14.1.3
     version: 14.1.3
+  md5:
+    specifier: ^2.3.0
+    version: 2.3.0
   mitt:
     specifier: ^3.0.1
     version: 3.0.1
@@ -2086,6 +2089,10 @@ packages:
     resolution: {integrity: sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==}
     dev: false
 
+  /charenc@0.0.2:
+    resolution: {integrity: sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=}
+    dev: false
+
   /chokidar@3.6.0:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
@@ -2201,6 +2208,10 @@ packages:
       which: 2.0.2
     dev: true
 
+  /crypt@0.0.2:
+    resolution: {integrity: sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=}
+    dev: false
+
   /css-select@5.1.0:
     resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
     dependencies:
@@ -3284,6 +3295,10 @@ packages:
       has-tostringtag: 1.0.2
     dev: false
 
+  /is-buffer@1.1.6:
+    resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
+    dev: false
+
   /is-callable@1.2.7:
     resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
     engines: {node: '>= 0.4'}
@@ -3653,6 +3668,14 @@ packages:
     hasBin: true
     dev: false
 
+  /md5@2.3.0:
+    resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
+    dependencies:
+      charenc: 0.0.2
+      crypt: 0.0.2
+      is-buffer: 1.1.6
+    dev: false
+
   /mdn-data@2.0.28:
     resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
     dev: true

+ 2 - 2
src/config/proxy.ts

@@ -1,12 +1,12 @@
 export const proxy = {
 	'/dev/': {
-		target: 'http://127.0.0.1:8001',
+		target: 'http://127.0.0.1:8008',
 		changeOrigin: true,
 		rewrite: (path: string) => path.replace(/^\/dev/, '')
 	},
 
 	'/prod/': {
-		target: 'https://show.cool-admin.com',
+		target: 'http://127.0.0.1:8008',
 		changeOrigin: true,
 		rewrite: (path: string) => path.replace(/^\/prod/, '/api')
 	}

+ 766 - 0
src/cool/utils/city.ts

@@ -0,0 +1,766 @@
+export const cityList = [
+	{
+		label: 'United States',
+		value: 'US'
+	},
+	{
+		label: 'Canada',
+		value: 'CA'
+	},
+	{
+		label: 'Hong Kong',
+		value: 'HK'
+	},
+	{
+		label: 'Mexico',
+		value: 'MX'
+	},
+	{
+		label: 'Brazil',
+		value: 'BR'
+	},
+	{
+		label: 'Argentina',
+		value: 'AR'
+	},
+	{
+		label: 'Peru',
+		value: 'PE'
+	},
+	{
+		label: 'Colombia',
+		value: 'CO'
+	},
+	{
+		label: 'Chile',
+		value: 'CL'
+	},
+	{
+		label: 'Venezuela',
+		value: 'VE'
+	},
+	{
+		label: 'Costa Rica',
+		value: 'CR'
+	},
+	{
+		label: 'Cuba',
+		value: 'CU'
+	},
+	{
+		label: 'Dominican Republic',
+		value: 'DO'
+	},
+	{
+		label: 'Ecuador',
+		value: 'EC'
+	},
+	{
+		label: 'El Salvador',
+		value: 'SV'
+	},
+	{
+		label: 'Guatemala',
+		value: 'GT'
+	},
+	{
+		label: 'Honduras',
+		value: 'HN'
+	},
+	{
+		label: 'Nicaragua',
+		value: 'NI'
+	},
+	{
+		label: 'Panama',
+		value: 'PA'
+	},
+	{
+		label: 'Paraguay',
+		value: 'PY'
+	},
+	{
+		label: 'Uruguay',
+		value: 'UY'
+	},
+	{
+		label: 'Guyana',
+		value: 'GY'
+	},
+	{
+		label: 'Suriname',
+		value: 'SR'
+	},
+	{
+		label: 'French Guiana',
+		value: 'GF'
+	},
+	{
+		label: 'Belize',
+		value: 'BZ'
+	},
+	{
+		label: 'Jamaica',
+		value: 'JM'
+	},
+	{
+		label: 'Haiti',
+		value: 'HT'
+	},
+	{
+		label: 'Bahamas',
+		value: 'BS'
+	},
+	{
+		label: 'Cuba',
+		value: 'CU'
+	},
+	{
+		label: 'Trinidad and Tobago',
+		value: 'TT'
+	},
+	{
+		label: 'Dominican Republic',
+		value: 'DO'
+	},
+	{
+		label: 'Saint Lucia',
+		value: 'LC'
+	},
+	{
+		label: 'Saint Vincent and the Grenadines',
+		value: 'VC'
+	},
+	{
+		label: 'Barbados',
+		value: 'BB'
+	},
+	{
+		label: 'Grenada',
+		value: 'GD'
+	},
+	{
+		label: 'Saint Kitts and Nevis',
+		value: 'KN'
+	},
+	{
+		label: 'Albania',
+		value: 'AL'
+	},
+	{
+		label: 'Andorra',
+		value: 'AD'
+	},
+	{
+		label: 'Armenia',
+		value: 'AM'
+	},
+	{
+		label: 'Austria',
+		value: 'AT'
+	},
+	{
+		label: 'Azerbaijan',
+		value: 'AZ'
+	},
+	{
+		label: 'Belarus',
+		value: 'BY'
+	},
+	{
+		label: 'Belgium',
+		value: 'BE'
+	},
+	{
+		label: 'Bosnia and Herzegovina',
+		value: 'BA'
+	},
+	{
+		label: 'Bulgaria',
+		value: 'BG'
+	},
+	{
+		label: 'Croatia',
+		value: 'HR'
+	},
+	{
+		label: 'Cyprus',
+		value: 'CY'
+	},
+	{
+		label: 'Czechia (Czech Republic)',
+		value: 'CZ'
+	},
+	{
+		label: 'Denmark',
+		value: 'DK'
+	},
+	{
+		label: 'Estonia',
+		value: 'EE'
+	},
+	{
+		label: 'Faroe Islands',
+		value: 'FO'
+	},
+	{
+		label: 'Finland',
+		value: 'FI'
+	},
+	{
+		label: 'France',
+		value: 'FR'
+	},
+	{
+		label: 'Georgia',
+		value: 'GE'
+	},
+	{
+		label: 'Germany',
+		value: 'DE'
+	},
+	{
+		label: 'Greece',
+		value: 'GR'
+	},
+	{
+		label: 'Greenland',
+		value: 'GL'
+	},
+	{
+		label: 'Hungary',
+		value: 'HU'
+	},
+	{
+		label: 'Iceland',
+		value: 'IS'
+	},
+	{
+		label: 'Ireland',
+		value: 'IE'
+	},
+	{
+		label: 'Italy',
+		value: 'IT'
+	},
+	{
+		label: 'Kazakhstan',
+		value: 'KZ'
+	},
+	{
+		label: 'Latvia',
+		value: 'LV'
+	},
+	{
+		label: 'Liechtenstein',
+		value: 'LI'
+	},
+	{
+		label: 'Lithuania',
+		value: 'LT'
+	},
+	{
+		label: 'Luxembourg',
+		value: 'LU'
+	},
+	{
+		label: 'North Macedonia (Macedonia)',
+		value: 'MK'
+	},
+	{
+		label: 'Malta',
+		value: 'MT'
+	},
+	{
+		label: 'Moldova',
+		value: 'MD'
+	},
+	{
+		label: 'Monaco',
+		value: 'MC'
+	},
+	{
+		label: 'Montenegro',
+		value: 'ME'
+	},
+	{
+		label: 'Netherlands',
+		value: 'NL'
+	},
+	{
+		label: 'Norway',
+		value: 'NO'
+	},
+	{
+		label: 'Poland',
+		value: 'PL'
+	},
+	{
+		label: 'Portugal',
+		value: 'PT'
+	},
+	{
+		label: 'Romania',
+		value: 'RO'
+	},
+	{
+		label: 'Russia',
+		value: 'RU'
+	},
+	{
+		label: 'San Marino',
+		value: 'SM'
+	},
+	{
+		label: 'Serbia',
+		value: 'RS'
+	},
+	{
+		label: 'Slovakia',
+		value: 'SK'
+	},
+	{
+		label: 'Slovenia',
+		value: 'SI'
+	},
+	{
+		label: 'Spain',
+		value: 'ES'
+	},
+	{
+		label: 'Sweden',
+		value: 'SE'
+	},
+	{
+		label: 'Switzerland',
+		value: 'CH'
+	},
+	{
+		label: 'Turkey',
+		value: 'TR'
+	},
+	{
+		label: 'Ukraine',
+		value: 'UA'
+	},
+	{
+		label: 'United Kingdom',
+		value: 'GB'
+	},
+	{
+		label: 'Vatican City',
+		value: 'VA'
+	},
+	{
+		label: 'Algeria',
+		value: 'DZ'
+	},
+	{
+		label: 'Angola',
+		value: 'AO'
+	},
+	{
+		label: 'Benin',
+		value: 'BJ'
+	},
+	{
+		label: 'Botswana',
+		value: 'BW'
+	},
+	{
+		label: 'Burkina Faso',
+		value: 'BF'
+	},
+	{
+		label: 'Burundi',
+		value: 'BI'
+	},
+	{
+		label: 'Cameroon',
+		value: 'CM'
+	},
+	{
+		label: 'Cape Verde',
+		value: 'CV'
+	},
+	{
+		label: 'Central African Republic',
+		value: 'CF'
+	},
+	{
+		label: 'Chad',
+		value: 'TD'
+	},
+	{
+		label: 'Comoros',
+		value: 'KM'
+	},
+	{
+		label: 'Congo (Congo-Brazzaville)',
+		value: 'CG'
+	},
+	{
+		label: 'Congo (Congo-Kinshasa)',
+		value: 'CD'
+	},
+	{
+		label: 'Djibouti',
+		value: 'DJ'
+	},
+	{
+		label: 'Egypt',
+		value: 'EG'
+	},
+	{
+		label: 'Equatorial Guinea',
+		value: 'GQ'
+	},
+	{
+		label: 'Eritrea',
+		value: 'ER'
+	},
+	{
+		label: 'Eswatini (fmr. Swaziland)',
+		value: 'SZ'
+	},
+	{
+		label: 'Ethiopia',
+		value: 'ET'
+	},
+	{
+		label: 'Gabon',
+		value: 'GA'
+	},
+	{
+		label: 'Gambia',
+		value: 'GM'
+	},
+	{
+		label: 'Ghana',
+		value: 'GH'
+	},
+	{
+		label: 'Guinea',
+		value: 'GN'
+	},
+	{
+		label: 'Guinea-Bissau',
+		value: 'GW'
+	},
+	{
+		label: "Côte d'Ivoire",
+		value: 'CI'
+	},
+	{
+		label: 'Kenya',
+		value: 'KE'
+	},
+	{
+		label: 'Lesotho',
+		value: 'LS'
+	},
+	{
+		label: 'Liberia',
+		value: 'LR'
+	},
+	{
+		label: 'Libya',
+		value: 'LY'
+	},
+	{
+		label: 'Madagascar',
+		value: 'MG'
+	},
+	{
+		label: 'Malawi',
+		value: 'MW'
+	},
+	{
+		label: 'Mali',
+		value: 'ML'
+	},
+	{
+		label: 'Mauritania',
+		value: 'MR'
+	},
+	{
+		label: 'Mauritius',
+		value: 'MU'
+	},
+	{
+		label: 'Mayotte',
+		value: 'YT'
+	},
+	{
+		label: 'Morocco',
+		value: 'MA'
+	},
+	{
+		label: 'Mozambique',
+		value: 'MZ'
+	},
+	{
+		label: 'Namibia',
+		value: 'NA'
+	},
+	{
+		label: 'Niger',
+		value: 'NE'
+	},
+	{
+		label: 'Nigeria',
+		value: 'NG'
+	},
+	{
+		label: 'Rwanda',
+		value: 'RW'
+	},
+	{
+		label: 'Réunion',
+		value: 'RE'
+	},
+	{
+		label: 'São Tomé and Príncipe',
+		value: 'ST'
+	},
+	{
+		label: 'Senegal',
+		value: 'SN'
+	},
+	{
+		label: 'Seychelles',
+		value: 'SC'
+	},
+	{
+		label: 'Sierra Leone',
+		value: 'SL'
+	},
+	{
+		label: 'Somalia',
+		value: 'SO'
+	},
+	{
+		label: 'South Africa',
+		value: 'ZA'
+	},
+	{
+		label: 'South Sudan',
+		value: 'SS'
+	},
+	{
+		label: 'Saint Helena',
+		value: 'SH'
+	},
+	{
+		label: 'Sudan',
+		value: 'SD'
+	},
+	{
+		label: 'Tanzania',
+		value: 'TZ'
+	},
+	{
+		label: 'Togo',
+		value: 'TG'
+	},
+	{
+		label: 'Tunisia',
+		value: 'TN'
+	},
+	{
+		label: 'Uganda',
+		value: 'UG'
+	},
+	{
+		label: 'Western Sahara',
+		value: 'EH'
+	},
+	{
+		label: 'Zambia',
+		value: 'ZM'
+	},
+	{
+		label: 'Zimbabwe',
+		value: 'ZW'
+	},
+	{
+		label: 'Afghanistan',
+		value: 'AF'
+	},
+	{
+		label: 'Armenia',
+		value: 'AM'
+	},
+	{
+		label: 'Azerbaijan',
+		value: 'AZ'
+	},
+	{
+		label: 'Bahrain',
+		value: 'BH'
+	},
+	{
+		label: 'Bangladesh',
+		value: 'BD'
+	},
+	{
+		label: 'Bhutan',
+		value: 'BT'
+	},
+	{
+		label: 'Brunei',
+		value: 'BN'
+	},
+	{
+		label: 'Cambodia',
+		value: 'KH'
+	},
+	{
+		label: 'China',
+		value: 'CN'
+	},
+	{
+		label: 'Cyprus',
+		value: 'CY'
+	},
+	{
+		label: 'Georgia',
+		value: 'GE'
+	},
+	{
+		label: 'India',
+		value: 'IN'
+	},
+	{
+		label: 'Indonesia',
+		value: 'ID'
+	},
+	{
+		label: 'Iran',
+		value: 'IR'
+	},
+	{
+		label: 'Iraq',
+		value: 'IQ'
+	},
+	{
+		label: 'Israel',
+		value: 'IL'
+	},
+	{
+		label: 'Japan',
+		value: 'JP'
+	},
+	{
+		label: 'Jordan',
+		value: 'JO'
+	},
+	{
+		label: 'Kazakhstan',
+		value: 'KZ'
+	},
+	{
+		label: 'Kuwait',
+		value: 'KW'
+	},
+	{
+		label: 'Kyrgyzstan',
+		value: 'KG'
+	},
+	{
+		label: 'Laos',
+		value: 'LA'
+	},
+	{
+		label: 'Lebanon',
+		value: 'LB'
+	},
+	{
+		label: 'Malaysia',
+		value: 'MY'
+	},
+	{
+		label: 'Maldives',
+		value: 'MV'
+	},
+	{
+		label: 'Mongolia',
+		value: 'MN'
+	},
+	{
+		label: 'Myanmar',
+		value: 'MM'
+	},
+	{
+		label: 'Nepal',
+		value: 'NP'
+	},
+	{
+		label: 'North Korea',
+		value: 'KP'
+	},
+	{
+		label: 'Oman',
+		value: 'OM'
+	},
+	{
+		label: 'Pakistan',
+		value: 'PK'
+	},
+	{
+		label: 'Palestine',
+		value: 'PS'
+	},
+	{
+		label: 'Philippines',
+		value: 'PH'
+	},
+	{
+		label: 'Qatar',
+		value: 'QA'
+	},
+	{
+		label: 'Saudi Arabia',
+		value: 'SA'
+	},
+	{
+		label: 'Singapore',
+		value: 'SG'
+	},
+	{
+		label: 'South Korea',
+		value: 'KR'
+	},
+	{
+		label: 'Sri Lanka',
+		value: 'LK'
+	},
+	{
+		label: 'Syria',
+		value: 'SY'
+	},
+	{
+		label: 'Taiwan',
+		value: 'TW'
+	},
+	{
+		label: 'Cayman Islands',
+		value: 'KY'
+	},
+	{
+		label: 'United Arab Emirates',
+		value: 'AE'
+	},
+	{
+		label: 'British Virgin Islands',
+		value: 'VG'
+	},
+	{
+		label: 'Macau',
+		value: 'MO'
+	},
+	{
+		label: 'Tajikistan',
+		value: 'TJ'
+	}
+];

+ 1 - 1
src/modules/base/pages/login/index.vue

@@ -16,7 +16,7 @@
 						<input
 							v-model="form.username"
 							placeholder="请输入用户名"
-							maxlength="20"
+							maxlength="60"
 							type="text"
 							:readonly="readonly"
 							autocomplete="off"

+ 0 - 255
src/modules/chat/components/index.vue

@@ -1,255 +0,0 @@
-<template>
-	<div class="cl-chat__icon" @click="open">
-		<el-badge :value="unCount" :hidden="!unCount">
-			<cl-svg name="icon-notice" :size="16" />
-		</el-badge>
-	</div>
-
-	<!-- 弹框 -->
-	<cl-dialog
-		v-model="visible"
-		title="聊天窗口"
-		height="70vh"
-		width="1200px"
-		padding="0"
-		keep-alive
-		:scrollbar="false"
-		:close-on-click-modal="false"
-		close-on-press-escape
-		:controls="['slot-expand', 'cl-flex1', 'fullscreen', 'close']"
-	>
-		<div
-			class="cl-chat"
-			:class="{
-				'is-mini': browser.isMini,
-				'is-expand': isExpand
-			}"
-		>
-			<div class="cl-chat__session">
-				<chat-session />
-			</div>
-
-			<div class="cl-chat__right">
-				<chat-message />
-			</div>
-		</div>
-
-		<!-- 展开按钮 -->
-		<template #slot-expand>
-			<button class="cl-dialog__controls-icon">
-				<el-icon v-if="!isExpand" @click="isExpand = true">
-					<notebook />
-				</el-icon>
-				<el-icon v-else @click="isExpand = false">
-					<arrow-left />
-				</el-icon>
-			</button>
-		</template>
-	</cl-dialog>
-</template>
-
-<script lang="ts" name="cl-chat" setup>
-import { nextTick, provide, ref } from 'vue';
-import dayjs from 'dayjs';
-import { useCool, module, useBrowser } from '/@/cool';
-import { useBase } from '/$/base';
-import { Notebook, ArrowLeft } from '@element-plus/icons-vue';
-import { debounce } from 'lodash-es';
-// import io from 'socket.io-client';
-import { Socket } from 'socket.io-client';
-import ChatMessage from './message.vue';
-import ChatSession from './session.vue';
-import type { Chat } from '../types';
-import { useStore } from '../store';
-
-const { mitt } = useCool();
-const { browser, onScreenChange } = useBrowser();
-
-// 缓存
-const { session, message } = useStore();
-
-// 缓存
-const { user } = useBase();
-
-// 模块配置
-const { options } = module.get('chat');
-
-// 是否可见
-const visible = ref(false);
-
-// 是否展开
-const isExpand = ref(true);
-
-// 未读消息
-const unCount = ref(parseInt(String(Math.random() * 100)));
-
-// Socket
-let socket: Socket;
-
-// 连接
-function connect() {
-	refresh();
-
-	// if (!socket) {
-	// socket = io(config.host + options.path, {
-	// 	auth: {
-	// 		token: user.token
-	// 	}
-	// });
-	// socket.on("connect", () => {
-	// 	console.log(`connect ${user.info?.nickName}`);
-	// 	// 监听消息
-	// 	socket.on("message", (msg) => {
-	// 		console.log(msg);
-	// 		mitt("chat-message", msg);
-	// 	});
-	// 	refresh();
-	// });
-	// socket.on("disconnect", (err) => {
-	// 	console.error(err);
-	// });
-	// }
-}
-
-// 打开
-function open() {
-	visible.value = true;
-	connect();
-}
-
-// 关闭
-function close() {
-	visible.value = false;
-}
-
-// 收起、展开
-function expand(value?: boolean) {
-	isExpand.value = value === undefined ? !isExpand.value : value;
-}
-
-// 发送消息
-function send(data: Chat.Message, isAppend?: boolean) {
-	// socket.emit("message", {});
-
-	if (isAppend) {
-		append(data);
-	}
-}
-
-// 追加消息
-function append(data: Chat.Message) {
-	message.list.push({
-		fromId: user.info?.id,
-		toId: session.value?.userId,
-		avatar: user.info?.headImg,
-		nickName: user.info?.nickName,
-		createTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
-		...data
-	});
-
-	scrollToBottom();
-}
-
-// 滚动到底部
-const scrollToBottom = debounce(() => {
-	nextTick(() => {
-		const box = document.querySelector('.cl-chat .chat-message .list');
-		box?.scroll({
-			top: 100000 + Math.random(),
-			behavior: 'smooth'
-		});
-	});
-}, 300);
-
-// 刷新
-async function refresh() {
-	await session.get();
-	await message.get();
-	scrollToBottom();
-}
-
-provide('chat', {
-	// get socket() {
-	// 	return socket;
-	// },
-	send,
-	append,
-	expand,
-	scrollToBottom
-});
-
-// 监听屏幕变化
-onScreenChange(() => {
-	isExpand.value = browser.isMini ? false : true;
-});
-
-defineExpose({
-	open,
-	close
-});
-</script>
-
-<style lang="scss">
-.cl-chat {
-	display: flex;
-	justify-content: flex-end;
-	background-color: #eee;
-	padding: 5px;
-	box-sizing: border-box;
-	position: relative;
-	color: #000;
-	height: 100%;
-
-	&__icon {
-		display: flex;
-		align-items: center;
-		justify-content: center;
-		width: 45px;
-
-		&:hover {
-			color: var(--color-primary);
-		}
-	}
-
-	&__footer {
-		padding: 9px 0;
-	}
-
-	&__session {
-		height: calc(100% - 10px);
-		width: 345px;
-		position: absolute;
-		left: 5px;
-		top: 5px;
-	}
-
-	&__right {
-		position: relative;
-		z-index: 99;
-		transition: width 0.3s;
-		width: 100%;
-	}
-
-	&.is-mini {
-		&.is-expand {
-			.cl-chat__session {
-				z-index: 100;
-			}
-		}
-
-		.cl-chat__session {
-			width: calc(100% - 10px);
-		}
-
-		.cl-chat__right {
-			width: 100% !important;
-		}
-	}
-
-	&.is-expand {
-		.cl-chat__right {
-			width: calc(100% - 350px);
-		}
-	}
-}
-</style>

+ 0 - 354
src/modules/chat/components/message.vue

@@ -1,354 +0,0 @@
-<template>
-	<div v-loading="message?.loading" class="chat-message" element-loading-text="消息列表加载中">
-		<!-- 头部 -->
-		<div class="head">
-			<template v-if="session?.value">
-				<div class="avatar">
-					<cl-avatar :size="30" shape="square" :src="session?.value.avatar" />
-				</div>
-				<span class="name">与“{{ session?.value.nickName }}”聊天中</span>
-			</template>
-		</div>
-
-		<!-- 消息列表 -->
-		<el-scrollbar class="list">
-			<ul>
-				<li v-for="(item, index) in list" :key="index">
-					<div
-						class="item"
-						:class="{
-							'is-right': item.isMy
-						}"
-					>
-						<div class="avatar">
-							<cl-avatar :size="36" shape="square" :src="item.avatar" />
-						</div>
-
-						<div
-							class="det"
-							@contextmenu="
-								e => {
-									onContextMenu(e, item);
-								}
-							"
-						>
-							<div class="h">
-								<span class="name">{{ item.nickName }}</span>
-							</div>
-							<div class="content">
-								<!-- 文本 -->
-								<div v-if="item.contentType == 0" class="is-text">
-									<span>{{ item.content.text }}</span>
-								</div>
-
-								<!-- 图片 -->
-								<div v-else-if="item.contentType == 1" class="is-image">
-									<el-image
-										:src="item.content.imageUrl"
-										:preview-src-list="previewUrls"
-										:initial-index="item._index"
-										scroll-container=".chat-message .list"
-									/>
-								</div>
-							</div>
-						</div>
-					</div>
-				</li>
-			</ul>
-		</el-scrollbar>
-
-		<!-- 底部 -->
-		<div class="footer">
-			<div class="tools">
-				<ul>
-					<cl-upload :show-file-list="false" @success="onImageSend">
-						<li>
-							<el-icon><picture-filled /></el-icon>
-						</li>
-					</cl-upload>
-
-					<li>
-						<el-icon><video-camera /></el-icon>
-					</li>
-
-					<li>
-						<el-icon><microphone /></el-icon>
-					</li>
-
-					<li>
-						<el-icon><location /></el-icon>
-					</li>
-				</ul>
-			</div>
-
-			<div class="input">
-				<el-input
-					v-model="value"
-					type="textarea"
-					:rows="4"
-					resize="none"
-					:autosize="{
-						minRows: 4,
-						maxRows: 10
-					}"
-					placeholder="输入内容"
-				></el-input>
-				<el-button type="success" :disabled="!value" @click="onTextSend">发送</el-button>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { computed, ref } from 'vue';
-import { useChat } from '../hooks';
-import { useStore } from '../store';
-import { PictureFilled, VideoCamera, Microphone, Location } from '@element-plus/icons-vue';
-import { useBase } from '/$/base';
-import { ContextMenu } from '@cool-vue/crud';
-import { useClipboard } from '@vueuse/core';
-import { Chat } from '../types';
-import { ElMessage } from 'element-plus';
-
-const { user } = useBase();
-const { chat } = useChat();
-const { message, session } = useStore();
-const { copy } = useClipboard();
-
-const value = ref('');
-
-// 过滤列表
-const list = computed(() => {
-	let n = 0;
-
-	return message.list.map(e => {
-		if (e.contentType == 1) {
-			e._index = n++;
-		}
-
-		// 是否自己发的消息
-		e.isMy = e.fromId == user.info?.id;
-
-		return e;
-	});
-});
-
-// 预览图片地址
-const previewUrls = computed(() =>
-	message.list
-		.filter(e => e.contentType == 1)
-		.map(e => e.content?.imageUrl)
-		.filter(Boolean)
-);
-
-// 文本消息
-function onTextSend() {
-	chat.send(
-		{
-			contentType: 0,
-			content: {
-				text: value.value
-			}
-		},
-		true
-	);
-	value.value = '';
-}
-
-// 图片消息
-function onImageSend(res: any) {
-	chat.send(
-		{
-			contentType: 1,
-			content: {
-				imageUrl: res.url
-			}
-		},
-		true
-	);
-	value.value = '';
-}
-
-// 右键菜单
-function onContextMenu(e: Event, item: Chat.Message) {
-	ContextMenu.open(e, {
-		hover: {
-			target: 'content'
-		},
-		list: [
-			{
-				label: '复制',
-				callback(done) {
-					copy(item.content.text || '');
-					ElMessage.success('复制成功');
-					done();
-				}
-			},
-			{
-				label: '转发'
-			},
-			{
-				label: '删除'
-			}
-		]
-	});
-}
-</script>
-
-<style lang="scss" scoped>
-.chat-message {
-	display: flex;
-	flex-direction: column;
-	background-color: #fff;
-	border-radius: 6px;
-	height: 100%;
-	box-sizing: border-box;
-
-	.head {
-		display: flex;
-		align-items: center;
-		height: 50px;
-		padding: 0 10px;
-
-		.name {
-			margin-left: 10px;
-			font-size: 14px;
-		}
-
-		ul {
-			li {
-				list-style: none;
-			}
-		}
-	}
-
-	.list {
-		flex: 1;
-		background-color: #f7f7f7;
-
-		ul {
-			& > li {
-				list-style: none;
-
-				.date {
-					display: flex;
-					align-items: center;
-					justify-content: center;
-					height: 40px;
-					font-size: 12px;
-				}
-
-				.item {
-					display: flex;
-					padding: 10px;
-
-					.avatar {
-						margin-right: 10px;
-					}
-
-					.det {
-						.h {
-							display: flex;
-							align-items: center;
-							.name {
-								font-size: 12px;
-								color: #666;
-							}
-						}
-
-						.content {
-							display: flex;
-							flex-direction: column;
-							margin-top: 5px;
-
-							.is-text {
-								background-color: #fff;
-								padding: 8px;
-								border-radius: 0 6px 6px 6px;
-								max-width: 400px;
-								font-size: 14px;
-							}
-
-							.is-image {
-								background-color: #fff;
-
-								.el-image {
-									display: block;
-									min-height: 100px;
-									max-width: 200px;
-									border-radius: 4px;
-								}
-							}
-						}
-					}
-
-					&.is-right {
-						flex-direction: row-reverse;
-
-						.avatar {
-							margin-left: 10px;
-							margin-right: 0;
-						}
-
-						.det {
-							.h {
-								justify-content: flex-end;
-							}
-
-							.content {
-								.is-text {
-									border-radius: 6px 0 6px 6px;
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-
-	.footer {
-		padding: 10px;
-
-		.tools {
-			display: flex;
-			margin-bottom: 10px;
-
-			ul {
-				display: flex;
-				align-items: center;
-				flex: 1;
-
-				li {
-					height: 26px;
-					width: 26px;
-					border-radius: 4px;
-					margin-right: 10px;
-					list-style: none;
-					display: flex;
-					justify-content: center;
-					align-items: center;
-					cursor: pointer;
-					font-size: 18px;
-
-					&:hover {
-						background-color: #f7f7f7;
-					}
-				}
-			}
-		}
-
-		.input {
-			display: flex;
-			position: relative;
-
-			.el-button {
-				margin-left: 10px;
-				position: absolute;
-				right: 10px;
-				bottom: 10px;
-			}
-		}
-	}
-}
-</style>

+ 0 - 209
src/modules/chat/components/session.vue

@@ -1,209 +0,0 @@
-<template>
-	<div class="chat-session">
-		<div class="head">
-			<el-input v-model="keyWord" placeholder="关键字搜索" clearable></el-input>
-
-			<ul class="tools">
-				<li>
-					<el-icon><plus /></el-icon>
-				</li>
-
-				<li @click="session.get()">
-					<el-icon><refresh /></el-icon>
-				</li>
-			</ul>
-		</div>
-
-		<div v-loading="session?.loading" class="list">
-			<el-scrollbar :ref="setRefs('scroller')" class="scroller">
-				<div
-					v-for="(item, index) in list"
-					:key="index"
-					class="item"
-					:class="{
-						'is-active': item.id == session?.value?.id
-					}"
-					@click="toDetail(item)"
-				>
-					<div class="avatar">
-						<el-badge :value="item.num" :hidden="item.num == 0">
-							<cl-avatar shape="square" :src="item.avatar" />
-						</el-badge>
-					</div>
-
-					<div class="det">
-						<p class="name">{{ item.nickName }}</p>
-						<p class="message">
-							{{ item.text }}
-						</p>
-					</div>
-
-					<div class="status">
-						<p class="date">{{ item.createTime }}</p>
-					</div>
-				</div>
-
-				<el-empty
-					v-if="list.length == 0"
-					:image-size="100"
-					description="暂无会话"
-				></el-empty>
-			</el-scrollbar>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { computed, nextTick, ref } from 'vue';
-import { useChat } from '../hooks';
-import { useStore } from '../store';
-import { Refresh, Plus } from '@element-plus/icons-vue';
-import { Chat } from '../types';
-import { useBrowser, useCool } from '/@/cool';
-import { useDialog } from '@cool-vue/crud';
-
-const { browser } = useBrowser();
-const { chat } = useChat();
-const { session, message } = useStore();
-const { refs, setRefs } = useCool();
-
-useDialog({
-	onFullscreen() {
-		nextTick(() => {
-			refs.scroller?.update();
-		});
-	}
-});
-
-// 关键字
-const keyWord = ref('');
-
-// 过滤列表
-const list = computed(() => session?.list.filter(e => e.nickName?.includes(keyWord.value)) || []);
-
-// 会话详情
-async function toDetail(item: Chat.Session) {
-	if (browser.isMini) {
-		chat.expand(false);
-	}
-	session.set(item);
-	await message.get({ page: 1 });
-	chat.scrollToBottom();
-}
-</script>
-
-<style lang="scss" scoped>
-.chat-session {
-	height: 100%;
-	width: 100%;
-	background-color: #fff;
-	border-radius: 6px;
-
-	.head {
-		display: flex;
-		border-bottom: 1px solid #f7f7f7;
-		padding: 10px;
-
-		:deep(.el-input) {
-			height: 30px;
-
-			.el-input__wrapper {
-				background-color: #eee;
-				box-shadow: none;
-			}
-		}
-
-		.tools {
-			display: inline-flex;
-			align-items: center;
-
-			li {
-				height: 30px;
-				width: 30px;
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				cursor: pointer;
-				margin-left: 10px;
-				border-radius: 4px;
-				background-color: #eee;
-				color: #666;
-
-				.el-icon {
-					font-size: 16px;
-				}
-
-				&:hover {
-					background-color: #ddd;
-				}
-			}
-		}
-	}
-
-	.list {
-		height: calc(100% - 51px);
-		overflow: hidden;
-
-		.scroller {
-			height: 100%;
-		}
-
-		.item {
-			display: flex;
-			padding: 15px 10px;
-			cursor: pointer;
-
-			.avatar {
-				margin-right: 10px;
-
-				:deep(.el-badge__content) {
-					transform: translateY(-50%) translateX(calc(100% - 5px)) scale(0.8) !important;
-				}
-			}
-
-			.det {
-				flex: 1;
-
-				.name,
-				.message {
-					overflow: hidden;
-					text-overflow: ellipsis;
-					display: -webkit-box;
-					-webkit-box-orient: vertical;
-					-webkit-line-clamp: 1;
-				}
-
-				.name {
-					font-size: 14px;
-					margin-bottom: 4px;
-				}
-
-				.message {
-					font-size: 12px;
-					color: #666;
-				}
-			}
-
-			.status {
-				display: flex;
-				flex-direction: column;
-				align-items: flex-end;
-				font-size: 12px;
-
-				.date {
-					margin-bottom: 5px;
-					color: #999;
-				}
-			}
-
-			&.is-active {
-				background-color: #eee;
-			}
-
-			&:not(.is-active):hover {
-				background-color: #f7f7f7;
-			}
-		}
-	}
-}
-</style>

+ 0 - 15
src/modules/chat/config.ts

@@ -1,15 +0,0 @@
-import type { ModuleConfig } from '/@/cool';
-
-export default (): ModuleConfig => {
-	return {
-		toolbar: {
-			order: 2,
-			h5: false,
-			component: import('./components/index.vue')
-		},
-		options: {
-			// socket.io 连接地址
-			path: '/chat'
-		}
-	};
-};

+ 0 - 10
src/modules/chat/hooks/index.ts

@@ -1,10 +0,0 @@
-import { inject } from 'vue';
-import { Chat } from '../types';
-
-export function useChat() {
-	const chat = inject('chat') as Chat.Provide;
-
-	return {
-		chat
-	};
-}

+ 0 - 56
src/modules/chat/service/message.ts

@@ -1,56 +0,0 @@
-import { Service } from '/@/cool';
-import Mock from 'mockjs';
-
-@Service('chat/message')
-class ChatMessage {
-	async page() {
-		return new Promise(resolve => {
-			const data = Mock.mock({
-				'list|20': [
-					{
-						id: '@id',
-						nickName: '@cname',
-						createTime: '@datetime(HH:mm:ss)',
-						text: '@cparagraph(5)',
-						'contentType|0-1': 0,
-						'userId|1-2': 1,
-						avatar() {
-							return Mock.Random.image(
-								'40x40',
-								Mock.Random.color(),
-								'#FFF',
-								'png',
-								this.nickName[0]
-							);
-						},
-						content() {
-							return JSON.stringify({
-								text: this.text,
-								imageUrl: Mock.Random.image(
-									'100x100',
-									Mock.Random.color(),
-									'#FFF',
-									'png',
-									this.nickName
-								)
-							});
-						}
-					}
-				]
-			});
-
-			setTimeout(() => {
-				resolve({
-					list: data.list,
-					pagination: {
-						total: 20,
-						page: 1,
-						size: 20
-					}
-				});
-			}, 1000);
-		});
-	}
-}
-
-export default ChatMessage;

+ 0 - 43
src/modules/chat/service/session.ts

@@ -1,43 +0,0 @@
-import { Service } from '/@/cool';
-import Mock from 'mockjs';
-
-@Service('chat/session')
-class ChatSession {
-	async page() {
-		return new Promise(resolve => {
-			const data = Mock.mock({
-				'list|20': [
-					{
-						id: '@id',
-						nickName: '@cname',
-						createTime: '@datetime(HH:mm:ss)',
-						text: '@cparagraph(5)',
-						'num|0-99': 0,
-						avatar() {
-							return Mock.Random.image(
-								'40x40',
-								Mock.Random.color(),
-								'#FFF',
-								'png',
-								this.nickName[0]
-							);
-						}
-					}
-				]
-			});
-
-			setTimeout(() => {
-				resolve({
-					list: data.list,
-					pagination: {
-						total: 20,
-						page: 1,
-						size: 20
-					}
-				});
-			}, 1000);
-		});
-	}
-}
-
-export default ChatSession;

+ 0 - 12
src/modules/chat/store/index.ts

@@ -1,12 +0,0 @@
-import { useMessageStore } from './message';
-import { useSessionStore } from './session';
-
-export function useStore() {
-	const session = useSessionStore();
-	const message = useMessageStore();
-
-	return {
-		session,
-		message
-	};
-}

+ 0 - 46
src/modules/chat/store/message.ts

@@ -1,46 +0,0 @@
-import { defineStore } from 'pinia';
-import { ref } from 'vue';
-import { service } from '/@/cool';
-
-export const useMessageStore = defineStore('chat-message', () => {
-	// 加载状态
-	const loading = ref(false);
-
-	// 列表
-	const list = ref<any[]>([]);
-
-	// 分页
-	const pagination = ref({
-		page: 1,
-		total: 0,
-		size: 20
-	});
-
-	// 获取列表
-	async function get(params?: any) {
-		loading.value = true;
-
-		// 清空
-		if (params?.page == 1) {
-			list.value = [];
-		}
-
-		// 发送请求
-		await service.chat.message.page(params).then(res => {
-			list.value = res.list.map(e => {
-				e.content = JSON.parse(e.content);
-				return e;
-			});
-			pagination.value = res.pagination;
-		});
-
-		loading.value = false;
-	}
-
-	return {
-		loading,
-		list,
-		pagination,
-		get
-	};
-});

+ 0 - 46
src/modules/chat/store/session.ts

@@ -1,46 +0,0 @@
-import { defineStore } from 'pinia';
-import { ref } from 'vue';
-import { service } from '/@/cool';
-
-export const useSessionStore = defineStore('chat-session', () => {
-	// 加载状态
-	const loading = ref(false);
-
-	// 列表
-	const list = ref<any[]>([]);
-
-	// 选中
-	const value = ref<any>();
-
-	// 获取列表
-	async function get(params?: any) {
-		loading.value = true;
-
-		// 发送请求
-		await service.chat.session.page(params).then(res => {
-			// 默认加载第一个会话的消息
-			if (!value.value) {
-				set(res.list[0]);
-			}
-
-			// 设置列表
-			list.value = res.list;
-		});
-
-		loading.value = false;
-	}
-
-	// 设置值
-	function set(data: any) {
-		// 设置值
-		value.value = data;
-	}
-
-	return {
-		loading,
-		list,
-		value,
-		get,
-		set
-	};
-});

+ 0 - 36
src/modules/chat/types/index.d.ts

@@ -1,36 +0,0 @@
-import { Socket } from 'socket.io-client';
-
-export namespace Chat {
-	enum ContentType {
-		'text' = 0,
-		'image' = 1,
-		'video' = 2
-	}
-
-	interface Message {
-		fromId?: string;
-		toId?: string;
-		content: {
-			text?: string;
-			imageUrl?: string;
-			[key: string]: any;
-		};
-		contentType: ContentType;
-		[key: string]: any;
-	}
-
-	interface Session {
-		id: string;
-		avatar: string;
-		nickName: string;
-		[key: string]: any;
-	}
-
-	interface Provide {
-		socket?: Socket;
-		send(message: Message, isAppend?: boolean): void;
-		append(message: Message): void;
-		expand(shouldExpand?: boolean): void;
-		scrollToBottom(): void;
-	}
-}

+ 0 - 27
src/modules/demo/config.ts

@@ -1,27 +0,0 @@
-import type { ModuleConfig } from '/@/cool';
-
-export default (): ModuleConfig => {
-	return {
-		components: [() => import('./views/crud/components/code.vue')],
-
-		views: [
-			{
-				// 单个参数
-				// path: "/demo/test/route/:id",
-
-				// 多个参数
-				// path: "/demo/test/route/:id/:name",
-
-				// 参数可选
-				path: '/demo/test/route/:id/:name?',
-
-				// 更多看文档:https://router.vuejs.org/zh/guide/essentials/route-matching-syntax.html
-
-				meta: {
-					label: '动态路由参数'
-				},
-				component: () => import('./views/test/route.vue')
-			}
-		]
-	};
-};

+ 0 - 5
src/modules/demo/directives/color.ts

@@ -1,5 +0,0 @@
-export default {
-	created(el: HTMLElement, binding: any) {
-		el.style.color = binding.value;
-	}
-};

+ 0 - 154
src/modules/demo/service/test.ts

@@ -1,154 +0,0 @@
-import { Service } from '/@/cool';
-import Mock from 'mockjs';
-import { uuid } from '/@/cool/utils';
-import { orderBy } from 'lodash-es';
-
-interface User {
-	id: string;
-	name: string;
-	wages: number;
-	status: 0 | 1;
-	occupation: number;
-	avatar: string;
-	phone: string;
-	createTime: string;
-}
-
-// 模拟数据
-const data = Mock.mock({
-	'list|66': [
-		{
-			id: '@id',
-			name: '@cname',
-			createTime: '@datetime(yyyy-MM-dd)',
-			'wages|50000-100000': 50000,
-			'status|0-1': 0,
-			account() {
-				return Mock.Random.string('lower', 8);
-			},
-			occupation() {
-				return Mock.Random.integer(0, 5);
-			},
-			avatar() {
-				return Mock.Random.image('40x40', Mock.Random.color(), '#FFF', 'png', this.name[0]);
-			},
-			phone() {
-				return Mock.Random.integer(13000000000, 19999999999);
-			}
-		}
-	]
-});
-
-const userList: User[] = data.list;
-
-@Service('test')
-class Test {
-	// 分页列表
-	async page(params: any) {
-		const { keyWord, page, size, sort, order } = params || {};
-
-		console.log('[test]', params);
-
-		// 关键字查询
-		const keyWordLikeFields = ['phone', 'name'];
-
-		// 等值查询
-		const fieldEq = ['createTime', 'occupation', 'status'];
-
-		// 模糊查询
-		const likeFields = ['phone', 'name'];
-
-		// 过滤后的列表
-		const list = orderBy(userList, order, sort).filter((e: any) => {
-			let f = true;
-
-			if (keyWord !== undefined) {
-				f = !!keyWordLikeFields.find(k => String(e[k]).includes(String(params.keyWord)));
-			}
-
-			fieldEq.forEach(k => {
-				if (f) {
-					if (params[k] !== undefined) {
-						f = e[k] == params[k];
-					}
-				}
-			});
-
-			likeFields.forEach(k => {
-				if (f) {
-					if (params[k] !== undefined) {
-						f = String(e[k]).includes(String(params[k]));
-					}
-				}
-			});
-
-			return f;
-		});
-
-		return new Promise(resolve => {
-			// 模拟延迟
-			setTimeout(
-				() => {
-					resolve({
-						list: list.slice((page - 1) * size, page * size),
-						pagination: {
-							total: list.length,
-							page,
-							size
-						},
-						subData: {
-							wages: list.reduce((a, b) => {
-								return a + b.wages;
-							}, 0)
-						}
-					});
-				},
-				Mock.Random.integer(300, 600)
-			);
-		});
-	}
-
-	// 更新
-	async update(params: { id: any; [key: string]: any }) {
-		const item = userList.find(e => e.id == params.id);
-
-		if (item) {
-			Object.assign(item, params);
-		}
-	}
-
-	// 新增
-	async add(params: any) {
-		const id = uuid();
-
-		userList.push({
-			id,
-			...params
-		});
-
-		return id;
-	}
-
-	// 详情
-	async info(params: { id: any }) {
-		const { id } = params || {};
-		return userList.find(e => e.id == id);
-	}
-
-	// 删除
-	async delete(params: { ids: any[] }) {
-		const { ids = [] } = params || {};
-
-		ids.forEach(id => {
-			const index = userList.findIndex(e => e.id == id);
-			userList.splice(index, 1);
-		});
-	}
-
-	// 全部列表
-	async list() {
-		return userList;
-	}
-}
-
-export default Test;

+ 0 - 6
src/modules/demo/service/user/follow.ts

@@ -1,6 +0,0 @@
-import { BaseService, Service } from '/@/cool';
-
-@Service('demo/user/follow')
-class DemoUserFollow extends BaseService {}
-
-export default DemoUserFollow;

+ 0 - 33
src/modules/demo/service/user/info.ts

@@ -1,33 +0,0 @@
-import axios from 'axios';
-import { BaseService, Service } from '/@/cool';
-import dayjs from 'dayjs';
-
-@Service('demo/user/info')
-class DemoUserInfo extends BaseService {
-	// 测试方法,使用 request 请求数据
-	t1() {
-		return this.request({
-			url: '/t1' // 测试地址,实际项目中请更换为真实接口地址
-		});
-	}
-
-	// 自定义请求,通过 axios 返回数据
-	t2() {
-		return axios({
-			url: 'https://'
-		});
-	}
-
-	// 自定义请求,通过 Promise 返回数据
-	t3() {
-		return new Promise((resolve, reject) => {
-			setTimeout(() => {
-				resolve({
-					date: dayjs().format('YYYY-MM-DD HH:mm:ss')
-				});
-			}, 1500);
-		});
-	}
-}
-
-export default DemoUserInfo;

+ 0 - 138
src/modules/demo/views/crud/components/adv-search/base.vue

@@ -1,138 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">base</el-tag>
-			<span>起步</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['adv-search/base.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="起步" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!--【很重要】高级搜索组件按钮 -->
-						<cl-adv-btn />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!--【很重要】高级搜索组件 -->
-					<cl-adv-search ref="AdvSearch" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useAdvSearch, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-// cl-adv-search 配置
-//【很重要】该组件基于 cl-form 故很多示例都可复用
-const AdvSearch = useAdvSearch({
-	// 配置如 cl-form 一样
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-function refresh(params?: any) {
-	Crud.value?.refresh(params);
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 152
src/modules/demo/views/crud/components/adv-search/custom.vue

@@ -1,152 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">custom</el-tag>
-			<span>自定义</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['adv-search/custom.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="自定义" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!--【很重要】高级搜索组件按钮 -->
-						<cl-adv-btn>更多搜索</cl-adv-btn>
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!--【很重要】高级搜索组件 -->
-					<cl-adv-search ref="AdvSearch">
-						<!-- 自定义按钮 -->
-						<template #slot-btn>
-							<el-button @click="toSearch">自定义</el-button>
-						</template>
-					</cl-adv-search>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useAdvSearch, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-// cl-adv-search 配置
-//【很重要】该组件基于 cl-form 故很多示例都可复用
-const AdvSearch = useAdvSearch({
-	// 配置如 cl-form 一样
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	],
-
-	title: '更多搜索',
-	size: '50%',
-	op: ['close', 'search', 'slot-btn']
-});
-
-function refresh(params?: any) {
-	Crud.value?.refresh(params);
-}
-
-// 自定义搜索
-function toSearch() {
-	refresh({ page: 1 });
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 41
src/modules/demo/views/crud/components/code.vue

@@ -1,41 +0,0 @@
-<template>
-	<cl-editor-preview v-if="!isHide" :ref="setRefs('preview')" name="monaco" :tabs="tabs">
-		<el-button @click="open">代码</el-button>
-	</cl-editor-preview>
-</template>
-
-<script setup lang="ts" name="demo-code">
-import { useCool } from '/@/cool';
-import { type PropType, computed } from 'vue';
-import { demo } from 'virtual:demo';
-import { basename } from '/@/cool/utils';
-import { isEmpty } from 'lodash-es';
-
-const props = defineProps({
-	files: {
-		type: Array as PropType<string[]>,
-		default: () => []
-	}
-});
-
-const { refs, setRefs } = useCool();
-
-// 是否隐藏
-const isHide = computed(() => isEmpty(demo));
-
-// 文件列表
-const tabs = computed(() => {
-	return props.files?.map(e => {
-		return {
-			name: basename(e),
-			language: e.includes('.vue') ? 'html' : 'typescript',
-			data: demo[e]
-		};
-	});
-});
-
-// 打开
-function open() {
-	refs.preview.open();
-}
-</script>

+ 0 - 574
src/modules/demo/views/crud/components/crud/all.vue

@@ -1,574 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">all</el-tag>
-			<span>完整示例</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['crud/all.vue']" />
-
-			<cl-dialog v-model="visible" title="完整示例" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!-- 刷新按钮 -->
-						<cl-refresh-btn />
-
-						<!-- 新增按钮 -->
-						<cl-add-btn />
-
-						<!-- 批量删除按钮 -->
-						<cl-multi-delete-btn />
-
-						<!-- 筛选 -->
-						<cl-filter label="状态筛选">
-							<!-- 配置props,选择后会自动过滤列表 -->
-							<cl-select :options="options.status" prop="status" :width="120" />
-						</cl-filter>
-
-						<!-- 字典 -->
-						<cl-filter label="工作(字典)">
-							<cl-select
-								tree
-								:options="dict.get('occupation')"
-								prop="occupation"
-								:width="140"
-								check-strictly
-							/>
-						</cl-filter>
-
-						<cl-flex1 />
-
-						<!-- 导入 -->
-						<cl-import-btn template="/用户导入模版.xlsx" />
-
-						<!-- 导出 -->
-						<cl-export-btn :columns="Table?.columns" />
-
-						<!-- 自定义列 -->
-						<cl-column-custom
-							:ref="setRefs('columnCustom')"
-							:columns="Table?.columns"
-						/>
-
-						<!-- 关键字搜索 -->
-						<cl-search-key placeholder="搜索姓名、手机号" :width="250" />
-
-						<!-- 高级搜索按钮 -->
-						<cl-adv-btn />
-					</cl-row>
-
-					<cl-row>
-						<!-- 表格 -->
-						<cl-table
-							ref="Table"
-							show-summary
-							:summary-method="onSummaryMethod"
-							:auto-height="false"
-						>
-							<!-- 展开信息 -->
-							<template #column-detail="{ scope }">
-								<div style="padding: 0 10px">
-									<el-descriptions border :column="3">
-										<el-descriptions-item label="ID">
-											{{ scope.row.id }}
-										</el-descriptions-item>
-
-										<el-descriptions-item label="姓名">
-											{{ scope.row.name }}
-										</el-descriptions-item>
-
-										<el-descriptions-item label="存款">
-											{{ scope.row.wages }}
-										</el-descriptions-item>
-
-										<el-descriptions-item label="出生年月">
-											{{ scope.row.createTime }}
-										</el-descriptions-item>
-									</el-descriptions>
-								</div>
-							</template>
-
-							<!-- 自定义列 -->
-							<template #column-wages="{ scope }">
-								<span>{{ scope.row.wages }}🤑</span>
-							</template>
-						</cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-
-						<!-- 分页 -->
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-
-					<!-- 高级搜索 -->
-					<cl-adv-search ref="AdvSearch" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script lang="tsx" name="demo-crud" setup>
-import { useCrud, useUpsert, useTable, useAdvSearch, useSearch } from '@cool-vue/crud';
-import { useDict } from '/$/dict';
-import { reactive, ref } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
-import { useCool } from '/@/cool';
-
-// 基础
-const { service, refs, setRefs } = useCool();
-
-// 字典
-const { dict } = useDict();
-
-// 选项,统一命名options,存放所有的下拉等其他选项列表数据
-const options = reactive({
-	status: [
-		{
-			label: '启用',
-			value: 1
-		},
-		{
-			label: '禁用',
-			type: 'danger',
-			value: 0
-		}
-	]
-});
-
-// 合计数据
-const subData = reactive({
-	wages: 0
-});
-
-// crud
-const Crud = useCrud(
-	{
-		// 绑定的服务,如:service.demo.goods、service.base.sys.user
-		service: service.test,
-
-		// 刷新事件
-		async onRefresh(params, { next }) {
-			const res = await next(params);
-			Object.assign(subData, res.subData);
-		}
-	},
-	app => {
-		// Crud 加载完,默认刷新一次
-		app.refresh({
-			size: 10
-			// status: 1 // 带额外参数的请求
-		});
-	}
-);
-
-// 刷新列表,统一调用这个方法去刷新
-function refresh(params?: any) {
-	Crud.value?.refresh(params);
-}
-
-// 新增、编辑
-// 插入类型 <Eps.UserInfoEntity>,prop 和 data 会有提示
-const Upsert = useUpsert<Eps.UserInfoEntity>({
-	items: [
-		// 分组
-		{
-			type: 'tabs',
-			props: {
-				type: 'card',
-				labels: [
-					{
-						label: '基础信息',
-						value: 'base'
-					},
-					{
-						label: '其他配置',
-						value: 'other'
-					}
-				]
-			}
-		},
-		{
-			label: '头像',
-			prop: 'avatarUrl',
-			group: 'base',
-			component: {
-				name: 'cl-upload'
-			}
-		},
-		{
-			label: '账号',
-			group: 'base',
-			prop: 'account',
-			component: {
-				name: 'el-input'
-			}
-		},
-		// 动态配置,新增显示、编辑隐藏
-		() => {
-			return () => {
-				return {
-					label: '密码',
-					group: 'base',
-					prop: 'password',
-					hidden: Upsert.value?.mode == 'update', // 通过 mode 参数判断
-					component: {
-						name: 'el-input',
-						props: {
-							type: 'password'
-						}
-					}
-				};
-			};
-		},
-		{
-			group: 'base',
-			prop: 'user',
-			component: {
-				name: 'cl-form-card',
-				props: {
-					label: '用户信息(多层级展示)'
-				}
-			},
-			children: [
-				{
-					label: '姓名',
-					prop: 'name',
-					required: true,
-					component: {
-						name: 'el-input'
-					}
-				},
-				{
-					label: '存款',
-					prop: 'wages',
-					component: {
-						name: 'el-input-number'
-					}
-				}
-			]
-		},
-		{
-			group: 'base',
-			prop: 'contact',
-			component: {
-				name: 'cl-form-card',
-				props: {
-					label: '联系信息',
-					expand: false
-				}
-			},
-			children: [
-				{
-					label: '手机号',
-					prop: 'phone',
-					component: {
-						name: 'el-input'
-					}
-				},
-				{
-					label: '省市区',
-					prop: 'pca',
-					group: 'base',
-					component: {
-						name: 'cl-distpicker'
-					}
-				}
-			]
-		},
-		{
-			group: 'other',
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'el-tree-select',
-				props: {
-					data: dict.get('occupation'),
-					checkStrictly: true
-				}
-			}
-		},
-		{
-			group: 'other',
-			label: '身份证照片',
-			prop: 'idCardPic',
-			component: {
-				name: 'cl-upload',
-				props: {
-					isSpace: true,
-					size: [200, 300]
-				}
-			}
-		}
-	],
-
-	// 详情钩子
-	onInfo(data, { next, done }) {
-		// 继续请求 info 接口,可以带其他自定义参数
-		// next({
-		// 	id: data.id,
-		//	status: 1
-		// });
-
-		// 使用其他接口
-		// service.demo.goods.info({ id: data.id }).then((res) => {
-		// 	done(res);
-		// });
-
-		// 直接取列表的数据返回
-		done(data);
-	},
-
-	// 提交钩子
-	onSubmit(data, { next, close, done }) {
-		console.log('onSubmit', data);
-		// 继续请求 update/add 接口
-		next(data);
-
-		// 自定义接口
-		// service.demo.goods
-		// 	.update(data)
-		// 	.then(() => {
-		// 		ElMessage.success("保存成功");
-
-		// 		// 操作完,刷新列表
-		// 		refresh();
-
-		// 		// 关闭窗口
-		// 		close();
-		// 	})
-		// 	.catch((err) => {
-		// 		ElMessage.error(err.message);
-
-		// 		// 关闭加载状态
-		// 		done();
-		// 	});
-	},
-
-	// 打开后,数据加载完,onInfo 之后
-	onOpened(data) {
-		if (Upsert.value?.mode != 'info') {
-			ElMessage.info('编辑中');
-		}
-	},
-
-	// 关闭钩子
-	onClose(action, done) {
-		if (Upsert.value?.mode == 'update') {
-			if (action == 'close') {
-				return ElMessageBox.confirm('还没填完,确定关闭不?', '提示', {
-					type: 'warning'
-				})
-					.then(() => {
-						done();
-						ElMessage.info('好吧');
-					})
-					.catch(() => {
-						ElMessage.success('请继续编辑');
-					});
-			}
-		}
-
-		done();
-	}
-});
-
-// 表格
-const Table = useTable({
-	columns: [
-		{
-			type: 'selection',
-			width: 60
-		},
-		// 展开列
-		{
-			label: '展开',
-			type: 'expand',
-			prop: 'detail',
-			width: 60
-		},
-		{
-			label: '头像',
-			prop: 'avatar',
-			width: 100,
-			component: {
-				name: 'cl-image',
-				props: {
-					size: 40
-				}
-			}
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 120
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140,
-
-			// 带搜索组件
-			search: {
-				component: {
-					name: 'el-input',
-					props: {
-						placeholder: '搜索手机号'
-					}
-				}
-			}
-		},
-		{
-			label: '账号',
-			prop: 'account',
-			minWidth: 150
-		},
-		{
-			label: '存款(元)',
-			prop: 'wages',
-			minWidth: 150,
-			sortable: 'desc' // 默认倒序
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			dictColor: true,
-			minWidth: 150,
-			dictAllLevels: true, // 显示所有等级
-
-			// 带搜索组件
-			search: {
-				component: {
-					name: 'cl-select',
-					props: {
-						options: dict.get('occupation')
-					}
-				}
-			}
-		},
-		{
-			label: '状态',
-			orderNum: 2,
-			prop: 'status',
-			minWidth: 100,
-			component: {
-				name: 'cl-switch'
-			}
-		},
-		{
-			label: '出生年月',
-			orderNum: 1,
-			minWidth: 165,
-			prop: 'createTime',
-			sortable: 'custom',
-			search: {
-				component: {
-					name: 'cl-date-picker',
-					props: {
-						type: 'date',
-						valueFormat: 'YYYY-MM-DD',
-						placeholder: '搜索日期'
-					}
-				}
-			}
-		},
-		{
-			type: 'op',
-			width: 320,
-			// 静态配置按钮
-			// buttons: ["info", "edit", "delete"],
-			// 动态配置按钮
-			buttons({ scope }) {
-				return [
-					'info',
-					'edit',
-					'delete',
-					{
-						label: '自定义',
-						onClick() {
-							ElMessage.info(`他是:${scope.row.name}`);
-						}
-					}
-				];
-			}
-		}
-	]
-});
-
-// 合计
-function onSummaryMethod() {
-	// 添加自定义列组件后
-	return ['合计', '', ...refs.columnCustom.summary(subData)];
-}
-
-// 高级搜索
-const AdvSearch = useAdvSearch({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			hook: {
-				bind: 'string'
-			},
-			component: {
-				name: 'el-select',
-				options: dict.get('occupation')
-			}
-		}
-	]
-});
-
-// 搜索
-const Search = useSearch({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 145
src/modules/demo/views/crud/components/crud/base.vue

@@ -1,145 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">base</el-tag>
-			<span>起步</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['crud/base.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="起步" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-refresh-btn />
-						<cl-add-btn />
-						<cl-multi-delete-btn />
-
-						<cl-flex1 />
-
-						<cl-search-key />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { useCool } from '/@/cool';
-
-const { service } = useCool();
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		// test 为测试数据模式,详细说明移步到 service 例子
-		service: 'test'
-	},
-	app => {
-		//【很重要】首次请求,数据一并添加到请求参数中
-		app.refresh({
-			size: 10,
-			status: 1
-		});
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			buttons: ['edit', 'delete']
-		}
-	]
-});
-
-// cl-upsert 配置
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 164
src/modules/demo/views/crud/components/crud/dict.vue

@@ -1,164 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">dict</el-tag>
-			<span>修改文案 / 接口</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['crud/dict.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="修改文案 / 接口" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-refresh-btn />
-						<cl-add-btn />
-						<cl-multi-delete-btn />
-
-						<cl-flex1 />
-
-						<cl-search-key />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		//【很重要】配置 service,如:service.base.sys.user
-		service: 'test',
-
-		//【很重要】字典配置,文案和请求方法等
-		dict: {
-			// 修改请求
-			// 比如说默认列表请求的是 page 接口,可以修改成 getUserList 等等,这取决于后端有没有这个接口。
-			api: {
-				list: 'list',
-				add: 'add',
-				update: 'update',
-				delete: 'delete',
-				info: 'info',
-				page: 'page'
-			},
-
-			// 修改文案
-			label: {
-				op: '操作',
-				add: '添加',
-				delete: '移除',
-				multiDelete: '批量移除',
-				update: '修改',
-				refresh: '刷新',
-				info: '详情'
-			}
-		}
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			buttons: ['edit', 'delete']
-		}
-	]
-});
-
-// cl-upsert 配置
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 182
src/modules/demo/views/crud/components/crud/event.vue

@@ -1,182 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">event</el-tag>
-			<span>事件监听</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['crud/event.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="事件监听" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-refresh-btn />
-						<cl-add-btn />
-						<cl-multi-delete-btn />
-
-						<cl-flex1 />
-
-						<cl-search-key />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table">
-							<!-- 自定义按钮 -->
-							<template #slot-btn="{ scope }">
-								<el-button @click="onEvent(scope.row)">自定义事件</el-button>
-							</template>
-						</cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { useCool } from '/@/cool';
-import { ElMessage } from 'element-plus';
-
-const { service } = useCool();
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		// 配置 service
-		service: 'test',
-
-		//【很重要】监听刷新事件,每次调用 Crud.value.refresh() 会触发
-		onRefresh(params, { next }) {
-			// 默认使用 next(params),也可以自己对数据进行处理
-			next({
-				...params,
-				status: 1
-			});
-		},
-
-		// 监听删除事件,点击删除按钮触发
-		onDelete(selection, { next }) {
-			// 传入 ids,批量删除多个数据
-			next({
-				ids: selection.map(e => e.id)
-			});
-		}
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			width: 300,
-			buttons: ['edit', 'delete', 'slot-btn']
-		}
-	]
-});
-
-// cl-upsert 配置
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-// 调用 Crud 方法
-function onEvent(row: any) {
-	ElMessage.info('自定义打开新增');
-
-	// 打开新增表单
-	Crud.value?.rowAdd();
-
-	// 打开编辑表单
-	// Crud.value?.rowEdit(row);
-
-	// 打开删除提示框
-	// Crud.value?.rowDelete(row);
-
-	// 获取已请求的参数
-	// Crud.value?.getParams();
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 185
src/modules/demo/views/crud/components/crud/service.vue

@@ -1,185 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">service</el-tag>
-			<span>Service 配置</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['crud/service.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="Service 配置" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-refresh-btn />
-						<cl-add-btn />
-						<cl-multi-delete-btn />
-
-						<cl-flex1 />
-
-						<cl-search-key />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useCool } from '/@/cool';
-
-//【很重要】service 是所有请求的集合,是一个对象(刷新页面和保存代码会自动读取后端的所有接口)
-const { service } = useCool();
-console.log('service', service);
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		//【很重要】配置 service,如:service.demo.goods
-		// 不需要到具体的方法,如:service.demo.goods.page,这是错误的!
-		service: service.demo.goods
-
-		// 自定义配置1,添加本地 service 文件。
-		// 【很重要】参考 /src/modules/demo/service/test.ts
-		// 【很重要】必须放在目录 modules/*/service/ 下,才会自动注入到 service 中
-		// service: service.test
-
-		// 自定义配置2,针对一些特殊场景
-		// service: {
-		// 	page(params: any) {
-		// 		// params 请求参数
-		// 		//【很重要】必须返回一个 Promise 格式
-		// 		return Promise.resolve({
-		// 			list: [],
-		// 			pagination: {
-		// 				total: 1,
-		// 				page: 1,
-		// 				size: 20
-		// 			}
-		// 		});
-		// 	}
-
-		// 	// add、delete、update、info、list 也是如此配置
-		// }
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '商品名称',
-			prop: 'title',
-			minWidth: 140
-		},
-		{
-			label: '价格',
-			prop: 'price',
-			minWidth: 140
-		},
-		{
-			label: '主图',
-			prop: 'mainImage',
-			minWidth: 140,
-			component: {
-				name: 'cl-image',
-				props: {
-					size: 60
-				}
-			}
-		},
-		{
-			label: '描述',
-			prop: 'description',
-			minWidth: 200,
-			showOverflowTooltip: true
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			buttons: ['edit', 'delete']
-		}
-	]
-});
-
-// cl-upsert 配置
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '商品名称',
-			prop: 'title',
-			required: true,
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '价格',
-			prop: 'price',
-			required: true,
-			component: {
-				name: 'el-input-number'
-			}
-		},
-		{
-			label: '主图',
-			prop: 'mainImage',
-			required: true,
-			component: {
-				name: 'cl-upload'
-			}
-		},
-		{
-			label: '描述',
-			prop: 'description',
-			component: {
-				name: 'el-input',
-				props: {
-					type: 'textarea',
-					rows: 4
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 118
src/modules/demo/views/crud/components/form/children.vue

@@ -1,118 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">children</el-tag>
-			<span>层级显示</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/children.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '层级显示',
-		items: [
-			{
-				label: '姓名',
-				prop: 'name',
-				component: {
-					name: 'el-input'
-				}
-			},
-			{
-				label: '年龄',
-				prop: 'age',
-				value: 18,
-				component: {
-					name: 'el-input-number'
-				}
-			},
-
-			// 基础信息
-			{
-				component: {
-					//【很重要】使用 cl-form-card 组件渲染,也可以使用自定义
-					name: 'cl-form-card',
-					props: {
-						// 标题
-						label: '基础信息',
-						// 是否展开,默认 true
-						expand: true
-					}
-				},
-				children: [
-					{
-						label: '账号',
-						prop: 'account',
-						component: {
-							name: 'el-input'
-						}
-					},
-					{
-						label: '密码',
-						prop: 'password',
-						component: {
-							name: 'el-input'
-						}
-					}
-				]
-			},
-
-			// 其他信息
-			{
-				component: {
-					name: 'cl-form-card',
-					props: {
-						label: '其他信息',
-						expand: false
-					}
-				},
-				children: [
-					{
-						label: '身份证',
-						prop: 'idcard',
-						component: {
-							name: 'el-input'
-						}
-					},
-					{
-						label: '学校',
-						prop: 'school',
-						component: {
-							name: 'el-input'
-						}
-					},
-					{
-						label: '专业',
-						prop: 'major',
-						component: {
-							name: 'el-input'
-						}
-					}
-				]
-			}
-		],
-		on: {
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 124
src/modules/demo/views/crud/components/form/component/index.vue

@@ -1,124 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">component</el-tag>
-			<span>组件渲染</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code
-				:files="[
-					'form/component/index.vue',
-					'form/component/select-labels.vue',
-					'form/component/select-status.vue',
-					'form/component/select-work.vue',
-					'form/component/select-work2.vue'
-				]"
-			/>
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form">
-				<!-- 年龄插槽 -->
-				<template #slot-age="{ scope }">
-					<!-- scope 为表单值 -->
-					<el-input-number v-model="scope.age" :min="18" :max="100"></el-input-number>
-				</template>
-			</cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-import { ElMessage } from 'element-plus';
-import SelectWork from './select-work2.vue';
-import SelectLabels from './select-labels.vue';
-import SelectStatus from './select-status.vue';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '组件配置',
-
-		items: [
-			{
-				label: '昵称',
-				prop: 'name',
-				// 组件配置方式1:标签名(方便,但是不建议组件全局注册)
-				value: '神仙',
-				component: {
-					// 必须是“全局注册”的组件名,如 element-plus 的 el-input、el-date-picker 等
-					name: 'el-input'
-				}
-			},
-			{
-				label: '手机号',
-				prop: 'phone',
-				value: '13255022000',
-				component: {
-					name: 'el-input',
-					// 自定义插槽
-					slots: {
-						prepend() {
-							return '+86';
-						}
-					}
-				}
-			},
-			{
-				label: '年龄',
-				prop: 'age',
-				// 组件配置方式2:插槽(万能,就是代码多写点)
-				value: 18,
-				component: {
-					// 必须是 "slot-" 开头
-					name: 'slot-age'
-				}
-			},
-			// -- start 组件配置方式3:组件实例(不想全局注册,但又想组件化)
-			{
-				label: '工作',
-				prop: 'work',
-				value: '设计',
-				component: {
-					// 双向绑定
-					vm: SelectWork
-				}
-			},
-			{
-				label: '标签',
-				prop: 'labels',
-				value: ['多金', '深情'],
-				component: {
-					// scope[prop]绑定
-					vm: SelectLabels
-				}
-			},
-			{
-				label: '状态',
-				prop: 'status',
-				value: 1,
-				component: {
-					// useForm 绑定
-					vm: SelectStatus
-				}
-			}
-			// -- end
-		],
-		on: {
-			submit(data, { close }) {
-				ElMessage.info(
-					`${data.name || '无名'}(${data.age || 18}岁)工作:${data.work || '无'}`
-				);
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 38
src/modules/demo/views/crud/components/form/component/select-labels.vue

@@ -1,38 +0,0 @@
-<template>
-	<!--【很重要】直接绑定表单值 scope[prop] -->
-	<!-- !符号,只是为了类型提示不错误 -->
-	<el-select v-model="scope[prop!]" multiple>
-		<el-option
-			v-for="(item, index) in list"
-			:key="index"
-			:label="item.label"
-			:value="item.label"
-		/>
-	</el-select>
-</template>
-
-<!--【很重要】必须要有name,避免注册后和其他冲突 -->
-<script setup lang="ts" name="select-labels">
-import { ref } from 'vue';
-
-const props = defineProps({
-	scope: null, // 表单值
-	prop: String // 表单项配置的 prop
-});
-
-// 选项列表
-const list = ref<{ label: string; value: string }[]>([
-	{
-		label: '帅气',
-		value: '帅气' // 测试直接使用label,真实情况可能是1,2,3,4或者id
-	},
-	{
-		label: '多金',
-		value: '多金'
-	},
-	{
-		label: '深情',
-		value: '深情'
-	}
-]);
-</script>

+ 0 - 43
src/modules/demo/views/crud/components/form/component/select-status.vue

@@ -1,43 +0,0 @@
-<template>
-	<!--【很重要】直接绑定status,或者使用 form[prop!] -->
-	<el-radio-group v-model="form.status">
-		<el-radio v-for="(item, index) in list" :key="index" :value="item.value">
-			{{ item.label }}
-		</el-radio>
-	</el-radio-group>
-</template>
-
-<!--【很重要】必须要有name,避免注册后和其他冲突 -->
-<script setup lang="ts" name="select-status">
-import { useForm } from '@cool-vue/crud';
-import { computed, ref } from 'vue';
-
-const props = defineProps({
-	scope: null, // 表单值
-	prop: String // 表单项配置的 prop
-});
-
-// 使用 useForm,能直接获取到上级的表单实例,
-// 比如操作表单的 Form.value?.submit、Form.value?.close等
-// 获取表单值,Form.value?.form
-const Form = useForm();
-
-// 表单值,包一层不会太难受
-const form = computed(() => Form.value?.form || {});
-
-// 选项列表
-const list = ref<{ label: string; value: number }[]>([
-	{
-		label: '很好',
-		value: 1
-	},
-	{
-		label: '不舒服',
-		value: 2
-	},
-	{
-		label: '要嘎了',
-		value: 3
-	}
-]);
-</script>

+ 0 - 59
src/modules/demo/views/crud/components/form/component/select-work.vue

@@ -1,59 +0,0 @@
-<template>
-	<el-select v-model="active" @change="onChange">
-		<el-option
-			v-for="(item, index) in list"
-			:key="index"
-			:label="item.label"
-			:value="item.label"
-		/>
-	</el-select>
-</template>
-
-<!-- 【很重要】必须要有name,避免注册后和其他冲突 -->
-<script setup lang="ts" name="select-work">
-import { ref, watch } from 'vue';
-
-const props = defineProps({
-	modelValue: String
-});
-
-const emit = defineEmits(['update:modelValue', 'change']);
-
-//【很重要】绑定值
-// 这种方式虽然麻烦,但是可扩展性高,一些复杂的数据结构可以按这种方式绑定值
-const active = ref();
-
-// 选项列表
-const list = ref<{ label: string; value: string }[]>([
-	{
-		label: '倒茶',
-		value: '倒茶' // 测试直接使用label,真实情况可能是1,2,3,4或者id
-	},
-	{
-		label: '设计',
-		value: '设计'
-	},
-	{
-		label: '开发',
-		value: '开发'
-	}
-]);
-
-//【很重要】更新绑定值,表单提交才能得到选择后的
-function onChange(val: string) {
-	emit('update:modelValue', val);
-	emit('change', val);
-}
-
-//【很重要】使用监听的方式,避免表单打开数据是异步获取的情况
-watch(
-	() => props.modelValue,
-	val => {
-		// 设置选中的值
-		active.value = val;
-	},
-	{
-		immediate: true
-	}
-);
-</script>

+ 0 - 38
src/modules/demo/views/crud/components/form/component/select-work2.vue

@@ -1,38 +0,0 @@
-<template>
-	<el-select v-model="active">
-		<el-option
-			v-for="(item, index) in list"
-			:key="index"
-			:label="item.label"
-			:value="item.label"
-		/>
-	</el-select>
-</template>
-
-<!-- 【很重要】必须要有name,避免注册后和其他冲突 -->
-<script setup lang="ts" name="select-work2">
-import { ref, useModel } from 'vue';
-
-const props = defineProps({
-	modelValue: String
-});
-
-//【很重要】绑定值,使用 useModel 的方式双向绑定
-const active = useModel(props, 'modelValue');
-
-// 选项列表
-const list = ref<{ label: string; value: string }[]>([
-	{
-		label: '倒茶',
-		value: '倒茶' // 测试直接使用label,真实情况可能是1,2,3,4或者id
-	},
-	{
-		label: '设计',
-		value: '设计'
-	},
-	{
-		label: '开发',
-		value: '开发'
-	}
-]);
-</script>

+ 0 - 122
src/modules/demo/views/crud/components/form/config.vue

@@ -1,122 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">config</el-tag>
-			<span>参数配置</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/config.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form">
-				<!-- 按钮插槽 -->
-				<template #slot-btns>
-					<el-button type="danger">按钮插槽</el-button>
-				</template>
-			</cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-import { ElMessage } from 'element-plus';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '参数配置',
-
-		// 打开是否重置表单
-		isReset: false,
-
-		// 默认表单值
-		form: {
-			nickName: '神仙都没用'
-		},
-
-		// 表单配置
-		props: {
-			// 标签宽度
-			labelWidth: '120px',
-
-			// 标签位置
-			labelPosition: 'top'
-		},
-
-		// 窗口的高。配置后,在窗口内部滚动。默认整个页面滚动
-		height: '60vh',
-
-		// 窗口的宽,默认 50%
-		width: '60%',
-
-		// 窗口设置
-		dialog: {
-			// 是否隐藏头部
-			hideHeader: false,
-
-			// 顶部操作按钮,默认["fullscreen", "close"]
-			// fullscreen 全屏
-			// close 关闭
-			controls: ['close']
-		},
-
-		// 底部操作按钮
-		op: {
-			// 默认靠右布局
-			justify: 'flex-end',
-
-			// 保存按钮文字
-			saveButtonText: '提交',
-
-			// 关闭按钮文字
-			closeButtonText: '关闭',
-
-			// 是否隐藏
-			hidden: false,
-
-			// 按钮配置
-			buttons: [
-				// 自定义
-				{
-					label: '自定义按钮',
-					onClick() {
-						ElMessage.success('自定义按钮点击');
-					}
-				},
-				// close 关闭
-				'close',
-				// save 保存
-				'save',
-				// 插槽使用,配合 template,往上看 cl-form 组件
-				'slot-btns'
-			]
-		},
-
-		// 表单项配置
-		items: [
-			{
-				label: '昵称',
-				prop: 'nickName',
-				component: {
-					name: 'el-input'
-				}
-			}
-		],
-
-		// 事件
-		on: {
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 154
src/modules/demo/views/crud/components/form/crud.vue

@@ -1,154 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">crud</el-tag>
-			<span>内嵌CRUD</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/crud.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form">
-				<template #slot-crud>
-					<cl-crud ref="Crud" border>
-						<cl-row>
-							<!-- 刷新按钮 -->
-							<cl-refresh-btn />
-							<!-- 新增按钮 -->
-							<cl-add-btn />
-							<!-- 删除按钮 -->
-							<cl-multi-delete-btn />
-							<cl-flex1 />
-							<!-- 关键字搜索 -->
-							<cl-search-key placeholder="搜索姓名、手机号" />
-						</cl-row>
-
-						<cl-row>
-							<!-- 数据表格 -->
-							<cl-table ref="Table" />
-						</cl-row>
-
-						<cl-row>
-							<cl-flex1 />
-							<!-- 分页控件 -->
-							<cl-pagination />
-						</cl-row>
-
-						<!-- 新增、编辑 -->
-						<cl-upsert ref="Upsert" />
-					</cl-crud>
-				</template>
-			</cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useForm, useTable, useUpsert } from '@cool-vue/crud';
-import { useCool } from '/@/cool';
-
-const { service } = useCool();
-
-// cl-upsert
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			component: {
-				name: 'el-date-picker'
-			}
-		}
-	]
-});
-
-// cl-table
-const Table = useTable({
-	autoHeight: false,
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			type: 'op'
-		}
-	]
-});
-
-// cl-crud
-const Crud = useCrud(
-	{
-		service: service.test
-	},
-	app => {
-		app.refresh({
-			size: 10
-		});
-	}
-);
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '内嵌CRUD',
-		props: {
-			labelPosition: 'top'
-		},
-		dialog: {
-			height: '70vh',
-			width: '1000px'
-		},
-		items: [
-			{
-				label: '姓名',
-				prop: 'name',
-				component: {
-					name: 'el-input',
-					props: {
-						placeholder: '请填写姓名'
-					}
-				},
-				rules: {
-					required: true,
-					message: '姓名不能为空'
-				}
-			},
-			{
-				label: '内嵌 cl-crud',
-				component: {
-					name: 'slot-crud'
-				}
-			}
-		],
-		on: {
-			submit() {
-				Form.value?.close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 64
src/modules/demo/views/crud/components/form/disabled.vue

@@ -1,64 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">disabled</el-tag>
-			<span>组件禁用</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/disabled.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '组件禁用',
-		items: [
-			{
-				label: '账号',
-				prop: 'account',
-				component: {
-					name: 'el-input',
-					props: {
-						// 设置 boolean 值控制组件的禁用状态(前提是组件支持这个参数,element 的组件几乎都有)
-						disabled: true
-					}
-				}
-			},
-			{
-				label: '密码',
-				prop: 'password',
-				component: {
-					name: 'el-input'
-				}
-			}
-		],
-		on: {
-			open() {
-				// 通用 setProps 方法去设置 disabled, 1.5s后禁用
-				setTimeout(() => {
-					Form.value?.setProps('password', { disabled: true });
-				}, 1500);
-			},
-
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 93
src/modules/demo/views/crud/components/form/event.vue

@@ -1,93 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">event</el-tag>
-			<span>组件事件</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/event.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-import { ElMessage } from 'element-plus';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '组件事件',
-		items: [
-			{
-				label: '账号',
-				prop: 'account',
-				component: {
-					name: 'el-input',
-					props: {
-						// 组件内 emit 的用 on[name] 接收,如 onChange、onInput、onBlur 等
-						// 前提是组件内有触发事件
-						onBlur() {
-							ElMessage.info('账号检查中');
-						}
-					}
-				}
-			},
-			{
-				label: '是否实名',
-				prop: 'status',
-				value: 1,
-				component: {
-					name: 'el-radio-group',
-					options: [
-						{
-							label: '关闭',
-							value: 0
-						},
-						{
-							label: '开启',
-							value: 1
-						}
-					],
-					props: {
-						// 值改变事件
-						onChange(val: number) {
-							if (val == 1) {
-								// 显示表单项
-								Form.value?.showItem('idcard');
-							} else {
-								// 隐藏表单项
-								Form.value?.hideItem('idcard');
-								// 清空值
-								Form.value?.setForm('idcard', undefined);
-							}
-						}
-					}
-				}
-			},
-			{
-				label: '身份证',
-				prop: 'idcard',
-				component: {
-					name: 'el-input'
-				}
-			}
-		],
-		on: {
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 105
src/modules/demo/views/crud/components/form/group.vue

@@ -1,105 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">group</el-tag>
-			<span>分组显示</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/group.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '分组显示',
-		items: [
-			{
-				//【很重要】必须为 tabs
-				type: 'tabs',
-				props: {
-					// 分组样式
-					type: 'card',
-					// 分组列表,必须是 { label, value } 的数组格式
-					labels: [
-						{
-							label: '基础信息', // 标题
-							value: 'base' // 唯一标识
-						},
-						{
-							label: '认证信息',
-							value: 'auth'
-						}
-					]
-				}
-			},
-			// 基础信息
-			{
-				group: 'base', // 标识
-				label: '账号',
-				prop: 'account',
-				required: true,
-				component: {
-					name: 'el-input'
-				}
-			},
-			{
-				group: 'base', // 标识
-				label: '密码',
-				prop: 'password',
-				required: true,
-				component: {
-					name: 'el-input'
-				}
-			},
-
-			// 其他信息 group = other
-			{
-				group: 'auth', // 标识
-				label: '身份证',
-				prop: 'idcard',
-				required: true,
-				component: {
-					name: 'el-input'
-				}
-			},
-			{
-				group: 'auth', // 标识
-				label: '学校',
-				prop: 'school',
-				component: {
-					name: 'el-input'
-				}
-			},
-			{
-				group: 'auth', // 标识
-				label: '专业',
-				prop: 'major',
-				component: {
-					name: 'el-input'
-				}
-			}
-		],
-		on: {
-			//【提示】当第一组验证通过后,会自动切换到下一组展示,直到全部通过才可提交
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 77
src/modules/demo/views/crud/components/form/hidden.vue

@@ -1,77 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">hidden</el-tag>
-			<span>隐藏/显示</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/hidden.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '隐藏/显示',
-		items: [
-			{
-				label: '状态',
-				prop: 'status',
-				value: 0,
-				component: {
-					name: 'el-radio-group',
-					options: [
-						{
-							label: '关闭',
-							value: 0
-						},
-						{
-							label: '开启',
-							value: 1
-						}
-					]
-				}
-			},
-			{
-				label: '账号',
-				prop: 'account',
-				component: {
-					name: 'el-input'
-				}
-			},
-			{
-				//【很重要】是否隐藏
-				hidden({ scope }) {
-					// scope 为表单值
-					// 返回一个 boolean 来控制当前表单项的隐藏/显示
-					return scope.status != 1;
-				},
-				label: '密码',
-				prop: 'password',
-				component: {
-					name: 'el-input'
-				}
-			}
-		],
-		on: {
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 98
src/modules/demo/views/crud/components/form/layout.vue

@@ -1,98 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">layout</el-tag>
-			<span>布局</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/layout.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '布局',
-		items: [
-			{
-				//【span】参考文档:https://element-plus.gitee.io/zh-CN/component/layout.html
-				// 使用 1/24 分栏,默认 24
-				span: 12,
-				label: '昵称',
-				prop: 'nickname',
-				component: {
-					name: 'el-input'
-				}
-			},
-			{
-				span: 12,
-				label: '手机号',
-				prop: 'phone',
-				component: {
-					name: 'el-input',
-					props: {
-						maxlength: 11
-					}
-				}
-			},
-			{
-				//【flex】使宽度不填充满
-				flex: false,
-				label: '标签',
-				prop: 'label',
-				component: {
-					name: 'el-input'
-				}
-			},
-			{
-				label: '状态',
-				prop: 'status',
-				value: 1,
-				component: {
-					name: 'el-radio-group',
-					options: [
-						{
-							label: '开启',
-							value: 1
-						},
-						{
-							label: '关闭',
-							value: 0
-						}
-					]
-				}
-			},
-			{
-				label: '备注',
-				prop: 'remark',
-				component: {
-					name: 'el-input',
-					props: {
-						type: 'textarea',
-						rows: 4
-					}
-				}
-			}
-		],
-		on: {
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 83
src/modules/demo/views/crud/components/form/open.vue

@@ -1,83 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">open</el-tag>
-			<span>起步</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/open.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<!--【很重要】ref 一定要对应 useForm 定义的值 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '起步',
-
-		items: [
-			{
-				label: '昵称',
-				// 绑定值的标识,表单提交及回显会自动根据 prop 获取对应的值
-				prop: 'nickname',
-				// 组件绑定
-				component: {
-					// 必须是“全局注册”的组件名,如 element-plus 的 el-input、el-date-picker 等
-					name: 'el-input',
-
-					// 绑定的组件参数配置,如 clearable、placeholder 等
-					// 组件内 emit 的用 on[name] 接收,如 onChange、onInput、onBlur 等
-					props: {
-						placeholder: '请输入昵称',
-						clearable: true,
-						onChange(value: string) {}
-					}
-				}
-			},
-			{
-				label: '年龄',
-				prop: 'age',
-				component: {
-					name: 'el-input-number'
-				},
-				// 默认值,第一次打开有效
-				value: 18
-			}
-		],
-		on: {
-			// 打开时触发
-			open() {},
-
-			// 关闭时触发。当配置该方法时,关闭事件会被阻断,使用 done() 关闭窗口
-			close(action, done) {
-				// action 为关闭窗口的触发动作 "save" | "close"
-				// done 关闭事件
-				done();
-			},
-
-			// 提交时触发
-			submit(data, { done, close }) {
-				// data 为表单值
-				// done 关闭加载事件、但不关闭窗口
-				// close 关闭窗口
-
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 172
src/modules/demo/views/crud/components/form/options.vue

@@ -1,172 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">options</el-tag>
-			<span>选项框配置</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/options.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-import { computed, reactive } from 'vue';
-
-const Form = useForm();
-
-// 觉得麻烦就 any,如 { user: [] as any[] }
-const options = reactive<{ [key: string]: { label: string; value: any }[] }>({
-	user: []
-});
-
-function open() {
-	Form.value?.open({
-		title: '选项框配置',
-		items: [
-			{
-				label: '下拉框',
-				prop: 'select',
-				component: {
-					name: 'el-select',
-					props: {
-						clearable: true // 可清除
-					},
-					options: [
-						{
-							label: 'javascript',
-							value: 1
-						},
-						{
-							label: 'vue',
-							value: 2
-						},
-						{
-							label: 'html',
-							value: 3
-						},
-						{
-							label: 'css',
-							value: 4
-						}
-					]
-				}
-			},
-			{
-				label: '单选框',
-				prop: 'radio',
-				value: 1,
-				component: {
-					name: 'el-radio-group',
-					options: [
-						{
-							label: '手机',
-							value: 1
-						},
-						{
-							label: '电脑',
-							value: 2
-						},
-						{
-							label: '电视',
-							value: 3
-						}
-					]
-				}
-			},
-			{
-				label: '多选框',
-				prop: 'checkbox',
-				value: [2, 3],
-				component: {
-					name: 'el-checkbox-group',
-					options: [
-						{
-							label: '咖啡',
-							value: 1
-						},
-						{
-							label: '汉堡',
-							value: 2
-						},
-						{
-							label: '炸鸡',
-							value: 3
-						},
-						{
-							label: '奶茶',
-							value: 4
-						}
-					]
-				}
-			},
-			{
-				label: '动态配置1',
-				prop: 'd1',
-				component: {
-					name: 'el-select',
-					// 动态设置方法1,在 on.open 事件配置 options
-					options: []
-				}
-			},
-			{
-				label: '动态配置2',
-				prop: 'd2',
-				component: {
-					name: 'el-select',
-					// 动态设置方法2,使用 computed 更新 options
-					options: computed(() => options.user)
-				}
-			}
-		],
-		on: {
-			open() {
-				// 模拟 1.5s 后取的数据
-				setTimeout(() => {
-					// 动态设置方法1,使用 setOptions 方法设置
-					// d1 为 prop 值
-					Form.value?.setOptions('d1', [
-						{
-							label: '😊',
-							value: 1
-						},
-						{
-							label: '😭',
-							value: 2
-						},
-						{
-							label: '😘',
-							value: 3
-						}
-					]);
-
-					// 动态设置方法2,直接设置 options.user,由 computed 更新
-					options.user = [
-						{
-							label: '💰',
-							value: 1
-						},
-						{
-							label: '🚗',
-							value: 2
-						}
-					];
-				}, 1500);
-			},
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 109
src/modules/demo/views/crud/components/form/plugin/index.vue

@@ -1,109 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">plugin</el-tag>
-			<span>插件的使用</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open('manager')">管理者</el-button>
-			<el-button @click="open('user')">用户</el-button>
-			<demo-code :files="['form/plugin/index.vue', 'form/plugin/role.ts']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-import { setRole } from './role';
-
-const Form = useForm();
-
-function open(role: string) {
-	Form.value?.open(
-		{
-			title: '插件的使用',
-
-			items: [
-				{
-					label: '姓名',
-					prop: 'name',
-					required: true,
-					component: {
-						name: 'el-input'
-					}
-				},
-				{
-					// 自定义参数 role,匹配插件传入的角色
-					role: 'user',
-					label: '面试职位',
-					prop: 'work',
-					value: 1,
-					component: {
-						name: 'el-radio-group',
-						options: [
-							{
-								label: '前端开发',
-								value: 1
-							},
-							{
-								label: '后端开发',
-								value: 2
-							},
-							{
-								label: 'UI设计',
-								value: 3
-							}
-						]
-					}
-				},
-				{
-					role: 'user',
-					label: '期望薪资',
-					prop: 'salary',
-					value: 5000,
-					component: {
-						name: 'el-input-number',
-						props: {
-							min: 2000,
-							max: 100000
-						}
-					}
-				},
-				{
-					role: 'manager',
-					label: '入职时间',
-					prop: 'date',
-					component: {
-						name: 'el-date-picker'
-					}
-				},
-				{
-					role: 'manager',
-					label: '负责人',
-					prop: 'head',
-					component: {
-						name: 'el-input'
-					}
-				}
-			],
-			on: {
-				submit(data, { done, close }) {
-					close();
-				}
-			}
-		},
-		[
-			// 自定义插件,角色权限控制
-			setRole(role)
-		]
-	);
-}
-</script>

+ 0 - 20
src/modules/demo/views/crud/components/form/plugin/role.ts

@@ -1,20 +0,0 @@
-/**
- * 角色权限控制
- * @param role
- * @returns
- */
-export function setRole(role?: string): ClForm.Plugin {
-	return ({ exposed }) => {
-		function deep(arr: ClForm.Item[]) {
-			arr.forEach(e => {
-				if (e.role) {
-					e.hidden = e.role != role;
-				}
-
-				deep(e.children || []);
-			});
-		}
-
-		deep(exposed.config.items);
-	};
-}

+ 0 - 75
src/modules/demo/views/crud/components/form/required.vue

@@ -1,75 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">required</el-tag>
-			<span>必填项配置</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/required.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form"></cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '必填项配置',
-		items: [
-			{
-				label: '昵称',
-				prop: 'nickname',
-				component: {
-					name: 'el-input'
-				},
-				// 是否必填,默认判断绑定值是否空
-				required: true
-			},
-			{
-				label: '手机号',
-				prop: 'phone',
-				component: {
-					name: 'el-input',
-					props: {
-						maxlength: 11
-					}
-				},
-				// 自定义规则
-				// 基础用法可参考:https://element-plus.gitee.io/zh-CN/component/form.html
-				// 高级用法可参考:https://github.com/yiminghe/async-validator
-				rules: [
-					{
-						required: true,
-						validator: (rule, value, callback) => {
-							if (value === '') {
-								callback(new Error('手机号不能为空'));
-							} else if (!/^1[3456789]\d{9}$/.test(value)) {
-								callback(new Error('手机号格式错误'));
-							} else {
-								callback();
-							}
-						}
-					}
-				]
-			}
-		],
-		on: {
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-</script>

+ 0 - 123
src/modules/demo/views/crud/components/form/rules.vue

@@ -1,123 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">rules</el-tag>
-			<span>添加/删除表单项</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['form/rules.vue']" />
-
-			<!-- 自定义表单组件 -->
-			<cl-form ref="Form">
-				<template #slot-cert="{ scope }">
-					<div class="cert">
-						<!--【很重要】prop、rules 配置格式如下 -->
-						<el-form-item
-							v-for="(item, index) in scope.cert"
-							:key="index"
-							:label="`证书${index + 1}`"
-							:prop="`cert.${index}.label`"
-							:rules="{
-								message: `请填写证书${index + 1}`,
-								required: true
-							}"
-						>
-							<div class="row">
-								<!-- 输入框 -->
-								<el-input v-model="item.label" placeholder="请填写证书"></el-input>
-
-								<!-- 删除行 -->
-								<el-icon @click="rowDel(index)">
-									<delete />
-								</el-icon>
-							</div>
-						</el-form-item>
-
-						<!-- 添加行 -->
-						<el-row type="flex" justify="end">
-							<el-button @click="rowAdd()">添加证书</el-button>
-						</el-row>
-					</div>
-				</template>
-			</cl-form>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useForm } from '@cool-vue/crud';
-import { Delete } from '@element-plus/icons-vue';
-
-const Form = useForm();
-
-function open() {
-	Form.value?.open({
-		title: '添加/删除表单项',
-		items: [
-			{
-				label: '昵称',
-				prop: 'nickname',
-				component: {
-					name: 'el-input'
-				},
-				required: true
-			},
-			{
-				prop: 'cert',
-				//【很重要】默认数据格式,以实际业务为主。
-				value: [
-					{
-						label: ''
-					}
-				],
-				component: {
-					name: 'slot-cert'
-				}
-			}
-		],
-		on: {
-			submit(data, { close }) {
-				close();
-			}
-		}
-	});
-}
-
-function rowAdd() {
-	Form.value?.form.cert.push({
-		label: ''
-	});
-}
-
-function rowDel(index: number) {
-	Form.value?.form.cert.splice(index, 1);
-}
-</script>
-
-<style lang="scss" scoped>
-.cert {
-	.row {
-		display: flex;
-		align-items: center;
-
-		.el-input {
-			flex: 1;
-			margin-right: 10px;
-		}
-
-		.el-icon {
-			cursor: pointer;
-
-			&:hover {
-				color: red;
-			}
-		}
-	}
-}
-</style>

+ 0 - 168
src/modules/demo/views/crud/components/other/tips.vue

@@ -1,168 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">tips</el-tag>
-			<span>代码类型提示</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['other/tips.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="代码类型提示" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-refresh-btn />
-						<cl-add-btn />
-						<cl-multi-delete-btn />
-
-						<cl-flex1 />
-
-						<cl-search-key />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useCool } from '/@/cool';
-
-const { service } = useCool();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: service.demo.goods
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-//【很重要】添加类型标注 <Eps.DemoGoodsEntity>,也可以自定义<{ title: string; price: number }>
-const Table = useTable<Eps.DemoGoodsEntity>({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '商品标题',
-			prop: 'title', //【很重要】编辑的时候会提示 DemoGoodsEntity 实体的属性名
-			minWidth: 140
-		},
-		{
-			label: '主图',
-			prop: 'mainImage',
-			minWidth: 140,
-			component: {
-				name: 'cl-image',
-				props: {
-					size: 60
-				}
-			}
-		},
-		{
-			label: '价格',
-			prop: 'price',
-			minWidth: 120
-		},
-		{
-			label: '库存',
-			prop: 'stock',
-			minWidth: 120
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op'
-		}
-	]
-});
-
-// cl-upsert 配置
-//【很重要】添加类型标注 <Eps.DemoGoodsEntity>,也可以自定义<{ title: string; price: number }>
-const Upsert = useUpsert<Eps.DemoGoodsEntity>({
-	items: [
-		{
-			label: '商品标题',
-			prop: 'title', //【很重要】编辑的时候会提示 DemoGoodsEntity 实体的属性名
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '主图',
-			prop: 'mainImage',
-			component: {
-				name: 'cl-upload'
-			}
-		},
-		{
-			label: '价格',
-			prop: 'price',
-			hook: 'number',
-			component: {
-				name: 'el-input-number',
-				props: {
-					min: 0.01,
-					max: 10000
-				}
-			}
-		},
-		{
-			label: '库存',
-			prop: 'stock',
-			component: {
-				name: 'el-input-number',
-				props: {
-					min: 0,
-					max: 1000
-				}
-			}
-		}
-	],
-	onSubmit(data, { next }) {
-		// 【很重要】data 的类型也会被定义成 DemoGoodsEntity
-
-		next({
-			...data,
-			title: data.title
-		});
-	}
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 28
src/modules/demo/views/crud/components/other/tsx/index.scss

@@ -1,28 +0,0 @@
-.tsx-list {
-	.item {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		border: 1px solid var(--el-border-color);
-		padding: 10px;
-		margin-bottom: 10px;
-		cursor: pointer;
-		border-radius: 4px;
-
-		.el-icon {
-			display: none;
-		}
-
-		&:hover {
-			background-color: var(--el-bg-color-page);
-		}
-
-		&.is-active {
-			color: var(--el-color-primary);
-
-			.el-icon {
-				display: block;
-			}
-		}
-	}
-}

+ 0 - 109
src/modules/demo/views/crud/components/other/tsx/index.tsx

@@ -1,109 +0,0 @@
-import { defineComponent, ref } from 'vue';
-import { Check } from '@element-plus/icons-vue';
-import './index.scss';
-
-interface Item {
-	name: string;
-	value: number;
-}
-
-export default defineComponent({
-	emits: ['checked'],
-
-	setup(props, { emit, expose, slots }) {
-		// 列表数据
-		const list = ref<Item[]>([
-			{
-				name: '鸡腿堡',
-				value: 1
-			},
-			{
-				name: '牛肉堡',
-				value: 2
-			}
-		]);
-
-		// 选择值
-		const active = ref();
-
-		// 是否可见
-		const visible = ref(false);
-
-		// 打开
-		function open() {
-			visible.value = true;
-		}
-
-		// 选择
-		function toCheck(item: Item) {
-			active.value = item.value;
-
-			// 自定义事件
-			emit('checked', item);
-		}
-
-		// 暴露方法和变量,使上级可以使用 ref 的方式来调用
-		expose({
-			toCheck
-		});
-
-		// 必须返回一个方法
-		return () => {
-			return (
-				<div class="scope">
-					<div class="h">
-						<el-tag size="small" effect="dark">
-							tsx
-						</el-tag>
-						<span>tsx示例</span>
-					</div>
-
-					<div class="c">
-						<el-button onClick={open}>预览</el-button>
-						<demo-code files={['other/tsx/index.tsx']} />
-
-						{/* ref 的绑定值必须 .value */}
-						<cl-dialog v-model={visible.value} title="tsx示例">
-							<div class="tsx-list">
-								{/* 循环的使用 */}
-								{list.value.map(item => {
-									// 插槽的使用
-									return slots.default ? (
-										slots.default(item)
-									) : (
-										<div
-											// 动态样式的使用
-											class={[
-												'item',
-												{
-													'is-active': item.value == active.value
-												}
-											]}
-											// 事件的使用
-											onClick={() => toCheck(item)}
-										>
-											<span>{item.name}</span>
-
-											<el-icon>
-												<Check />
-											</el-icon>
-										</div>
-									);
-								})}
-							</div>
-						</cl-dialog>
-					</div>
-
-					<div class="f">
-						<span class="date">2024-01-01</span>
-					</div>
-				</div>
-			);
-		};
-	}
-
-	// 不推荐用该方法,在 setup 中返回模板信息
-	// render() {
-	// 	return <div></div>;
-	// }
-});

+ 0 - 147
src/modules/demo/views/crud/components/search/base.vue

@@ -1,147 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">base</el-tag>
-			<span>起步</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['search/base.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="起步" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!--【很重要】搜索组件 -->
-						<cl-search ref="Search" :reset-btn="true" />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useSearch, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-// cl-search 配置
-//【很重要】该组件基于 cl-form 故很多示例都可复用
-const Search = useSearch({
-	// 配置如 cl-form 一样
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true,
-
-					// 值改变的时候刷新列表
-					onChange(val: string) {
-						refresh({
-							name: val,
-							page: 1
-						});
-					}
-				}
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	],
-
-	onChange(data, prop) {
-		console.log(data, prop);
-	}
-});
-
-function refresh(params?: any) {
-	Crud.value?.refresh(params);
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 176
src/modules/demo/views/crud/components/search/custom.vue

@@ -1,176 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">custom</el-tag>
-			<span>自定义</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['search/custom.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="自定义" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!--【很重要】搜索组件 -->
-						<cl-search
-							ref="Search"
-							:reset-btn="true"
-							:on-load="onLoad"
-							:on-search="onSearch"
-						>
-							<!-- 自定义按钮 -->
-							<template #buttons="scope">
-								<el-button @click="toSearch(scope)">自定义按钮</el-button>
-							</template>
-						</cl-search>
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useSearch, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { ElMessage } from 'element-plus';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-// cl-search 配置
-//【很重要】该组件基于 cl-form 故很多示例都可复用
-const Search = useSearch({
-	// 配置如 cl-form 一样
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true,
-
-					// 值改变的时候刷新列表
-					onChange(val: string) {
-						refresh({
-							name: val,
-							page: 1
-						});
-					}
-				}
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-function refresh(params?: any) {
-	Crud.value?.refresh(params);
-}
-
-// cl-search 初始化
-function onLoad(data: any) {
-	data.name = '白小纯';
-}
-
-// cl-search 配置 onSearch 后,必须使用 next 方法继续请求
-function onSearch(data: any, { next }: { next: (data: any) => void }) {
-	ElMessage.info('开始搜索');
-	// 这边可以处理其他事务
-	next(data);
-}
-
-// 自定义搜索,data 为表单数据
-function toSearch(data: any) {
-	ElMessage.info('自定义搜索');
-
-	refresh({
-		page: 1,
-		...data
-	});
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 152
src/modules/demo/views/crud/components/search/layout.vue

@@ -1,152 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">layout</el-tag>
-			<span>布局</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['search/layout.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="布局" width="80%">
-				<cl-crud ref="Crud">
-					<!--【很重要】搜索组件 -->
-					<cl-search ref="Search" :reset-btn="true" />
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useSearch, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-// cl-search 配置
-//【很重要】该组件基于 cl-form 故很多示例都可复用
-const Search = useSearch({
-	// 取消行内表单模式
-	inline: false,
-
-	// 表单参数
-	props: {
-		labelPosition: 'top'
-	},
-
-	// 配置如 cl-form 一样
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			span: 6,
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true,
-
-					// 值改变的时候刷新列表
-					onChange(val: string) {
-						refresh({
-							name: val,
-							page: 1
-						});
-					}
-				}
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			span: 6,
-			component: {
-				name: 'el-input',
-				props: {
-					clearable: true
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			span: 6,
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-function refresh(params?: any) {
-	Crud.value?.refresh(params);
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 109
src/modules/demo/views/crud/components/table/base.vue

@@ -1,109 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">base</el-tag>
-			<span>起步</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/base.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="起步" width="80%">
-				<!--【很重要】需要包含在 cl-crud 组件内 -->
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!-- 参数文档查看:https://element-plus.org/zh-CN/component/table.html#table-%E5%B1%9E%E6%80%A7 -->
-						<cl-table ref="Table" stripe></cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		// 测试数据,移步到 cl-crud 例子查看
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	// 是否自动计算表格高度,表格高度等于减去上区域和下区域的高度
-	//【很重要】在弹窗或者上级不确定高度中,设置 autoHeight: false,避免显示异常。也可以手动设置最大高度 maxHeight: 500
-	autoHeight: false,
-
-	// 右键菜单,移步到右键菜单示例中查看
-	contextMenu: ['refresh'],
-
-	// 列配置,点击 columns 查看描述
-	// 更多配置查看 el-table-column 文档,https://element-plus.org/zh-CN/component/table.html#table-column-%E5%B1%9E%E6%80%A7
-	columns: [
-		{
-			// 是否为多选框操作列
-			type: 'selection'
-
-			// 是否为序号列
-			// type: "index"
-		},
-		{
-			// 表头标题
-			label: '姓名',
-
-			// 绑定值
-			prop: 'name',
-
-			// 最小宽度
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			// 字典匹配,移步到字典示例中查看
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			// 是否排序,desc, asc
-			sortable: 'desc'
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 98
src/modules/demo/views/crud/components/table/children.vue

@@ -1,98 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">children</el-tag>
-			<span>多级表头</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/children.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="多级表头" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '用户信息',
-			prop: 'baseInfo',
-			minWidth: 250,
-
-			// 配置 children 参数
-			children: [
-				{
-					label: '姓名',
-					prop: 'name',
-					minWidth: 140
-				},
-				{
-					label: '手机号',
-					prop: 'phone',
-					minWidth: 140
-				}
-			]
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 108
src/modules/demo/views/crud/components/table/column-custom.vue

@@ -1,108 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">column-custom</el-tag>
-			<span>自定义列展示</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/column-custom.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="自定义列展示" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!--【很重要】组件配置,设置为 Table 的 columns,也可以自定义 -->
-						<cl-column-custom :columns="Table?.columns" />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table"></cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '状态',
-			prop: 'status',
-			dict: [
-				{
-					label: '启用',
-					value: 1,
-					type: 'success'
-				},
-				{
-					label: '禁用',
-					value: 0,
-					type: 'danger'
-				}
-			],
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 108
src/modules/demo/views/crud/components/table/component/index.vue

@@ -1,108 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">component</el-tag>
-			<span>组件渲染</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/component/index.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="组件渲染" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table"></cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { ElMessage } from 'element-plus';
-import UserInfo from './user-info.vue';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140,
-
-			//【很重要】组件实例方式渲染
-			component: {
-				vm: UserInfo
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140,
-
-			//【很重要】组件名方式渲染
-			component: {
-				// 组件名,组件必须全局注册了
-				name: 'el-input',
-
-				// 传入参数
-				props: {
-					onChange(val) {
-						ElMessage.info(val);
-					}
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 30
src/modules/demo/views/crud/components/table/component/user-info.vue

@@ -1,30 +0,0 @@
-<template>
-	<div class="user-info">
-		<cl-avatar :size="36" />
-
-		<div class="det">
-			<p>{{ scope?.name }}</p>
-		</div>
-	</div>
-</template>
-
-<!-- name 必须填写且唯一 -->
-<script setup lang="ts" name="user-info">
-const props = defineProps({
-	prop: String, // 列配置的 prop
-	scope: null // 列数据
-});
-</script>
-
-<style lang="scss" scoped>
-.user-info {
-	display: flex;
-	align-items: center;
-
-	.det {
-		flex: 1;
-		margin-left: 10px;
-		text-align: left;
-	}
-}
-</style>

+ 0 - 191
src/modules/demo/views/crud/components/table/context-menu.vue

@@ -1,191 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">context-menu</el-tag>
-			<span>右键菜单</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/context-menu.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="右键菜单">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table"></cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { ElMessage } from 'element-plus';
-import { EditPen, MoreFilled } from '@element-plus/icons-vue';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-
-	// 右键菜单配置,为 [] 时则不显示内容
-	contextMenu: [
-		'refresh', // 刷新
-		'check', // 选择行
-		'edit', // 弹出编辑框
-		'delete', // 弹出删除提示
-		'info', // 弹出详情
-		'order-desc', // 使列倒序
-		'order-asc', // 使列升序
-		{
-			label: '禁用状态',
-			disabled: true
-		},
-		{
-			label: '带图标',
-			prefixIcon: EditPen,
-			suffixIcon: MoreFilled
-		},
-		{
-			label: '超出隐藏,看我有很多字非常多',
-			ellipsis: true
-		},
-		{
-			label: '多层级',
-			children: [
-				{
-					label: 'A',
-					children: [
-						{
-							label: 'A-1',
-							callback(done) {
-								ElMessage.success('点击了A-1');
-								done();
-							}
-						}
-					]
-				},
-				{
-					label: 'B'
-				},
-				{
-					label: 'C'
-				}
-			]
-		},
-		// row 行数据
-		// column 列属性
-		// event 事件对象
-		(row, column, event) => {
-			// 必须返回一个对象
-			return {
-				label: '自定义2',
-				callback(done) {
-					ElMessage.info('获取中');
-
-					setTimeout(() => {
-						ElMessage.success(`Ta 是${row.name}`);
-
-						// 关闭右键菜单,只有在用到 callback 方法时才需要
-						done();
-					}, 500);
-				}
-			};
-		}
-	],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-// cl-upsert 配置,详细移步到 cl-upsert 示例查看
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 156
src/modules/demo/views/crud/components/table/dict.vue

@@ -1,156 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">dict</el-tag>
-			<span>字典匹配</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/dict.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="字典匹配" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { computed, reactive, ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-const options = reactive({
-	occupation: [] as { label: string; value: any }[]
-});
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-
-			//【很重要】字典匹配
-			// 使用字典模块的 get 方法绑定,菜单地址 /dict/list
-			dict: dict.get('occupation'),
-
-			// 是否使用不同颜色区分
-			dictColor: true,
-
-			minWidth: 140
-		},
-		{
-			label: '等级',
-			prop: 'occupation',
-
-			//【很重要】动态匹配列表的情况,使用 computed
-			dict: computed(() => options.occupation),
-
-			minWidth: 140
-		},
-		{
-			label: '状态',
-			prop: 'status',
-
-			// 自定义匹配列表
-			dict: [
-				{
-					label: '启用', // 显示文本
-					value: 1, // 匹配值
-					type: 'success' // el-tag 的type:success、danger、warning、info 默认 primary
-				},
-				{
-					label: '禁用',
-					value: 0,
-					type: 'danger'
-				}
-			],
-
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-
-	// 模拟接口获取数据
-	setTimeout(() => {
-		options.occupation = [
-			{
-				label: 'A',
-				value: 0
-			},
-			{
-				label: 'B',
-				value: 1
-			},
-			{
-				label: 'C',
-				value: 2
-			},
-			{
-				label: 'D',
-				value: 3
-			},
-			{
-				label: 'E',
-				value: 4
-			},
-			{
-				label: 'F',
-				value: 5
-			}
-		];
-	}, 1500);
-}
-</script>

+ 0 - 98
src/modules/demo/views/crud/components/table/formatter.vue

@@ -1,98 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">formatter</el-tag>
-			<span>数据格式化</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/formatter.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="数据格式化" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-09-26</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="tsx">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140,
-			formatter(row) {
-				return '📱' + row.phone;
-			}
-		},
-		{
-			label: '用户信息',
-			minWidth: 200,
-			// tsx 方式渲染
-			// 【很重要】使用 tsx 语法时,script 的 lang 一定要设置为 tsx
-			formatter(row) {
-				// row 为当前行数据
-				return (
-					<el-row>
-						<cl-avatar size={30} />
-						<el-text style={{ marginLeft: '10px' }}>{row.name}</el-text>
-					</el-row>
-				);
-			}
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 127
src/modules/demo/views/crud/components/table/hidden.vue

@@ -1,127 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">hidden</el-tag>
-			<span>隐藏/显示</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/hidden.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="隐藏/显示" width="80%">
-				<cl-crud ref="Crud">
-					<!--配置一个 tab -->
-					<el-tabs v-model="active">
-						<el-tab-pane label="员工" name="user"></el-tab-pane>
-						<el-tab-pane label="企业" name="company"></el-tab-pane>
-					</el-tabs>
-
-					<cl-row>
-						<!-- 使用方法 showColumn 显示 -->
-						<el-button @click="showColumn('account')">显示账号</el-button>
-
-						<!-- 使用方法 hideColumn 隐藏 -->
-						<el-button @click="hideColumn('account')">隐藏账号</el-button>
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table"></cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { computed, ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		// 测试数据,移步到 cl-crud 例子查看
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-const active = ref('user');
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: 'ID',
-			prop: 'id',
-			minWidth: 140,
-
-			//【很重要】配置 hidden 参数,格式为 boolean 或者 Vue.ComputedRef<boolean>
-			hidden: computed(() => {
-				return active.value != 'company';
-			})
-		},
-		{
-			label: '账号',
-			prop: 'account',
-			minWidth: 140,
-			hidden: true // 默认 false
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-function hideColumn(prop: string) {
-	Table.value?.hideColumn(prop);
-}
-
-function showColumn(prop: string) {
-	Table.value?.showColumn(prop);
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 164
src/modules/demo/views/crud/components/table/op.vue

@@ -1,164 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">op</el-tag>
-			<span>操作栏</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/op.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="操作栏" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table">
-							<!-- 插槽的渲染方式 #[component.name] -->
-							<template #slot-btns="{ scope }">
-								<el-button
-									@click="
-										() => {
-											ElMessage.info(scope.row.name);
-										}
-									"
-									>插槽按钮</el-button
-								>
-							</template>
-						</cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!-- 新增、编辑 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { ElMessage } from 'element-plus';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			//【很重要】type 必须是 op
-			type: 'op',
-
-			width: 410, // 宽度
-
-			//【很重要】操作按钮配置,edit 和 info 必须搭配 cl-upsert 实现
-			// edit 编辑,预先获取 service 的 info 接口数据,并带入 cl-upsert 的表单值中
-			// info 详情,cl-upsert 内的组件全部传入 disabled 参数
-			// delete 删除,调用 service 的 delete 接口删除行数据
-			buttons: [
-				'edit',
-				'info',
-				'delete',
-				{
-					label: '自定义',
-					onClick({ scope }) {
-						// scope 行作用域 { row, column, $index, store }
-						ElMessage.info('点击了自定义按钮');
-					}
-				},
-				'slot-btns'
-			]
-
-			// 动态返回按钮配置
-			// 用于控制是否根据状态显示按钮
-			// buttons({ scope }) {
-			//     return ['edit', 'info', 'delete']
-			// }
-		}
-	]
-});
-
-// cl-upsert 配置,详细移步到 cl-upsert 示例查看
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true, // 树形方式选择
-					checkStrictly: true, // 任意层级都能点
-					options: dict.get('occupation') // 使用字典数据
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 48
src/modules/demo/views/crud/components/table/plugin/column.tsx

@@ -1,48 +0,0 @@
-import { merge } from 'lodash-es';
-import { defineComponent } from 'vue';
-
-const columns = {
-	UserInfo: {
-		label: '用户信息',
-		minWidth: 200,
-		component: {
-			vm: defineComponent({
-				name: 'user-info',
-
-				props: {
-					scope: null
-				},
-
-				setup(props) {
-					return () => {
-						return (
-							<div>
-								<p>{props.scope.name}</p>
-								<p>{props.scope.phone}</p>
-							</div>
-						);
-					};
-				}
-			})
-		}
-	}
-} as { [key: string]: DeepPartial<ClTable.Column> };
-
-/**
- * 列标签匹配,方便多个列表公用同一个组件
- * @returns
- */
-export function setColumn(): ClTable.Plugin {
-	return ({ exposed }) => {
-		function deep(arr: ClTable.Column[]) {
-			arr.forEach(e => {
-				if (e.tag) {
-					merge(e, columns[e.tag]);
-				}
-				deep(e.children || []);
-			});
-		}
-
-		deep(exposed.columns);
-	};
-}

+ 0 - 86
src/modules/demo/views/crud/components/table/plugin/index.vue

@@ -1,86 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">plugin</el-tag>
-			<span>插件的使用</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/plugin/index.vue', 'table/plugin/column.tsx']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="插件的使用" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { setColumn } from './column';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			tag: 'UserInfo'
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	],
-
-	//【很重要】配置插件
-	plugins: [setColumn()]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 143
src/modules/demo/views/crud/components/table/search.vue

@@ -1,143 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">search</el-tag>
-			<span>表头搜索</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/search.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="表头搜索" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140,
-
-			//【很重要】搜索参数配置
-			search: {
-				isInput: false, // 默认false,是否输入框模式
-				value: '', // 默认值
-				refreshOnChange: true, // 默认false,搜索时刷新数据,service 的 page 接口请求参数为 { page: 1, [绑定的prop]: 输入值 }
-				// 自定义渲染组件
-				component: {
-					name: 'el-input',
-					props: {
-						placeholder: '搜索姓名'
-					}
-				}
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140,
-
-			//【很重要】搜索参数配置
-			search: {
-				// 自定义渲染组件
-				component: {
-					name: 'el-input',
-					props: {
-						placeholder: '搜索手机号',
-
-						// 自定义 change 事件
-						onChange(val) {
-							Crud.value?.refresh({
-								page: 1,
-								phone: val
-							});
-						}
-					}
-				}
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140,
-
-			//【很重要】搜索参数配置
-			search: {
-				refreshOnChange: false, // cl-select 自带 onChange 刷新,故不需要这个参数
-				component: {
-					name: 'cl-select',
-					props: {
-						tree: true, // 树形方式选择
-						checkStrictly: true, // 任意层级都能点
-						options: dict.get('occupation') // 使用字典数据
-					}
-				}
-			}
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc',
-
-			//【很重要】搜索参数配置
-			search: {
-				component: {
-					name: 'cl-date-picker', // cl-date-picker 自带 onChange 刷新
-					props: {
-						type: 'date',
-						valueFormat: 'YYYY-MM-DD'
-					}
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 110
src/modules/demo/views/crud/components/table/selection.vue

@@ -1,110 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">selection</el-tag>
-			<span>多选框数据</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/selection.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="多选框数据" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<el-button @click="selectRow">选中2行</el-button>
-						<el-button :disabled="Table?.selection.length == 0" @click="clear"
-							>取消选择</el-button
-						>
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<el-text>已选 {{ Table?.selection.length }} 人</el-text>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh', 'check'],
-
-	columns: [
-		{
-			type: 'selection'
-		},
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-function selectRow() {
-	const [a, b] = Table.value?.data || [];
-
-	// 选中2个
-	Table.value?.toggleRowSelection(a);
-	Table.value?.toggleRowSelection(b);
-}
-
-function clear() {
-	// 更多方法查看文档:https://element-plus.gitee.io/zh-CN/component/table.html#table-%E6%96%B9%E6%B3%95
-	Table.value?.clearSelection();
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 97
src/modules/demo/views/crud/components/table/slot.vue

@@ -1,97 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">slot</el-tag>
-			<span>插槽的使用</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/slot.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="插槽的使用" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table">
-							<!--【很重要】必须与 prop 名保持一致,格式:column-[prop] -->
-							<template #column-name="{ scope }">
-								<cl-row type="flex" align="middle">
-									<cl-avatar :size="36" :style="{ marginRight: '10px' }" />
-									<el-text>{{ scope.row.name }}</el-text>
-								</cl-row>
-							</template>
-
-							<template #column-phone="{ scope }"> 📱{{ scope.row.phone }} </template>
-						</cl-table>
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			headerAlign: 'left',
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 116
src/modules/demo/views/crud/components/table/span-method.vue

@@ -1,116 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">span-method</el-tag>
-			<span>合并行或列</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/span-method.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="合并行或列" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table" :span-method="onSpanMethod" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { type TableColumnCtx } from 'element-plus';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '存款',
-			prop: 'wages',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-interface SpanMethodProps {
-	row: any;
-	column: TableColumnCtx<any>;
-	rowIndex: number;
-	columnIndex: number;
-}
-
-function onSpanMethod({ row, column, rowIndex, columnIndex }: SpanMethodProps) {
-	// 根据实际业务需求调整返回值 { rowspan, colspan }
-	if (columnIndex === 0) {
-		if (rowIndex % 2 === 0) {
-			return {
-				rowspan: 2,
-				colspan: 1
-			};
-		} else {
-			return {
-				rowspan: 0,
-				colspan: 0
-			};
-		}
-	}
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 95
src/modules/demo/views/crud/components/table/summary.vue

@@ -1,95 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">summary</el-tag>
-			<span>表尾合计行</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['table/summary.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="表尾合计行" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<cl-table ref="Table" show-summary :summary-method="getSummaries" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '存款',
-			prop: 'wages',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		}
-	]
-});
-
-function getSummaries() {
-	return ['合计', '$' + Table.value?.data.reduce((a, b) => a + b.wages, 0)];
-}
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 133
src/modules/demo/views/crud/components/upsert/base.vue

@@ -1,133 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">base</el-tag>
-			<span>起步</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['upsert/base.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="起步" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!-- 打开新增表单的按钮 -->
-						<cl-add-btn />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!--【很重要】新增、编辑的表单组件 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			// edit 打开编辑表单
-			buttons: ['edit', 'delete']
-		}
-	]
-});
-
-// cl-upsert 配置
-//【很重要】该组件基于 cl-form 故很多示例都可复用
-const Upsert = useUpsert({
-	// 配置如 cl-form 一样
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 210
src/modules/demo/views/crud/components/upsert/event.vue

@@ -1,210 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">event</el-tag>
-			<span>事件</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['upsert/event.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="事件" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!-- 打开新增表单的按钮 -->
-						<cl-add-btn />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!--【很重要】新增、编辑的表单组件 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { useCool } from '/@/cool';
-
-const { service } = useCool();
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			// edit 打开编辑表单
-			buttons: ['edit', 'delete']
-		}
-	]
-});
-
-// cl-upsert 配置
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	],
-
-	// 以下事件按顺序触发
-
-	// 弹窗打开的事件,这个时候还未有表单数据
-	onOpen() {
-		console.log('onOpen');
-	},
-
-	// 获取详情,编辑的时候会触发
-	async onInfo(data, { next, done }) {
-		// 不配置 onInfo 的时候默认执行 next(data),调用 service 的 info 接口获取详情
-		// next(data);
-
-		// 自定义,需要对请求数据进行处理或者返回处理后的数据
-		const res = await next({
-			id: data.id
-		});
-
-		done({
-			...res,
-			name: `[${res.name}]`
-		});
-	},
-
-	// 弹窗打开后,已经得到了表单数据
-	onOpened(data) {
-		// 判定是否编辑模式
-		if (Upsert.value?.mode == 'update') {
-			// 对数据处理
-			data.phone += '000';
-		}
-	},
-
-	// 提交事件的钩子
-	// data 表单提交数据
-	// next 继续往下执行
-	// done 关闭加载
-	// close 关闭弹窗
-	async onSubmit(data, { next, done, close }) {
-		// 不配置 onSubmit 的时候默认执行 next(data),提交后会去请求 service 的 update/add 接口
-		// next(data);
-
-		// 自定义如下
-		// 场景1:提交时对参数额外的处理
-		// next({
-		// 	...data,
-		// 	status: 1,
-		// 	createTime: dayjs().format("YYYY-MM-DD")
-		// });
-
-		// 场景2:提交前、后的操作
-		// 之前,模拟获取 userId
-		const userId = await service.test.info({ id: 1 });
-
-		// 返回值
-		const res = await next({
-			userId,
-			data
-		});
-
-		// 之后
-		// console.log(res);
-	},
-
-	// 关闭时触发
-	onClose(action, done) {
-		// action 关闭的类型
-		console.log('action,', action);
-
-		// 使用 done 关闭窗口
-		done();
-	},
-
-	// 关闭后触发
-	onClosed() {
-		console.log('onClosed');
-	}
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 200
src/modules/demo/views/crud/components/upsert/hook/index.vue

@@ -1,200 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">hook</el-tag>
-			<span>Hook的使用</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['upsert/hook/index.vue', 'upsert/hook/reg-pca2.ts']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="Hook的使用" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!-- 打开新增表单的按钮 -->
-						<cl-add-btn />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!--【很重要】新增、编辑的表单组件 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '省市区',
-			prop: 'pca',
-			formatter(row) {
-				return row.province ? row.province + '-' + row.city + '-' + row.district : '-';
-			},
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			buttons: ['edit', 'delete']
-		}
-	]
-});
-
-// cl-upsert 配置
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			component: {
-				name: 'el-input'
-			}
-		},
-		{
-			label: '省市区',
-			prop: 'pca2',
-
-			//【很重要】hook 参数配置
-			hook: {
-				bind(value, { form }) {
-					// 将3个参数合并成一个数组,带入级联选择器
-					return [form.province, form.city, form.district];
-				},
-				submit(value, { form, prop }) {
-					// 提交的时候将数组拆分成3个字段提交
-					const [province, city, district] = value || [];
-					form.province = province;
-					form.city = city;
-					form.district = district;
-
-					// 删除 prop 绑定值
-					form[prop] = undefined;
-				}
-			},
-			// 注册到全局后可直接使用,注册代码看 ./reg-pca2.ts
-			// hook: "pca2",
-
-			component: {
-				name: 'cl-distpicker'
-			}
-		},
-		{
-			label: '标签',
-			prop: 'labels',
-			//【很重要】使用内置方法,避免一些辣鸡后端要你这么传给他
-			hook: {
-				// labels 的数据为 1,2,3
-
-				// 绑定的时候将 labels 按 , 分割成数组
-				bind: ['split', 'number'],
-
-				// 提交的时候将 labels 拼接成字符串
-				submit: ['join']
-			},
-			component: {
-				name: 'el-select',
-				props: {
-					multiple: true
-				},
-				options: [
-					{
-						label: '帅气',
-						value: 1
-					},
-					{
-						label: '多金',
-						value: 2
-					},
-					{
-						label: '有才华',
-						value: 3
-					}
-				]
-			}
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	]
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 14
src/modules/demo/views/crud/components/upsert/hook/reg-pca2.ts

@@ -1,14 +0,0 @@
-import { registerFormHook } from '@cool-vue/crud';
-
-// 注册 hook
-registerFormHook('pca2', (value, { method, form, prop }) => {
-	if (method == 'bind') {
-		return [form.province, form.city, form.district];
-	} else {
-		const [province, city, district] = value || [];
-		form.province = province;
-		form.city = city;
-		form.district = district;
-		form[prop] = undefined;
-	}
-});

+ 0 - 146
src/modules/demo/views/crud/components/upsert/mode.vue

@@ -1,146 +0,0 @@
-<template>
-	<div class="scope">
-		<div class="h">
-			<el-tag size="small" effect="dark">mode</el-tag>
-			<span>不同模式</span>
-		</div>
-
-		<div class="c">
-			<el-button @click="open">预览</el-button>
-			<demo-code :files="['upsert/mode.vue']" />
-
-			<!-- 自定义表格组件 -->
-			<cl-dialog v-model="visible" title="不同模式" width="80%">
-				<cl-crud ref="Crud">
-					<cl-row>
-						<!-- 打开新增表单的按钮 -->
-						<cl-add-btn />
-					</cl-row>
-
-					<cl-row>
-						<cl-table ref="Table" />
-					</cl-row>
-
-					<cl-row>
-						<cl-flex1 />
-						<cl-pagination />
-					</cl-row>
-
-					<!--【很重要】新增、编辑的表单组件 -->
-					<cl-upsert ref="Upsert" />
-				</cl-crud>
-			</cl-dialog>
-		</div>
-
-		<div class="f">
-			<span class="date">2024-01-01</span>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
-import { ref } from 'vue';
-import { useDict } from '/$/dict';
-import { ElMessage } from 'element-plus';
-
-const { dict } = useDict();
-
-// cl-crud 配置
-const Crud = useCrud(
-	{
-		service: 'test'
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-// cl-table 配置
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: ['refresh'],
-
-	columns: [
-		{
-			label: '姓名',
-			prop: 'name',
-			minWidth: 140
-		},
-		{
-			label: '手机号',
-			prop: 'phone',
-			minWidth: 140
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			dict: dict.get('occupation'),
-			minWidth: 140
-		},
-		{
-			label: '创建时间',
-			prop: 'createTime',
-			minWidth: 170,
-			sortable: 'desc'
-		},
-		{
-			type: 'op',
-			width: 240,
-			buttons: ['info', 'edit', 'delete']
-		}
-	]
-});
-
-// cl-upsert 配置
-const Upsert = useUpsert({
-	items: [
-		{
-			label: '姓名',
-			prop: 'name',
-			component: {
-				name: 'el-input'
-			}
-		},
-		//【很重要】只有返回方法的时候才能使用 Upsert
-		() => {
-			return {
-				label: '手机号',
-				prop: 'phone',
-
-				// 新增的时候隐藏
-				// hidden: Upsert.value?.mode == "add",
-
-				component: {
-					name: 'el-input',
-					props: {
-						// 编辑的时候禁用
-						disabled: Upsert.value?.mode == 'update'
-					}
-				}
-			};
-		},
-		{
-			label: '工作',
-			prop: 'occupation',
-			component: {
-				name: 'cl-select',
-				props: {
-					tree: true,
-					checkStrictly: true,
-					options: dict.get('occupation')
-				}
-			}
-		}
-	],
-	onOpen() {
-		ElMessage.info(`当前模式:` + Upsert.value?.mode);
-	}
-});
-
-const visible = ref(false);
-
-function open() {
-	visible.value = true;
-}
-</script>

+ 0 - 294
src/modules/demo/views/crud/index.vue

@@ -1,294 +0,0 @@
-<template>
-	<el-scrollbar>
-		<div class="crud-demo">
-			<el-tabs v-model="active" type="card" @tab-change="onTabChange">
-				<el-tab-pane v-for="(a, ai) in list" :key="ai" :label="a.title" :name="a.title">
-					<div v-for="(b, bi) in a.children" :key="bi" class="group">
-						<p class="label"># {{ b.label }}</p>
-
-						<el-row :gutter="10">
-							<el-col
-								v-for="(c, ci) in b.children"
-								:key="ci"
-								:xs="24"
-								:sm="12"
-								:md="8"
-								:lg="6"
-							>
-								<component :is="c" />
-							</el-col>
-						</el-row>
-					</div>
-				</el-tab-pane>
-			</el-tabs>
-		</div>
-	</el-scrollbar>
-</template>
-
-<script lang="ts" setup name="demo-crud">
-import { ref, onActivated } from 'vue';
-
-import CrudBase from './components/crud/base.vue';
-import CrudAll from './components/crud/all.vue';
-import CrudDict from './components/crud/dict.vue';
-import CrudEvent from './components/crud/event.vue';
-import CrudService from './components/crud/service.vue';
-
-import FormOpen from './components/form/open.vue';
-import FormConfig from './components/form/config.vue';
-import FormRequired from './components/form/required.vue';
-import FormLayout from './components/form/layout.vue';
-import FormOptions from './components/form/options.vue';
-import FormHidden from './components/form/hidden.vue';
-import FormDisabled from './components/form/disabled.vue';
-import FormEvent from './components/form/event.vue';
-import FormGroup from './components/form/group.vue';
-import FormChildren from './components/form/children.vue';
-import FormCrud from './components/form/crud.vue';
-import FormRules from './components/form/rules.vue';
-import FormComponent from './components/form/component/index.vue';
-import FormPlugin from './components/form/plugin/index.vue';
-
-import TableBase from './components/table/base.vue';
-import TableFormatter from './components/table/formatter.vue';
-import TableOp from './components/table/op.vue';
-import TableSearch from './components/table/search.vue';
-import TableSelection from './components/table/selection.vue';
-import TableSlot from './components/table/slot.vue';
-import TableSummary from './components/table/summary.vue';
-import TableHidden from './components/table/hidden.vue';
-import TableChildren from './components/table/children.vue';
-import TableContextMenu from './components/table/context-menu.vue';
-import TableDict from './components/table/dict.vue';
-import TableSpanMethod from './components/table/span-method.vue';
-import TableColumnCustom from './components/table/column-custom.vue';
-import TableComponent from './components/table/component/index.vue';
-import TablePlugin from './components/table/plugin/index.vue';
-
-import UpsertBase from './components/upsert/base.vue';
-import UpsertEvent from './components/upsert/event.vue';
-import UpsertMode from './components/upsert/mode.vue';
-import UpsertHook from './components/upsert/hook/index.vue';
-
-import SearchBase from './components/search/base.vue';
-import SearchCustom from './components/search/custom.vue';
-import SearchLayout from './components/search/layout.vue';
-
-import AdvSearchBase from './components/adv-search/base.vue';
-import AdvSearchCustom from './components/adv-search/custom.vue';
-
-import OtherTsx from './components/other/tsx';
-import OtherTips from './components/other/tips.vue';
-import { useCool } from '/@/cool';
-
-const { route, router } = useCool();
-
-const active = ref();
-
-const list = [
-	{
-		title: 'cl-crud',
-		children: [
-			{
-				label: '基础',
-				children: [CrudBase, CrudService, CrudDict, CrudEvent]
-			},
-			{
-				label: '高级',
-				children: [CrudAll]
-			}
-		]
-	},
-	{
-		title: 'cl-table',
-		children: [
-			{
-				label: '基础',
-				children: [
-					TableBase,
-					TableFormatter,
-					TableOp,
-					TableSearch,
-					TableSelection,
-					TableSlot,
-					TableDict,
-					TableHidden,
-					TableContextMenu,
-					TableSummary,
-					TableSpanMethod,
-					TableChildren
-				]
-			},
-			{
-				label: '高级',
-				children: [TableColumnCustom, TableComponent, TablePlugin]
-			}
-		]
-	},
-	{
-		title: 'cl-upsert',
-		children: [
-			{
-				label: '基础',
-				children: [UpsertBase, UpsertEvent, UpsertMode]
-			},
-			{
-				label: '高级',
-				children: [UpsertHook]
-			}
-		]
-	},
-	{
-		title: 'cl-form',
-		children: [
-			{
-				label: '基础',
-				children: [
-					FormOpen,
-					FormConfig,
-					FormRequired,
-					FormLayout,
-					FormOptions,
-					FormHidden,
-					FormDisabled,
-					FormEvent,
-					FormGroup,
-					FormChildren,
-					FormCrud
-				]
-			},
-			{
-				label: '高级',
-				children: [FormRules, FormComponent, FormPlugin]
-			}
-		]
-	},
-	{
-		title: 'cl-search',
-		children: [
-			{
-				label: '基础',
-				children: [SearchBase, SearchCustom, SearchLayout]
-			}
-		]
-	},
-	{
-		title: 'cl-adv-search',
-		children: [
-			{
-				label: '基础',
-				children: [AdvSearchBase, AdvSearchCustom]
-			}
-		]
-	},
-	{
-		title: 'other',
-		children: [
-			{
-				label: '高级',
-				children: [OtherTsx, OtherTips]
-			}
-		]
-	}
-];
-
-function onTabChange(val: any) {
-	router.replace({
-		query: {
-			key: val
-		}
-	});
-}
-
-onActivated(() => {
-	const { key } = route.query;
-	active.value = (key || 'cl-crud') as string;
-});
-</script>
-
-<style lang="scss" scoped>
-.el-scrollbar {
-	background-color: var(--el-bg-color);
-}
-
-.crud-demo {
-	padding: 10px;
-
-	:deep(.scope) {
-		border-radius: 8px;
-		margin-bottom: 10px;
-		white-space: nowrap;
-		border: 1px solid var(--el-border-color-light);
-		cursor: pointer;
-		transition: all 0.3s;
-
-		.h {
-			display: flex;
-			align-items: center;
-			height: 30px;
-			padding: 10px;
-			font-size: 12px;
-
-			.el-tag {
-				margin-right: 10px;
-			}
-		}
-
-		.c {
-			height: 50px;
-			padding: 10px;
-			box-sizing: border-box;
-
-			&._svg {
-				.cl-svg {
-					margin-right: 15px;
-				}
-			}
-
-			a {
-				font-size: 13px;
-				position: relative;
-
-				&:hover {
-					&:after {
-						content: '';
-						width: 100%;
-						height: 1px;
-						position: absolute;
-						bottom: -2px;
-						left: 0;
-						background-color: var(--color-primary);
-					}
-				}
-			}
-		}
-
-		.f {
-			display: flex;
-			align-items: center;
-			justify-content: space-between;
-			height: 30px;
-			padding: 10px;
-			font-size: 12px;
-
-			.date {
-				color: #999;
-			}
-		}
-
-		&:hover {
-			box-shadow: 0px 0px 10px 1px var(--el-color-info-light-9);
-		}
-	}
-
-	.group {
-		margin-bottom: 20px;
-
-		.label {
-			margin-bottom: 10px;
-			font-size: 15px;
-			font-weight: bold;
-		}
-	}
-}
-</style>

+ 0 - 84
src/modules/demo/views/home/components/category-ratio.vue

@@ -1,84 +0,0 @@
-<template>
-	<div class="category-ratio">
-		<div class="category-ratio__header">
-			<span>销售额类别占比</span>
-		</div>
-
-		<div class="category-ratio__container">
-			<v-chart :option="chartOption" autoresize />
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { reactive } from 'vue';
-
-const chartOption = reactive({
-	tooltip: {
-		trigger: 'item',
-		formatter: '{a} <br/>{b}: {c} ({d}%)'
-	},
-	legend: {
-		bottom: 30,
-		left: 'center',
-		data: ['手机', '相机', '耳机', '音箱', '手表']
-	},
-	series: [
-		{
-			type: 'pie',
-			radius: ['50%', '60%'],
-			center: ['50%', '40%'],
-			avoidLabelOverlap: false,
-			label: {
-				show: false,
-				position: 'center'
-			},
-			emphasis: {
-				label: {
-					show: true,
-					fontSize: '30',
-					fontWeight: 'bold'
-				}
-			},
-			labelLine: {
-				show: false
-			},
-			data: [
-				{ value: 335, name: '手机' },
-				{ value: 310, name: '相机' },
-				{ value: 234, name: '耳机' },
-				{ value: 135, name: '音箱' },
-				{ value: 500, name: '手表' }
-			],
-			itemStyle: {
-				borderColor: '#fff',
-				borderWidth: 4
-			},
-			roundCap: 1
-		}
-	]
-});
-</script>
-
-<style lang="scss" scoped>
-.category-ratio {
-	&__header {
-		display: flex;
-		align-items: center;
-		height: 50px;
-		font-size: 15px;
-		font-weight: bold;
-		padding: 0 20px;
-	}
-
-	&__container {
-		height: 385px;
-		padding: 0 20px;
-		box-sizing: border-box;
-
-		.echarts {
-			width: 100%;
-		}
-	}
-}
-</style>

+ 0 - 90
src/modules/demo/views/home/components/count-effect.vue

@@ -1,90 +0,0 @@
-<template>
-	<div class="count-effect">
-		<div class="card">
-			<div class="card__header">
-				<span class="label">总销售额</span>
-				<span class="value">¥15920</span>
-			</div>
-
-			<div class="card__container">
-				<el-progress :percentage="value" :stroke-width="10" :show-text="false" />
-			</div>
-
-			<div class="card__footer">
-				<ul class="count-effect__cop">
-					<li>
-						<span>周同比</span>
-
-						<div class="fall">
-							<el-icon>
-								<bottom-right />
-							</el-icon>
-
-							<span>-4%</span>
-						</div>
-					</li>
-
-					<li>
-						<span>日同比</span>
-
-						<div class="rise">
-							<el-icon>
-								<top-right />
-							</el-icon>
-
-							<span>+7%</span>
-						</div>
-					</li>
-				</ul>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { ref } from 'vue';
-import { BottomRight, TopRight } from '@element-plus/icons-vue';
-
-const value = ref(0);
-
-setTimeout(() => {
-	value.value = Math.random() * 30 + 30;
-}, 0);
-</script>
-
-<style lang="scss" scoped>
-.count-effect {
-	&__cop {
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-		width: 100%;
-
-		li {
-			display: flex;
-			list-style: none;
-			flex: 1;
-
-			.fall,
-			.rise {
-				display: flex;
-				align-items: center;
-				margin-left: 10px;
-			}
-
-			.fall {
-				color: #13ae7c;
-			}
-
-			.rise {
-				color: #f21e37;
-			}
-		}
-	}
-
-	.card__container {
-		padding-top: 15px;
-		box-sizing: border-box;
-	}
-}
-</style>

+ 0 - 89
src/modules/demo/views/home/components/count-paid.vue

@@ -1,89 +0,0 @@
-<template>
-	<div class="count-paid">
-		<div class="card">
-			<div class="card__header">
-				<span class="label">付款笔数</span>
-				<span class="value">6560</span>
-			</div>
-
-			<div class="card__container">
-				<v-chart :option="chartOption" autoresize />
-			</div>
-
-			<div class="card__footer">
-				<span class="label">转化率</span>
-				<span class="value">60%</span>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { reactive } from 'vue';
-
-const chartOption = reactive({
-	grid: {
-		left: '10%',
-		top: 0,
-		right: '10%',
-		bottom: 0
-	},
-	xAxis: {
-		type: 'category',
-		data: ['00:00', '2:00', '4:00', '6:00', '8:00', '10:00', '12:00', '14:00'],
-		boundaryGap: false
-	},
-	yAxis: {
-		type: 'value',
-		splitLine: {
-			show: false
-		},
-		axisTick: {
-			show: false
-		},
-		axisLine: {
-			show: false
-		},
-		axisLabel: {
-			show: false
-		}
-	},
-	series: [
-		{
-			barWidth: 18,
-			name: '付款笔数',
-			type: 'bar',
-			data: new Array(8).fill(1).map(() => Math.random() * 50 + 20),
-			itemStyle: {
-				color: '#4165d7'
-			}
-		},
-		{
-			type: 'bar',
-			barWidth: 18,
-			xAxisIndex: 0,
-			barGap: '-100%',
-			data: [100, 100, 100, 100, 100, 100, 100, 100],
-			itemStyle: {
-				color: '#f1f1f9'
-			},
-			zlevel: -1
-		}
-	]
-});
-</script>
-
-<style lang="scss" scoped>
-.count-paid {
-	.card {
-		.echarts {
-			height: 50px;
-			width: 100%;
-		}
-
-		&__container {
-			padding: 0;
-		}
-	}
-}
-</style>

+ 0 - 80
src/modules/demo/views/home/components/count-user.vue

@@ -1,80 +0,0 @@
-<template>
-	<div class="count-sales">
-		<div class="card">
-			<div class="card__header">
-				<span class="label">总用户数</span>
-				<span class="value">3685</span>
-			</div>
-
-			<div class="card__container">
-				<ul class="count-sales__cop">
-					<li>
-						<span>周同比</span>
-
-						<div class="fall">
-							<el-icon>
-								<bottom-right />
-							</el-icon>
-
-							<span>-6%</span>
-						</div>
-					</li>
-
-					<li>
-						<span>日同比</span>
-
-						<div class="rise">
-							<el-icon>
-								<top-right />
-							</el-icon>
-
-							<span>+12%</span>
-						</div>
-					</li>
-				</ul>
-			</div>
-
-			<div class="card__footer">
-				<span class="label">日增用户数</span>
-				<span class="value">69</span>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { BottomRight, TopRight } from '@element-plus/icons-vue';
-</script>
-
-<style lang="scss" scoped>
-.count-sales {
-	&__cop {
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-		height: 50px;
-
-		li {
-			display: flex;
-			list-style: none;
-			flex: 1;
-			color: var(--el-color-info);
-
-			.fall,
-			.rise {
-				display: flex;
-				align-items: center;
-				margin-left: 10px;
-			}
-
-			.fall {
-				color: #13ae7c;
-			}
-
-			.rise {
-				color: #f21e37;
-			}
-		}
-	}
-}
-</style>

+ 0 - 125
src/modules/demo/views/home/components/count-views.vue

@@ -1,125 +0,0 @@
-<template>
-	<div class="count-views">
-		<div class="card">
-			<div class="card__header">
-				<span class="label">总访问量</span>
-				<span class="value">8846</span>
-			</div>
-
-			<div class="card__container">
-				<v-chart :option="chartOption" autoresize />
-			</div>
-
-			<div class="card__footer">
-				<span class="label">日访问量</span>
-				<span class="value">1351</span>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { reactive } from 'vue';
-import * as echarts from 'echarts';
-
-const chartOption = reactive({
-	grid: {
-		left: 0,
-		top: 1,
-		right: 0,
-		bottom: 1
-	},
-	xAxis: {
-		type: 'category',
-		boundaryGap: false,
-		axisLine: {
-			show: false
-		},
-		data: [
-			'00:00',
-			'2:00',
-			'4:00',
-			'6:00',
-			'8:00',
-			'10:00',
-			'12:00',
-			'14:00',
-			'16:00',
-			'18:00',
-			'20:00',
-			'22:00'
-		]
-	},
-	yAxis: {
-		type: 'value',
-		splitLine: {
-			show: false
-		},
-		axisTick: {
-			show: false
-		},
-		axisLine: {
-			show: false
-		},
-		axisLabel: {
-			show: false
-		}
-	},
-	series: [
-		{
-			type: 'line',
-			smooth: true,
-			showSymbol: false,
-			symbol: 'circle',
-			symbolSize: 6,
-			data: new Array(12)
-				.fill(1)
-				.map(() => parseInt((Math.random() * 1000).toFixed(0)) + 500),
-			areaStyle: {
-				color: new echarts.graphic.LinearGradient(
-					0,
-					0,
-					0,
-					1,
-					[
-						{
-							offset: 0,
-							color: '#D1E5FF'
-						},
-						{
-							offset: 1,
-							color: '#FFFFFF'
-						}
-					],
-					false
-				)
-			},
-			itemStyle: {
-				color: '#4165d7'
-			},
-			lineStyle: {
-				width: 2
-			}
-		}
-	]
-});
-</script>
-
-<style lang="scss" scoped>
-.count-views {
-	.card {
-		.echarts {
-			height: 50px;
-			width: 100%;
-		}
-
-		&__container {
-			padding: 0;
-		}
-
-		&__footer {
-			border-top: 0;
-		}
-	}
-}
-</style>

+ 0 - 265
src/modules/demo/views/home/components/hot-search.vue

@@ -1,265 +0,0 @@
-<template>
-	<div class="hot-search">
-		<div class="hot-search__header">
-			<span>线上热门搜索</span>
-		</div>
-
-		<div class="hot-search__container">
-			<el-row class="hot-search__chart" :gutter="20">
-				<el-col :md="12" :xs="24">
-					<div class="block">
-						<div class="count">
-							<div class="number">
-								<span>搜索用户数</span>
-								<span>1242</span>
-							</div>
-							<div class="rise">
-								<i class="el-icon-top-right"></i>
-								<span>+7%</span>
-							</div>
-						</div>
-
-						<v-chart :option="chartOption()" autoresize />
-					</div>
-				</el-col>
-
-				<el-col :md="12" :xs="24">
-					<div class="block is-last">
-						<div class="count">
-							<div class="number">
-								<span>关注用户数</span>
-								<span>365</span>
-							</div>
-							<div class="rise">
-								<i class="el-icon-top-right"></i>
-								<span>+2%</span>
-							</div>
-						</div>
-
-						<v-chart :option="chartOption()" autoresize />
-					</div>
-				</el-col>
-			</el-row>
-
-			<div class="hot-search__table">
-				<cl-crud ref="Crud">
-					<cl-table ref="Table" :border="false" />
-				</cl-crud>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import * as echarts from 'echarts';
-import { useCrud, useTable } from '@cool-vue/crud';
-
-const Crud = useCrud(
-	{
-		service: {
-			page() {
-				return Promise.resolve({
-					list: [
-						{
-							keyWord: '无线耳机',
-							users: 983,
-							ud: 5
-						},
-						{
-							keyWord: '运动耳机',
-							users: 763,
-							ud: -3
-						},
-						{
-							keyWord: '蓝牙音箱',
-							users: 328,
-							ud: 7
-						},
-						{
-							keyWord: '4k显示屏',
-							users: 144,
-							ud: 4
-						},
-						{
-							keyWord: '罗技 G530',
-							users: 121,
-							ud: -1
-						}
-					]
-				});
-			}
-		}
-	},
-	app => {
-		app.refresh();
-	}
-);
-
-const Table = useTable({
-	autoHeight: false,
-	contextMenu: [],
-	columns: [
-		{
-			label: '排名',
-			type: 'index',
-			width: 60
-		},
-		{
-			label: '搜索关键词',
-			prop: 'keyWord',
-			minWidth: 100
-		},
-		{
-			label: '用户数',
-			prop: 'users',
-			minWidth: 100
-		},
-		{
-			label: '周涨幅',
-			prop: 'ud',
-			sortable: 'desc',
-			minWidth: 100
-		}
-	]
-});
-
-function chartOption() {
-	return {
-		grid: {
-			left: 0,
-			top: 10,
-			right: 0,
-			bottom: 0
-		},
-		xAxis: {
-			type: 'category',
-			data: [],
-			boundaryGap: false
-		},
-		yAxis: {
-			type: 'value',
-			splitLine: {
-				show: false
-			},
-			axisTick: {
-				show: false
-			},
-			axisLine: {
-				show: false
-			},
-			axisLabel: {
-				show: false
-			}
-		},
-		series: [
-			{
-				name: '总访问量',
-				type: 'line',
-				smooth: true,
-				showSymbol: false,
-				symbol: 'circle',
-				symbolSize: 6,
-				data: new Array(12)
-					.fill(1)
-					.map(() => parseInt((Math.random() * 1000).toFixed(0)) + 500),
-				areaStyle: {
-					color: new echarts.graphic.LinearGradient(
-						0,
-						0,
-						0,
-						1,
-						[
-							{
-								offset: 0,
-								color: '#D1E5FF'
-							},
-							{
-								offset: 1,
-								color: '#FFFFFF'
-							}
-						],
-						false
-					)
-				},
-				itemStyle: {
-					color: '#4165d7'
-				},
-				lineStyle: {
-					width: 2
-				}
-			}
-		]
-	};
-}
-</script>
-
-<style lang="scss" scoped>
-.hot-search {
-	&__header {
-		display: flex;
-		align-items: center;
-		height: 50px;
-		font-size: 15px;
-		font-weight: bold;
-		padding: 0 20px;
-	}
-
-	&__container {
-		padding-bottom: 10px;
-	}
-
-	&__chart {
-		padding: 0 20px;
-
-		.block {
-			.count {
-				display: flex;
-				align-items: center;
-				justify-content: space-between;
-				margin-bottom: 10px;
-				height: 40px;
-
-				.fall,
-				.rise {
-					display: flex;
-					align-items: center;
-					margin-left: 10px;
-					font-size: 15px;
-				}
-
-				.fall {
-					color: #13ae7c;
-				}
-
-				.rise {
-					color: #f21e37;
-				}
-
-				.number {
-					display: flex;
-					align-items: center;
-
-					span {
-						font-size: 13px;
-
-						&:last-child {
-							margin-left: 10px;
-							font-size: 15px;
-							font-weight: bold;
-						}
-					}
-				}
-			}
-
-			.echarts {
-				height: 70px;
-				width: 100%;
-			}
-		}
-	}
-
-	&__table {
-		margin: 0 10px;
-	}
-}
-</style>

+ 0 - 201
src/modules/demo/views/home/components/sales-rank.vue

@@ -1,201 +0,0 @@
-<template>
-	<div class="sales-rank">
-		<div class="sales-rank__header">
-			<span>门店销售额排名</span>
-		</div>
-
-		<div class="sales-rank__container">
-			<div class="sales-rank__filter">
-				<ul>
-					<li
-						v-for="(item, index) in options.type"
-						:key="index"
-						:class="{
-							active: item.value == type
-						}"
-						@click="changeDate(item.value)"
-					>
-						{{ item.label }}
-					</li>
-				</ul>
-
-				<el-date-picker v-model="date" type="date" />
-			</div>
-
-			<ul class="sales-rank__list">
-				<li>
-					<span>1</span>
-					<span>北京市朝阳区三里屯路</span>
-					<span>323201</span>
-				</li>
-				<li>
-					<span>2</span>
-					<span>北京市朝阳区建国路-华贸购物中心</span>
-					<span>278442</span>
-				</li>
-				<li>
-					<span>3</span>
-					<span>北京市朝阳区朝阳北路</span>
-					<span>202368</span>
-				</li>
-				<li>
-					<span>4</span>
-					<span>北京市东城区王府井大街</span>
-					<span>156320</span>
-				</li>
-				<li>
-					<span>5</span>
-					<span>北京市西城区西单北大街-大悦城</span>
-					<span>98852</span>
-				</li>
-			</ul>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import dayjs from 'dayjs';
-import { reactive, ref } from 'vue';
-
-// 日期
-const date = ref(dayjs().format('YYYY-MM-DD'));
-
-// 类型
-const type = ref('day');
-
-// 选项
-const options = reactive({
-	type: [
-		{
-			label: '今日',
-			value: 'day'
-		},
-		{
-			label: '本周',
-			value: 'week'
-		},
-		{
-			label: '本月',
-			value: 'month'
-		},
-		{
-			label: '全年',
-			value: 'year'
-		}
-	]
-});
-
-function changeDate(value: string) {
-	type.value = value;
-}
-</script>
-
-<style lang="scss" scoped>
-.sales-rank {
-	&__header {
-		display: flex;
-		align-items: center;
-		height: 50px;
-		font-size: 15px;
-		font-weight: bold;
-		padding: 0 20px;
-	}
-
-	&__container {
-		padding: 0 20px;
-	}
-
-	&__filter {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		height: 40px;
-
-		ul {
-			display: flex;
-			align-items: center;
-			margin-right: 20px;
-			flex: 1;
-			max-width: 220px;
-
-			li {
-				list-style: none;
-				font-size: 14px;
-				cursor: pointer;
-				color: var(--el-color-info);
-				white-space: nowrap;
-				margin-right: 10px;
-				flex: 1;
-
-				&.active {
-					color: #000;
-				}
-
-				&:not(.active):hover {
-					color: #666;
-				}
-			}
-		}
-
-		:deep(.el-date-editor) {
-			width: 150px;
-
-			.el-input__inner {
-				color: #333;
-			}
-		}
-	}
-
-	&__list {
-		height: 260px;
-
-		li {
-			display: flex;
-			align-items: center;
-			height: 51px;
-			list-style: none;
-			font-size: 13px;
-			cursor: pointer;
-
-			span {
-				&:first-child {
-					display: inline-block;
-					height: 14px;
-					width: 14px;
-					border-radius: 14px;
-					text-align: center;
-					line-height: 14px;
-					font-size: 12px;
-				}
-
-				&:nth-child(2) {
-					flex: 1;
-					margin: 0 10px;
-					letter-spacing: 0.5px;
-					color: #222;
-					overflow: hidden;
-					text-overflow: ellipsis;
-					display: -webkit-box;
-					-webkit-box-orient: vertical;
-					-webkit-line-clamp: 1;
-				}
-			}
-
-			&:nth-last-child(n + 3) {
-				span {
-					&:first-child {
-						background-color: #000;
-						color: #fff;
-					}
-				}
-			}
-
-			&:hover {
-				span:nth-child(2) {
-					text-decoration: underline;
-				}
-			}
-		}
-	}
-}
-</style>

+ 0 - 151
src/modules/demo/views/home/components/tab-chart.vue

@@ -1,151 +0,0 @@
-<template>
-	<div class="tab-chart">
-		<div class="tab-chart__header">
-			<ul class="tab-chart__tab">
-				<li class="active">销售额</li>
-				<li>访问量</li>
-			</ul>
-
-			<span class="tab-chart__year">2023</span>
-		</div>
-
-		<div class="tab-chart__container">
-			<v-chart :option="chartOption" autoresize />
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { reactive } from 'vue';
-
-const barWidth = 15;
-
-const chartOption = reactive<any>({
-	grid: {
-		top: '20px',
-		bottom: '30px',
-		right: '10px',
-		containLabel: true
-	},
-	xAxis: {
-		type: 'category',
-		data: [],
-		offset: 5,
-		axisLine: {
-			show: false
-		},
-		axisTick: {
-			show: false
-		}
-	},
-	yAxis: {
-		type: 'value',
-		offset: 20,
-		splitLine: {
-			show: false
-		},
-		axisTick: {
-			show: false
-		},
-		axisLine: {
-			show: false
-		}
-	},
-	tooltip: {
-		trigger: 'axis',
-		formatter: (comp: any) => {
-			const [serie] = comp;
-
-			return `${serie.seriesName}:${serie.value}`;
-		},
-		axisPointer: {
-			show: true,
-			status: 'shadow',
-			z: -1,
-			type: 'shadow'
-		},
-		extraCssText: 'width:120px; white-space:pre-wrap'
-	},
-	series: [
-		{
-			barWidth,
-			name: '付款笔数',
-			type: 'bar',
-			data: [],
-			itemStyle: {
-				color: '#4165d7'
-			}
-		},
-		{
-			type: 'bar',
-			barWidth,
-			xAxisIndex: 0,
-			barGap: '-100%',
-			data: [],
-			itemStyle: {
-				color: '#f1f1f9'
-			},
-			zlevel: -1
-		}
-	]
-});
-
-chartOption.xAxis.data = new Array(12).fill(1).map((e, i) => i + 1 + '月');
-chartOption.series[0].data = new Array(12).fill(1).map(() => parseInt(String(Math.random() * 100)));
-chartOption.series[1].data = new Array(12).fill(100);
-</script>
-
-<style lang="scss" scoped>
-.tab-chart {
-	&__header {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		height: 50px;
-		padding: 0 20px;
-
-		ul {
-			li {
-				list-style: none;
-				float: left;
-				margin-right: 20px;
-				font-size: 15px;
-				color: #dbdbdb;
-				cursor: pointer;
-
-				&.active {
-					color: #000;
-					font-weight: bold;
-				}
-			}
-		}
-	}
-
-	&__year {
-		font-size: 14px;
-		position: relative;
-
-		&::before {
-			display: block;
-			content: '';
-			height: 8px;
-			width: 8px;
-			border-radius: 8px;
-			background-color: #000;
-			position: absolute;
-			left: -15px;
-			top: 4px;
-		}
-	}
-
-	&__container {
-		height: 300px;
-		padding: 0 15px;
-
-		.echarts {
-			height: 100%;
-			width: 100%;
-		}
-	}
-}
-</style>

+ 0 - 122
src/modules/demo/views/home/index.vue

@@ -1,122 +0,0 @@
-<template>
-	<el-scrollbar>
-		<div class="view-home">
-			<el-row :gutter="15">
-				<el-col :lg="6" :md="12" :xs="24">
-					<div class="card">
-						<count-user />
-					</div>
-				</el-col>
-				<el-col :lg="6" :md="12" :xs="24">
-					<div class="card">
-						<count-views />
-					</div>
-				</el-col>
-				<el-col :lg="6" :md="12" :xs="24">
-					<div class="card">
-						<count-paid />
-					</div>
-				</el-col>
-				<el-col :lg="6" :md="12" :xs="24">
-					<div class="card">
-						<count-effect />
-					</div>
-				</el-col>
-			</el-row>
-
-			<el-row :gutter="15">
-				<el-col :lg="14" :xs="24">
-					<div class="card">
-						<tab-chart />
-					</div>
-				</el-col>
-				<el-col :lg="10" :xs="24">
-					<div class="card">
-						<sales-rank />
-					</div>
-				</el-col>
-			</el-row>
-
-			<el-row :gutter="15">
-				<el-col :lg="14" :sm="24">
-					<div class="card card--last">
-						<hot-search />
-					</div>
-				</el-col>
-				<el-col :lg="10" :sm="24">
-					<div class="card card--last">
-						<category-ratio />
-					</div>
-				</el-col>
-			</el-row>
-		</div>
-	</el-scrollbar>
-</template>
-
-<script lang="ts" name="home" setup>
-import CategoryRatio from './components/category-ratio.vue';
-import CountUser from './components/count-user.vue';
-import CountViews from './components/count-views.vue';
-import CountPaid from './components/count-paid.vue';
-import CountEffect from './components/count-effect.vue';
-import TabChart from './components/tab-chart.vue';
-import SalesRank from './components/sales-rank.vue';
-import HotSearch from './components/hot-search.vue';
-</script>
-
-<style lang="scss">
-.view-home {
-	overflow-x: hidden;
-
-	.card {
-		background-color: #fff;
-		border-radius: 6px;
-		margin-bottom: 15px;
-		font-size: 12px;
-		letter-spacing: 0.5px;
-		color: #000;
-		line-height: 1;
-
-		&__header {
-			display: flex;
-			align-items: center;
-			height: 50px;
-			padding: 0 20px;
-
-			.label {
-				font-size: 12px;
-			}
-
-			.value {
-				font-size: 18px;
-				font-weight: bold;
-				margin-left: 10px;
-			}
-		}
-
-		&__container {
-			padding: 0 20px;
-			height: 50px;
-		}
-
-		&__footer {
-			display: flex;
-			align-items: center;
-			height: 50px;
-			border-top: 1px solid #f7f7f7;
-			font-size: 12px;
-			margin: 0 5px;
-			padding: 0 15px;
-			box-sizing: border-box;
-
-			.label {
-				margin-right: 10px;
-			}
-
-			.value {
-				font-size: 13px;
-			}
-		}
-	}
-}
-</style>

+ 0 - 52
src/modules/demo/views/test/route.vue

@@ -1,52 +0,0 @@
-<template>
-	<div class="page">
-		<el-descriptions title="动态路由参数" border :column="1">
-			<el-descriptions-item label="ID">{{ $route.params.id || '-' }}</el-descriptions-item>
-			<el-descriptions-item label="Name">{{ name || '无' }}</el-descriptions-item>
-		</el-descriptions>
-
-		<div class="op">
-			<el-button @click="toLink(1)">链接1</el-button>
-			<el-button @click="toLink(2)">链接2</el-button>
-		</div>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { random } from 'lodash-es';
-import { useCool } from '/@/cool';
-
-const props = defineProps({
-	id: String,
-	name: String
-});
-
-const { router } = useCool();
-
-function toLink(n: number) {
-	switch (n) {
-		case 1:
-			router.push(`/demo/test/route/${random(100)}/神仙都没用`);
-			break;
-
-		case 2:
-			router.push(`/demo/test/route/${random(100)}`);
-			break;
-	}
-}
-</script>
-
-<style lang="scss" scoped>
-.page {
-	background-color: var(--el-bg-color);
-	padding: 10px;
-
-	:deep(.el-descriptions__label) {
-		width: 100px;
-	}
-
-	.op {
-		margin-top: 20px;
-	}
-}
-</style>

+ 3 - 2
src/modules/helper/components/auto-menu/btn.vue

@@ -1,9 +1,10 @@
 <template>
-	<el-badge v-if="!browser.isMini" is-dot>
+	<!-- <el-badge v-if="!browser.isMini" is-dot>
 		<div class="btn" @click="toCode">
 			<span>AI 极速编码</span>
 		</div>
-	</el-badge>
+	</el-badge> -->
+	<div></div>
 </template>
 
 <script lang="ts" name="auto-menu" setup>

+ 159 - 0
src/modules/payment/views/auth.vue

@@ -0,0 +1,159 @@
+<template>
+	<el-scrollbar style="background-color: #fff">
+		<div class="view-home">
+			<div class="title">欢迎来到 Fusion Pay 后台管理系统 {{ merchant?.name }}</div>
+			<div class="subtitle" v-if="!merchant.business && !merchant.individual">
+				您还没有认证信息,请先认证
+				<el-row :gutter="15">
+					<el-col :span="3">
+						<el-button type="primary" @click="openBusiness = true">企业认证</el-button>
+					</el-col>
+					<el-col :span="3">
+						<el-button type="primary" @click="openIndividual = true"
+							>个人认证</el-button
+						>
+					</el-col>
+				</el-row>
+			</div>
+			{{ merchant?.business?.status }}
+			{{ merchant?.individual?.status }}
+			<div
+				class="subtitle"
+				v-if="merchant?.business?.status == 0 || merchant?.individual?.status == 0"
+			>
+				正在审核中,请耐心等待
+			</div>
+			<div
+				class="subtitle"
+				v-if="merchant?.business?.status == -1 || merchant?.individual?.status == -1"
+			>
+				审核未通过,请重新认证 <br />
+				<span style="color: red; font-size: 16px"
+					>{{ merchant?.business?.refused }} {{ merchant?.individual?.refused }}</span
+				>
+				<el-row :gutter="15">
+					<el-col :span="3">
+						<el-button type="primary" @click="openBusiness = true">企业认证</el-button>
+					</el-col>
+					<el-col :span="3">
+						<el-button type="primary" @click="openIndividual = true"
+							>个人认证</el-button
+						>
+					</el-col>
+				</el-row>
+			</div>
+			<business
+				:mch-id="merchant.id"
+				:business="merchant.business"
+				@close="openBusiness = false"
+				@submit="getUser"
+				ref="business"
+				:open="openBusiness"
+			/>
+			<individual
+				:mch-id="merchant.id"
+				:individual="merchant.individual"
+				@close="openIndividual = false"
+				@submit="getUser"
+				ref="individual"
+				:open="openIndividual"
+			/>
+		</div>
+	</el-scrollbar>
+</template>
+
+<script lang="ts" name="home" setup>
+import Business from './components/business.vue';
+import Individual from './components/individual.vue';
+import { onMounted, ref } from 'vue';
+import { useUserStore } from '/@/modules/base/store/user';
+const userInfo = ref({});
+const user = useUserStore();
+const merchant = ref({} as any);
+const business = ref(null);
+const individual = ref(null);
+const getUser = () => {
+	openBusiness.value = false;
+	openIndividual.value = false;
+	user.get().then(res => {
+		userInfo.value = res;
+		merchant.value = res.merchant;
+	});
+};
+onMounted(() => {
+	getUser();
+});
+const openBusiness = ref(false);
+const openIndividual = ref(false);
+</script>
+
+<style lang="scss">
+.view-home {
+	overflow-x: hidden;
+
+	.card {
+		background-color: #fff;
+		border-radius: 6px;
+		margin-bottom: 15px;
+		font-size: 12px;
+		letter-spacing: 0.5px;
+		color: #000;
+		line-height: 1;
+
+		&__header {
+			display: flex;
+			align-items: center;
+			height: 50px;
+			padding: 0 20px;
+
+			.label {
+				font-size: 12px;
+			}
+
+			.value {
+				font-size: 18px;
+				font-weight: bold;
+				margin-left: 10px;
+			}
+		}
+
+		&__container {
+			padding: 0 20px;
+			height: 50px;
+		}
+
+		&__footer {
+			display: flex;
+			align-items: center;
+			height: 50px;
+			border-top: 1px solid #f7f7f7;
+			font-size: 12px;
+			margin: 0 5px;
+			padding: 0 15px;
+			box-sizing: border-box;
+
+			.label {
+				margin-right: 10px;
+			}
+
+			.value {
+				font-size: 13px;
+			}
+		}
+	}
+	.title {
+		margin-top: 15px;
+		margin-left: 15px;
+		font-size: 40px;
+		font-weight: bold;
+	}
+	.subtitle {
+		margin-top: 15px;
+		margin-left: 15px;
+
+		color: #666;
+		font-size: 40px;
+		font-weight: bold;
+	}
+}
+</style>

+ 236 - 0
src/modules/payment/views/business.vue

@@ -0,0 +1,236 @@
+<template>
+	<cl-crud ref="Crud">
+		<cl-row>
+			<!-- 刷新按钮 -->
+			<cl-refresh-btn />
+			<!-- 新增按钮 -->
+			<cl-add-btn />
+			<!-- 删除按钮 -->
+			<cl-multi-delete-btn />
+
+			<cl-flex1 />
+			<!-- 关键字搜索 -->
+			<cl-search-key placeholder="搜索关键字" />
+		</cl-row>
+
+		<cl-row>
+			<!-- 数据表格 -->
+			<cl-table ref="Table" />
+		</cl-row>
+
+		<cl-row>
+			<cl-flex1 />
+			<!-- 分页控件 -->
+			<cl-pagination />
+		</cl-row>
+
+		<!-- 新增、编辑 -->
+		<cl-upsert ref="Upsert" />
+	</cl-crud>
+</template>
+
+<script lang="ts" name="payment-business" setup>
+import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
+import { useCool } from '/@/cool';
+
+const { service } = useCool();
+
+// cl-upsert
+const Upsert = useUpsert({
+	items: [
+		{
+			label: '商户ID',
+			prop: 'mchId',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '企业名称',
+			prop: 'name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '类型',
+			prop: 'type',
+			component: {
+				name: 'el-select',
+				options: [
+					{ label: '其他', value: 'OTHER' },
+					{ label: '公共有限公司', value: 'PUBLIC_LIMITED_COMPANY' },
+					{ label: '合伙企业', value: 'PARTNERSHIP' },
+					{ label: '慈善机构', value: 'CHARITY' },
+					{ label: '私营有限公司', value: 'PRIVATE_LIMITED_COMPANY' },
+					{ label: '股份公司', value: 'JOINT_STOCK_COMPANY' },
+					{ label: '独资经营企业', value: 'SOLE_TRADER' },
+					{ label: '有限责任公司', value: 'LIMITED_LIABILITY' }
+				],
+				props: {}
+			},
+			value: [],
+			required: true
+		},
+		{
+			label: '国家代码',
+			prop: 'country_code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '地址',
+			prop: 'address_line',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } },
+			required: true
+		},
+		{
+			label: '邮编',
+			prop: 'post_code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '邮箱',
+			prop: 'email',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '电话',
+			prop: 'phone_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '注册号',
+			prop: 'registration_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '联系人',
+			prop: 'contact',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '注册日期',
+			prop: 'registration_date',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'date', valueFormat: 'YYYY-MM-DD' }
+			},
+			required: true
+		},
+		{
+			label: '合作伙伴文件编号',
+			prop: 'partners_document_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴文件类型',
+			prop: 'partners_document_type',
+			component: {
+				name: 'el-select',
+				options: [
+					{ label: '护照', value: 'PASSPORT' },
+					{ label: '驾驶执照', value: 'DRIVINGLICENCE' },
+					{ label: '身份证', value: 'IDCARD' }
+				],
+				props: {}
+			},
+			value: [],
+			required: true
+		},
+		{
+			label: '合作伙伴类型',
+			prop: 'partners_kind',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴名称',
+			prop: 'partners_name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴百分比',
+			prop: 'partners_percentage_share',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴地址国家',
+			prop: 'partners_address_country',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '交易地址',
+			prop: 'trading_address',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } },
+			required: true
+		},
+		{
+			label: '交易城市',
+			prop: 'trading_city',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '交易国家',
+			prop: 'trading_country',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{ label: 'ID', prop: 'merchant', hook: 'number', component: { name: 'el-input-number' } }
+	]
+});
+
+// cl-table
+const Table = useTable({
+	columns: [
+		{ type: 'selection' },
+		{ label: '商户ID', prop: 'mchId', minWidth: 140 },
+		{ label: '企业名称', prop: 'name', minWidth: 140 },
+		{ label: '类型', prop: 'type', dict: [], minWidth: 120 },
+		{ label: '国家代码', prop: 'country_code', minWidth: 140 },
+		{ label: '地址', prop: 'address_line', showOverflowTooltip: true, minWidth: 200 },
+		{ label: '邮编', prop: 'post_code', minWidth: 140 },
+		{ label: '邮箱', prop: 'email', minWidth: 140 },
+		{ label: '电话', prop: 'phone_number', minWidth: 140 },
+		{ label: '注册号', prop: 'registration_number', minWidth: 140 },
+		{
+			label: '创建时间',
+			prop: 'createTime',
+			minWidth: 170,
+			sortable: 'desc',
+			component: { name: 'cl-date-text' }
+		},
+		{
+			label: '更新时间',
+			prop: 'updateTime',
+			minWidth: 170,
+			sortable: 'custom',
+			component: { name: 'cl-date-text' }
+		},
+		{ type: 'op', buttons: ['edit', 'delete'] }
+	]
+});
+
+// cl-crud
+const Crud = useCrud(
+	{
+		service: service.payment.business
+	},
+	app => {
+		app.refresh();
+	}
+);
+
+// 刷新
+function refresh(params?: any) {
+	Crud.value?.refresh(params);
+}
+</script>

+ 135 - 0
src/modules/payment/views/channel.vue

@@ -0,0 +1,135 @@
+<template>
+	<cl-crud ref="Crud">
+		<cl-row>
+			<!-- 刷新按钮 -->
+			<cl-refresh-btn />
+			<!-- 新增按钮 -->
+			<cl-add-btn />
+			<!-- 删除按钮 -->
+			<cl-multi-delete-btn />
+
+			<cl-flex1 />
+			<!-- 关键字搜索 -->
+			<cl-search-key placeholder="搜索关键字" />
+		</cl-row>
+
+		<cl-row>
+			<!-- 数据表格 -->
+			<cl-table ref="Table"> </cl-table>
+		</cl-row>
+
+		<cl-row>
+			<cl-flex1 />
+			<!-- 分页控件 -->
+			<cl-pagination />
+		</cl-row>
+
+		<!-- 新增、编辑 -->
+		<cl-upsert ref="Upsert" />
+	</cl-crud>
+</template>
+
+<script lang="ts" name="payment-channel" setup>
+import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
+import { useCool } from '/@/cool';
+import { ref } from 'vue';
+
+const { service } = useCool();
+const currencyList = ref<any[]>([]);
+
+// cl-upsert
+const Upsert = useUpsert({
+	items: [
+		{
+			label: '渠道名称',
+			prop: 'name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '渠道代码',
+			prop: 'code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '渠道描述',
+			prop: 'description',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } }
+		},
+		{
+			label: 'key',
+			prop: 'apiKey',
+			component: { name: 'el-input', props: { clearable: true } }
+		},
+		{
+			label: 'secret',
+			prop: 'apiSecret',
+			component: { name: 'el-input', props: { clearable: true } }
+		},
+		{
+			label: 'url',
+			prop: 'apiUrl',
+			component: { name: 'el-input', props: { clearable: true } }
+		},
+
+		{
+			label: '是否启用',
+			prop: 'isEnabled',
+			flex: false,
+			component: { name: 'cl-switch' },
+			required: true
+		},
+		{
+			label: '支持的货币',
+			prop: 'supportedCurrencies',
+			hook: {
+				bind: ['split', 'number']
+			},
+			component: {
+				name: 'el-select',
+
+				props: { multiple: true, options: currencyList }
+			},
+			required: true
+		}
+	],
+	async onOpen() {
+		const currencyList = await service.payment.currency.list();
+		Upsert.value?.setOptions(
+			'supportedCurrencies',
+			currencyList.map(v => ({ label: v.name, value: v.id }))
+		);
+		// Upsert.value?.setForm(
+		// 	'supportedCurrencies',
+		// 	data && data.length > 0 ? data.currencies.split(',') : []
+		// );
+	}
+});
+// cl-table
+const Table = useTable({
+	columns: [
+		{ type: 'selection' },
+		{ label: '渠道名称', prop: 'name' },
+		{ label: '渠道代码', prop: 'code' },
+		{ label: '渠道描述', prop: 'description', showOverflowTooltip: true },
+		{ label: '是否启用', prop: 'isEnabled', component: { name: 'cl-switch' } },
+		{ type: 'op', buttons: ['edit', 'delete'] }
+	]
+});
+
+// cl-crud
+const Crud = useCrud(
+	{
+		service: service.payment.channel
+	},
+	app => {
+		app.refresh();
+	}
+);
+
+// 刷新
+function refresh(params?: any) {
+	Crud.value?.refresh(params);
+}
+</script>

+ 224 - 0
src/modules/payment/views/components/business.vue

@@ -0,0 +1,224 @@
+<template>
+	<!-- 新增、编辑 -->
+	<cl-crud ref="Crud">
+		<cl-upsert ref="Upsert" />
+	</cl-crud>
+</template>
+
+<script lang="ts" setup>
+import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
+import { useCool } from '/@/cool';
+import { watch } from 'vue';
+import { cityList } from '/@/cool/utils/city';
+const { service } = useCool();
+const emit = defineEmits(['close', 'submit']);
+const props = defineProps({
+	open: {
+		type: Boolean,
+		default: false
+	},
+	mchId: {
+		type: String,
+		default: ''
+	},
+	business: {
+		type: Object,
+		default: null
+	}
+});
+watch(
+	() => props.open,
+	() => {
+		if (props.open) {
+			if (props.business) {
+				Upsert.value?.edit(props.business);
+			} else {
+				Upsert.value?.add();
+			}
+		}
+	}
+);
+// cl-upsert
+const Upsert = useUpsert({
+	items: [
+		{
+			label: '企业名称',
+			prop: 'name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '类型',
+			prop: 'type',
+			component: {
+				name: 'el-select',
+				options: [
+					{ label: '其他', value: 'OTHER' },
+					{ label: '公共有限公司', value: 'PUBLIC_LIMITED_COMPANY' },
+					{ label: '合伙企业', value: 'PARTNERSHIP' },
+					{ label: '慈善机构', value: 'CHARITY' },
+					{ label: '私营有限公司', value: 'PRIVATE_LIMITED_COMPANY' },
+					{ label: '股份公司', value: 'JOINT_STOCK_COMPANY' },
+					{ label: '独资经营企业', value: 'SOLE_TRADER' },
+					{ label: '有限责任公司', value: 'LIMITED_LIABILITY' }
+				],
+				props: {}
+			},
+			value: [],
+			required: true
+		},
+		{
+			label: '国家代码',
+			prop: 'country_code',
+			component: {
+				name: 'el-select',
+				options: cityList,
+				props: { clearable: true }
+			},
+			required: true
+		},
+		{
+			label: '城市',
+			prop: 'city',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '地址',
+			prop: 'address_line',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } },
+			required: true
+		},
+		{
+			label: '邮编',
+			prop: 'post_code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '邮箱',
+			prop: 'email',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '电话',
+			prop: 'phone_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '注册号',
+			prop: 'registration_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '联系人',
+			prop: 'contact',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '注册日期',
+			prop: 'registration_date',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'date', valueFormat: 'YYYY-MM-DD' }
+			},
+			required: true
+		},
+		{
+			label: '合作伙伴文件编号',
+			prop: 'partners_document_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴文件类型',
+			prop: 'partners_document_type',
+			component: {
+				name: 'el-select',
+				options: [
+					{ label: '护照', value: 'PASSPORT' },
+					{ label: '驾驶执照', value: 'DRIVINGLICENCE' },
+					{ label: '身份证', value: 'IDCARD' }
+				],
+				props: {}
+			},
+			value: [],
+			required: true
+		},
+		{
+			label: '合作伙伴类型',
+			prop: 'partners_kind',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴名称',
+			prop: 'partners_name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴百分比',
+			prop: 'partners_percentage_share',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '合作伙伴地址国家',
+			prop: 'partners_address_country',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '交易地址',
+			prop: 'trading_address',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } },
+			required: true
+		},
+		{
+			label: '交易城市',
+			prop: 'trading_city',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '交易国家',
+			prop: 'trading_country',
+			component: {
+				name: 'el-select',
+				options: cityList,
+				props: { clearable: true }
+			},
+			required: true
+		}
+	],
+	dialog: {
+		title: '企业认证'
+	},
+	onClose: (data, done) => {
+		emit('close', false);
+		done();
+	},
+	async onSubmit(data, { done, close, next }) {
+		emit('submit');
+		next({
+			...data,
+			merchantId: props.mchId
+		});
+	}
+});
+
+// cl-crud
+const Crud = useCrud(
+	{
+		service: service.payment.business
+	},
+	app => {
+		app.refresh();
+	}
+);
+</script>

+ 183 - 0
src/modules/payment/views/components/individual.vue

@@ -0,0 +1,183 @@
+<template>
+	<cl-crud ref="Crud">
+		<cl-upsert ref="Upsert" />
+	</cl-crud>
+</template>
+
+<script lang="ts" setup>
+import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
+import { useCool } from '/@/cool';
+import { watch } from 'vue';
+import { cityList } from '/@/cool/utils/city';
+const emit = defineEmits(['close', 'submit']);
+const props = defineProps({
+	open: {
+		type: Boolean,
+		default: false
+	},
+	mchId: {
+		type: String,
+		default: ''
+	},
+	individual: {
+		type: Object,
+		default: null
+	}
+});
+watch(
+	() => props.open,
+	() => {
+		if (props.open) {
+			if (props.individual) {
+				Upsert.value?.edit(props.individual);
+			} else {
+				Upsert.value?.add();
+			}
+		}
+	}
+);
+const { service } = useCool();
+
+// cl-upsert
+const Upsert = useUpsert({
+	items: [
+		{
+			label: '名字',
+			prop: 'first_name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '姓氏',
+			prop: 'last_name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '国家代码',
+			prop: 'country_code',
+			component: { name: 'el-select', options: cityList, props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '城市',
+			prop: 'city',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '地址',
+			prop: 'address_line',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } },
+			required: true
+		},
+		{
+			label: '邮编',
+			prop: 'post_code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '邮箱',
+			prop: 'email',
+			component: {
+				name: 'el-input',
+				props: {
+					clearable: true
+				}
+			},
+			rules: [
+				{
+					type: 'email',
+					message: '请输入正确的邮箱格式',
+					trigger: ['blur', 'change']
+				}
+			],
+			required: true
+		},
+		{
+			label: '电话',
+			prop: 'phone_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '证书类型',
+			prop: 'certificate_type',
+			component: {
+				name: 'el-select',
+				options: [
+					{ label: '身份证', value: 'IDCARD' }, // 身份证
+					{ label: '驾驶证', value: 'DRIVINGLICENCE' }, // 驾驶证
+					{ label: '护照', value: 'PASSPORT' }, // 护照
+					{ label: '税务识别号', value: 'TaxIdentificationNumber' }, // 税务识别号
+					{ label: '劳务识别码', value: 'IdentificationCodeForWorkers' }, // 劳务识别码
+					{ label: '身份识别编号', value: 'PersonalIdentificationCode' }, // 身份识别编号
+					{ label: '外国人身份证', value: 'ForeignersIDCard' }, // 外国人身份证
+					{ label: '特殊的临时居留许可', value: 'SpecialPermitOfStay' } // 特殊的临时居留许可
+				],
+				props: {}
+			},
+			value: [],
+			required: true
+		},
+		{
+			label: '身份证号',
+			prop: 'id_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '出生日期',
+			prop: 'birth_date',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'date', valueFormat: 'YYYY-MM-DD' }
+			},
+			required: true
+		},
+		{
+			label: '有效期开始',
+			prop: 'valid_time_start',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'datetime', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+			},
+			required: true
+		},
+		{
+			label: '有效期结束',
+			prop: 'valid_time_end',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'datetime', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+			},
+			required: true
+		}
+	],
+	dialog: {
+		title: '个人认证'
+	},
+	onClose: (data, done) => {
+		emit('close', false);
+		done();
+	},
+	async onSubmit(data, { done, close, next }) {
+		emit('submit');
+		next({
+			...data,
+			merchantId: props.mchId
+		});
+	}
+});
+
+// cl-crud
+const Crud = useCrud(
+	{
+		service: service.payment.individual
+	},
+	app => {
+		app.refresh();
+	}
+);
+</script>

+ 83 - 0
src/modules/payment/views/components/transfer.vue

@@ -0,0 +1,83 @@
+<template>
+	<div>
+		<el-dialog v-model="show" title="划转" width="500px">
+			<el-form :model="form" label-width="100px">
+				<el-form-item label="划转金额">
+					<el-input v-model="form.amount" />
+				</el-form-item>
+				<el-form-item label="划转钱包">
+					<el-select v-model="form.to_wallet_id">
+						<el-option
+							v-for="item in payAddressList"
+							:key="item.id"
+							:label="item.channel + '-' + item.currency"
+							:value="item.id"
+						/>
+					</el-select>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" :loading="loading" @click="onSubmit">提交</el-button>
+					<el-button @click="show = false">取消</el-button>
+				</el-form-item>
+			</el-form>
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref, watchEffect, defineModel, defineEmits } from 'vue';
+import { useCool } from '/@/cool';
+import { ElMessage } from 'element-plus';
+const { service } = useCool();
+const show = defineModel('show', { type: Boolean });
+const emit = defineEmits(['refresh']);
+const loading = ref(false);
+const props = defineProps({
+	fromWalletId: {
+		type: String,
+		default: ''
+	},
+	channel: {
+		type: String,
+		default: ''
+	}
+});
+const form = ref({
+	amount: '',
+	from_wallet_id: '',
+	to_wallet_id: ''
+});
+const payAddressList: any = ref([]);
+watchEffect(() => {
+	if (show.value) {
+		service.payment.wallet
+			.list({
+				channel: props.channel
+			})
+			.then((res: any) => {
+				// 过滤掉当前钱包
+				payAddressList.value = res.filter((item: any) => item.id !== props.fromWalletId);
+			});
+	}
+});
+const onSubmit = () => {
+	console.log(form.value);
+	loading.value = true;
+	service.payment.order
+		.transfer({
+			...form.value,
+			from_wallet_id: props.fromWalletId
+		})
+		.then((res: any) => {
+			ElMessage.success('划转提交成功');
+			emit('refresh');
+			show.value = false;
+		})
+		.catch((err: any) => {
+			ElMessage.error(err.message);
+		})
+		.finally(() => {
+			loading.value = false;
+		});
+};
+</script>

+ 85 - 0
src/modules/payment/views/components/withdraw.vue

@@ -0,0 +1,85 @@
+<template>
+	<div>
+		<el-dialog v-model="show" title="提现" width="500px">
+			<el-form :model="form" label-width="100px">
+				<el-form-item label="提现金额">
+					<el-input v-model="form.amount" />
+				</el-form-item>
+				<el-form-item label="收款账户">
+					<el-select v-model="form.payee_address_id">
+						<el-option
+							v-for="item in payAddressList"
+							:key="item.id"
+							:label="item.address"
+							:value="item.id"
+						/>
+					</el-select>
+				</el-form-item>
+				<el-form-item>
+					<el-button :loading="loading" type="primary" @click="onSubmit">提交</el-button>
+					<el-button @click="show = false">取消</el-button>
+				</el-form-item>
+			</el-form>
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref, watchEffect, defineModel, defineEmits } from 'vue';
+import { useCool } from '/@/cool';
+import { ElMessage } from 'element-plus';
+const { service } = useCool();
+const emit = defineEmits(['refresh']);
+const show = defineModel('show', { type: Boolean });
+const loading = ref(false);
+const props = defineProps({
+	currency: {
+		type: String,
+		default: ''
+	},
+	channel: {
+		type: String,
+		default: ''
+	}
+});
+const form = ref({
+	amount: '',
+	payee_address_id: '',
+	channel: props.channel,
+	currency: props.currency
+});
+const payAddressList: any = ref([]);
+watchEffect(() => {
+	if (show.value) {
+		service.payment.payee_address
+			.list({
+				channel: props.channel,
+				currency: props.currency
+			})
+			.then((res: any) => {
+				payAddressList.value = res;
+			});
+	}
+});
+const onSubmit = () => {
+	console.log(form.value);
+	loading.value = true;
+	service.payment.order
+		.withdraw({
+			...form.value,
+			currency: props.currency,
+			channel: props.channel
+		})
+		.then((res: any) => {
+			ElMessage.success('提现提交成功');
+			emit('refresh');
+			show.value = false;
+		})
+		.catch((err: any) => {
+			ElMessage.error(err.message);
+		})
+		.finally(() => {
+			loading.value = false;
+		});
+};
+</script>

+ 101 - 0
src/modules/payment/views/currency.vue

@@ -0,0 +1,101 @@
+<template>
+	<cl-crud ref="Crud">
+		<cl-row>
+			<!-- 刷新按钮 -->
+			<cl-refresh-btn />
+			<!-- 新增按钮 -->
+			<cl-add-btn />
+			<!-- 删除按钮 -->
+			<cl-multi-delete-btn />
+
+			<cl-flex1 />
+			<!-- 关键字搜索 -->
+			<cl-search-key placeholder="搜索关键字" />
+		</cl-row>
+
+		<cl-row>
+			<!-- 数据表格 -->
+			<cl-table ref="Table" />
+		</cl-row>
+
+		<cl-row>
+			<cl-flex1 />
+			<!-- 分页控件 -->
+			<cl-pagination />
+		</cl-row>
+
+		<!-- 新增、编辑 -->
+		<cl-upsert ref="Upsert" />
+	</cl-crud>
+</template>
+
+<script lang="ts" name="payment-currency" setup>
+import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
+import { useCool } from '/@/cool';
+
+const { service } = useCool();
+
+// cl-upsert
+const Upsert = useUpsert({
+	items: [
+		{
+			label: '货币名称',
+			prop: 'name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '货币代码',
+			prop: 'code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '货币符号',
+			prop: 'symbol',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '获取类型',
+			prop: 'type',
+			component: {
+				name: 'el-select',
+				options: [
+					{ label: '法币', value: 1 },
+					{ label: '数字货币', value: 2 }
+				],
+				props: {}
+			},
+			value: [],
+			required: true
+		}
+	]
+});
+
+// cl-table
+const Table = useTable({
+	columns: [
+		{ type: 'selection' },
+		{ label: '货币名称', prop: 'name', minWidth: 140 },
+		{ label: '货币代码', prop: 'code', minWidth: 140 },
+		{ label: '货币符号', prop: 'symbol', minWidth: 140 },
+		{ type: 'op', buttons: ['edit', 'delete'] }
+	]
+});
+
+// cl-crud
+const Crud = useCrud(
+	{
+		service: service.payment.currency
+	},
+	app => {
+		app.refresh();
+	}
+);
+
+// 刷新
+function refresh(params?: any) {
+	Crud.value?.refresh(params);
+}
+</script>

+ 172 - 0
src/modules/payment/views/individual.vue

@@ -0,0 +1,172 @@
+<template>
+	<cl-crud ref="Crud">
+		<cl-row>
+			<!-- 刷新按钮 -->
+			<cl-refresh-btn />
+			<!-- 新增按钮 -->
+			<cl-add-btn />
+			<!-- 删除按钮 -->
+			<cl-multi-delete-btn />
+
+			<cl-flex1 />
+			<!-- 关键字搜索 -->
+			<cl-search-key placeholder="搜索关键字" />
+		</cl-row>
+
+		<cl-row>
+			<!-- 数据表格 -->
+			<cl-table ref="Table" />
+		</cl-row>
+
+		<cl-row>
+			<cl-flex1 />
+			<!-- 分页控件 -->
+			<cl-pagination />
+		</cl-row>
+
+		<!-- 新增、编辑 -->
+		<cl-upsert ref="Upsert" />
+	</cl-crud>
+</template>
+
+<script lang="ts" name="payment-individual" setup>
+import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
+import { useCool } from '/@/cool';
+
+const { service } = useCool();
+
+// cl-upsert
+const Upsert = useUpsert({
+	items: [
+		{
+			label: '商户ID',
+			prop: 'mch_id',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '名字',
+			prop: 'first_name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '姓氏',
+			prop: 'last_name',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '国家代码',
+			prop: 'country_code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '地址',
+			prop: 'address_line',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } },
+			required: true
+		},
+		{
+			label: '邮编',
+			prop: 'post_code',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '邮箱',
+			prop: 'email',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '电话',
+			prop: 'phone_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '证书类型',
+			prop: 'certificate_type',
+			component: { name: 'el-checkbox-group', options: [], props: {} },
+			value: [],
+			required: true
+		},
+		{
+			label: '身份证号',
+			prop: 'id_number',
+			component: { name: 'el-input', props: { clearable: true } },
+			required: true
+		},
+		{
+			label: '出生日期',
+			prop: 'birth_date',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'date', valueFormat: 'YYYY-MM-DD' }
+			},
+			required: true
+		},
+		{
+			label: '有效期开始',
+			prop: 'valid_time_start',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'datetime', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+			},
+			required: true
+		},
+		{
+			label: '有效期结束',
+			prop: 'valid_time_end',
+			component: {
+				name: 'el-date-picker',
+				props: { type: 'datetime', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+			},
+			required: true
+		},
+		{ label: 'ID', prop: 'merchant', hook: 'number', component: { name: 'el-input-number' } }
+	]
+});
+
+// cl-table
+const Table = useTable({
+	columns: [
+		{ type: 'selection' },
+		{ label: '商户ID', prop: 'mch_id', minWidth: 140 },
+		{ label: '名字', prop: 'first_name', minWidth: 140 },
+		{ label: '姓氏', prop: 'last_name', minWidth: 140 },
+		{
+			label: '创建时间',
+			prop: 'createTime',
+			minWidth: 170,
+			sortable: 'desc',
+			component: { name: 'cl-date-text' }
+		},
+		{
+			label: '更新时间',
+			prop: 'updateTime',
+			minWidth: 170,
+			sortable: 'custom',
+			component: { name: 'cl-date-text' }
+		},
+		{ type: 'op', buttons: ['edit', 'delete'] }
+	]
+});
+
+// cl-crud
+const Crud = useCrud(
+	{
+		service: service.payment.individual
+	},
+	app => {
+		app.refresh();
+	}
+);
+
+// 刷新
+function refresh(params?: any) {
+	Crud.value?.refresh(params);
+}
+</script>

+ 148 - 0
src/modules/payment/views/merchant.vue

@@ -0,0 +1,148 @@
+<template>
+	<cl-crud ref="Crud">
+		<cl-row>
+			<!-- 刷新按钮 -->
+			<cl-refresh-btn />
+			<!-- 新增按钮 -->
+			<cl-add-btn />
+			<!-- 删除按钮 -->
+			<cl-multi-delete-btn />
+			<cl-flex1 />
+			<!-- 关键字搜索 -->
+			<cl-search-key placeholder="请输入商户号模糊搜索" />
+		</cl-row>
+
+		<cl-row>
+			<!-- 数据表格 -->
+			<cl-table ref="Table" :contextMenu="[]"> </cl-table>
+		</cl-row>
+
+		<cl-row>
+			<cl-flex1 />
+			<!-- 分页控件 -->
+			<cl-pagination />
+		</cl-row>
+		<cl-upsert ref="Upsert"> </cl-upsert>
+	</cl-crud>
+</template>
+
+<script lang="ts" name="pay-merchant" setup>
+import { useCrud, useTable, useUpsert, useForm } from '@cool-vue/crud';
+import { useCool } from '/@/cool';
+import md5 from 'md5';
+import { ref } from 'vue';
+const { service } = useCool();
+
+// cl-upsert
+const Upsert = useUpsert({
+	dialog: {
+		width: '800px'
+	},
+	props: {
+		labelWidth: 120
+	},
+	items: [
+		{
+			prop: 'name',
+			label: '商户名',
+			required: true,
+			component: { name: 'el-input' },
+			span: 12
+		},
+		() => {
+			return {
+				prop: 'mchId',
+				label: '商户号',
+				required: true,
+				component: { name: 'el-input', props: { disabled: Upsert.value?.mode !== 'add' } },
+				span: 12
+			};
+		},
+		{
+			prop: 'email',
+			label: '邮箱',
+			required: true,
+			component: { name: 'el-input' },
+			span: 12
+		},
+		() => {
+			return {
+				prop: 'password',
+				label: '登录密码',
+				span: 12,
+				required: Upsert.value?.mode == 'add',
+				component: {
+					name: 'el-input',
+					props: {
+						type: 'password'
+					}
+				},
+				rules: [
+					{
+						min: 6,
+						max: 16,
+						message: '密码长度在 6 到 16 个字符'
+					}
+				]
+			};
+		},
+
+		{
+			prop: 'status',
+			label: '状态',
+			value: 1,
+			component: {
+				name: 'el-radio-group',
+				options: [
+					{
+						label: '开启',
+						value: 1
+					},
+					{
+						label: '关闭',
+						value: 0
+					}
+				]
+			}
+		},
+
+		{
+			prop: 'remark',
+			label: '备注',
+			component: { name: 'el-input', props: { type: 'textarea', rows: 4 } }
+		}
+	]
+});
+
+// cl-table
+const Table = useTable({
+	autoHeight: false,
+	columns: [
+		{ type: 'selection' },
+		{ prop: 'name', label: '商户名' },
+		{ prop: 'mchId', label: '商户号' },
+		{ prop: 'email', label: '邮箱' },
+		{
+			prop: 'status',
+			label: '状态',
+			minWidth: 120,
+			component: {
+				name: 'cl-switch'
+			}
+		},
+		{ prop: 'remark', label: '备注', showOverflowTooltip: true },
+		{ prop: 'createTime', label: '创建时间', sortable: 'desc', width: 160 },
+		{ type: 'op', width: 300, buttons: ['edit', 'delete'] }
+	]
+});
+
+// cl-crud
+const Crud = useCrud(
+	{
+		service: service.payment.merchant
+	},
+	app => {
+		app.refresh();
+	}
+);
+</script>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio