Compare commits
12 Commits
01baba4c8b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f5c10bf892 | |||
| 74aaa8f806 | |||
| e63494061b | |||
| 5df064e80a | |||
| 4d723758df | |||
| cef8121008 | |||
| 70111ce18b | |||
| bc9dd34425 | |||
| 8e2f49b243 | |||
| d885beb56d | |||
| 54b2d27b81 | |||
| 4e0a32701e |
@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@marsio/vue-split-pane": "^1.0.0",
|
||||||
"less": "^4.3.0",
|
"less": "^4.3.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"tdesign-vue-next": "^1.14.2",
|
"tdesign-vue-next": "^1.14.2",
|
||||||
@ -33,8 +34,8 @@
|
|||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-promise": "^7.2.1",
|
"eslint-plugin-promise": "^7.2.1",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"prettier": "^3.5.2",
|
|
||||||
"less": "^4.3.0",
|
"less": "^4.3.0",
|
||||||
|
"prettier": "^3.5.2",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"unplugin-auto-import": "^19.3.0",
|
"unplugin-auto-import": "^19.3.0",
|
||||||
"unplugin-vue-components": "^28.7.0",
|
"unplugin-vue-components": "^28.7.0",
|
||||||
|
|||||||
168
pnpm-lock.yaml
generated
168
pnpm-lock.yaml
generated
@ -8,6 +8,9 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@marsio/vue-split-pane':
|
||||||
|
specifier: ^1.0.0
|
||||||
|
version: 1.0.0(typescript@5.8.3)
|
||||||
less:
|
less:
|
||||||
specifier: ^4.3.0
|
specifier: ^4.3.0
|
||||||
version: 4.3.0
|
version: 4.3.0
|
||||||
@ -667,6 +670,36 @@ packages:
|
|||||||
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
'@emotion/babel-plugin@11.13.5':
|
||||||
|
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
|
||||||
|
|
||||||
|
'@emotion/cache@11.14.0':
|
||||||
|
resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
|
||||||
|
|
||||||
|
'@emotion/css@11.13.5':
|
||||||
|
resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==}
|
||||||
|
|
||||||
|
'@emotion/hash@0.9.2':
|
||||||
|
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
|
||||||
|
|
||||||
|
'@emotion/memoize@0.9.0':
|
||||||
|
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
|
||||||
|
|
||||||
|
'@emotion/serialize@1.3.3':
|
||||||
|
resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
|
||||||
|
|
||||||
|
'@emotion/sheet@1.4.0':
|
||||||
|
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
|
||||||
|
|
||||||
|
'@emotion/unitless@0.10.0':
|
||||||
|
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
|
||||||
|
|
||||||
|
'@emotion/utils@1.4.2':
|
||||||
|
resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
|
||||||
|
|
||||||
|
'@emotion/weak-memoize@0.4.0':
|
||||||
|
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.5':
|
'@esbuild/aix-ppc64@0.25.5':
|
||||||
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -878,6 +911,12 @@ packages:
|
|||||||
'@leichtgewicht/ip-codec@2.0.5':
|
'@leichtgewicht/ip-codec@2.0.5':
|
||||||
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
|
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
|
||||||
|
|
||||||
|
'@marsio/vue-draggable@1.0.9':
|
||||||
|
resolution: {integrity: sha512-gjj4cgFd9uQUdTMU3mbNKarASb4UAPH4D9DL4WicsjLtCjAXbthpls0GtfMLuPlfmFAhBjhQAzKMhpGDIEa9VA==}
|
||||||
|
|
||||||
|
'@marsio/vue-split-pane@1.0.0':
|
||||||
|
resolution: {integrity: sha512-7XEaVDjVhnoYSVcbgPvONT/U3F6n/oKbcNaj0i+lE2EfP2P38kIKyhHbSgbSGx+8i/DAP5vQtcjLIcleWb4N+A==}
|
||||||
|
|
||||||
'@microsoft/api-extractor-model@7.30.6':
|
'@microsoft/api-extractor-model@7.30.6':
|
||||||
resolution: {integrity: sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==}
|
resolution: {integrity: sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==}
|
||||||
|
|
||||||
@ -1699,6 +1738,10 @@ packages:
|
|||||||
babel-plugin-dynamic-import-node@2.3.3:
|
babel-plugin-dynamic-import-node@2.3.3:
|
||||||
resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==}
|
resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==}
|
||||||
|
|
||||||
|
babel-plugin-macros@3.1.0:
|
||||||
|
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
|
||||||
|
engines: {node: '>=10', npm: '>=6'}
|
||||||
|
|
||||||
babel-plugin-polyfill-corejs2@0.4.12:
|
babel-plugin-polyfill-corejs2@0.4.12:
|
||||||
resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==}
|
resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1880,6 +1923,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
|
clsx@1.2.1:
|
||||||
|
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
color-convert@1.9.3:
|
color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
|
|
||||||
@ -2111,6 +2158,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
convert-source-map@1.9.0:
|
||||||
|
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
@ -2154,6 +2204,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.0.9
|
postcss: ^8.0.9
|
||||||
|
|
||||||
|
css-in-js-utils@3.1.0:
|
||||||
|
resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==}
|
||||||
|
|
||||||
css-loader@6.11.0:
|
css-loader@6.11.0:
|
||||||
resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==}
|
resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==}
|
||||||
engines: {node: '>= 12.13.0'}
|
engines: {node: '>= 12.13.0'}
|
||||||
@ -2632,6 +2685,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
find-root@1.1.0:
|
||||||
|
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -2867,6 +2923,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||||
engines: {node: '>=10.17.0'}
|
engines: {node: '>=10.17.0'}
|
||||||
|
|
||||||
|
hyphenate-style-name@1.1.0:
|
||||||
|
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -2919,6 +2978,9 @@ packages:
|
|||||||
inherits@2.0.4:
|
inherits@2.0.4:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
|
inline-style-prefixer@7.0.1:
|
||||||
|
resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==}
|
||||||
|
|
||||||
ipaddr.js@1.9.1:
|
ipaddr.js@1.9.1:
|
||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
@ -4105,6 +4167,10 @@ packages:
|
|||||||
source-map-support@0.5.21:
|
source-map-support@0.5.21:
|
||||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||||
|
|
||||||
|
source-map@0.5.7:
|
||||||
|
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
source-map@0.6.1:
|
source-map@0.6.1:
|
||||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -4201,6 +4267,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.2.15
|
postcss: ^8.2.15
|
||||||
|
|
||||||
|
stylis@4.2.0:
|
||||||
|
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
||||||
|
|
||||||
supports-color@5.5.0:
|
supports-color@5.5.0:
|
||||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -5535,6 +5604,60 @@ snapshots:
|
|||||||
|
|
||||||
'@discoveryjs/json-ext@0.5.7': {}
|
'@discoveryjs/json-ext@0.5.7': {}
|
||||||
|
|
||||||
|
'@emotion/babel-plugin@11.13.5':
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-module-imports': 7.25.9
|
||||||
|
'@babel/runtime': 7.26.9
|
||||||
|
'@emotion/hash': 0.9.2
|
||||||
|
'@emotion/memoize': 0.9.0
|
||||||
|
'@emotion/serialize': 1.3.3
|
||||||
|
babel-plugin-macros: 3.1.0
|
||||||
|
convert-source-map: 1.9.0
|
||||||
|
escape-string-regexp: 4.0.0
|
||||||
|
find-root: 1.1.0
|
||||||
|
source-map: 0.5.7
|
||||||
|
stylis: 4.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@emotion/cache@11.14.0':
|
||||||
|
dependencies:
|
||||||
|
'@emotion/memoize': 0.9.0
|
||||||
|
'@emotion/sheet': 1.4.0
|
||||||
|
'@emotion/utils': 1.4.2
|
||||||
|
'@emotion/weak-memoize': 0.4.0
|
||||||
|
stylis: 4.2.0
|
||||||
|
|
||||||
|
'@emotion/css@11.13.5':
|
||||||
|
dependencies:
|
||||||
|
'@emotion/babel-plugin': 11.13.5
|
||||||
|
'@emotion/cache': 11.14.0
|
||||||
|
'@emotion/serialize': 1.3.3
|
||||||
|
'@emotion/sheet': 1.4.0
|
||||||
|
'@emotion/utils': 1.4.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@emotion/hash@0.9.2': {}
|
||||||
|
|
||||||
|
'@emotion/memoize@0.9.0': {}
|
||||||
|
|
||||||
|
'@emotion/serialize@1.3.3':
|
||||||
|
dependencies:
|
||||||
|
'@emotion/hash': 0.9.2
|
||||||
|
'@emotion/memoize': 0.9.0
|
||||||
|
'@emotion/unitless': 0.10.0
|
||||||
|
'@emotion/utils': 1.4.2
|
||||||
|
csstype: 3.1.3
|
||||||
|
|
||||||
|
'@emotion/sheet@1.4.0': {}
|
||||||
|
|
||||||
|
'@emotion/unitless@0.10.0': {}
|
||||||
|
|
||||||
|
'@emotion/utils@1.4.2': {}
|
||||||
|
|
||||||
|
'@emotion/weak-memoize@0.4.0': {}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.5':
|
'@esbuild/aix-ppc64@0.25.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -5675,6 +5798,25 @@ snapshots:
|
|||||||
|
|
||||||
'@leichtgewicht/ip-codec@2.0.5': {}
|
'@leichtgewicht/ip-codec@2.0.5': {}
|
||||||
|
|
||||||
|
'@marsio/vue-draggable@1.0.9(typescript@5.8.3)':
|
||||||
|
dependencies:
|
||||||
|
clsx: 1.2.1
|
||||||
|
lodash: 4.17.21
|
||||||
|
vue: 3.5.17(typescript@5.8.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- typescript
|
||||||
|
|
||||||
|
'@marsio/vue-split-pane@1.0.0(typescript@5.8.3)':
|
||||||
|
dependencies:
|
||||||
|
'@emotion/css': 11.13.5
|
||||||
|
'@marsio/vue-draggable': 1.0.9(typescript@5.8.3)
|
||||||
|
inline-style-prefixer: 7.0.1
|
||||||
|
lodash: 4.17.21
|
||||||
|
vue: 3.5.17(typescript@5.8.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
|
||||||
'@microsoft/api-extractor-model@7.30.6(@types/node@22.15.33)':
|
'@microsoft/api-extractor-model@7.30.6(@types/node@22.15.33)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@microsoft/tsdoc': 0.15.1
|
'@microsoft/tsdoc': 0.15.1
|
||||||
@ -6821,6 +6963,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
object.assign: 4.1.7
|
object.assign: 4.1.7
|
||||||
|
|
||||||
|
babel-plugin-macros@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.26.9
|
||||||
|
cosmiconfig: 7.1.0
|
||||||
|
resolve: 1.22.10
|
||||||
|
|
||||||
babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.9):
|
babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.9):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/compat-data': 7.26.8
|
'@babel/compat-data': 7.26.8
|
||||||
@ -7054,6 +7202,8 @@ snapshots:
|
|||||||
|
|
||||||
clone@1.0.4: {}
|
clone@1.0.4: {}
|
||||||
|
|
||||||
|
clsx@1.2.1: {}
|
||||||
|
|
||||||
color-convert@1.9.3:
|
color-convert@1.9.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.3
|
color-name: 1.1.3
|
||||||
@ -7116,6 +7266,8 @@ snapshots:
|
|||||||
|
|
||||||
content-type@1.0.5: {}
|
content-type@1.0.5: {}
|
||||||
|
|
||||||
|
convert-source-map@1.9.0: {}
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cookie-signature@1.0.6: {}
|
cookie-signature@1.0.6: {}
|
||||||
@ -7168,6 +7320,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
|
|
||||||
|
css-in-js-utils@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
hyphenate-style-name: 1.1.0
|
||||||
|
|
||||||
css-loader@6.11.0(webpack@5.98.0):
|
css-loader@6.11.0(webpack@5.98.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
icss-utils: 5.1.0(postcss@8.5.6)
|
icss-utils: 5.1.0(postcss@8.5.6)
|
||||||
@ -7719,6 +7875,8 @@ snapshots:
|
|||||||
make-dir: 3.1.0
|
make-dir: 3.1.0
|
||||||
pkg-dir: 4.2.0
|
pkg-dir: 4.2.0
|
||||||
|
|
||||||
|
find-root@1.1.0: {}
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 5.0.0
|
locate-path: 5.0.0
|
||||||
@ -7956,6 +8114,8 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@2.1.0: {}
|
human-signals@2.1.0: {}
|
||||||
|
|
||||||
|
hyphenate-style-name@1.1.0: {}
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
@ -7996,6 +8156,10 @@ snapshots:
|
|||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
|
inline-style-prefixer@7.0.1:
|
||||||
|
dependencies:
|
||||||
|
css-in-js-utils: 3.1.0
|
||||||
|
|
||||||
ipaddr.js@1.9.1: {}
|
ipaddr.js@1.9.1: {}
|
||||||
|
|
||||||
ipaddr.js@2.2.0: {}
|
ipaddr.js@2.2.0: {}
|
||||||
@ -9163,6 +9327,8 @@ snapshots:
|
|||||||
buffer-from: 1.1.2
|
buffer-from: 1.1.2
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
|
|
||||||
|
source-map@0.5.7: {}
|
||||||
|
|
||||||
source-map@0.6.1: {}
|
source-map@0.6.1: {}
|
||||||
|
|
||||||
sourcemap-codec@1.4.8: {}
|
sourcemap-codec@1.4.8: {}
|
||||||
@ -9261,6 +9427,8 @@ snapshots:
|
|||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
postcss-selector-parser: 6.1.2
|
postcss-selector-parser: 6.1.2
|
||||||
|
|
||||||
|
stylis@4.2.0: {}
|
||||||
|
|
||||||
supports-color@5.5.0:
|
supports-color@5.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 3.0.0
|
has-flag: 3.0.0
|
||||||
|
|||||||
27
src/Root.vue
27
src/Root.vue
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<b-e-flower-fall class="background" />
|
<b-e-flower-fall class="background" />
|
||||||
<root-layout v-if="ready" class="diselect" icp="粤ICP备2025368555号-1" domain="imyeyu.com" author="夜雨">
|
<root-layout v-if="ready" class="root-layout diselect" icp="粤ICP备2025368555号-1" domain="imyeyu.com" author="夜雨">
|
||||||
<router-view />
|
<router-view />
|
||||||
</root-layout>
|
</root-layout>
|
||||||
<popup />
|
<popup />
|
||||||
@ -10,16 +10,39 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BEFlowerFall, Popup } from "timi-web";
|
import { BEFlowerFall, Popup } from "timi-web";
|
||||||
import { RootLayout, UserProfilePopup } from "timi-tdesign-pc";
|
import { RootLayout, UserProfilePopup } from "timi-tdesign-pc";
|
||||||
|
import { SettingMapper } from "../../timi-web";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const ready = ref(false);
|
const ready = ref(false);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
await SettingMapper.loadSetting({
|
||||||
|
key: "GIT_ABOUT_ARTICLE"
|
||||||
|
});
|
||||||
ready.value = true;
|
ready.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------- 文件详情页更宽的布局 ----------
|
||||||
|
const inFileDetail = ref(false);
|
||||||
|
const largeWidth = computed(() => inFileDetail.value ? "1600px" : "1200px");
|
||||||
|
const width = computed(() => inFileDetail.value ? "1200px" : "900px");
|
||||||
|
watch(route, () => inFileDetail.value = route.name === "FileDetail");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.background {
|
.background {
|
||||||
background: url("@/assets/img/main.png") fixed right bottom;
|
background: url("@/assets/img/main.png") fixed right bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 2560px) {
|
||||||
|
.root-layout {
|
||||||
|
width: v-bind(largeWidth) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1920px) {
|
||||||
|
.root-layout {
|
||||||
|
width: v-bind(width) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -21,14 +21,26 @@ async function listFile(name: string, branch: string, path: string): Promise<Fil
|
|||||||
return axios.get(`${BASE_URI}/${name}:${branch}/file/list${path}`);
|
return axios.get(`${BASE_URI}/${name}:${branch}/file/list${path}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fileRaw(name: string, branch: string, path: string): Promise<ArrayBuffer> {
|
function fileRawURL(name: string, branch: string, path: string): string {
|
||||||
|
return `${API_HOST}${BASE_URI}/${name}:${branch}/file/raw${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fileRawByURL(rawURL: string): Promise<ArrayBuffer> {
|
||||||
return axios({
|
return axios({
|
||||||
url: `${BASE_URI}/${name}:${branch}/file/raw${path}`,
|
url: rawURL,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
responseType: "arraybuffer"
|
responseType: "arraybuffer"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fileRaw(name: string, branch: string, path: string): Promise<ArrayBuffer> {
|
||||||
|
return fileRawByURL(fileRawURL(name, branch, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fileMimeType(name: string, branch: string, path: string): Promise<string> {
|
||||||
|
return axios.get(`${BASE_URI}/${name}:${branch}/file/mime${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
function downloadArchive(name: string, branch: string) {
|
function downloadArchive(name: string, branch: string) {
|
||||||
window.open(`${API_HOST}${BASE_URI}/${name}:${branch}/archive`);
|
window.open(`${API_HOST}${BASE_URI}/${name}:${branch}/archive`);
|
||||||
}
|
}
|
||||||
@ -43,6 +55,9 @@ export default {
|
|||||||
pagePush,
|
pagePush,
|
||||||
listFile,
|
listFile,
|
||||||
fileRaw,
|
fileRaw,
|
||||||
|
fileRawURL,
|
||||||
|
fileRawByURL,
|
||||||
|
fileMimeType,
|
||||||
downloadArchive,
|
downloadArchive,
|
||||||
pageLog
|
pageLog
|
||||||
};
|
};
|
||||||
|
|||||||
363
src/components/FileViewer.vue
Normal file
363
src/components/FileViewer.vue
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
<template>
|
||||||
|
<div class="file-viewer diselect">
|
||||||
|
<t-tabs
|
||||||
|
class="tabs"
|
||||||
|
theme="card"
|
||||||
|
v-model="filePath"
|
||||||
|
:onRemove="onTabRemove"
|
||||||
|
>
|
||||||
|
<t-tab-panel
|
||||||
|
v-for="file in files"
|
||||||
|
:key="file.path"
|
||||||
|
:value="file.path"
|
||||||
|
:label="file.name"
|
||||||
|
:removable="file.removable"
|
||||||
|
@click="file.isPreview = false"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span
|
||||||
|
class="black"
|
||||||
|
:class="{ 'light-gray': file.isPreview }"
|
||||||
|
v-text="file.name"
|
||||||
|
@click="file.isPreview = false"
|
||||||
|
></span>
|
||||||
|
</template>
|
||||||
|
</t-tab-panel>
|
||||||
|
</t-tabs>
|
||||||
|
<div v-if="file" :class="`viewer ${clazz}`" @click="file.isPreview = false">
|
||||||
|
<loading class="loading" :showOn="isLoading" />
|
||||||
|
<div
|
||||||
|
v-if="file.viewerType === FileViewerType.NOT_SUPPORT"
|
||||||
|
class="not-support"
|
||||||
|
>
|
||||||
|
<p class="tips">此文件不支持预览</p>
|
||||||
|
<t-button
|
||||||
|
v-if="file.rawFile.size < IOSize.MB"
|
||||||
|
@click="previewAsText"
|
||||||
|
>尝试以文本文件解析预览</t-button>
|
||||||
|
</div>
|
||||||
|
<markdown-view
|
||||||
|
v-if="file.viewerType === FileViewerType.MARKDOWN || file.viewerType === FileViewerType.CODE"
|
||||||
|
class="content selectable"
|
||||||
|
:show-code-border="false"
|
||||||
|
max-height="auto"
|
||||||
|
:content="file.data"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="file.viewerType === FileViewerType.TEXT"
|
||||||
|
class="container selectable"
|
||||||
|
>
|
||||||
|
<div class="value" v-text="file.data"></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaComponent && file.rawURL" class="container">
|
||||||
|
<component
|
||||||
|
:is="mediaComponent"
|
||||||
|
class="value"
|
||||||
|
:src="file.rawURL"
|
||||||
|
:controls="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { IOSize, Loading, MarkdownView, Prismjs, PrismjsViewer, Toolkit } from "timi-web";
|
||||||
|
import { File, FileType, FileViewerType, OpenFile } from "@/types/Repository.ts";
|
||||||
|
import RepositoryAPI from "@/api/RepositoryAPI.ts";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "FileViewer"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
repoName: string;
|
||||||
|
branch: string;
|
||||||
|
}>(), {
|
||||||
|
});
|
||||||
|
const { repoName, branch } = toRefs(props);
|
||||||
|
|
||||||
|
const isLoading = ref<boolean>(false);
|
||||||
|
const files = ref<OpenFile[]>([]);
|
||||||
|
const file = ref<OpenFile>();
|
||||||
|
const filePath = ref<string>();
|
||||||
|
watch(file, file => filePath.value = file?.path);
|
||||||
|
|
||||||
|
const clazz = computed(() => {
|
||||||
|
if (file.value && file.value.viewerType) {
|
||||||
|
return Toolkit.className(file.value.viewerType.toString().toLowerCase());
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------- 切换分支 ----------
|
||||||
|
|
||||||
|
watch(branch, () => {
|
||||||
|
files.value = [];
|
||||||
|
file.value = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------- 打开文件 ----------
|
||||||
|
|
||||||
|
function openFile(treeFile: File) {
|
||||||
|
if (treeFile.type === FileType.FILE) {
|
||||||
|
// 已打开该文件
|
||||||
|
let opened = files.value.find(item => item.path === treeFile.path);
|
||||||
|
const isReadme = treeFile.path === "README.md";
|
||||||
|
if (!opened) {
|
||||||
|
opened = {
|
||||||
|
path: treeFile.path,
|
||||||
|
name: treeFile.name,
|
||||||
|
rawURL: RepositoryAPI.fileRawURL(repoName.value, branch.value, `/${treeFile.path}`),
|
||||||
|
rawFile: treeFile,
|
||||||
|
isPreview: !isReadme,
|
||||||
|
removable: !isReadme
|
||||||
|
} as OpenFile;
|
||||||
|
// 如果存在已打开预览,则替换该预览项
|
||||||
|
const previewIndex = files.value.findIndex(item => item.isPreview);
|
||||||
|
if (previewIndex === -1) {
|
||||||
|
files.value.push(opened);
|
||||||
|
} else {
|
||||||
|
files.value.splice(previewIndex, 1, opened);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opened.activatedAt = new Date().getTime();
|
||||||
|
file.value = opened;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openFile
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------- 切换文件 ----------
|
||||||
|
|
||||||
|
// 从 tab 切换文件
|
||||||
|
watch(filePath, value => {
|
||||||
|
if (filePath.value) {
|
||||||
|
const targetFile = files.value.find(item => item.path === value);
|
||||||
|
if (targetFile) {
|
||||||
|
targetFile.activatedAt = new Date().getTime();
|
||||||
|
file.value = targetFile;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.value = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(file, async () => {
|
||||||
|
if (file.value) {
|
||||||
|
if (!file.value.mimeType) {
|
||||||
|
isLoading.value = true;
|
||||||
|
// 获取 Mime-Type
|
||||||
|
file.value.mimeType = await RepositoryAPI.fileMimeType(repoName.value, branch.value, `/${file.value.path}`);
|
||||||
|
}
|
||||||
|
file.value.prismjsType = file.value.prismjsType ?? Prismjs.typeFromFileName(file.value.name);
|
||||||
|
if (file.value.prismjsType) {
|
||||||
|
if (!file.value.raw) {
|
||||||
|
isLoading.value = true;
|
||||||
|
file.value.raw = await RepositoryAPI.fileRawByURL(file.value.rawURL);
|
||||||
|
}
|
||||||
|
// 解码为文本
|
||||||
|
const properties = Prismjs.getFileProperties(file.value.prismjsType);
|
||||||
|
if (properties) {
|
||||||
|
if (!file.value.data) {
|
||||||
|
isLoading.value = true;
|
||||||
|
const data = new TextDecoder("utf-8").decode(file.value.raw);
|
||||||
|
if (properties.viewer === PrismjsViewer.CODE) {
|
||||||
|
file.value.data = Toolkit.format("```${type}\n${data}\n```", {
|
||||||
|
type: properties.prismjs,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
file.value.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (properties.viewer === PrismjsViewer.MARKDOWN) {
|
||||||
|
file.value.viewerType = FileViewerType.MARKDOWN;
|
||||||
|
} else if (properties.viewer === PrismjsViewer.CODE) {
|
||||||
|
file.value.viewerType = FileViewerType.CODE;
|
||||||
|
} else {
|
||||||
|
file.value.viewerType = FileViewerType.TEXT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!file.value.viewerType) {
|
||||||
|
if (file.value.mimeType) {
|
||||||
|
switch (true) {
|
||||||
|
case file.value.mimeType.startsWith("image/"):
|
||||||
|
file.value.viewerType = FileViewerType.IMAGE;
|
||||||
|
break;
|
||||||
|
case file.value.mimeType.startsWith("audio/"):
|
||||||
|
file.value.viewerType = FileViewerType.AUDIO;
|
||||||
|
break;
|
||||||
|
case file.value.mimeType.startsWith("video/"):
|
||||||
|
file.value.viewerType = FileViewerType.VIDEO;
|
||||||
|
break;
|
||||||
|
case file.value.mimeType.startsWith("text/"):
|
||||||
|
file.value.viewerType = FileViewerType.TEXT;
|
||||||
|
if (!file.value.data) {
|
||||||
|
if (!file.value.raw) {
|
||||||
|
isLoading.value = true;
|
||||||
|
file.value.raw = await RepositoryAPI.fileRawByURL(file.value.rawURL);
|
||||||
|
}
|
||||||
|
file.value.data = new TextDecoder("utf-8").decode(file.value.raw);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
file.value.viewerType = FileViewerType.NOT_SUPPORT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.value.viewerType = FileViewerType.NOT_SUPPORT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------- 关闭文件 ----------
|
||||||
|
|
||||||
|
const onTabRemove = (options: { index: number }) => {
|
||||||
|
files.value.splice(options.index, 1);
|
||||||
|
if (0 < files.value.length) {
|
||||||
|
file.value = files.value[files.value.length - 1];
|
||||||
|
} else {
|
||||||
|
file.value = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- 尝试以文本文件解码预览 ----------
|
||||||
|
|
||||||
|
async function previewAsText() {
|
||||||
|
if (file.value) {
|
||||||
|
// 解码为文本
|
||||||
|
if (!file.value.data) {
|
||||||
|
if (!file.value.raw) {
|
||||||
|
file.value.raw = await RepositoryAPI.fileRawByURL(file.value.rawURL);
|
||||||
|
}
|
||||||
|
file.value.data = new TextDecoder("utf-8").decode(file.value.raw);
|
||||||
|
}
|
||||||
|
file.value.viewerType = FileViewerType.TEXT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 媒体文件 ----------
|
||||||
|
|
||||||
|
const mediaComponent = computed(() => {
|
||||||
|
if (file.value) {
|
||||||
|
switch (file.value.viewerType) {
|
||||||
|
case FileViewerType.IMAGE:
|
||||||
|
return "img";
|
||||||
|
case FileViewerType.VIDEO:
|
||||||
|
return FileViewerType.VIDEO.toString().toLowerCase();
|
||||||
|
case FileViewerType.AUDIO:
|
||||||
|
return FileViewerType.AUDIO.toString().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.file-viewer {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
top: calc(96px + 30px);
|
||||||
|
position: sticky;
|
||||||
|
z-index: 9;
|
||||||
|
border-top: var(--tui-border);
|
||||||
|
border-bottom: var(--tui-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer {
|
||||||
|
height: calc(100% - 33px);
|
||||||
|
|
||||||
|
&.text {
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
min-height: calc(100% - 4px);
|
||||||
|
padding: 2px 4px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.code {
|
||||||
|
|
||||||
|
:deep(.tui-markdown-view) {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 3em;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(242, 242, 242, .9);
|
||||||
|
border-right: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class^="language-"] {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(pre[class*="language-"]) {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.markdown {
|
||||||
|
height: 100%;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.image,
|
||||||
|
&.audio,
|
||||||
|
&.video {
|
||||||
|
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
audio {
|
||||||
|
width: 40rem;
|
||||||
|
margin: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 60vh;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-support {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -26,8 +26,8 @@
|
|||||||
<span class="selectable" v-text="action.refName"></span>
|
<span class="selectable" v-text="action.refName"></span>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="action.commitList">
|
<template v-if="action.commitList">
|
||||||
<h4 v-if="showRepo || showBranch">提交记录:</h4>
|
|
||||||
<div class="commits">
|
<div class="commits">
|
||||||
|
<h4 v-if="showRepo || showBranch" class="title">提交记录:</h4>
|
||||||
<div
|
<div
|
||||||
class="commit"
|
class="commit"
|
||||||
v-for="(commit, commitIndex) in action.commitList"
|
v-for="(commit, commitIndex) in action.commitList"
|
||||||
@ -103,9 +103,17 @@ const { items } = toRefs(props);
|
|||||||
|
|
||||||
.commits {
|
.commits {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
.commit {
|
.commit {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
&:nth-last-child(n + 2) {
|
&:nth-last-child(n + 2) {
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<t-menu-item to="/" :value="Menu.LIST">仓库列表</t-menu-item>
|
<t-menu-item to="/" :value="Menu.LIST">仓库列表</t-menu-item>
|
||||||
<t-menu-item to="/log" :value="Menu.LOG">最近推送</t-menu-item>
|
<t-menu-item to="/log" :value="Menu.LOG">最近推送</t-menu-item>
|
||||||
|
<t-menu-item to="/about" :value="Menu.ABOUT">关于</t-menu-item>
|
||||||
<template #operations>
|
<template #operations>
|
||||||
<login-menu />
|
<login-menu />
|
||||||
</template>
|
</template>
|
||||||
@ -44,14 +45,16 @@ onMounted(async () => owner.value = await UserAPI.view(1));
|
|||||||
|
|
||||||
enum Menu {
|
enum Menu {
|
||||||
LIST = "LIST",
|
LIST = "LIST",
|
||||||
LOG = "LOG"
|
LOG = "LOG",
|
||||||
|
ABOUT = "ABOUT"
|
||||||
}
|
}
|
||||||
|
|
||||||
const regexMenu: {
|
const regexMenu: {
|
||||||
[key: string]: Menu
|
[key: string]: Menu
|
||||||
} = {
|
} = {
|
||||||
"RepositoryList": Menu.LIST,
|
"RepositoryList": Menu.LIST,
|
||||||
"RepositoryLog": Menu.LOG
|
"RepositoryLog": Menu.LOG,
|
||||||
|
"About": Menu.ABOUT
|
||||||
};
|
};
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -76,7 +79,7 @@ onMounted(() => syncMenuRouter(route.name as string));
|
|||||||
|
|
||||||
.header {
|
.header {
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 3;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
background: rgba(231, 234, 239, .8);
|
background: rgba(231, 234, 239, .8);
|
||||||
border-bottom: var(--tui-border);
|
border-bottom: var(--tui-border);
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="repository-layout" v-if="repositoryStore.value">
|
<div class="repository-layout" v-if="repositoryStore.value">
|
||||||
<t-layout class="header">
|
<div class="header">
|
||||||
<t-content>
|
<section class="top">
|
||||||
<t-space class="title" align="center">
|
<t-space class="left" align="center">
|
||||||
<h3 class="title" v-text="repositoryStore.value.name"></h3>
|
<h4 class="title" v-text="repositoryStore.value.name"></h4>
|
||||||
<p class="light-gray" v-text="repositoryStore.value.description"></p>
|
<p class="description light-gray" v-text="repositoryStore.value.description"></p>
|
||||||
</t-space>
|
</t-space>
|
||||||
</t-content>
|
<t-space class="right" align="center">
|
||||||
<t-aside class="aside" width="auto">
|
|
||||||
<t-space align="center">
|
|
||||||
<t-button class="repository-list" theme="default" variant="text" @click="backRepositoryList">
|
<t-button class="repository-list" theme="default" variant="text" @click="backRepositoryList">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon class="icon" name="LIST" />
|
<icon class="icon" name="LIST" />
|
||||||
@ -17,40 +15,40 @@
|
|||||||
</t-button>
|
</t-button>
|
||||||
<login-menu />
|
<login-menu />
|
||||||
</t-space>
|
</t-space>
|
||||||
</t-aside>
|
</section>
|
||||||
</t-layout>
|
<t-head-menu class="menu" v-model="menuValue">
|
||||||
<t-head-menu class="menu" v-model="menuValue">
|
<t-menu-item
|
||||||
<t-menu-item
|
:to="`/${repositoryStore.value.name}/${repositoryStore.value.defaultBranch}`"
|
||||||
:to="`/${repositoryStore.value.name}/${repositoryStore.value.defaultBranch}`"
|
:value="Menu.FILE"
|
||||||
:value="Menu.FILE"
|
>
|
||||||
>
|
项目文件
|
||||||
项目文件
|
</t-menu-item>
|
||||||
</t-menu-item>
|
<t-menu-item
|
||||||
<t-menu-item
|
:to="`/${repositoryStore.value.name}/commits`"
|
||||||
:to="`/${repositoryStore.value.name}/commits`"
|
:value="Menu.COMMIT_LOG"
|
||||||
:value="Menu.COMMIT_LOG"
|
>
|
||||||
>
|
更新记录
|
||||||
更新记录
|
</t-menu-item>
|
||||||
</t-menu-item>
|
<t-menu-item
|
||||||
<t-menu-item
|
:to="`/${repositoryStore.value.name}/issues`"
|
||||||
:to="`/${repositoryStore.value.name}/issues`"
|
:value="Menu.ISSUE"
|
||||||
:value="Menu.ISSUE"
|
>
|
||||||
>
|
问题反馈
|
||||||
问题反馈
|
</t-menu-item>
|
||||||
</t-menu-item>
|
<t-menu-item
|
||||||
<t-menu-item
|
:to="`/${repositoryStore.value.name}/merges`"
|
||||||
:to="`/${repositoryStore.value.name}/merges`"
|
:value="Menu.MERGE"
|
||||||
:value="Menu.MERGE"
|
>
|
||||||
>
|
合并请求
|
||||||
合并请求
|
</t-menu-item>
|
||||||
</t-menu-item>
|
<t-menu-item
|
||||||
<t-menu-item
|
:to="`/${repositoryStore.value.name}/releases`"
|
||||||
:to="`/${repositoryStore.value.name}/releases`"
|
:value="Menu.RELEASE"
|
||||||
:value="Menu.RELEASE"
|
>
|
||||||
>
|
版本发布
|
||||||
版本发布
|
</t-menu-item>
|
||||||
</t-menu-item>
|
</t-head-menu>
|
||||||
</t-head-menu>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
@ -119,11 +117,12 @@ onMounted(async () => repositoryStore.value = await RepositoryAPI.view(route.par
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.repository-layout {
|
.repository-layout {
|
||||||
|
display: flex;
|
||||||
border-bottom: var(--tui-border);
|
border-bottom: var(--tui-border);
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 50px;
|
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
background: rgba(231, 234, 239, .8);
|
background: rgba(231, 234, 239, .8);
|
||||||
@ -131,36 +130,49 @@ onMounted(async () => repositoryStore.value = await RepositoryAPI.view(route.par
|
|||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
|
||||||
.title {
|
.top {
|
||||||
padding-left: .5rem;
|
display: flex;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
border-bottom: var(--tui-border);
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.aside {
|
.menu {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-right: 1rem;
|
|
||||||
|
|
||||||
.repository-list {
|
:deep(.t-menu) {
|
||||||
|
margin: 0;
|
||||||
.icon {
|
|
||||||
margin-right: .5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.content {
|
||||||
top: 50px;
|
flex: 1;
|
||||||
z-index: 3;
|
display: flex;
|
||||||
position: sticky;
|
min-height: 520px;
|
||||||
background: rgba(255, 255, 255, .8);
|
flex-direction: column;
|
||||||
border-bottom: var(--tui-border);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
-webkit-backdrop-filter: blur(10px);
|
|
||||||
|
|
||||||
:deep(.t-menu) {
|
> * {
|
||||||
margin: 0;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
.repository-layout {
|
||||||
|
// - 底部
|
||||||
|
min-height: calc(100% - 110px);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
import repository from "./repository";
|
||||||
import IndexLayout from "@/layout/IndexLayout.vue";
|
import IndexLayout from "@/layout/IndexLayout.vue";
|
||||||
import RepositoryList from "@/views/index/RepositoryList.vue";
|
import RepositoryList from "@/views/index/RepositoryList.vue";
|
||||||
import RepositoryLog from "@/views/index/RepositoryLog.vue";
|
|
||||||
import {createRouter, createWebHistory} from "vue-router";
|
|
||||||
import repository from "./repository";
|
|
||||||
|
|
||||||
export default createRouter({
|
export default createRouter({
|
||||||
history: createWebHistory("/"),
|
history: createWebHistory("/"),
|
||||||
@ -16,12 +15,17 @@ export default createRouter({
|
|||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "RepositoryList",
|
name: "RepositoryList",
|
||||||
component: RepositoryList
|
component: RepositoryList
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/log",
|
path: "/log",
|
||||||
name: "RepositoryLog",
|
name: "RepositoryLog",
|
||||||
component: RepositoryLog
|
component: () => import("@/views/index/RepositoryLog.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/about",
|
||||||
|
name: "About",
|
||||||
|
component: () => import("@/views/index/About.vue")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,13 +1,4 @@
|
|||||||
import RepositoryLayout from "@/layout/RepositoryLayout.vue";
|
import RepositoryLayout from "@/layout/RepositoryLayout.vue";
|
||||||
import CommitLog from "@/views/repository/CommitLog.vue";
|
|
||||||
import FileDetail from "@/views/repository/FileDetail.vue";
|
|
||||||
import IssueDetail from "@/views/repository/IssueDetail.vue";
|
|
||||||
import IssueEdit from "@/views/repository/IssueEdit.vue";
|
|
||||||
import IssueList from "@/views/repository/IssueList.vue";
|
|
||||||
import MergeDetail from "@/views/repository/MergeDetail.vue";
|
|
||||||
import MergeEdit from "@/views/repository/MergeEdit.vue";
|
|
||||||
import MergeList from "@/views/repository/MergeList.vue";
|
|
||||||
import ReleaseList from "@/views/repository/ReleaseList.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
path: "/:repository",
|
path: "/:repository",
|
||||||
@ -17,7 +8,7 @@ export default {
|
|||||||
{
|
{
|
||||||
path: ":branch",
|
path: ":branch",
|
||||||
name: "FileDetail",
|
name: "FileDetail",
|
||||||
component: FileDetail,
|
component: () => import("@/views/repository/FileDetail.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
@ -25,47 +16,42 @@ export default {
|
|||||||
{
|
{
|
||||||
path: "commits",
|
path: "commits",
|
||||||
name: "CommitLog",
|
name: "CommitLog",
|
||||||
component: CommitLog
|
component: () => import("@/views/repository/CommitLog.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "issues",
|
path: "issues",
|
||||||
name: "IssueList",
|
name: "IssueList",
|
||||||
component: IssueList
|
component: () => import("@/views/repository/IssueList.vue")
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "issues",
|
|
||||||
name: "IssueList",
|
|
||||||
component: IssueList
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "issues/edit/:id?",
|
path: "issues/edit/:id?",
|
||||||
name: "IssueEdit",
|
name: "IssueEdit",
|
||||||
component: IssueEdit
|
component: () => import("@/views/repository/IssueEdit.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "issues/:id",
|
path: "issues/:id",
|
||||||
name: "IssueDetail",
|
name: "IssueDetail",
|
||||||
component: IssueDetail
|
component: () => import("@/views/repository/IssueDetail.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "merges",
|
path: "merges",
|
||||||
name: "MergeList",
|
name: "MergeList",
|
||||||
component: MergeList
|
component: () => import("@/views/repository/MergeList.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "merges/edit/:id?",
|
path: "merges/edit/:id?",
|
||||||
name: "MergeEdit",
|
name: "MergeEdit",
|
||||||
component: MergeEdit
|
component: () => import("@/views/repository/MergeEdit.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "merges/:id",
|
path: "merges/:id",
|
||||||
name: "MergeDetail",
|
name: "MergeDetail",
|
||||||
component: MergeDetail
|
component: () => import("@/views/repository/MergeDetail.vue")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "releases",
|
path: "releases",
|
||||||
name: "ReleaseList",
|
name: "ReleaseList",
|
||||||
component: ReleaseList
|
component: () => import("@/views/repository/ReleaseList.vue")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Model, Page, UserView } from "timi-web";
|
import { Model, Page, PrismjsType, UserView } from "timi-web";
|
||||||
|
|
||||||
export type Repository = {
|
export type Repository = {
|
||||||
id: number;
|
id: number;
|
||||||
@ -60,4 +60,32 @@ export type File = {
|
|||||||
lastCommitterDate: number;
|
lastCommitterDate: number;
|
||||||
|
|
||||||
children: boolean;
|
children: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FileViewerType {
|
||||||
|
|
||||||
|
NOT_SUPPORT = "NOT_SUPPORT",
|
||||||
|
AUDIO = "AUDIO",
|
||||||
|
VIDEO = "VIDEO",
|
||||||
|
IMAGE = "IMAGE",
|
||||||
|
TEXT = "TEXT",
|
||||||
|
CODE = "CODE",
|
||||||
|
MARKDOWN = "MARKDOWN",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OpenFile = {
|
||||||
|
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
raw?: ArrayBuffer;
|
||||||
|
rawURL: string;
|
||||||
|
rawFile: File;
|
||||||
|
data?: string;
|
||||||
|
removable: boolean;
|
||||||
|
isPreview: boolean;
|
||||||
|
activatedAt?: number;
|
||||||
|
mimeType?: string;
|
||||||
|
viewerType?: FileViewerType;
|
||||||
|
prismjsType?: PrismjsType;
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/views/index/About.vue
Normal file
33
src/views/index/About.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<article v-if="article" class="about">
|
||||||
|
<markdown-view class="content" :content="article.data" />
|
||||||
|
<comment
|
||||||
|
v-if="SettingMapper.is(SettingKey.ENABLE_COMMENT) && article.showComment"
|
||||||
|
:bizType="CommentBizType.ARTICLE"
|
||||||
|
:bizId="article.id!"
|
||||||
|
:titleStickyOffset="80"
|
||||||
|
:canComment="article.canComment"
|
||||||
|
/>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ArticleAPI, ArticleView, CommentBizType, MarkdownView, SettingKey, SettingMapper } from "timi-web";
|
||||||
|
import { Comment } from "timi-tdesign-pc";
|
||||||
|
|
||||||
|
const article = ref<ArticleView<any>>();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
article.value = await ArticleAPI.view(SettingMapper.getValue("GIT_ABOUT_ARTICLE") as unknown as number);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.about {
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: calc(100% - 4rem);
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,41 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="repository-list">
|
<div class="repository-list">
|
||||||
|
<loading :showOn="isLoading" margin="1rem" />
|
||||||
<t-list class="items" v-if="page" :split="true">
|
<t-list class="items" v-if="page" :split="true">
|
||||||
<t-list-item class="item" v-for="item in pageResult.list" :key="item.id">
|
<t-list-item class="item" v-for="item in pageResult.list" :key="item.id">
|
||||||
<t-layout>
|
<icon name="DUPLICATE" :scale="2" />
|
||||||
<t-aside class="icon" width="auto">
|
<div class="content">
|
||||||
<icon name="DUPLICATE" :scale="2" />
|
<div class="header">
|
||||||
</t-aside>
|
<h3 class="name">
|
||||||
<t-content class="content">
|
<router-link class="link black" :to="`/${item.name}/${item.defaultBranch}`">
|
||||||
<t-layout>
|
{{ item.name }}
|
||||||
<t-header class="header" height="auto">
|
</router-link>
|
||||||
<div class="name">
|
</h3>
|
||||||
<router-link class="link black" :to="`/${item.name}/${item.defaultBranch}`">
|
<div class="time light-gray"
|
||||||
{{ item.name }}
|
v-text="`最近推送 ${Time.toPassedDate(item.updatedAt)}`"
|
||||||
</router-link>
|
v-popup="Time.toDateTime(item.updatedAt)"
|
||||||
</div>
|
></div>
|
||||||
<div class="time light-gray"
|
</div>
|
||||||
v-text="`最近推送 ${Time.toPassedDate(item.updatedAt)}`"
|
<div class="description gray" v-text="item.description"></div>
|
||||||
v-popup="Time.toDateTime(item.updatedAt)"
|
</div>
|
||||||
></div>
|
|
||||||
</t-header>
|
|
||||||
<t-content>
|
|
||||||
<span class="gray" v-text="item.description"></span>
|
|
||||||
</t-content>
|
|
||||||
</t-layout>
|
|
||||||
</t-content>
|
|
||||||
</t-layout>
|
|
||||||
</t-list-item>
|
</t-list-item>
|
||||||
</t-list>
|
</t-list>
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<t-pagination
|
<t-pagination
|
||||||
class="pagination"
|
class="pages"
|
||||||
|
v-if="pageResult"
|
||||||
:total="pageResult.total"
|
:total="pageResult.total"
|
||||||
:pageSize="16"
|
:pageSize="16"
|
||||||
:showPageSize="false"
|
:showPageSize="false"
|
||||||
|
:current="currentPage"
|
||||||
|
:onCurrentChange="onPageChangeEvent"
|
||||||
>
|
>
|
||||||
<template #totalContent>
|
<template #totalContent>
|
||||||
<div class="total" v-text="`共 ${pageResult.total} 个项目`"></div>
|
<div style="flex: 1" v-text="`共 ${pageResult.total} 篇文章`"></div>
|
||||||
</template>
|
</template>
|
||||||
</t-pagination>
|
</t-pagination>
|
||||||
</footer>
|
</footer>
|
||||||
@ -44,22 +40,62 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import RepositoryAPI from "@/api/RepositoryAPI";
|
import RepositoryAPI from "@/api/RepositoryAPI";
|
||||||
import { Repository } from "@/types/Repository";
|
import { Repository } from "@/types/Repository";
|
||||||
import { Icon, Page, PageResult, Time } from "timi-web";
|
import { Icon, Loading, Page, PageResult, Time, Toolkit } from "timi-web";
|
||||||
|
|
||||||
const page = ref<Page>({
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const currentPage = computed(() => page.index + 1);
|
||||||
|
const page = reactive<Page>({
|
||||||
index: 0,
|
index: 0,
|
||||||
size: 12
|
size: 16
|
||||||
});
|
});
|
||||||
const pageResult = reactive<PageResult<Repository>>({
|
const pageResult = reactive<PageResult<Repository>>({
|
||||||
total: 0,
|
total: 0,
|
||||||
list: []
|
list: []
|
||||||
});
|
});
|
||||||
|
// 获取列表
|
||||||
onMounted(async () => {
|
const fetchList = Toolkit.debounce(async () => {
|
||||||
const result = await RepositoryAPI.page(page.value);
|
isLoading.value = true;
|
||||||
|
const result = await RepositoryAPI.page(page);
|
||||||
pageResult.total = result.total;
|
pageResult.total = result.total;
|
||||||
pageResult.list = result.list;
|
pageResult.list = result.list;
|
||||||
|
isLoading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听路由分页
|
||||||
|
watch(
|
||||||
|
() => route.query.p,
|
||||||
|
async (newP) => {
|
||||||
|
const pNum = Number(newP);
|
||||||
|
if (Number.isInteger(pNum) && 0 <= pNum) {
|
||||||
|
page.index = pNum;
|
||||||
|
await fetchList();
|
||||||
|
} else {
|
||||||
|
// 参数无效时重置为第一页
|
||||||
|
await router.push({
|
||||||
|
path: route.path,
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
p: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
function onPageChangeEvent(current: number) {
|
||||||
|
const newIndex = current - 1;
|
||||||
|
router.push({
|
||||||
|
path: route.path,
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
p: newIndex
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@ -71,25 +107,22 @@ onMounted(async () => {
|
|||||||
min-height: 520px;
|
min-height: 520px;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
|
display: flex;
|
||||||
.icon {
|
padding: 16px;
|
||||||
padding: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-left: .5rem;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
margin-left: 16px;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
flex: 1;
|
margin: 0;
|
||||||
display: block;
|
font-size: 16px;
|
||||||
|
|
||||||
.link {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="repository-log">
|
<div class="repository-log">
|
||||||
<push-log-timeline :items="list"/>
|
<push-log-timeline :items="list" />
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<loading :showOn="isLoading"/>
|
<loading :showOn="isLoading" />
|
||||||
<empty-tips :showOn="isFinished && !isLoading"/>
|
<empty-tips v-if="isFinished && !isLoading" />
|
||||||
<t-button v-show="!isFinished && !isLoading" @click="doFetchEvent">加载更多</t-button>
|
<t-button v-show="!isFinished && !isLoading" @click="doFetchEvent">加载更多</t-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +38,12 @@ onMounted(doFetchEvent);
|
|||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.repository-log {
|
.repository-log {
|
||||||
width: 100%;
|
width: calc(100% - 4rem);
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,30 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="repositoryStore.value" class="commit-log">
|
<div v-if="repositoryStore.value" class="commit-log">
|
||||||
<t-layout>
|
<div class="header">
|
||||||
<t-header class="header">
|
<t-select class="branch" v-model="branch" label="分支:" placeholder="全部" clearable>
|
||||||
<div>
|
<t-option
|
||||||
<t-select class="branch" v-model="branch" label="分支:" placeholder="全部" clearable>
|
v-for="item in repositoryStore.value.branchList"
|
||||||
<t-option
|
:key="item.name"
|
||||||
v-for="item in repositoryStore.value.branchList"
|
:value="item.name"
|
||||||
:key="item.name"
|
:label="item.name"
|
||||||
:value="item.name"
|
/>
|
||||||
:label="item.name"
|
</t-select>
|
||||||
/>
|
</div>
|
||||||
</t-select>
|
<div class="content">
|
||||||
</div>
|
<loading :showOn="isLoading" margin="0 0 1rem 0" />
|
||||||
</t-header>
|
<push-log-timeline :items="commits" :show-repo="false" :show-branch="!branch" />
|
||||||
<t-content class="content">
|
<div class="more">
|
||||||
<push-log-timeline :items="commits" :show-repo="false" :show-branch="!branch" />
|
<empty-tips :showOn="isFinished" />
|
||||||
<div class="more">
|
<t-button v-if="!isLoading && !isFinished" @click="doFetchEvent">加载更多</t-button>
|
||||||
<empty-tips :showOn="isFinished" />
|
</div>
|
||||||
<t-button v-if="!isFinished" @click="doFetchEvent">加载更多</t-button>
|
</div>
|
||||||
</div>
|
|
||||||
</t-content>
|
|
||||||
</t-layout>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { EmptyTips, Page } from "timi-web";
|
import { EmptyTips, Loading, Page } from "timi-web";
|
||||||
import RepositoryAPI from "@/api/RepositoryAPI";
|
import RepositoryAPI from "@/api/RepositoryAPI";
|
||||||
import { useRepositoryStore } from "@/store/repository.ts";
|
import { useRepositoryStore } from "@/store/repository.ts";
|
||||||
import { ActionLogView } from "@/types/Common.ts";
|
import { ActionLogView } from "@/types/Common.ts";
|
||||||
@ -32,33 +29,32 @@ import PushLogTimeline from "@/components/PushLogTimeline.vue";
|
|||||||
|
|
||||||
const repositoryStore = useRepositoryStore();
|
const repositoryStore = useRepositoryStore();
|
||||||
|
|
||||||
const branch = ref();
|
|
||||||
|
|
||||||
const page = ref<Page>({
|
const page = ref<Page>({
|
||||||
index: 0,
|
index: 0,
|
||||||
size: 12
|
size: 12
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const branch = ref();
|
||||||
const commits = reactive<ActionLogView[]>([]);
|
const commits = reactive<ActionLogView[]>([]);
|
||||||
|
const isLoading = ref(false);
|
||||||
const isFinished = ref(false);
|
const isFinished = ref(false);
|
||||||
|
|
||||||
const doFetchEvent = async () => {
|
const doFetchEvent = async () => {
|
||||||
if (repositoryStore.value) {
|
isLoading.value = true;
|
||||||
const result = await RepositoryAPI.pagePush(repositoryStore.value.name, branch.value ?? "all", page.value);
|
const result = await RepositoryAPI.pagePush(repositoryStore.value!.name, branch.value ?? "all", page.value);
|
||||||
const list = result.list;
|
const list = result.list;
|
||||||
commits.push(...list);
|
commits.push(...list);
|
||||||
page.value.index++;
|
page.value.index++;
|
||||||
if (result.list.length < page.value.size) {
|
if (result.list.length < page.value.size) {
|
||||||
isFinished.value = true;
|
isFinished.value = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
isLoading.value = false;
|
||||||
};
|
};
|
||||||
watch(branch, () => {
|
watch(branch, () => {
|
||||||
if (repositoryStore.value) {
|
page.value.index = 0;
|
||||||
page.value.index = 0;
|
commits.length = 0;
|
||||||
commits.length = 0;
|
isFinished.value = false;
|
||||||
isFinished.value = false;
|
doFetchEvent();
|
||||||
doFetchEvent();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
onMounted(doFetchEvent);
|
onMounted(doFetchEvent);
|
||||||
</script>
|
</script>
|
||||||
@ -66,7 +62,8 @@ onMounted(doFetchEvent);
|
|||||||
.commit-log {
|
.commit-log {
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
top: calc(50px + 49px);
|
top: 96px;
|
||||||
|
height: 50px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
@ -76,10 +73,15 @@ onMounted(doFetchEvent);
|
|||||||
border-bottom: var(--tui-border);
|
border-bottom: var(--tui-border);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
|
||||||
|
.branch {
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 1rem 2rem 1rem 1rem;
|
padding: 1rem 2rem 1rem 1rem;
|
||||||
|
min-height: 520px;
|
||||||
|
|
||||||
.more {
|
.more {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="file-detail" v-if="repositoryStore.value && repositoryStore.value.branchList">
|
<split-pane
|
||||||
<div class="left">
|
class="file-detail"
|
||||||
|
v-if="repositoryStore.value && repositoryStore.value.branchList"
|
||||||
|
split="vertical"
|
||||||
|
>
|
||||||
|
<pane class="left" initialSize="25%" minSize="10%">
|
||||||
<t-select class="branch" v-model="branch" label="分支:" :borderless="true">
|
<t-select class="branch" v-model="branch" label="分支:" :borderless="true">
|
||||||
<t-option
|
<t-option
|
||||||
v-for="item in repositoryStore.value.branchList"
|
v-for="item in repositoryStore.value.branchList"
|
||||||
@ -10,12 +14,14 @@
|
|||||||
></t-option>
|
></t-option>
|
||||||
</t-select>
|
</t-select>
|
||||||
<div class="tree-container">
|
<div class="tree-container">
|
||||||
|
<loading :showOn="treeLoading" margin="1rem" />
|
||||||
<t-tree
|
<t-tree
|
||||||
ref="tree"
|
ref="tree"
|
||||||
class="tree"
|
class="tree"
|
||||||
:data="treeItems"
|
:data="treeItems"
|
||||||
:keys="treeMap"
|
:keys="treeMap"
|
||||||
:line="true"
|
:line="true"
|
||||||
|
:label="treeLabel"
|
||||||
:activable="true"
|
:activable="true"
|
||||||
:transition="false"
|
:transition="false"
|
||||||
:active-multiple="false"
|
:active-multiple="false"
|
||||||
@ -23,6 +29,7 @@
|
|||||||
:expand-parent="true"
|
:expand-parent="true"
|
||||||
value-mode="all"
|
value-mode="all"
|
||||||
expand-all
|
expand-all
|
||||||
|
:empty="() => ''"
|
||||||
:load="treeLoad"
|
:load="treeLoad"
|
||||||
:onActive="onTreeActivated"
|
:onActive="onTreeActivated"
|
||||||
>
|
>
|
||||||
@ -30,11 +37,14 @@
|
|||||||
<icon name="FILE" fill="GRAY" v-if="node.data.type === FileType.FILE" />
|
<icon name="FILE" fill="GRAY" v-if="node.data.type === FileType.FILE" />
|
||||||
<icon name="FOLDER" v-if="node.data.type === FileType.DIRECTORY" />
|
<icon name="FOLDER" v-if="node.data.type === FileType.DIRECTORY" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #operations="{ node }">
|
||||||
|
<loading v-if="node.data.isLoading" :size="14" :tips="false" margin="0 .5rem 0 0" />
|
||||||
|
</template>
|
||||||
</t-tree>
|
</t-tree>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</pane>
|
||||||
<div class="right">
|
<pane class="right" minSize="50%">
|
||||||
<div class="header">
|
<div class="header bg-white">
|
||||||
<div class="clone-label">克隆地址</div>
|
<div class="clone-label">克隆地址</div>
|
||||||
<t-select class="clone-type" v-model="cloneType" borderless>
|
<t-select class="clone-type" v-model="cloneType" borderless>
|
||||||
<t-option
|
<t-option
|
||||||
@ -54,60 +64,24 @@
|
|||||||
<t-button theme="success" @click="doCopyCloneAddr">复制</t-button>
|
<t-button theme="success" @click="doCopyCloneAddr">复制</t-button>
|
||||||
<t-button @click="RepositoryAPI.downloadArchive(repositoryStore.value.name, branch)">下载源码</t-button>
|
<t-button @click="RepositoryAPI.downloadArchive(repositoryStore.value.name, branch)">下载源码</t-button>
|
||||||
</div>
|
</div>
|
||||||
<t-tabs
|
<file-viewer
|
||||||
class="tabs"
|
v-if="branch"
|
||||||
theme="card"
|
ref="fileViewerRef"
|
||||||
v-model="tabActivated"
|
:repo-name="repositoryStore.value!.name"
|
||||||
:onRemove="onTabRemove"
|
:branch="branch"
|
||||||
>
|
/>
|
||||||
<t-tab-panel
|
</pane>
|
||||||
v-for="file in tabFiles"
|
</split-pane>
|
||||||
:key="file.path"
|
|
||||||
:value="file.path"
|
|
||||||
:label="file.name"
|
|
||||||
:removable="file.removable"
|
|
||||||
@click="onClickOpenFile(file)"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<span
|
|
||||||
v-text="file.name"
|
|
||||||
@click="onClickOpenFile(file)"
|
|
||||||
></span>
|
|
||||||
</template>
|
|
||||||
</t-tab-panel>
|
|
||||||
</t-tabs>
|
|
||||||
<div v-if="tabActivatedFileViewer"
|
|
||||||
:class="`viewer ${viewerClass}`"
|
|
||||||
@click="onClickViewer"
|
|
||||||
>
|
|
||||||
<markdown-view
|
|
||||||
v-if="tabActivatedFile && tabActivatedFileViewer !== PrismjsViewer.TEXT"
|
|
||||||
class="content selectable"
|
|
||||||
:show-code-border="false"
|
|
||||||
max-height="auto"
|
|
||||||
:content="tabActivatedFile.data"
|
|
||||||
@click="onClickViewer"
|
|
||||||
/>
|
|
||||||
<textarea
|
|
||||||
v-if="tabActivatedFile && tabActivatedFileViewer === PrismjsViewer.TEXT"
|
|
||||||
class="content selectable"
|
|
||||||
:value="tabActivatedFile.data"
|
|
||||||
readonly
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
<div v-else class="viewer not-support">
|
|
||||||
<div class="content">不支持预览</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Icon, MarkdownView, Prismjs, PrismjsType, PrismjsViewer, Toolkit } from "timi-web";
|
import { Icon, Loading } from "timi-web";
|
||||||
import type { TreeInstanceFunctions, TreeProps } from "tdesign-vue-next";
|
import type { TreeInstanceFunctions, TreeProps } from "tdesign-vue-next";
|
||||||
import RepositoryAPI from "@/api/RepositoryAPI";
|
import RepositoryAPI from "@/api/RepositoryAPI";
|
||||||
import { File, FileType } from "@/types/Repository";
|
import { File, FileType } from "@/types/Repository";
|
||||||
import { useRepositoryStore } from "@/store/repository.ts";
|
import { useRepositoryStore } from "@/store/repository.ts";
|
||||||
import useClipboard from "vue-clipboard3";
|
import useClipboard from "vue-clipboard3";
|
||||||
|
import SplitPane, { Pane } from "@marsio/vue-split-pane";
|
||||||
|
import FileViewer from "@/components/FileViewer.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toClipboard } = useClipboard();
|
const { toClipboard } = useClipboard();
|
||||||
@ -115,6 +89,14 @@ const repositoryStore = useRepositoryStore();
|
|||||||
|
|
||||||
// ---------- 分支 ----------
|
// ---------- 分支 ----------
|
||||||
const branch = ref();
|
const branch = ref();
|
||||||
|
|
||||||
|
watch(branch, async () => {
|
||||||
|
if (!branch.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await router.replace(`/${repositoryStore.value!.name}/${branch.value}`);
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (repositoryStore.value && repositoryStore.value.defaultBranch) {
|
if (repositoryStore.value && repositoryStore.value.defaultBranch) {
|
||||||
branch.value = repositoryStore.value.defaultBranch;
|
branch.value = repositoryStore.value.defaultBranch;
|
||||||
@ -123,6 +105,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// ---------- 文件树 ----------
|
// ---------- 文件树 ----------
|
||||||
const tree = ref<TreeInstanceFunctions>();
|
const tree = ref<TreeInstanceFunctions>();
|
||||||
|
const treeLoading = ref(false);
|
||||||
const treeItems = ref<File[]>([]);
|
const treeItems = ref<File[]>([]);
|
||||||
const treeMap = {
|
const treeMap = {
|
||||||
value: "path",
|
value: "path",
|
||||||
@ -133,34 +116,46 @@ watch(branch, async () => {
|
|||||||
if (!repositoryStore.value || !branch.value) {
|
if (!repositoryStore.value || !branch.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
treeLoading.value = true;
|
||||||
treeItems.value = await RepositoryAPI.listFile(repositoryStore.value!.name, branch.value, "/");
|
treeItems.value = await RepositoryAPI.listFile(repositoryStore.value!.name, branch.value, "/");
|
||||||
treeItems.value.forEach((item) => item.children = item.type === FileType.DIRECTORY);
|
treeItems.value.forEach((item) => {
|
||||||
|
item.isLoading = false;
|
||||||
|
item.children = item.type === FileType.DIRECTORY;
|
||||||
|
});
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
treeLoading.value = false;
|
||||||
// 文件树
|
// 文件树
|
||||||
if (tree.value) {
|
if (tree.value) {
|
||||||
const items = treeItems.value;
|
const items = treeItems.value;
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.path === "README.md") {
|
if (item.path === "README.md") {
|
||||||
await doOpenFile(tree.value.getItem(item.path).data as File);
|
doOpenFile(tree.value.getItem(item.path).data as File);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const treeLabel: TreeProps["label"] = (_h, node) => node.data.name;
|
||||||
const treeLoad: TreeProps["load"] = node => {
|
const treeLoad: TreeProps["load"] = async (node) => {
|
||||||
return new Promise((resolve) => {
|
const file = node.data;
|
||||||
Toolkit.async(async () => {
|
file.isLoading = true;
|
||||||
const file = node.data;
|
const result = await RepositoryAPI.listFile(
|
||||||
const result = await RepositoryAPI.listFile(
|
repositoryStore.value!.name,
|
||||||
repositoryStore.value!.name,
|
branch.value,
|
||||||
branch.value,
|
`/${file.path}`
|
||||||
`/${file.path}`
|
);
|
||||||
);
|
if (result.length === 1 && result[0].type === FileType.DIRECTORY) {
|
||||||
result.forEach((item) => item.children = item.type === FileType.DIRECTORY);
|
const deep = result[0];
|
||||||
resolve(result);
|
file.name = `${file.name}/${deep.name}`;
|
||||||
});
|
file.path = `${file.path}/${deep.name}`;
|
||||||
|
return await treeLoad!(node);
|
||||||
|
}
|
||||||
|
result.forEach((item) => {
|
||||||
|
item.isLoading = false;
|
||||||
|
item.children = item.type === FileType.DIRECTORY;
|
||||||
});
|
});
|
||||||
|
file.isLoading = false;
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------- 克隆地址 ----------
|
// ---------- 克隆地址 ----------
|
||||||
@ -185,134 +180,37 @@ async function doCopyCloneAddr() {
|
|||||||
|
|
||||||
// ---------- 打开文件 ----------
|
// ---------- 打开文件 ----------
|
||||||
|
|
||||||
type OpenFile = {
|
const fileViewerRef = ref<InstanceType<typeof FileViewer>>();
|
||||||
path: string;
|
function doOpenFile(file: File) {
|
||||||
name: string;
|
fileViewerRef.value!.openFile(file);
|
||||||
data?: string;
|
|
||||||
removable: boolean;
|
|
||||||
isPreview: boolean;
|
|
||||||
activatedAt?: number;
|
|
||||||
prismjsType?: PrismjsType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabFiles = ref<OpenFile[]>([]);
|
|
||||||
const tabActivated = ref();
|
|
||||||
const tabActivatedFile = ref<OpenFile>();
|
|
||||||
const tabActivatedFileViewer = ref<PrismjsViewer>();
|
|
||||||
|
|
||||||
const viewerClass = computed(() => {
|
|
||||||
if (tabActivatedFileViewer.value) {
|
|
||||||
return Toolkit.className(tabActivatedFileViewer.value.toString().toLowerCase());
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
watch(branch, async () => {
|
|
||||||
if (!branch.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await router.replace(`/${repositoryStore.value!.name}/${branch.value}`);
|
|
||||||
tabFiles.value = [];
|
|
||||||
tabActivated.value = null;
|
|
||||||
});
|
|
||||||
watch(tabActivated, value => {
|
|
||||||
if (tabActivated.value) {
|
|
||||||
const targetFile = tabFiles.value.find(item => item.path === value);
|
|
||||||
if (targetFile) {
|
|
||||||
targetFile.activatedAt = new Date().getTime();
|
|
||||||
tabActivatedFile.value = targetFile;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tabActivatedFile.value = undefined;
|
|
||||||
});
|
|
||||||
watch(tabActivatedFile, () => {
|
|
||||||
if (tabActivatedFile.value && tabActivatedFile.value.prismjsType) {
|
|
||||||
const properties = Prismjs.getFileProperties(tabActivatedFile.value.prismjsType);
|
|
||||||
if (properties) {
|
|
||||||
tabActivatedFileViewer.value = properties.viewer;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tabActivatedFileViewer.value = undefined;
|
|
||||||
});
|
|
||||||
const doOpenFile = async (file: File) => {
|
|
||||||
if (file.type === FileType.FILE) {
|
|
||||||
let opened = tabFiles.value.find(item => item.path === file.path);
|
|
||||||
const previewIndex = tabFiles.value.findIndex(item => item.isPreview);
|
|
||||||
const isReadme = file.path === "README.md";
|
|
||||||
if (!opened) {
|
|
||||||
opened = {
|
|
||||||
path: file.path,
|
|
||||||
name: file.name,
|
|
||||||
isPreview: !isReadme,
|
|
||||||
removable: !isReadme,
|
|
||||||
prismjsType: PrismjsType.PlainText
|
|
||||||
};
|
|
||||||
if (previewIndex === -1) {
|
|
||||||
tabFiles.value.push(opened);
|
|
||||||
} else {
|
|
||||||
// 如果存在已打开预览,则替换该预览项
|
|
||||||
tabFiles.value.splice(previewIndex, 1, opened);
|
|
||||||
}
|
|
||||||
const raw = await RepositoryAPI.fileRaw(repositoryStore.value!.name, branch.value, `/${opened.path}`);
|
|
||||||
const decoder = new TextDecoder("utf-8");
|
|
||||||
opened.prismjsType = Prismjs.typeFromFileName(opened.name);
|
|
||||||
if (opened.prismjsType) {
|
|
||||||
const properties = Prismjs.getFileProperties(opened.prismjsType);
|
|
||||||
if (properties && properties.viewer === PrismjsViewer.CODE) {
|
|
||||||
opened.data = "```" + properties.prismjs + "\n" + decoder.decode(raw) + "\n```";
|
|
||||||
} else {
|
|
||||||
opened.data = decoder.decode(raw);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
opened.data = "该文件暂不支持预览";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opened.activatedAt = new Date().getTime();
|
|
||||||
tabActivated.value = file.path;
|
|
||||||
tabActivatedFile.value = opened;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO 明确类型会构建失败
|
|
||||||
const onTreeActivated = (_value: any, context: { node: any }) => {
|
const onTreeActivated = (_value: any, context: { node: any }) => {
|
||||||
doOpenFile(context.node.data);
|
doOpenFile(context.node.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTabRemove = (options: { index: number }) => {
|
|
||||||
tabFiles.value.splice(options.index, 1);
|
|
||||||
if (0 < tabFiles.value.length) {
|
|
||||||
tabActivated.value = tabFiles.value[tabFiles.value.length - 1].path;
|
|
||||||
} else {
|
|
||||||
tabActivated.value = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickOpenFile = (openFile: OpenFile) => {
|
|
||||||
openFile.isPreview = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickViewer = async () => {
|
|
||||||
const activated = tabFiles.value.find(item => item.path === tabActivated.value);
|
|
||||||
if (activated) {
|
|
||||||
activated.isPreview = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.file-detail {
|
.file-detail {
|
||||||
// 100% 可视高度 - 上下外边距 - 边框 - 顶部 - 菜单 - 底部
|
|
||||||
height: calc(100vh - 128px * 2 - 2px - 50px - 50px - 94px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
overflow: unset;
|
||||||
|
min-height: 520px;
|
||||||
|
|
||||||
|
:deep(.vue-splitpane-resizer) {
|
||||||
|
opacity: 1;
|
||||||
|
background: var(--tui-light-gray) padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
width: 16rem;
|
width: 100%;
|
||||||
z-index: 4;
|
height: 100% !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.branch {
|
.branch {
|
||||||
|
top: 96px;
|
||||||
|
z-index: 1;
|
||||||
|
position: sticky;
|
||||||
border-bottom: var(--tui-border);
|
border-bottom: var(--tui-border);
|
||||||
|
|
||||||
:deep(.t-select-input--borderless) {
|
:deep(.t-select-input--borderless) {
|
||||||
@ -336,8 +234,8 @@ const onClickViewer = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tree-container {
|
.tree-container {
|
||||||
height: 100%;
|
top: calc(50px + 49px + 30px);
|
||||||
overflow: auto;
|
position: sticky;
|
||||||
|
|
||||||
.tree {
|
.tree {
|
||||||
|
|
||||||
@ -360,11 +258,11 @@ const onClickViewer = async () => {
|
|||||||
.right {
|
.right {
|
||||||
width: calc(100% - 16rem);
|
width: calc(100% - 16rem);
|
||||||
display: flex;
|
display: flex;
|
||||||
border-left: var(--tui-border);
|
max-height: 100% !important;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
top: calc(50px + 49px);
|
top: 96px;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
@ -395,68 +293,6 @@ const onClickViewer = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
|
||||||
border-top: var(--tui-border);
|
|
||||||
|
|
||||||
:deep(.t-tabs__nav-container.t-tabs__nav--card::after) {
|
|
||||||
background: var(--tui-light-gray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewer {
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
&.text {
|
|
||||||
|
|
||||||
.content {
|
|
||||||
width: calc(100% - 8px);
|
|
||||||
height: calc(100% - 4px);
|
|
||||||
border: none;
|
|
||||||
padding: 2px 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
word-wrap: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.code {
|
|
||||||
|
|
||||||
:deep(.tui-markdown-view) {
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 3em;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(242, 242, 242, .9);
|
|
||||||
border-right: 1px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre[class^="language-"] {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.markdown {
|
|
||||||
height: 100%;
|
|
||||||
padding: .5rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.not-support {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(pre[class*="language-"]) {
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,75 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-layout class="issue-list" v-if="repositoryStore.value">
|
<div class="issue-list" v-if="repositoryStore.value">
|
||||||
<t-header class="header">
|
<header class="header">
|
||||||
<t-row justify="space-between">
|
<div class="search">
|
||||||
<t-col flex="auto">
|
<t-input-adornment prepend="搜索">
|
||||||
<t-input-adornment prepend="搜索">
|
<t-input v-model="page.keyword" placeholder="请输入关键字" />
|
||||||
<t-input v-model="page.keyword" placeholder="请输入关键字" />
|
</t-input-adornment>
|
||||||
</t-input-adornment>
|
</div>
|
||||||
</t-col>
|
<t-space class="filter">
|
||||||
<t-col class="right">
|
<t-select v-model="page.type" label="类型" placeholder="全部" autoWidth clearable>
|
||||||
<t-space>
|
<t-option v-for="(value, key) in Type" :key="key" :value="key" :label="value" />
|
||||||
<t-select v-model="page.type" label="类型" placeholder="全部" autoWidth clearable>
|
</t-select>
|
||||||
<t-option v-for="(value, key) in Type" :key="key" :value="key" :label="value" />
|
<t-select v-model="page.status" label="状态" placeholder="全部" autoWidth clearable>
|
||||||
</t-select>
|
<t-option v-for="(value, key) in Status" :key="key" :value="key" :label="value" />
|
||||||
<t-select v-model="page.status" label="状态" placeholder="全部" autoWidth clearable>
|
</t-select>
|
||||||
<t-option v-for="(value, key) in Status" :key="key" :value="key" :label="value" />
|
<router-link :to="`/${repositoryStore.value.name}/issues/edit`">
|
||||||
</t-select>
|
<t-button theme="success">新建反馈</t-button>
|
||||||
<router-link :to="`/${repositoryStore.value.name}/issues/edit`">
|
</router-link>
|
||||||
<t-button theme="success">新建反馈</t-button>
|
</t-space>
|
||||||
</router-link>
|
</header>
|
||||||
</t-space>
|
<t-list class="issues" :split="true">
|
||||||
</t-col>
|
<loading :showOn="isLoading" margin="1rem 0" />
|
||||||
</t-row>
|
<empty-tips v-if="!isLoading && pageResult?.total === 0" />
|
||||||
</t-header>
|
<t-list-item class="issue" v-for="item in pageResult?.list" :key="item.id">
|
||||||
<t-content class="list">
|
<t-space class="tags" :size="4">
|
||||||
<t-list v-if="pageResult" :split="true">
|
<div
|
||||||
<t-list-item class="action" v-for="item in pageResult.list" :key="item.id">
|
v-if="item.type"
|
||||||
<t-layout>
|
class="tag type"
|
||||||
<t-aside width="auto">
|
:class="colorType[(<any>Type)[item.type]]"
|
||||||
<t-space class="tags" :size="4">
|
v-text="(<any>Type)[item.type]"
|
||||||
<div
|
></div>
|
||||||
v-if="item.type"
|
<div
|
||||||
class="tag type"
|
v-if="item.status"
|
||||||
:class="colorType[(<any>Type)[item.type]]"
|
class="tag status"
|
||||||
v-text="(<any>Type)[item.type]"
|
:class="colorStatus[(<any>Status)[item.status]]"
|
||||||
></div>
|
v-text="(<any>Status)[item.status]"
|
||||||
<div
|
></div>
|
||||||
v-if="item.status"
|
</t-space>
|
||||||
class="tag status"
|
<div class="content">
|
||||||
:class="colorStatus[(<any>Status)[item.status]]"
|
<router-link :to="`/${repositoryStore.value.name}/issues/${item.id}`">
|
||||||
v-text="(<any>Status)[item.status]"
|
<t-link theme="default" size="large" hover="color" :content="item.title" />
|
||||||
></div>
|
</router-link>
|
||||||
</t-space>
|
<p class="time gray">
|
||||||
</t-aside>
|
<span v-if="item.closedAt" v-text="`关闭于 ${Time.toPassedDateTime(item.closedAt)}`"></span>
|
||||||
<t-content class="content">
|
<span v-else v-text="`创建于 ${Time.toPassedDateTime(item.createdAt)}`"></span>
|
||||||
<router-link :to="`/${repositoryStore.value.name}/issues/${item.id}`">
|
</p>
|
||||||
<t-link theme="default" size="large" hover="color" :content="item.title" />
|
</div>
|
||||||
</router-link>
|
</t-list-item>
|
||||||
<p class="time gray">
|
</t-list>
|
||||||
<span v-if="item.closedAt" v-text="`关闭于 ${Time.toPassedDateTime(item.closedAt)}`"></span>
|
<footer v-if="pageResult" class="footer">
|
||||||
<span v-else v-text="`创建于 ${Time.toPassedDateTime(item.createdAt)}`"></span>
|
|
||||||
</p>
|
|
||||||
</t-content>
|
|
||||||
</t-layout>
|
|
||||||
</t-list-item>
|
|
||||||
</t-list>
|
|
||||||
<empty-tips :showOn="pageResult?.total === 0" />
|
|
||||||
</t-content>
|
|
||||||
<t-footer class="footer" height="auto">
|
|
||||||
<t-pagination
|
<t-pagination
|
||||||
v-if="pageResult"
|
|
||||||
:total="pageResult.total"
|
:total="pageResult.total"
|
||||||
:pageSize="16"
|
:pageSize="16"
|
||||||
:showPageSize="false"
|
:showPageSize="false"
|
||||||
:onCurrentChange="(current: number) => page.index = current - 1"
|
:onCurrentChange="(current: number) => page.index = current - 1"
|
||||||
/>
|
/>
|
||||||
</t-footer>
|
</footer>
|
||||||
</t-layout>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { EmptyTips, PageResult, Time, Toolkit } from "timi-web";
|
import { EmptyTips, Loading, PageResult, Time, Toolkit } from "timi-web";
|
||||||
import IssueAPI from "@/api/IssueAPI";
|
import IssueAPI from "@/api/IssueAPI";
|
||||||
import { Issue, Page, Status, Type } from "@/types/Issue";
|
import { Issue, Page, Status, Type } from "@/types/Issue";
|
||||||
import { useRepositoryStore } from "@/store/repository.ts";
|
import { useRepositoryStore } from "@/store/repository.ts";
|
||||||
@ -96,10 +86,13 @@ const page = reactive<Page>({
|
|||||||
index: 0,
|
index: 0,
|
||||||
size: 12
|
size: 12
|
||||||
});
|
});
|
||||||
|
const isLoading = ref(false);
|
||||||
const pageResult = ref<PageResult<Issue>>();
|
const pageResult = ref<PageResult<Issue>>();
|
||||||
|
const fetchList = Toolkit.debounce(async () => {
|
||||||
const fetchList = Toolkit.debounce(async () => pageResult.value = await IssueAPI.page(page));
|
isLoading.value = true;
|
||||||
|
pageResult.value = await IssueAPI.page(page);
|
||||||
|
isLoading.value = false;
|
||||||
|
});
|
||||||
watch(page, fetchList);
|
watch(page, fetchList);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (repositoryStore.value) {
|
if (repositoryStore.value) {
|
||||||
@ -111,12 +104,15 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.issue-list {
|
.issue-list {
|
||||||
min-height: 480px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
top: calc(50px + 49px);
|
top: 96px;
|
||||||
z-index: 3;
|
height: 50px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
|
z-index: 3;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
background: rgba(231, 234, 239, .8);
|
background: rgba(231, 234, 239, .8);
|
||||||
@ -125,43 +121,49 @@ onMounted(async () => {
|
|||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
|
||||||
.t-row {
|
.search {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
padding: 0 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.issues {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
.tags {
|
.issue {
|
||||||
display: flex;
|
|
||||||
padding-top: 1px;
|
|
||||||
|
|
||||||
.tag {
|
:deep(.t-list-item-main) {
|
||||||
padding: 1px 5px;
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
&.type {
|
.tags {
|
||||||
color: #FFF;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding-top: 1px;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
padding: 1px 5px;
|
||||||
|
|
||||||
|
&.type {
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-left: .5rem;
|
flex: 1;
|
||||||
|
padding-left: .5rem;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,10 +178,4 @@ onMounted(async () => {
|
|||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 992px) {
|
|
||||||
.issue-list {
|
|
||||||
min-height: calc(100vh - 100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,90 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-layout v-if="repositoryStore.value" class="merge-list">
|
<div v-if="repositoryStore.value" class="merge-list">
|
||||||
<t-header class="header">
|
<header class="header">
|
||||||
<t-row justify="space-between">
|
<div class="search">
|
||||||
<t-col flex="auto">
|
<t-input-adornment prepend="搜索">
|
||||||
<t-input-adornment prepend="搜索">
|
<t-input v-model="page.keyword" placeholder="请输入关键字" />
|
||||||
<t-input v-model="page.keyword" placeholder="请输入关键字" />
|
</t-input-adornment>
|
||||||
</t-input-adornment>
|
</div>
|
||||||
</t-col>
|
<t-space class="filter">
|
||||||
<t-col class="right">
|
<t-select v-model="page.type" label="类型" placeholder="全部" autoWidth clearable>
|
||||||
<t-space>
|
<t-option v-for="(value, key) in Type" :key="key" :value="key" :label="value" />
|
||||||
<t-select
|
</t-select>
|
||||||
v-model="page.type"
|
<t-select v-model="page.status" label="状态" placeholder="全部" autoWidth clearable>
|
||||||
label="类型"
|
<t-option v-for="(value, key) in Status" :key="key" :value="key" :label="value" />
|
||||||
placeholder="全部"
|
</t-select>
|
||||||
autoWidth
|
<router-link :to="`/${repositoryStore.value.name}/merges/edit`">
|
||||||
clearable
|
<t-button theme="success">申请合并</t-button>
|
||||||
>
|
</router-link>
|
||||||
<t-option v-for="(value, key) in Type" :key="key" :value="key" :label="value" />
|
</t-space>
|
||||||
</t-select>
|
</header>
|
||||||
<t-select
|
<t-list class="merges" v-if="pageResult" :split="true">
|
||||||
v-model="page.status"
|
<loading :showOn="isLoading" margin="1rem 0" />
|
||||||
label="状态"
|
<empty-tips v-if="!isLoading && pageResult?.total === 0" />
|
||||||
placeholder="全部"
|
<t-list-item class="merge" v-for="item in pageResult.list" :key="item.id">
|
||||||
autoWidth
|
<t-space class="tags" :size="4">
|
||||||
clearable
|
<div
|
||||||
>
|
v-if="item.type"
|
||||||
<t-option v-for="(value, key) in Status" :key="key" :value="key" :label="value" />
|
class="tag type"
|
||||||
</t-select>
|
:class="colorType[(<any>Type)[item.type]]"
|
||||||
<router-link :to="`/${repositoryStore.value.name}/merges/edit`">
|
v-text="(<any>Type)[item.type]"
|
||||||
<t-button theme="success">申请合并</t-button>
|
></div>
|
||||||
</router-link>
|
<div
|
||||||
</t-space>
|
v-if="item.status"
|
||||||
</t-col>
|
class="tag status"
|
||||||
</t-row>
|
:class="colorStatus[(<any>Status)[item.status]]"
|
||||||
</t-header>
|
v-text="(<any>Status)[item.status]"
|
||||||
<t-content class="list">
|
></div>
|
||||||
<t-list v-if="pageResult" :split="true">
|
</t-space>
|
||||||
<t-list-item class="action" v-for="item in pageResult.list" :key="item.id">
|
<div class="content">
|
||||||
<t-layout>
|
<router-link :to="`/${repositoryStore.value.name}/merges/${item.id}`">
|
||||||
<t-aside width="auto">
|
<t-link theme="default" size="large" hover="color" :content="item.title" />
|
||||||
<t-space class="tags" :size="4">
|
</router-link>
|
||||||
<div
|
<p class="time gray">
|
||||||
v-if="item.type"
|
<span v-if="item.rejectedAt" v-text="`拒绝于 ${Time.toPassedDateTime(item.rejectedAt)}`"></span>
|
||||||
class="tag type"
|
<span v-else v-text="`申请于 ${Time.toPassedDateTime(item.createdAt)}`"></span>
|
||||||
:class="colorType[(<any>Type)[item.type]]"
|
</p>
|
||||||
v-text="(<any>Type)[item.type]"
|
</div>
|
||||||
></div>
|
</t-list-item>
|
||||||
<div
|
</t-list>
|
||||||
v-if="item.status"
|
<footer v-if="pageResult" class="footer">
|
||||||
class="tag status"
|
|
||||||
:class="colorStatus[(<any>Status)[item.status]]"
|
|
||||||
v-text="(<any>Status)[item.status]"
|
|
||||||
></div>
|
|
||||||
</t-space>
|
|
||||||
</t-aside>
|
|
||||||
<t-content class="content">
|
|
||||||
<router-link :to="`/${repositoryStore.value.name}/merges/${item.id}`">
|
|
||||||
<t-link theme="default" size="large" hover="color" :content="item.title" />
|
|
||||||
</router-link>
|
|
||||||
<p class="time gray">
|
|
||||||
<span
|
|
||||||
v-if="item.rejectedAt"
|
|
||||||
v-text="`拒绝于 ${Time.toPassedDateTime(item.rejectedAt)}`"
|
|
||||||
></span>
|
|
||||||
<span v-else v-text="`申请于 ${Time.toPassedDateTime(item.createdAt)}`"></span>
|
|
||||||
</p>
|
|
||||||
</t-content>
|
|
||||||
</t-layout>
|
|
||||||
</t-list-item>
|
|
||||||
</t-list>
|
|
||||||
<empty-tips :showOn="pageResult?.total === 0" />
|
|
||||||
</t-content>
|
|
||||||
<t-footer class="footer" height="auto">
|
|
||||||
<t-pagination
|
<t-pagination
|
||||||
v-if="pageResult"
|
|
||||||
:total="pageResult.total"
|
:total="pageResult.total"
|
||||||
:pageSize="16"
|
:pageSize="16"
|
||||||
:showPageSize="false"
|
:showPageSize="false"
|
||||||
:onCurrentChange="(current: number) => page.index = current - 1"
|
:onCurrentChange="(current: number) => page.index = current - 1"
|
||||||
/>
|
/>
|
||||||
</t-footer>
|
</footer>
|
||||||
</t-layout>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { EmptyTips, PageResult, Time, Toolkit } from "timi-web";
|
import { EmptyTips, Loading, PageResult, Time, Toolkit } from "timi-web";
|
||||||
import MergeAPI from "@/api/MergeAPI";
|
import MergeAPI from "@/api/MergeAPI";
|
||||||
import { Merge, Page, Status, Type } from "@/types/Merge";
|
import { Merge, Page, Status, Type } from "@/types/Merge";
|
||||||
import { useRepositoryStore } from "@/store/repository.ts";
|
import { useRepositoryStore } from "@/store/repository.ts";
|
||||||
@ -105,11 +80,13 @@ const colorStatus = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const repositoryStore = useRepositoryStore();
|
const repositoryStore = useRepositoryStore();
|
||||||
|
|
||||||
const page = reactive<Page>({
|
const page = reactive<Page>({
|
||||||
repositoryId: undefined,
|
repositoryId: undefined,
|
||||||
index: 0,
|
index: 0,
|
||||||
size: 12
|
size: 12
|
||||||
});
|
});
|
||||||
|
const isLoading = ref(false);
|
||||||
const pageResult = ref<PageResult<Merge>>();
|
const pageResult = ref<PageResult<Merge>>();
|
||||||
const fetchList = Toolkit.debounce(async () => pageResult.value = await MergeAPI.page(page));
|
const fetchList = Toolkit.debounce(async () => pageResult.value = await MergeAPI.page(page));
|
||||||
|
|
||||||
@ -124,11 +101,13 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.merge-list {
|
.merge-list {
|
||||||
min-height: 480px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
top: calc(50px + 49px);
|
top: 96px;
|
||||||
z-index: 3;
|
height: 50px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
@ -138,43 +117,49 @@ onMounted(async () => {
|
|||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
|
||||||
.t-row {
|
.search {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
padding: 0 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.merges {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
.tags {
|
.merge {
|
||||||
display: flex;
|
|
||||||
padding-top: 1px;
|
|
||||||
|
|
||||||
.tag {
|
:deep(.t-list-item-main) {
|
||||||
padding: 1px 5px;
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
&.type {
|
.tags {
|
||||||
color: #FFF;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding-top: 1px;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
padding: 1px 5px;
|
||||||
|
|
||||||
|
&.type {
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-left: .5rem;
|
flex: 1;
|
||||||
|
padding-left: .5rem;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,15 +170,8 @@ onMounted(async () => {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
background: rgba(255, 255, 255, .8);
|
background: rgba(255, 255, 255, .8);
|
||||||
border-top: var(--tui-border);
|
border-top: var(--tui-border);
|
||||||
border-top: var(--tui-border);
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 992px) {
|
|
||||||
.merge-list {
|
|
||||||
min-height: calc(100vh - 100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -37,14 +37,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</t-timeline-item>
|
</t-timeline-item>
|
||||||
|
<loading :showOn="isLoading" />
|
||||||
|
<empty-tips v-if="!isLoading && Toolkit.isEmpty(pageResult?.list)" />
|
||||||
</t-timeline>
|
</t-timeline>
|
||||||
<empty-tips :showOn="Toolkit.isEmpty(pageResult?.list)" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ReleaseAPI from "@/api/ReleaseAPI";
|
import ReleaseAPI from "@/api/ReleaseAPI";
|
||||||
import { Page, Release } from "@/types/Release";
|
import { Page, Release } from "@/types/Release";
|
||||||
import { CommonAPI, EmptyTips, Icon, IOSize, MarkdownView, PageResult, Time, Toolkit } from "timi-web";
|
import { CommonAPI, EmptyTips, Icon, IOSize, Loading, MarkdownView, PageResult, Time, Toolkit } from "timi-web";
|
||||||
import { useRepositoryStore } from "@/store/repository.ts";
|
import { useRepositoryStore } from "@/store/repository.ts";
|
||||||
|
|
||||||
const repositoryStore = useRepositoryStore();
|
const repositoryStore = useRepositoryStore();
|
||||||
@ -54,13 +55,14 @@ const page = reactive<Page>({
|
|||||||
index: 0,
|
index: 0,
|
||||||
size: 12
|
size: 12
|
||||||
});
|
});
|
||||||
|
const isLoading = ref(false);
|
||||||
const pageResult = ref<PageResult<Release>>();
|
const pageResult = ref<PageResult<Release>>();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (repositoryStore.value) {
|
isLoading.value = true;
|
||||||
page.repositoryId = repositoryStore.value.id;
|
page.repositoryId = repositoryStore.value!.id;
|
||||||
pageResult.value = await ReleaseAPI.page(page);
|
pageResult.value = await ReleaseAPI.page(page);
|
||||||
}
|
isLoading.value = false;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@ -121,10 +123,4 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 992px) {
|
|
||||||
.release-list {
|
|
||||||
min-height: calc(100vh - 98px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user