新增:基础组件
This commit is contained in:
parent
25ab98b90a
commit
7ceb5c4e38
279
design/pencil-new.pen
Normal file
279
design/pencil-new.pen
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
{
|
||||||
|
"version": "2.6",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "bi8Au",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"name": "Login Page",
|
||||||
|
"theme": {
|
||||||
|
"Mode": "Light",
|
||||||
|
"Base": "Neutral",
|
||||||
|
"Accent": "Default"
|
||||||
|
},
|
||||||
|
"clip": true,
|
||||||
|
"width": 800,
|
||||||
|
"height": 600,
|
||||||
|
"fill": "#f8b6b6ff",
|
||||||
|
"layout": "none",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "loginCard",
|
||||||
|
"x": 216,
|
||||||
|
"y": 36,
|
||||||
|
"width": 305,
|
||||||
|
"height": 533,
|
||||||
|
"fill": "#f9ebebff",
|
||||||
|
"cornerRadius": 8,
|
||||||
|
"stroke": {
|
||||||
|
"align": "inside",
|
||||||
|
"thickness": 1,
|
||||||
|
"fill": "#000000"
|
||||||
|
},
|
||||||
|
"layout": "vertical",
|
||||||
|
"gap": 24,
|
||||||
|
"padding": 24,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "logoContainer",
|
||||||
|
"width": "fill_container",
|
||||||
|
"layout": "vertical",
|
||||||
|
"justifyContent": "center",
|
||||||
|
"alignItems": "center",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "logo",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "PolyMarket",
|
||||||
|
"lineHeight": 1.2,
|
||||||
|
"textAlign": "center",
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 24,
|
||||||
|
"fontWeight": "600"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "titleContainer",
|
||||||
|
"width": "fill_container",
|
||||||
|
"layout": "vertical",
|
||||||
|
"gap": 8,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "title",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "Sign in to your account",
|
||||||
|
"lineHeight": 1.5,
|
||||||
|
"textAlign": "center",
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 18,
|
||||||
|
"fontWeight": "500"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "subtitle",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "Enter your email and password to sign in",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlign": "center",
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "normal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "emailInputGroup",
|
||||||
|
"width": "fill_container",
|
||||||
|
"layout": "vertical",
|
||||||
|
"gap": 6,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "emailLabelContainer",
|
||||||
|
"width": "fill_container",
|
||||||
|
"alignItems": "center",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "emailLabel",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "Email",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "500"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "emailInput",
|
||||||
|
"width": "fill_container",
|
||||||
|
"height": 40,
|
||||||
|
"fill": "#000000",
|
||||||
|
"cornerRadius": 6,
|
||||||
|
"stroke": {
|
||||||
|
"align": "inside",
|
||||||
|
"thickness": 1,
|
||||||
|
"fill": "#000000"
|
||||||
|
},
|
||||||
|
"padding": [
|
||||||
|
8,
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "emailPlaceholder",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "your.email@example.com",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "normal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "passwordInputGroup",
|
||||||
|
"width": "fill_container",
|
||||||
|
"layout": "vertical",
|
||||||
|
"gap": 6,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "W4jnH",
|
||||||
|
"width": "fill_container",
|
||||||
|
"justifyContent": "space_between",
|
||||||
|
"alignItems": "center",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "passwordLabel",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "Password",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "500"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "forgotPassword",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "Forgot password?",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlign": "right",
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "normal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "passwordInput",
|
||||||
|
"width": "fill_container",
|
||||||
|
"height": 40,
|
||||||
|
"fill": "#000000",
|
||||||
|
"cornerRadius": 6,
|
||||||
|
"stroke": {
|
||||||
|
"align": "inside",
|
||||||
|
"thickness": 1,
|
||||||
|
"fill": "#000000"
|
||||||
|
},
|
||||||
|
"padding": [
|
||||||
|
8,
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "passwordPlaceholder",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "••••••••",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "normal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "signInButton",
|
||||||
|
"width": "fill_container",
|
||||||
|
"height": 40,
|
||||||
|
"fill": "#000000",
|
||||||
|
"cornerRadius": 6,
|
||||||
|
"gap": 6,
|
||||||
|
"padding": [
|
||||||
|
8,
|
||||||
|
16
|
||||||
|
],
|
||||||
|
"justifyContent": "center",
|
||||||
|
"alignItems": "center",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "signInText",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "Sign in",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlign": "center",
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "500"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "createAccountContainer",
|
||||||
|
"width": "fill_container",
|
||||||
|
"layout": "vertical",
|
||||||
|
"justifyContent": "center",
|
||||||
|
"alignItems": "center",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "createAccount",
|
||||||
|
"fill": "#000000",
|
||||||
|
"content": "Create account",
|
||||||
|
"lineHeight": 1.4285714285714286,
|
||||||
|
"textAlign": "center",
|
||||||
|
"textAlignVertical": "middle",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "normal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
215
design/pencil-polymarket.pen
Normal file
215
design/pencil-polymarket.pen
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
{
|
||||||
|
"version": "2.6",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "simpleMarketCard",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"name": "Simple Market Card",
|
||||||
|
"theme": {
|
||||||
|
"Mode": "Light",
|
||||||
|
"Base": "Neutral",
|
||||||
|
"Accent": "Default"
|
||||||
|
},
|
||||||
|
"enabled": false,
|
||||||
|
"clip": true,
|
||||||
|
"width": 350,
|
||||||
|
"height": 200,
|
||||||
|
"fill": "#ffffffff",
|
||||||
|
"layout": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "marketCard",
|
||||||
|
"x": 20,
|
||||||
|
"y": 20,
|
||||||
|
"width": 310,
|
||||||
|
"height": 160,
|
||||||
|
"fill": "#ffffffff",
|
||||||
|
"cornerRadius": 8,
|
||||||
|
"stroke": {
|
||||||
|
"align": "inside",
|
||||||
|
"thickness": 1,
|
||||||
|
"fill": "#e7e7e7ff"
|
||||||
|
},
|
||||||
|
"layout": "none",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "marketImage",
|
||||||
|
"x": 12,
|
||||||
|
"y": 12,
|
||||||
|
"width": 40,
|
||||||
|
"height": 40,
|
||||||
|
"fill": "#f0f0f0",
|
||||||
|
"cornerRadius": 4,
|
||||||
|
"layout": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "marketTitle",
|
||||||
|
"x": 60,
|
||||||
|
"y": 12,
|
||||||
|
"name": "Mamdan opens city-owned grocery store b...",
|
||||||
|
"fill": "#000000",
|
||||||
|
"textGrowth": "fixed-width-height",
|
||||||
|
"width": 169,
|
||||||
|
"height": 16,
|
||||||
|
"content": "Mamdan opens city-owned grocery store b...",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 14,
|
||||||
|
"fontWeight": "500"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "chanceContainer",
|
||||||
|
"x": 258,
|
||||||
|
"y": 12,
|
||||||
|
"width": 42,
|
||||||
|
"height": 40,
|
||||||
|
"layout": "none",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "progressBar",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 40,
|
||||||
|
"height": 40,
|
||||||
|
"fill": "#ffffff",
|
||||||
|
"cornerRadius": 20,
|
||||||
|
"stroke": {
|
||||||
|
"align": "center",
|
||||||
|
"thickness": 4,
|
||||||
|
"fill": "#e0e0e0"
|
||||||
|
},
|
||||||
|
"layout": "none",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "progressFill",
|
||||||
|
"x": 2,
|
||||||
|
"y": 2,
|
||||||
|
"width": 36,
|
||||||
|
"height": 36,
|
||||||
|
"fill": "#ffffff",
|
||||||
|
"cornerRadius": 18,
|
||||||
|
"stroke": {
|
||||||
|
"align": "center",
|
||||||
|
"thickness": 4,
|
||||||
|
"fill": "#ff0000"
|
||||||
|
},
|
||||||
|
"layout": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "chanceValue",
|
||||||
|
"x": 10,
|
||||||
|
"y": 12,
|
||||||
|
"fill": "#ff0000",
|
||||||
|
"content": "17%",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontWeight": "600"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "chanceLabel",
|
||||||
|
"x": 0,
|
||||||
|
"y": 42,
|
||||||
|
"fill": "#808080",
|
||||||
|
"content": "chance",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 10,
|
||||||
|
"fontWeight": "normal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "optionYes",
|
||||||
|
"x": 12,
|
||||||
|
"y": 72,
|
||||||
|
"width": 149,
|
||||||
|
"height": 40,
|
||||||
|
"fill": "#e6f9e6",
|
||||||
|
"cornerRadius": 4,
|
||||||
|
"layout": "none",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "optionTextYes",
|
||||||
|
"x": 61,
|
||||||
|
"y": 10,
|
||||||
|
"fill": "#008000",
|
||||||
|
"content": "Yes",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontWeight": "500"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "frame",
|
||||||
|
"id": "optionNo",
|
||||||
|
"x": 166,
|
||||||
|
"y": 72,
|
||||||
|
"width": 132,
|
||||||
|
"height": 40,
|
||||||
|
"fill": "#ffe6e6",
|
||||||
|
"cornerRadius": 4,
|
||||||
|
"layout": "none",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "optionTextNo",
|
||||||
|
"x": 55,
|
||||||
|
"y": 10,
|
||||||
|
"fill": "#ff0000",
|
||||||
|
"content": "No",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontWeight": "500"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "marketInfo",
|
||||||
|
"x": 12,
|
||||||
|
"y": 124,
|
||||||
|
"fill": "#808080",
|
||||||
|
"content": "$155k Vol.",
|
||||||
|
"fontFamily": "Inter",
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontWeight": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "icon_font",
|
||||||
|
"id": "giftIcon",
|
||||||
|
"x": 240,
|
||||||
|
"y": 124,
|
||||||
|
"width": 16,
|
||||||
|
"height": 16,
|
||||||
|
"iconFontName": "gift",
|
||||||
|
"iconFontFamily": "mdi",
|
||||||
|
"fill": "#808080"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "icon_font",
|
||||||
|
"id": "bookmarkIcon",
|
||||||
|
"x": 270,
|
||||||
|
"y": 124,
|
||||||
|
"width": 16,
|
||||||
|
"height": 16,
|
||||||
|
"iconFontName": "bookmark",
|
||||||
|
"iconFontFamily": "mdi",
|
||||||
|
"fill": "#808080"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
5676
design/pencil-shadcn.pen
Normal file
5676
design/pencil-shadcn.pen
Normal file
File diff suppressed because it is too large
Load Diff
37
package-lock.json
generated
37
package-lock.json
generated
@ -8,9 +8,11 @@
|
|||||||
"name": "polyclientvuetify",
|
"name": "polyclientvuetify",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mdi/font": "^7.4.47",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.27",
|
"vue": "^3.5.27",
|
||||||
"vue-router": "^5.0.1"
|
"vue-router": "^5.0.1",
|
||||||
|
"vuetify": "^4.0.0-beta.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.58.1",
|
"@playwright/test": "^1.58.1",
|
||||||
@ -1445,6 +1447,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mdi/font": {
|
||||||
|
"version": "7.4.47",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@mdi/font/-/font-7.4.47.tgz",
|
||||||
|
"integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -6820,6 +6828,33 @@
|
|||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vuetify": {
|
||||||
|
"version": "4.0.0-beta.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vuetify/-/vuetify-4.0.0-beta.0.tgz",
|
||||||
|
"integrity": "sha512-efBGozz9obv7bAqqFgSC1PAxO2NZltJzor+EDZx0Qxp8GssvIOzMQoMnK3axRbldIa5yfWdPAsbZ+lO8tJxgjg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/johnleider"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.7",
|
||||||
|
"vite-plugin-vuetify": ">=2.1.0",
|
||||||
|
"vue": "^3.5.0",
|
||||||
|
"webpack-plugin-vuetify": ">=3.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite-plugin-vuetify": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"webpack-plugin-vuetify": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-xmlserializer": {
|
"node_modules/w3c-xmlserializer": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||||
|
|||||||
@ -17,9 +17,11 @@
|
|||||||
"format": "prettier --write --experimental-cli src/"
|
"format": "prettier --write --experimental-cli src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mdi/font": "^7.4.47",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.27",
|
"vue": "^3.5.27",
|
||||||
"vue-router": "^5.0.1"
|
"vue-router": "^5.0.1",
|
||||||
|
"vuetify": "^4.0.0-beta.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.58.1",
|
"@playwright/test": "^1.58.1",
|
||||||
|
|||||||
35
src/App.vue
35
src/App.vue
@ -1,11 +1,32 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const currentRoute = computed(() => {
|
||||||
|
return route.path
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1>You did it!</h1>
|
<v-app>
|
||||||
<p>
|
<v-app-bar color="primary" dark>
|
||||||
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
|
<v-app-bar-title>PolyMarket</v-app-bar-title>
|
||||||
documentation
|
<v-spacer></v-spacer>
|
||||||
</p>
|
<v-btn text to="/" :class="{ active: currentRoute === '/' }"> Home </v-btn>
|
||||||
|
<v-btn text to="/trade" :class="{ active: currentRoute === '/trade' }"> Trade </v-btn>
|
||||||
|
</v-app-bar>
|
||||||
|
<v-main>
|
||||||
|
<router-view />
|
||||||
|
</v-main>
|
||||||
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
/* Global styles can be added here */
|
||||||
|
.active {
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
72
src/components/HorizontalProgressBar.vue
Normal file
72
src/components/HorizontalProgressBar.vue
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div class="horizontal-progress-bar" :class="className">
|
||||||
|
<div class="progress-track" :style="trackStyle"></div>
|
||||||
|
<div class="progress-fill" :style="{ ...fillStyle, width: `${progressPercentage}%` }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 最大值
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
// 当前进度值
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
// 自定义类名
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 轨道样式
|
||||||
|
trackStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// 填充样式
|
||||||
|
fillStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算进度百分比
|
||||||
|
const progressPercentage = computed(() => {
|
||||||
|
return Math.min(100, Math.max(0, (props.value / props.max) * 100))
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.horizontal-progress-bar {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-track {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #0066cc;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
199
src/components/MarketCard.vue
Normal file
199
src/components/MarketCard.vue
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="market-card" elevation="0" :rounded="'lg'">
|
||||||
|
<!-- Market Image -->
|
||||||
|
<v-avatar class="market-image" :size="40" :color="'#f0f0f0'" :rounded="'sm'">
|
||||||
|
<!-- Placeholder for market image -->
|
||||||
|
</v-avatar>
|
||||||
|
|
||||||
|
<!-- Market Title -->
|
||||||
|
<v-card-title class="market-title" :text="true" :shrink="true">
|
||||||
|
{{ marketTitle }}
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<!-- Chance Container -->
|
||||||
|
<div class="chance-container">
|
||||||
|
<v-progress-circular
|
||||||
|
class="progress-bar"
|
||||||
|
:size="60"
|
||||||
|
:width="4"
|
||||||
|
:value="chanceValue"
|
||||||
|
:color="progressColor"
|
||||||
|
:background="'#e0e0e0'"
|
||||||
|
>
|
||||||
|
<template v-slot:default>
|
||||||
|
<span class="chance-value">{{ chanceValue }}%</span>
|
||||||
|
</template>
|
||||||
|
</v-progress-circular>
|
||||||
|
<span class="chance-label">chance</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Options -->
|
||||||
|
<v-btn
|
||||||
|
class="option-yes"
|
||||||
|
:width="140"
|
||||||
|
:height="40"
|
||||||
|
:color="'#e6f9e6'"
|
||||||
|
:rounded="'sm'"
|
||||||
|
:text="true"
|
||||||
|
>
|
||||||
|
<span class="option-text-yes">Yes</span>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="option-no"
|
||||||
|
:width="140"
|
||||||
|
:height="40"
|
||||||
|
:color="'#ffe6e6'"
|
||||||
|
:rounded="'sm'"
|
||||||
|
:text="true"
|
||||||
|
>
|
||||||
|
<span class="option-text-no">No</span>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- Market Info -->
|
||||||
|
<span class="market-info">{{ marketInfo }}</span>
|
||||||
|
<v-icon class="gift-icon" size="16">mdi-gift</v-icon>
|
||||||
|
<v-icon class="bookmark-icon" size="16">mdi-bookmark</v-icon>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
marketTitle: {
|
||||||
|
type: String,
|
||||||
|
default: 'Mamdan opens city-owned grocery store b...',
|
||||||
|
},
|
||||||
|
chanceValue: {
|
||||||
|
type: Number,
|
||||||
|
default: 17,
|
||||||
|
},
|
||||||
|
marketInfo: {
|
||||||
|
type: String,
|
||||||
|
default: '$155k Vol.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算进度条颜色,从红色(0%)到绿色(100%)
|
||||||
|
const progressColor = computed(() => {
|
||||||
|
// 红色在HSL中是0度,绿色是120度
|
||||||
|
const hue = (props.chanceValue / 100) * 120
|
||||||
|
return `hsl(${hue}, 100%, 50%)`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.market-card {
|
||||||
|
position: relative;
|
||||||
|
width: 310px;
|
||||||
|
height: 160px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e7e7e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-image {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-title {
|
||||||
|
position: absolute;
|
||||||
|
left: 60px;
|
||||||
|
top: 12px;
|
||||||
|
width: 180px;
|
||||||
|
max-height: 32px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #000000;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chance-container {
|
||||||
|
position: absolute;
|
||||||
|
left: 248px;
|
||||||
|
top: 10px;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chance-value {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chance-label {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #808080;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-yes {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 72px;
|
||||||
|
background-color: #e6f9e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text-yes {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #008000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-no {
|
||||||
|
position: absolute;
|
||||||
|
left: 158px;
|
||||||
|
top: 72px;
|
||||||
|
background-color: #ffe6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text-no {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-info {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 124px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 240px;
|
||||||
|
top: 124px;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 270px;
|
||||||
|
top: 124px;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
486
src/components/OrderBook.vue
Normal file
486
src/components/OrderBook.vue
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="order-book">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="order-book-header">
|
||||||
|
<h3 class="order-book-title">Order Book</h3>
|
||||||
|
<div class="order-book-vol">
|
||||||
|
$4.4k Vol.
|
||||||
|
<v-icon size="14" class="order-book-icon">mdi-chevron-up</v-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Trade Tabs -->
|
||||||
|
<div class="trade-tabs-container">
|
||||||
|
<v-tabs v-model="activeTrade" class="trade-tabs" density="comfortable">
|
||||||
|
<v-tab value="up" class="trade-tab">Trade Up</v-tab>
|
||||||
|
<v-tab value="down" class="trade-tab">Trade Down</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
<div class="maker-rebate">
|
||||||
|
<span class="maker-rebate-text">Maker Rebate</span>
|
||||||
|
<v-icon size="14">mdi-refresh</v-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Order Book Content -->
|
||||||
|
<div class="order-book-content">
|
||||||
|
<!-- Order List -->
|
||||||
|
<div class="order-list">
|
||||||
|
<div class="order-list-header">
|
||||||
|
<div class="order-list-header-price">PRICE</div>
|
||||||
|
<div class="order-list-header-shares">SHARES</div>
|
||||||
|
<div class="order-list-header-total">TOTAL</div>
|
||||||
|
</div>
|
||||||
|
<!-- Asks Orders -->
|
||||||
|
<div class="asks-label">Asks</div>
|
||||||
|
<div v-for="(ask, index) in asksWithCumulativeTotal" :key="index" class="order-item">
|
||||||
|
<div class="order-progress">
|
||||||
|
<HorizontalProgressBar
|
||||||
|
:max="maxAsksTotal"
|
||||||
|
:value="ask.total"
|
||||||
|
:fillStyle="{ backgroundColor: '#ffb3ba' }"
|
||||||
|
:trackStyle="{ backgroundColor: 'transparent' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="order-price asks-price">{{ ask.price }}¢</div>
|
||||||
|
<div class="order-shares">{{ ask.shares.toFixed(2) }}</div>
|
||||||
|
<div class="order-total">{{ ask.cumulativeTotal.toFixed(2) }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- Bids Orders -->
|
||||||
|
<div class="bids-label">Bids</div>
|
||||||
|
<div
|
||||||
|
v-for="(bid, index) in bidsWithCumulativeTotal"
|
||||||
|
:key="index"
|
||||||
|
class="order-item bids-item"
|
||||||
|
>
|
||||||
|
<div class="order-progress">
|
||||||
|
<HorizontalProgressBar
|
||||||
|
:max="maxBidsTotal"
|
||||||
|
:value="bid.total"
|
||||||
|
:fillStyle="{ backgroundColor: '#b3ffb3' }"
|
||||||
|
:trackStyle="{ backgroundColor: 'transparent' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="order-price bids-price">{{ bid.price }}¢</div>
|
||||||
|
<div class="order-shares">{{ bid.shares.toFixed(2) }}</div>
|
||||||
|
<div class="order-total">{{ bid.cumulativeTotal.toFixed(2) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="order-book-footer">
|
||||||
|
<div class="last-price">Last: {{ lastPrice }}¢</div>
|
||||||
|
<div class="spread">Spread: {{ spread }}¢</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import HorizontalProgressBar from './HorizontalProgressBar.vue'
|
||||||
|
|
||||||
|
// State
|
||||||
|
const activeTrade = ref('up')
|
||||||
|
const lastPrice = ref(37)
|
||||||
|
const spread = ref(1)
|
||||||
|
|
||||||
|
// Mock asks data (卖出订单)
|
||||||
|
const asks = ref([
|
||||||
|
{ price: 45, shares: 1000.0 },
|
||||||
|
{ price: 44, shares: 2500.0 },
|
||||||
|
{ price: 43, shares: 1800.0 },
|
||||||
|
{ price: 42, shares: 3200.0 },
|
||||||
|
{ price: 41, shares: 2000.0 },
|
||||||
|
{ price: 40, shares: 1500.0 },
|
||||||
|
{ price: 39, shares: 800.0 },
|
||||||
|
{ price: 38, shares: 500.0 },
|
||||||
|
{ price: 37, shares: 300.0 },
|
||||||
|
])
|
||||||
|
|
||||||
|
// Mock bids data (买入订单)
|
||||||
|
const bids = ref([
|
||||||
|
{ price: 36, shares: 200.0 },
|
||||||
|
{ price: 35, shares: 500.0 },
|
||||||
|
{ price: 34, shares: 1000.0 },
|
||||||
|
{ price: 33, shares: 1500.0 },
|
||||||
|
{ price: 32, shares: 2000.0 },
|
||||||
|
{ price: 31, shares: 2500.0 },
|
||||||
|
{ price: 30, shares: 3000.0 },
|
||||||
|
{ price: 29, shares: 2800.0 },
|
||||||
|
{ price: 28, shares: 2500.0 },
|
||||||
|
{ price: 27, shares: 2000.0 },
|
||||||
|
{ price: 26, shares: 1500.0 },
|
||||||
|
{ price: 25, shares: 1000.0 },
|
||||||
|
])
|
||||||
|
|
||||||
|
// Simulate dynamic data updates
|
||||||
|
setInterval(() => {
|
||||||
|
// Update random ask price and shares
|
||||||
|
const randomAskIndex = Math.floor(Math.random() * asks.value.length)
|
||||||
|
asks.value[randomAskIndex] = {
|
||||||
|
...asks.value[randomAskIndex],
|
||||||
|
shares: Math.max(0, asks.value[randomAskIndex].shares + Math.floor(Math.random() * 100) - 50),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update random bid price and shares
|
||||||
|
const randomBidIndex = Math.floor(Math.random() * bids.value.length)
|
||||||
|
bids.value[randomBidIndex] = {
|
||||||
|
...bids.value[randomBidIndex],
|
||||||
|
shares: Math.max(0, bids.value[randomBidIndex].shares + Math.floor(Math.random() * 100) - 50),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last price
|
||||||
|
lastPrice.value = lastPrice.value + Math.floor(Math.random() * 3) - 1
|
||||||
|
|
||||||
|
// Update spread
|
||||||
|
spread.value = spread.value + Math.floor(Math.random() * 2) - 1
|
||||||
|
if (spread.value < 1) spread.value = 1
|
||||||
|
}, 2000) // Update every 2 seconds
|
||||||
|
|
||||||
|
// Calculate cumulative total for asks
|
||||||
|
const asksWithCumulativeTotal = computed(() => {
|
||||||
|
let cumulativeTotal = 0
|
||||||
|
// Sort asks by price in descending order
|
||||||
|
const sortedAsks = [...asks.value].sort((a, b) => a.price - b.price)
|
||||||
|
|
||||||
|
return sortedAsks
|
||||||
|
.map((ask) => {
|
||||||
|
// Calculate current ask's value
|
||||||
|
const askValue = (ask.price * ask.shares) / 100 // Convert cents to dollars
|
||||||
|
cumulativeTotal += askValue
|
||||||
|
return {
|
||||||
|
...ask,
|
||||||
|
total: cumulativeTotal, // Use calculated total instead of fixed value
|
||||||
|
cumulativeTotal,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.price - a.price)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Calculate cumulative total for bids
|
||||||
|
const bidsWithCumulativeTotal = computed(() => {
|
||||||
|
let cumulativeTotal = 0
|
||||||
|
const sortedBids = [...bids.value].sort((a, b) => b.price - a.price)
|
||||||
|
|
||||||
|
return sortedBids.map((bid) => {
|
||||||
|
// Calculate current bid's value
|
||||||
|
const bidValue = (bid.price * bid.shares) / 100 // Convert cents to dollars
|
||||||
|
cumulativeTotal += bidValue
|
||||||
|
return {
|
||||||
|
...bid,
|
||||||
|
total: cumulativeTotal, // Use calculated total instead of fixed value
|
||||||
|
cumulativeTotal,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Reverse to display in descending order
|
||||||
|
})
|
||||||
|
|
||||||
|
// Calculate max total from cumulative totals
|
||||||
|
const maxAsksTotal = computed(() => {
|
||||||
|
const askTotals = asksWithCumulativeTotal.value.map((item) => item.cumulativeTotal)
|
||||||
|
const allTotals = [...askTotals]
|
||||||
|
return Math.max(...allTotals)
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxBidsTotal = computed(() => {
|
||||||
|
const bidTotals = bidsWithCumulativeTotal.value.map((item) => item.cumulativeTotal)
|
||||||
|
const allTotals = [...bidTotals]
|
||||||
|
return Math.max(...allTotals)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.order-book {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-progress-bar {
|
||||||
|
height: 33px !important;
|
||||||
|
border-radius: 0px !important;
|
||||||
|
}
|
||||||
|
.progress-fill {
|
||||||
|
border-radius: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-book-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-book-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-book-vol {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666666;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-book-icon {
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-tabs-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16px 12px 16px;
|
||||||
|
border-bottom: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-tabs {
|
||||||
|
border-bottom: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-tab {
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: none;
|
||||||
|
color: #666666;
|
||||||
|
min-width: 120px;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-tab.v-tab--active {
|
||||||
|
color: #0066cc;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-tab:not(.v-tab--active) {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maker-rebate {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maker-rebate-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ff9800;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-book-content {
|
||||||
|
display: flex;
|
||||||
|
padding: 12px 16px;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.depth-chart {
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.depth-chart-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.depth-bar-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.depth-bar {
|
||||||
|
border-radius: 2px 0 0 2px;
|
||||||
|
height: 80%;
|
||||||
|
min-width: 4px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asks-bar {
|
||||||
|
background-color: #ffb3ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bids-bar {
|
||||||
|
background-color: #b3ffb3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-header-price,
|
||||||
|
.order-list-header-shares,
|
||||||
|
.order-list-header-total {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666666;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-header-price {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-header-shares {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-header-total {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asks-label,
|
||||||
|
.bids-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 8px 0 4px 0;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asks-label {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bids-label {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #008000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0px 0;
|
||||||
|
border-bottom: none;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-progress {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asks-price {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bids-price {
|
||||||
|
color: #008000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-price {
|
||||||
|
width: 80px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-shares {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-total {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bids-item {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-book-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-price,
|
||||||
|
.spread {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Adjustments */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.order-book-content {
|
||||||
|
padding: 8px 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-progress {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.depth-chart {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-header-price {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-header-total {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-price {
|
||||||
|
width: 60px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-shares {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-total {
|
||||||
|
width: 80px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
756
src/components/TradeComponent.vue
Normal file
756
src/components/TradeComponent.vue
Normal file
@ -0,0 +1,756 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="trade-component">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact">
|
||||||
|
<v-tab value="buy" class="minimal-tab">Buy</v-tab>
|
||||||
|
<v-tab value="sell" class="minimal-tab">Sell</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
<v-menu>
|
||||||
|
<template v-slot:activator="{ props, isActive }">
|
||||||
|
<v-btn v-bind="props" class="limit-btn" text end>
|
||||||
|
{{ limitType }}
|
||||||
|
<v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item @click="limitType = 'Market'">
|
||||||
|
<v-list-item-title>Market</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item @click="limitType = 'Limit'">
|
||||||
|
<v-list-item-title>Limit</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-menu append :location="'end'">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-list-item v-bind="props" @click.stop>
|
||||||
|
<v-list-item-title>More</v-list-item-title>
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-icon icon="mdi-chevron-right" size="x-small"></v-icon>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>Merge</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title>Split</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Market Mode View -->
|
||||||
|
<template v-if="isMarketMode">
|
||||||
|
<!-- Balance > 0: Show Trade Interface -->
|
||||||
|
<template v-if="balance > 0">
|
||||||
|
<!-- Price Options -->
|
||||||
|
<div class="price-options">
|
||||||
|
<v-btn
|
||||||
|
class="yes-btn"
|
||||||
|
:class="{ active: selectedOption === 'yes' }"
|
||||||
|
text
|
||||||
|
@click="handleOptionChange('yes')"
|
||||||
|
>
|
||||||
|
Yes {{ selectedOption === 'yes' ? '19¢' : '18¢' }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="no-btn"
|
||||||
|
:class="{ active: selectedOption === 'no' }"
|
||||||
|
text
|
||||||
|
@click="handleOptionChange('no')"
|
||||||
|
>
|
||||||
|
No {{ selectedOption === 'no' ? '82¢' : '81¢' }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Total and To Win (Buy模式) 或 You'll receive (Sell模式) -->
|
||||||
|
<div class="total-section">
|
||||||
|
<!-- Buy模式 -->
|
||||||
|
<template v-if="activeTab === 'buy'">
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="label">Total</span>
|
||||||
|
<span class="total-value">${{ totalPrice }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="label">To win</span>
|
||||||
|
<span class="to-win-value">
|
||||||
|
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
|
$20
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- Sell模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="label">You'll receive</span>
|
||||||
|
<span class="to-win-value">
|
||||||
|
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
|
${{ totalPrice }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Button -->
|
||||||
|
<v-btn class="action-btn">
|
||||||
|
{{ actionButtonText }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Balance <= 0: Show Deposit Interface -->
|
||||||
|
<template v-else>
|
||||||
|
<!-- Price Options -->
|
||||||
|
<div class="price-options">
|
||||||
|
<v-btn
|
||||||
|
class="yes-btn"
|
||||||
|
:class="{ active: selectedOption === 'yes' }"
|
||||||
|
text
|
||||||
|
@click="handleOptionChange('yes')"
|
||||||
|
>
|
||||||
|
Yes {{ selectedOption === 'yes' ? '19¢' : '18¢' }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="no-btn"
|
||||||
|
:class="{ active: selectedOption === 'no' }"
|
||||||
|
text
|
||||||
|
@click="handleOptionChange('no')"
|
||||||
|
>
|
||||||
|
No {{ selectedOption === 'no' ? '82¢' : '81¢' }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Amount Section -->
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="amount-header">
|
||||||
|
<div>
|
||||||
|
<span class="label amount-label">Amount</span>
|
||||||
|
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Amount Buttons -->
|
||||||
|
<div class="amount-buttons">
|
||||||
|
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
|
||||||
|
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
|
||||||
|
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
|
||||||
|
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Deposit Button -->
|
||||||
|
<v-btn
|
||||||
|
class="deposit-btn"
|
||||||
|
@click="deposit"
|
||||||
|
>
|
||||||
|
Deposit
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Limit Mode View -->
|
||||||
|
<template v-else>
|
||||||
|
<!-- Price Options -->
|
||||||
|
<div class="price-options">
|
||||||
|
<v-btn
|
||||||
|
class="yes-btn"
|
||||||
|
:class="{ active: selectedOption === 'yes' }"
|
||||||
|
text
|
||||||
|
@click="handleOptionChange('yes')"
|
||||||
|
>
|
||||||
|
Yes {{ selectedOption === 'yes' ? '19¢' : '18¢' }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="no-btn"
|
||||||
|
:class="{ active: selectedOption === 'no' }"
|
||||||
|
text
|
||||||
|
@click="handleOptionChange('no')"
|
||||||
|
>
|
||||||
|
No {{ selectedOption === 'no' ? '82¢' : '81¢' }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Limit Price -->
|
||||||
|
<div class="input-group limit-price-group">
|
||||||
|
<div class="limit-price-header">
|
||||||
|
<span class="label">Limit Price</span>
|
||||||
|
<div class="price-input">
|
||||||
|
<v-btn class="adjust-btn" icon @click="decreasePrice">
|
||||||
|
<v-icon>mdi-minus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="limitPrice"
|
||||||
|
type="number"
|
||||||
|
min="0.01"
|
||||||
|
step="0.01"
|
||||||
|
class="price-input-field"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
></v-text-field>
|
||||||
|
<v-btn class="adjust-btn" icon @click="increasePrice">
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Shares -->
|
||||||
|
<div class="input-group shares-group">
|
||||||
|
<div class="shares-header">
|
||||||
|
<span class="label">Shares</span>
|
||||||
|
<div class="shares-input">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="shares"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
class="shares-input-field"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
></v-text-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Buy模式的份额调整按钮 -->
|
||||||
|
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
||||||
|
<v-btn class="share-btn" @click="adjustShares(-100)">-100</v-btn>
|
||||||
|
<v-btn class="share-btn" @click="adjustShares(-10)">-10</v-btn>
|
||||||
|
<v-btn class="share-btn" @click="adjustShares(10)">+10</v-btn>
|
||||||
|
<v-btn class="share-btn" @click="adjustShares(100)">+100</v-btn>
|
||||||
|
<v-btn class="share-btn" @click="adjustShares(200)">+200</v-btn>
|
||||||
|
</div>
|
||||||
|
<!-- Sell模式的份额调整按钮 -->
|
||||||
|
<div v-else class="shares-buttons">
|
||||||
|
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||||||
|
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||||
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||||
|
</div>
|
||||||
|
<div v-if="activeTab === 'buy'" class="matching-info">
|
||||||
|
<v-icon size="14">mdi-information</v-icon>
|
||||||
|
<span>20.00 matching</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Set Expiration -->
|
||||||
|
<div class="input-group expiration-group">
|
||||||
|
<div class="expiration-header">
|
||||||
|
<span class="label">Set Expiration</span>
|
||||||
|
<v-switch v-model="expirationEnabled" class="expiration-switch" hide-details></v-switch>
|
||||||
|
</div>
|
||||||
|
<!-- Expiration Time Dropdown -->
|
||||||
|
<v-select
|
||||||
|
v-if="expirationEnabled"
|
||||||
|
v-model="expirationTime"
|
||||||
|
:items="expirationOptions"
|
||||||
|
class="expiration-select"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
></v-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Total and To Win (Buy模式) 或 You'll receive (Sell模式) -->
|
||||||
|
<div class="total-section">
|
||||||
|
<!-- Buy模式 -->
|
||||||
|
<template v-if="activeTab === 'buy'">
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="label">Total</span>
|
||||||
|
<span class="total-value">${{ totalPrice }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="label">To win</span>
|
||||||
|
<span class="to-win-value">
|
||||||
|
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
|
$20
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- Sell模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="total-row">
|
||||||
|
<span class="label">You'll receive</span>
|
||||||
|
<span class="to-win-value">
|
||||||
|
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
|
${{ totalPrice }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Button -->
|
||||||
|
<v-btn class="action-btn">
|
||||||
|
{{ actionButtonText }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
// State
|
||||||
|
const activeTab = ref('buy')
|
||||||
|
const limitType = ref('Limit')
|
||||||
|
const expirationEnabled = ref(false)
|
||||||
|
const selectedOption = ref('no') // 'yes' or 'no'
|
||||||
|
const limitPrice = ref(0.82) // 初始限价,单位:美元
|
||||||
|
const shares = ref(20) // 初始份额
|
||||||
|
const expirationTime = ref('5m') // 初始过期时间
|
||||||
|
const expirationOptions = ref(['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d']) // 过期时间选项
|
||||||
|
|
||||||
|
// Market mode state
|
||||||
|
const isMarketMode = computed(() => limitType.value === 'Market')
|
||||||
|
const amount = ref(0) // Market mode amount
|
||||||
|
const balance = ref(0) // Market mode balance
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(['optionChange'])
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
const currentPrice = computed(() => {
|
||||||
|
return `${(limitPrice.value * 100).toFixed(0)}¢`
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalPrice = computed(() => {
|
||||||
|
return (limitPrice.value * shares.value).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const actionButtonText = computed(() => {
|
||||||
|
return `${activeTab.value} ${selectedOption.value.charAt(0).toUpperCase() + selectedOption.value.slice(1)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const handleOptionChange = (option: 'yes' | 'no') => {
|
||||||
|
selectedOption.value = option
|
||||||
|
// 切换选项时更新限价
|
||||||
|
if (option === 'yes') {
|
||||||
|
limitPrice.value = 0.19
|
||||||
|
} else {
|
||||||
|
limitPrice.value = 0.82
|
||||||
|
}
|
||||||
|
emit('optionChange', option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限价调整方法
|
||||||
|
const decreasePrice = () => {
|
||||||
|
limitPrice.value = Math.max(0.01, limitPrice.value - 0.01)
|
||||||
|
}
|
||||||
|
|
||||||
|
const increasePrice = () => {
|
||||||
|
limitPrice.value += 0.01
|
||||||
|
}
|
||||||
|
|
||||||
|
// 份额调整方法
|
||||||
|
const adjustShares = (amount: number) => {
|
||||||
|
shares.value = Math.max(0, shares.value + amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 份额百分比调整方法(仅在Sell模式下使用)
|
||||||
|
const setSharesPercentage = (percentage: number) => {
|
||||||
|
// 假设最大份额为100,实际应用中可能需要根据用户的可用份额来计算
|
||||||
|
const maxShares = 100
|
||||||
|
shares.value = Math.round((maxShares * percentage) / 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Market mode methods
|
||||||
|
const adjustAmount = (value: number) => {
|
||||||
|
amount.value += value
|
||||||
|
if (amount.value < 0) amount.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMaxAmount = () => {
|
||||||
|
amount.value = balance.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const deposit = () => {
|
||||||
|
console.log('Depositing amount:', amount.value)
|
||||||
|
// 实际应用中,这里应该调用存款API
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.trade-component {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-sell-tabs {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimal-tabs {
|
||||||
|
--tabs-height: 32px !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimal-tab {
|
||||||
|
height: 32px !important;
|
||||||
|
min-height: 32px !important;
|
||||||
|
padding: 4px 12px !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除了.tab-btn样式,使用v-tabs的默认样式 */
|
||||||
|
|
||||||
|
.limit-btn {
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: none;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes-btn {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes-btn.active {
|
||||||
|
background-color: #e6f9e6;
|
||||||
|
color: #008000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-btn {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #000000;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-btn.active {
|
||||||
|
background-color: #ff0000;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduce height for Set Expiration section */
|
||||||
|
.expiration-group {
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Limit Price header flex layout */
|
||||||
|
.limit-price-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust label margin for flex layout */
|
||||||
|
.limit-price-header .label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust price input width for flex layout */
|
||||||
|
.limit-price-header .price-input {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 200px;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666666;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adjust-btn {
|
||||||
|
color: #666666;
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-input-field {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shares header flex layout */
|
||||||
|
.shares-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust label margin for flex layout */
|
||||||
|
.shares-header .label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust shares input width for flex layout */
|
||||||
|
.shares-header .shares-input {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 200px;
|
||||||
|
margin-left: 12px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shares-input {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shares-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shares-input-field {
|
||||||
|
width: 100%;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shares-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-btn {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: none;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matching-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expiration-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expiration-switch {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expiration-select {
|
||||||
|
margin-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-section {
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.to-win-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #0066cc;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Market Mode Styles */
|
||||||
|
.amount-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-label {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999999;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-btn {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: none;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deposit-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #0066cc;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式调整 - 小屏幕设备 */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.header {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-options {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-section {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-value,
|
||||||
|
.shares-value,
|
||||||
|
.total-value,
|
||||||
|
.to-win-value {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-btn {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-label {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-btn {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deposit-btn {
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,10 +3,12 @@ import { createPinia } from 'pinia'
|
|||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import vuetify from './plugins/vuetify'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(vuetify)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
43
src/plugins/vuetify.ts
Normal file
43
src/plugins/vuetify.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { createVuetify } from 'vuetify'
|
||||||
|
import * as components from 'vuetify/components'
|
||||||
|
import * as directives from 'vuetify/directives'
|
||||||
|
import 'vuetify/styles'
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
|
||||||
|
export default createVuetify({
|
||||||
|
components,
|
||||||
|
directives,
|
||||||
|
theme: {
|
||||||
|
defaultTheme: 'light',
|
||||||
|
themes: {
|
||||||
|
light: {
|
||||||
|
dark: false,
|
||||||
|
colors: {
|
||||||
|
primary: '#1A73E8',
|
||||||
|
secondary: '#5F6368',
|
||||||
|
accent: '#34A853',
|
||||||
|
error: '#EA4335',
|
||||||
|
info: '#4285F4',
|
||||||
|
success: '#34A853',
|
||||||
|
warning: '#FBBC05',
|
||||||
|
surface: '#FFFFFF',
|
||||||
|
background: '#F5F5F5'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
dark: true,
|
||||||
|
colors: {
|
||||||
|
primary: '#1E88E5',
|
||||||
|
secondary: '#9E9E9E',
|
||||||
|
accent: '#4CAF50',
|
||||||
|
error: '#F44336',
|
||||||
|
info: '#2196F3',
|
||||||
|
success: '#4CAF50',
|
||||||
|
warning: '#FFC107',
|
||||||
|
surface: '#1E1E1E',
|
||||||
|
background: '#121212'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -1,8 +1,21 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import Home from '../views/Home.vue'
|
||||||
|
import Trade from '../views/Trade.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [],
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/trade',
|
||||||
|
name: 'trade',
|
||||||
|
component: Trade
|
||||||
|
}
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
93
src/views/Home.vue
Normal file
93
src/views/Home.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="home-container">
|
||||||
|
<v-row justify="center" align="center" class="home-header">
|
||||||
|
<h1 class="home-title">PolyMarket</h1>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row justify="center" align="center" class="home-subtitle">
|
||||||
|
<p>Welcome to PolyMarket - Your Gateway to Decentralized Trading</p>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row justify="center" align="center" class="home-content">
|
||||||
|
<v-col cols="12" md="8" lg="6">
|
||||||
|
<v-card class="home-card">
|
||||||
|
<v-card-title>Market Overview</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<p>Explore the latest market data and trading opportunities.</p>
|
||||||
|
<v-btn
|
||||||
|
class="home-btn"
|
||||||
|
color="primary"
|
||||||
|
@click="navigateToTrade"
|
||||||
|
>
|
||||||
|
Go to Trading
|
||||||
|
</v-btn>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const navigateToTrade = () => {
|
||||||
|
// Navigate to trade page (we'll add this route later)
|
||||||
|
console.log('Navigate to trade page')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.home-container {
|
||||||
|
padding: 40px 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-title {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #0066cc;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-subtitle {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-subtitle p {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #666666;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-content {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-card-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-card-text {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-btn {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/views/Trade.vue
Normal file
45
src/views/Trade.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="trade-container">
|
||||||
|
<v-row justify="center" align="center" class="trade-header">
|
||||||
|
<h1 class="trade-title">Trading</h1>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row justify="center" align="center" class="trade-content">
|
||||||
|
<v-col cols="12" md="10" lg="8">
|
||||||
|
<v-card class="trade-card">
|
||||||
|
<v-card-title>Order Book</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<OrderBook />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import OrderBook from '../components/OrderBook.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.trade-container {
|
||||||
|
padding: 40px 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-header {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #0066cc;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user