feat: 添加交易方式配置,更新相关组件以支持限价和市价委托
This commit is contained in:
@@ -94,6 +94,8 @@ export type EaringsDetailData = Treaty.Data<typeof client.api.earnings.details.p
|
|||||||
|
|
||||||
export type EaringsDetailBody = TreatyBody<typeof client.api.earnings.details.post>;
|
export type EaringsDetailBody = TreatyBody<typeof client.api.earnings.details.post>;
|
||||||
|
|
||||||
|
export type SpotOrderBody = TreatyBody<typeof client.api.spot_order.create.post>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用版本信息
|
* 应用版本信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
9
src/views/trade/components/confirm-order.vue
Normal file
9
src/views/trade/components/confirm-order.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script lang='ts' setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
Hello world
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang='css' scoped></style>
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
type OrderType = "limit" | "market";
|
|
||||||
type TradeAction = "buy" | "sell";
|
|
||||||
|
|
||||||
interface TradeFormData {
|
|
||||||
orderType: OrderType;
|
|
||||||
price: string;
|
|
||||||
amount: string;
|
|
||||||
total: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
action: TradeAction;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const formData = reactive<TradeFormData>({
|
|
||||||
orderType: "limit",
|
|
||||||
price: "",
|
|
||||||
amount: "",
|
|
||||||
total: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: 后续从API获取用户余额
|
|
||||||
// const { data: balance } = await client.api.wallet.balances.get({ query: { accountType: 'trading' } })
|
|
||||||
const availableBalance = ref({
|
|
||||||
base: "0.00000000", // BTC
|
|
||||||
quote: "10000.00", // USDT
|
|
||||||
});
|
|
||||||
|
|
||||||
const isBuy = computed(() => props.action === "buy");
|
|
||||||
const buttonColor = computed(() => isBuy.value ? "success" : "danger");
|
|
||||||
const buttonText = computed(() => isBuy.value ? "买入 BTC" : "卖出 BTC");
|
|
||||||
|
|
||||||
// 计算总价
|
|
||||||
watch(() => [formData.price, formData.amount], () => {
|
|
||||||
if (formData.price && formData.amount) {
|
|
||||||
const price = Number.parseFloat(formData.price);
|
|
||||||
const amount = Number.parseFloat(formData.amount);
|
|
||||||
if (!Number.isNaN(price) && !Number.isNaN(amount)) {
|
|
||||||
formData.total = (price * amount).toFixed(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 根据总价计算数量
|
|
||||||
watch(() => formData.total, (newTotal) => {
|
|
||||||
if (newTotal && formData.price) {
|
|
||||||
const total = Number.parseFloat(newTotal);
|
|
||||||
const price = Number.parseFloat(formData.price);
|
|
||||||
if (!Number.isNaN(total) && !Number.isNaN(price) && price > 0) {
|
|
||||||
formData.amount = (total / price).toFixed(8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function setPercentage(percent: number) {
|
|
||||||
if (isBuy.value) {
|
|
||||||
// 买入:根据USDT余额计算
|
|
||||||
const balance = Number.parseFloat(availableBalance.value.quote);
|
|
||||||
const total = balance * percent;
|
|
||||||
formData.total = total.toFixed(2);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 卖出:根据BTC余额计算
|
|
||||||
const balance = Number.parseFloat(availableBalance.value.base);
|
|
||||||
formData.amount = (balance * percent).toFixed(8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
// TODO: 后续调用交易API
|
|
||||||
// await client.api.trading.order.post({
|
|
||||||
// symbol: 'BTC_USDT',
|
|
||||||
// side: props.action,
|
|
||||||
// type: formData.orderType,
|
|
||||||
// price: formData.orderType === 'limit' ? formData.price : undefined,
|
|
||||||
// quantity: formData.amount
|
|
||||||
// })
|
|
||||||
console.log("提交订单", {
|
|
||||||
action: props.action,
|
|
||||||
...formData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="trade-form">
|
|
||||||
<!-- 订单类型切换 -->
|
|
||||||
<div class="order-type-tabs">
|
|
||||||
<button
|
|
||||||
:class="{ active: formData.orderType === 'limit' }"
|
|
||||||
@click="formData.orderType = 'limit'"
|
|
||||||
>
|
|
||||||
限价
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
:class="{ active: formData.orderType === 'market' }"
|
|
||||||
@click="formData.orderType = 'market'"
|
|
||||||
>
|
|
||||||
市价
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 可用余额 -->
|
|
||||||
<div class="balance-info">
|
|
||||||
<span class="label">可用</span>
|
|
||||||
<span class="value">
|
|
||||||
{{ isBuy ? availableBalance.quote : availableBalance.base }}
|
|
||||||
{{ isBuy ? 'USDT' : 'BTC' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 价格输入 -->
|
|
||||||
<div v-if="formData.orderType === 'limit'" class="input-group">
|
|
||||||
<label>价格</label>
|
|
||||||
<div class="input-wrapper">
|
|
||||||
<input
|
|
||||||
v-model="formData.price"
|
|
||||||
type="number"
|
|
||||||
placeholder="请输入价格"
|
|
||||||
>
|
|
||||||
<span class="unit">USDT</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 数量输入 -->
|
|
||||||
<div class="input-group">
|
|
||||||
<label>数量</label>
|
|
||||||
<div class="input-wrapper">
|
|
||||||
<input
|
|
||||||
v-model="formData.amount"
|
|
||||||
type="number"
|
|
||||||
placeholder="请输入数量"
|
|
||||||
>
|
|
||||||
<span class="unit">BTC</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 百分比选择 -->
|
|
||||||
<div class="percentage-buttons">
|
|
||||||
<button @click="setPercentage(0.25)">
|
|
||||||
25%
|
|
||||||
</button>
|
|
||||||
<button @click="setPercentage(0.5)">
|
|
||||||
50%
|
|
||||||
</button>
|
|
||||||
<button @click="setPercentage(0.75)">
|
|
||||||
75%
|
|
||||||
</button>
|
|
||||||
<button @click="setPercentage(1)">
|
|
||||||
100%
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 总额 -->
|
|
||||||
<div class="input-group">
|
|
||||||
<label>总额</label>
|
|
||||||
<div class="input-wrapper">
|
|
||||||
<input
|
|
||||||
v-model="formData.total"
|
|
||||||
type="number"
|
|
||||||
placeholder="请输入总额"
|
|
||||||
>
|
|
||||||
<span class="unit">USDT</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 提交按钮 -->
|
|
||||||
<ion-button
|
|
||||||
expand="block"
|
|
||||||
:color="buttonColor"
|
|
||||||
class="submit-button"
|
|
||||||
@click="handleSubmit"
|
|
||||||
>
|
|
||||||
{{ buttonText }}
|
|
||||||
</ion-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.trade-form {
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-type-tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-type-tabs button {
|
|
||||||
flex: 1;
|
|
||||||
padding: 8px 0;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--ion-text-color-step-400);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-type-tabs button.active {
|
|
||||||
color: var(--ion-color-primary);
|
|
||||||
border-bottom-color: var(--ion-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--ion-text-color-step-400);
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-info .value {
|
|
||||||
color: var(--ion-text-color);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--ion-text-color-step-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--ion-color-light);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper input {
|
|
||||||
flex: 1;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--ion-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper input::placeholder {
|
|
||||||
color: var(--ion-text-color-step-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper .unit {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--ion-text-color-step-400);
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.percentage-buttons {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.percentage-buttons button {
|
|
||||||
padding: 6px 0;
|
|
||||||
background: var(--ion-color-light);
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--ion-text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.percentage-buttons button:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
background: var(--ion-color-light-shade);
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-button {
|
|
||||||
margin-top: 8px;
|
|
||||||
--border-radius: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,19 +1,71 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
import type { TradeWayConfig } from "../config";
|
||||||
|
import type { SpotOrderBody } from "@/api/types";
|
||||||
|
import { modalController } from "@ionic/vue";
|
||||||
import { caretDownOutline } from "ionicons/icons";
|
import { caretDownOutline } from "ionicons/icons";
|
||||||
|
import { tradeWayConfig } from "../config";
|
||||||
|
|
||||||
|
const model = defineModel({ type: Object as PropType<SpotOrderBody>, required: true });
|
||||||
|
|
||||||
|
function onSelectTradeWay(item: TradeWayConfig) {
|
||||||
|
model.value.orderType = item.value;
|
||||||
|
modalController.dismiss();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-faint flex items-center justify-between px-4 py-2 rounded-md">
|
<div id="open-modal" class="bg-faint flex items-center justify-between px-4 py-2 rounded-md">
|
||||||
<div class="text-xs font-medium text-text-300">
|
<div class="text-xs font-medium text-text-300">
|
||||||
市场
|
市场
|
||||||
</div>
|
</div>
|
||||||
<ion-icon :icon="caretDownOutline" />
|
<ion-icon :icon="caretDownOutline" />
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-faint flex items-center justify-between px-4 py-2 rounded-md">
|
|
||||||
<div class="text-xs font-medium text-text-400">
|
<ion-modal trigger="open-modal" :breakpoints="[0, 0.8]" :initial-breakpoint="0.8" handle>
|
||||||
市场价格
|
<div class="ion-padding">
|
||||||
|
<div class="text-xs pb-3 text-text-300">
|
||||||
|
基础委托
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div v-for="(item, index) in tradeWayConfig" :key="index" class="flex items-center" @click="onSelectTradeWay(item)">
|
||||||
|
<div class="flex items-center flex-1">
|
||||||
|
<Icon :icon="item.icon" class="text-2xl mr-4" />
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div class="text-base font-semibold">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-text-500">
|
||||||
|
{{ item.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="model.orderType === item.value" class="text-primary">
|
||||||
|
<Icon icon="ic:sharp-check-circle" class="text-2xl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Icon icon="ic:twotone-more-horiz" class="text-2xl mr-4" />
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div class="text-sm text-text-500">
|
||||||
|
更多委托类型,敬请期待
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ion-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='css' scoped></style>
|
<style lang='css' scoped>
|
||||||
|
ion-input.count {
|
||||||
|
--background: var(--ion-color-faint);
|
||||||
|
--padding-start: 16px;
|
||||||
|
--padding-end: 16px;
|
||||||
|
--padding-top: 4px;
|
||||||
|
--padding-bottom: 4px;
|
||||||
|
--border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
48
src/views/trade/config.ts
Normal file
48
src/views/trade/config.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
export enum TradeWayValueEnum {
|
||||||
|
LIMIT = "limit",
|
||||||
|
MARKET = "market",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TradeWayValue = `${TradeWayValueEnum}`;
|
||||||
|
|
||||||
|
export interface TradeWayConfig {
|
||||||
|
name: string;
|
||||||
|
value: TradeWayValue;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tradeWayConfig: TradeWayConfig[] = [
|
||||||
|
{
|
||||||
|
name: "限价委托",
|
||||||
|
value: "limit",
|
||||||
|
description: "以指定价格买入或卖出",
|
||||||
|
icon: "hugeicons:trade-up",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const confirmOrderSchema = yup.object({
|
||||||
|
price: yup.number().when("way", {
|
||||||
|
is: TradeWayValueEnum.LIMIT !== undefined,
|
||||||
|
then: yup
|
||||||
|
.number()
|
||||||
|
.typeError("请输入有效的价格")
|
||||||
|
.required("价格为必填项")
|
||||||
|
.moreThan(0, "价格必须大于0"),
|
||||||
|
otherwise: yup.number().notRequired(),
|
||||||
|
}),
|
||||||
|
amount: yup
|
||||||
|
.number()
|
||||||
|
.typeError("请输入有效的数量")
|
||||||
|
.required("数量为必填项")
|
||||||
|
.moreThan(0, "数量必须大于0"),
|
||||||
|
way: yup
|
||||||
|
.mixed<TradeWayValue>()
|
||||||
|
.oneOf(
|
||||||
|
Object.values(TradeWayValueEnum),
|
||||||
|
"请选择有效的交易方式",
|
||||||
|
)
|
||||||
|
.required("交易方式为必填项"),
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ChartingLibraryWidgetOptions } from "#/charting_library";
|
import type { ChartingLibraryWidgetOptions } from "#/charting_library";
|
||||||
|
import type { SpotOrderBody } from "@/api/types";
|
||||||
import type { TradingViewInst } from "@/tradingview/index";
|
import type { TradingViewInst } from "@/tradingview/index";
|
||||||
import { modalController } from "@ionic/vue";
|
import { modalController } from "@ionic/vue";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
@@ -12,6 +13,7 @@ import OrdersPanel from "./components/orders-panel.vue";
|
|||||||
import TradePairsModal from "./components/trade-pairs-modal.vue";
|
import TradePairsModal from "./components/trade-pairs-modal.vue";
|
||||||
import TradeSwitch from "./components/trade-switch.vue";
|
import TradeSwitch from "./components/trade-switch.vue";
|
||||||
import TradeWay from "./components/trade-way.vue";
|
import TradeWay from "./components/trade-way.vue";
|
||||||
|
import { confirmOrderSchema, TradeWayValueEnum } from "./config";
|
||||||
|
|
||||||
const mode = useRouteQuery<TradeTypeEnum>("mode", TradeTypeEnum.BUY);
|
const mode = useRouteQuery<TradeTypeEnum>("mode", TradeTypeEnum.BUY);
|
||||||
const symbol = useRouteQuery<string>("symbol", "BTCUSD");
|
const symbol = useRouteQuery<string>("symbol", "BTCUSD");
|
||||||
@@ -22,6 +24,14 @@ const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
const tradingViewInst = useTemplateRef<TradingViewInst>("tradingViewInst");
|
const tradingViewInst = useTemplateRef<TradingViewInst>("tradingViewInst");
|
||||||
|
const [form] = useResetRef<SpotOrderBody>({
|
||||||
|
orderType: TradeWayValueEnum.LIMIT,
|
||||||
|
quantity: "",
|
||||||
|
side: mode.value,
|
||||||
|
symbol: symbol.value,
|
||||||
|
memo: "",
|
||||||
|
price: "",
|
||||||
|
});
|
||||||
|
|
||||||
async function openTradePairs() {
|
async function openTradePairs() {
|
||||||
const modal = await modalController.create({
|
const modal = await modalController.create({
|
||||||
@@ -39,6 +49,14 @@ async function openTradePairs() {
|
|||||||
symbol.value = result;
|
symbol.value = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
confirmOrderSchema.validate(form.value).then(() => {
|
||||||
|
console.log("submit successfully");
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log("submit failed:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -69,14 +87,20 @@ async function openTradePairs() {
|
|||||||
|
|
||||||
<div class="grid grid-cols-5 px-4">
|
<div class="grid grid-cols-5 px-4">
|
||||||
<div class="col-span-3 space-y-2">
|
<div class="col-span-3 space-y-2">
|
||||||
<TradeSwitch v-model:active="mode" />
|
<TradeSwitch v-model:active="mode" @update:active="val => form.side = val" />
|
||||||
<TradeWay />
|
<TradeWay v-model="form" />
|
||||||
<ion-input label="金额" class="count" inputmode="decimal" type="number" placeholder="请输入交易金额">
|
<template v-if="form.orderType === 'limit'">
|
||||||
|
<ion-input v-model="form.price" label="价格" class="count" inputmode="decimal" type="number" placeholder="请输入价格(USDT)" />
|
||||||
|
</template>
|
||||||
|
<ion-input v-model="form.quantity" label="数量" class="count" inputmode="decimal" type="number" placeholder="请输入交易数量">
|
||||||
|
<span slot="end">{{ symbol }}</span>
|
||||||
|
</ion-input>
|
||||||
|
<ion-input v-model="form.price" label="金额" class="count" inputmode="decimal" type="number" placeholder="请输入交易金额">
|
||||||
<span slot="end">USDT</span>
|
<span slot="end">USDT</span>
|
||||||
</ion-input>
|
</ion-input>
|
||||||
<ion-range class="range" aria-label="Range with ticks" :pin="true" :ticks="true" :snaps="true" :min="0" :max="5" />
|
<!-- <ion-range class="range" aria-label="Range with ticks" :pin="true" :ticks="true" :snaps="true" :min="0" :max="5" /> -->
|
||||||
<ion-button expand="block" size="small" shape="round" :color="mode === 'buy' ? 'success' : 'danger'">
|
<ion-button expand="block" size="small" shape="round" :color="mode === TradeTypeEnum.BUY ? 'success' : 'danger'" @click="handleSubmit">
|
||||||
{{ mode === 'buy' ? '买入' : '卖出' }}
|
{{ mode === TradeTypeEnum.BUY ? '买入' : '卖出' }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2" />
|
<div class="col-span-2" />
|
||||||
|
|||||||
Reference in New Issue
Block a user