|
|
|
@@ -2,9 +2,6 @@
|
|
|
|
|
<div v-can="pageAccess" class="point-rule-edit">
|
|
|
|
|
<t-form class="form" label-align="left" label-width="7rem">
|
|
|
|
|
<card-container v-entrance-transition>
|
|
|
|
|
<t-form-item v-if="isGlobalManager" label="门店" required-mark :arrow="isCreateMode" @click="openStorePicker">
|
|
|
|
|
<span class="picker-text" v-text="selectedStoreName" />
|
|
|
|
|
</t-form-item>
|
|
|
|
|
<t-form-item label="触发来源" required-mark arrow @click="openTriggerSourcePicker">
|
|
|
|
|
<span class="picker-text" v-text="getTriggerSourceText(formModel.triggerSourceType)" />
|
|
|
|
|
</t-form-item>
|
|
|
|
@@ -23,18 +20,28 @@
|
|
|
|
|
</t-form-item>
|
|
|
|
|
</card-container>
|
|
|
|
|
<card-container v-entrance-transition="70">
|
|
|
|
|
<t-form-item label="积分动作" arrow @click="openPointActionPicker">
|
|
|
|
|
<span class="picker-text" v-text="getPointActionText(formModel.pointAction)" />
|
|
|
|
|
</t-form-item>
|
|
|
|
|
<t-form-item label="积分数量" required-mark>
|
|
|
|
|
<t-input
|
|
|
|
|
v-model.trim="formModel.pointValueText"
|
|
|
|
|
type="number"
|
|
|
|
|
align="right"
|
|
|
|
|
borderless
|
|
|
|
|
placeholder="请输入数量"
|
|
|
|
|
/>
|
|
|
|
|
<t-form-item label="规则模式" arrow @click="openRuleModePicker">
|
|
|
|
|
<span class="picker-text" v-text="getRuleModeText(formModel.ruleMode)" />
|
|
|
|
|
</t-form-item>
|
|
|
|
|
<template v-if="formModel.ruleMode === 'GROOVY'">
|
|
|
|
|
<t-form-item label="执行脚本" required-mark arrow @click="openScriptPicker">
|
|
|
|
|
<span class="picker-text" v-text="selectedScriptText" />
|
|
|
|
|
</t-form-item>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<t-form-item label="积分动作" arrow @click="openPointActionPicker">
|
|
|
|
|
<span class="picker-text" v-text="getPointActionText(formModel.pointAction)" />
|
|
|
|
|
</t-form-item>
|
|
|
|
|
<t-form-item label="积分数量" required-mark>
|
|
|
|
|
<t-input
|
|
|
|
|
v-model.trim="formModel.pointValueText"
|
|
|
|
|
type="number"
|
|
|
|
|
align="right"
|
|
|
|
|
borderless
|
|
|
|
|
placeholder="请输入数量"
|
|
|
|
|
/>
|
|
|
|
|
</t-form-item>
|
|
|
|
|
</template>
|
|
|
|
|
</card-container>
|
|
|
|
|
<card-container v-entrance-transition="140">
|
|
|
|
|
<t-form-item label="生效开始" arrow @click="openBeginAtPicker">
|
|
|
|
@@ -57,15 +64,6 @@
|
|
|
|
|
</card-container>
|
|
|
|
|
</t-form>
|
|
|
|
|
</div>
|
|
|
|
|
<t-popup v-model="storePickerVisible" placement="bottom">
|
|
|
|
|
<t-picker
|
|
|
|
|
v-model="storePickerDraft"
|
|
|
|
|
:columns="storePickerColumns"
|
|
|
|
|
title="选择门店"
|
|
|
|
|
@cancel="storePickerVisible = false"
|
|
|
|
|
@confirm="confirmStorePicker"
|
|
|
|
|
/>
|
|
|
|
|
</t-popup>
|
|
|
|
|
<t-popup v-model="eventPickerVisible" placement="bottom">
|
|
|
|
|
<t-picker
|
|
|
|
|
v-model="eventPickerDraft"
|
|
|
|
@@ -102,6 +100,51 @@
|
|
|
|
|
@confirm="confirmPointActionPicker"
|
|
|
|
|
/>
|
|
|
|
|
</t-popup>
|
|
|
|
|
<t-popup v-model="ruleModePickerVisible" placement="bottom">
|
|
|
|
|
<t-picker
|
|
|
|
|
v-model="ruleModePickerDraft"
|
|
|
|
|
:columns="ruleModePickerColumns"
|
|
|
|
|
title="规则模式"
|
|
|
|
|
@cancel="ruleModePickerVisible = false"
|
|
|
|
|
@confirm="confirmRuleModePicker"
|
|
|
|
|
/>
|
|
|
|
|
</t-popup>
|
|
|
|
|
<bottom-popup
|
|
|
|
|
v-model="scriptPickerVisible"
|
|
|
|
|
title="选择执行脚本"
|
|
|
|
|
height="min(80vh, 48rem)"
|
|
|
|
|
@left-button-click="cancelScriptPicker"
|
|
|
|
|
@right-button-click="confirmScriptPicker"
|
|
|
|
|
>
|
|
|
|
|
<t-collapse
|
|
|
|
|
v-if="availableScriptList.length"
|
|
|
|
|
v-model="expandedScriptIdList"
|
|
|
|
|
class="script-collapse"
|
|
|
|
|
:expand-mutex="true"
|
|
|
|
|
>
|
|
|
|
|
<t-collapse-panel
|
|
|
|
|
v-for="script in availableScriptList"
|
|
|
|
|
:key="getScriptId(script)"
|
|
|
|
|
:value="getScriptId(script)"
|
|
|
|
|
class="script-panel"
|
|
|
|
|
placement="bottom"
|
|
|
|
|
>
|
|
|
|
|
<template #header>
|
|
|
|
|
<t-checkbox
|
|
|
|
|
:model-value="scriptPickerDraftId === getScriptId(script)"
|
|
|
|
|
:label="getScriptName(script)"
|
|
|
|
|
borderless
|
|
|
|
|
@click.stop
|
|
|
|
|
@change="selectScript(getScriptId(script))"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
<div class="script-description">
|
|
|
|
|
<markdown-view :content="script.description || '暂无脚本说明'" />
|
|
|
|
|
</div>
|
|
|
|
|
</t-collapse-panel>
|
|
|
|
|
</t-collapse>
|
|
|
|
|
<t-empty v-else description="暂无可选积分脚本" />
|
|
|
|
|
</bottom-popup>
|
|
|
|
|
<t-popup v-model="beginAtPickerVisible" placement="bottom">
|
|
|
|
|
<t-date-time-picker
|
|
|
|
|
v-model="beginAtDraftText"
|
|
|
|
@@ -127,7 +170,8 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { Time } from "timi-web";
|
|
|
|
|
import { BottomPopup } from "timi-tdesign-mobile";
|
|
|
|
|
import { MarkdownView, Time } from "timi-web";
|
|
|
|
|
import { useUserStore } from "timi-web-api";
|
|
|
|
|
import EventAPI from "@/api/EventAPI";
|
|
|
|
|
import PointAPI from "@/api/PointAPI";
|
|
|
|
@@ -135,8 +179,10 @@ import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
|
|
|
|
import { useNavBarStore } from "@/store/navBarStore";
|
|
|
|
|
import type { EventItem } from "@/types/EventItem";
|
|
|
|
|
import type {
|
|
|
|
|
GroovyScript,
|
|
|
|
|
PointAction,
|
|
|
|
|
PointRule,
|
|
|
|
|
PointRuleMode,
|
|
|
|
|
PointRuleStatus,
|
|
|
|
|
PointRuleTriggerSourceType
|
|
|
|
|
} from "@/types/Point";
|
|
|
|
@@ -170,30 +216,24 @@ const isSaving = ref(false);
|
|
|
|
|
const formModel = reactive({
|
|
|
|
|
id: "",
|
|
|
|
|
storeId: "",
|
|
|
|
|
ruleMode: "SIMPLE" as PointRuleMode,
|
|
|
|
|
triggerSourceType: "EVENT_RECORD" as PointRuleTriggerSourceType,
|
|
|
|
|
triggerRefId: "",
|
|
|
|
|
name: "",
|
|
|
|
|
status: "DRAFT" as PointRuleStatus,
|
|
|
|
|
pointAction: "EARN" as PointAction,
|
|
|
|
|
pointValueText: "",
|
|
|
|
|
groovyScriptId: "",
|
|
|
|
|
beginAtText: "",
|
|
|
|
|
endAtText: "",
|
|
|
|
|
exclusive: false,
|
|
|
|
|
remark: ""
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ---------- 门店与事件 ----------
|
|
|
|
|
// ---------- 事件与脚本 ----------
|
|
|
|
|
|
|
|
|
|
const eventList = ref<EventItem[]>([]);
|
|
|
|
|
const selectedStoreName = computed(() => currentStoreStore.storeList.find(item => item.id === formModel.storeId)?.name || "请选择门店");
|
|
|
|
|
const selectedEventName = computed(() => eventList.value.find(item => item.id === formModel.triggerRefId)?.name || "请选择触发事件");
|
|
|
|
|
const storePickerVisible = ref(false);
|
|
|
|
|
const storePickerDraft = ref<PickerValue[]>([]);
|
|
|
|
|
const storePickerColumns = computed(() => [
|
|
|
|
|
currentStoreStore.storeList
|
|
|
|
|
.map(item => ({ label: item.name || item.code || "未命名门店", value: item.id || "" }))
|
|
|
|
|
.filter(item => item.value)
|
|
|
|
|
]);
|
|
|
|
|
const eventPickerVisible = ref(false);
|
|
|
|
|
const eventPickerDraft = ref<PickerValue[]>([]);
|
|
|
|
|
const eventPickerColumns = computed(() => [
|
|
|
|
@@ -202,28 +242,15 @@ const eventPickerColumns = computed(() => [
|
|
|
|
|
.filter(item => item.value)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
function openStorePicker(): void {
|
|
|
|
|
if (!isCreateMode.value || !storePickerColumns.value[0]?.length) {
|
|
|
|
|
return;
|
|
|
|
|
const scriptList = ref<GroovyScript[]>([]);
|
|
|
|
|
const selectedScript = computed(() => scriptList.value.find(item => item.id === formModel.groovyScriptId));
|
|
|
|
|
const availableScriptList = computed(() => scriptList.value.filter(item => item.id));
|
|
|
|
|
const selectedScriptText = computed(() => {
|
|
|
|
|
if (!selectedScript.value) {
|
|
|
|
|
return "请选择执行脚本";
|
|
|
|
|
}
|
|
|
|
|
storePickerDraft.value = [formModel.storeId];
|
|
|
|
|
storePickerVisible.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function confirmStorePicker(value: Array<string | number>): Promise<void> {
|
|
|
|
|
const storeId = String(value[0] || "");
|
|
|
|
|
if (storeId && storeId !== formModel.storeId) {
|
|
|
|
|
formModel.storeId = storeId;
|
|
|
|
|
formModel.triggerRefId = "";
|
|
|
|
|
try {
|
|
|
|
|
await loadEventList();
|
|
|
|
|
} catch {
|
|
|
|
|
eventList.value = [];
|
|
|
|
|
Message.error("触发事件加载失败");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
storePickerVisible.value = false;
|
|
|
|
|
}
|
|
|
|
|
return selectedScript.value.name || selectedScript.value.code || "未命名脚本";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function openEventPicker(): void {
|
|
|
|
|
if (!eventPickerColumns.value[0]?.length) {
|
|
|
|
@@ -245,14 +272,10 @@ function confirmEventPicker(value: Array<string | number>): void {
|
|
|
|
|
async function prepareStoreScope(): Promise<void> {
|
|
|
|
|
if (isGlobalManager.value) {
|
|
|
|
|
await currentStoreStore.loadStoreList();
|
|
|
|
|
const queryStoreId = String(route.query.storeId || "");
|
|
|
|
|
formModel.storeId = currentStoreStore.storeList.some(item => item.id === queryStoreId)
|
|
|
|
|
? queryStoreId
|
|
|
|
|
: currentStoreStore.currentStoreId;
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
await currentStoreStore.loadLoginStore();
|
|
|
|
|
}
|
|
|
|
|
const store = await currentStoreStore.loadLoginStore();
|
|
|
|
|
formModel.storeId = store?.id || "";
|
|
|
|
|
formModel.storeId = currentStoreStore.currentStoreId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadEventList(): Promise<void> {
|
|
|
|
@@ -265,6 +288,10 @@ async function loadEventList(): Promise<void> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadScriptList(): Promise<void> {
|
|
|
|
|
scriptList.value = await PointAPI.ruleScriptList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------- 规则选项 ----------
|
|
|
|
|
|
|
|
|
|
const statusOptions: Array<{ label: string; value: PointRuleStatus }> = [
|
|
|
|
@@ -286,9 +313,27 @@ const pointActionOptions: Array<{ label: string; value: PointAction }> = [
|
|
|
|
|
{ label: "发放积分", value: "EARN" },
|
|
|
|
|
{ label: "消耗积分", value: "CONSUME" }
|
|
|
|
|
];
|
|
|
|
|
const ruleModeOptions: Array<{ label: string; value: PointRuleMode }> = [
|
|
|
|
|
{ label: "简单规则", value: "SIMPLE" },
|
|
|
|
|
{ label: "脚本规则", value: "GROOVY" }
|
|
|
|
|
];
|
|
|
|
|
const pointActionPickerVisible = ref(false);
|
|
|
|
|
const pointActionPickerDraft = ref<PickerValue[]>([]);
|
|
|
|
|
const pointActionPickerColumns = [pointActionOptions];
|
|
|
|
|
const ruleModePickerVisible = ref(false);
|
|
|
|
|
const ruleModePickerDraft = ref<PickerValue[]>([]);
|
|
|
|
|
const ruleModePickerColumns = [ruleModeOptions];
|
|
|
|
|
const scriptPickerVisible = ref(false);
|
|
|
|
|
const scriptPickerDraftId = ref("");
|
|
|
|
|
const expandedScriptIdList = ref<Array<string | number>>([]);
|
|
|
|
|
|
|
|
|
|
function getScriptId(script: GroovyScript): string {
|
|
|
|
|
return script.id || "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getScriptName(script: GroovyScript): string {
|
|
|
|
|
return script.name || script.code || "未命名脚本";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openStatusPicker(): void {
|
|
|
|
|
statusPickerDraft.value = [formModel.status];
|
|
|
|
@@ -332,6 +377,52 @@ function confirmPointActionPicker(value: Array<string | number>): void {
|
|
|
|
|
pointActionPickerVisible.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openRuleModePicker(): void {
|
|
|
|
|
ruleModePickerDraft.value = [formModel.ruleMode];
|
|
|
|
|
ruleModePickerVisible.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function confirmRuleModePicker(value: Array<string | number>): void {
|
|
|
|
|
const ruleMode = value[0];
|
|
|
|
|
if (ruleMode !== "SIMPLE" && ruleMode !== "GROOVY") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
formModel.ruleMode = ruleMode;
|
|
|
|
|
if (ruleMode === "SIMPLE") {
|
|
|
|
|
formModel.groovyScriptId = "";
|
|
|
|
|
} else {
|
|
|
|
|
formModel.pointValueText = "";
|
|
|
|
|
}
|
|
|
|
|
ruleModePickerVisible.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openScriptPicker(): void {
|
|
|
|
|
if (!availableScriptList.value.length) {
|
|
|
|
|
Message.warning("暂无可选积分脚本");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
scriptPickerDraftId.value = formModel.groovyScriptId;
|
|
|
|
|
expandedScriptIdList.value = formModel.groovyScriptId ? [formModel.groovyScriptId] : [];
|
|
|
|
|
scriptPickerVisible.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectScript(scriptId: string): void {
|
|
|
|
|
if (scriptId && availableScriptList.value.some(item => item.id === scriptId)) {
|
|
|
|
|
scriptPickerDraftId.value = scriptId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cancelScriptPicker(): void {
|
|
|
|
|
scriptPickerVisible.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function confirmScriptPicker(): void {
|
|
|
|
|
if (scriptPickerDraftId.value && availableScriptList.value.some(item => item.id === scriptPickerDraftId.value)) {
|
|
|
|
|
formModel.groovyScriptId = scriptPickerDraftId.value;
|
|
|
|
|
}
|
|
|
|
|
scriptPickerVisible.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getStatusText(status: PointRuleStatus): string {
|
|
|
|
|
return statusOptions.find(item => item.value === status)?.label || "草稿";
|
|
|
|
|
}
|
|
|
|
@@ -344,6 +435,10 @@ function getPointActionText(action: PointAction): string {
|
|
|
|
|
return action === "CONSUME" ? "消耗积分" : "发放积分";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getRuleModeText(ruleMode: PointRuleMode): string {
|
|
|
|
|
return ruleMode === "GROOVY" ? "脚本规则" : "简单规则";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------- 生效时间 ----------
|
|
|
|
|
|
|
|
|
|
const minimumDateTimeText = "1970-01-01 00:00";
|
|
|
|
@@ -405,7 +500,7 @@ function resolveTimestamp(value: string): number | undefined {
|
|
|
|
|
|
|
|
|
|
function validateForm(): boolean {
|
|
|
|
|
if (!formModel.storeId) {
|
|
|
|
|
Message.warning("请选择门店");
|
|
|
|
|
Message.warning("当前门店不可用");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!formModel.name.trim()) {
|
|
|
|
@@ -416,7 +511,12 @@ function validateForm(): boolean {
|
|
|
|
|
Message.warning("请选择触发事件");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!resolvePositiveInteger(formModel.pointValueText)) {
|
|
|
|
|
if (formModel.ruleMode === "GROOVY") {
|
|
|
|
|
if (!formModel.groovyScriptId) {
|
|
|
|
|
Message.warning("请选择执行脚本");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else if (!resolvePositiveInteger(formModel.pointValueText)) {
|
|
|
|
|
Message.warning("积分数量必须是正整数");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
@@ -438,14 +538,14 @@ async function saveRule(): Promise<void> {
|
|
|
|
|
const payload: PointRule = {
|
|
|
|
|
id: formModel.id || undefined,
|
|
|
|
|
storeId: formModel.storeId,
|
|
|
|
|
ruleMode: "SIMPLE",
|
|
|
|
|
ruleMode: formModel.ruleMode,
|
|
|
|
|
triggerSourceType: formModel.triggerSourceType,
|
|
|
|
|
triggerRefType: formModel.triggerSourceType === "EVENT_RECORD" ? "EVENT" : undefined,
|
|
|
|
|
triggerRefId: formModel.triggerSourceType === "EVENT_RECORD" ? formModel.triggerRefId : undefined,
|
|
|
|
|
name: formModel.name.trim(),
|
|
|
|
|
status: formModel.status,
|
|
|
|
|
pointValue: resolvePointValue() ?? null,
|
|
|
|
|
groovyScriptId: undefined,
|
|
|
|
|
pointValue: formModel.ruleMode === "SIMPLE" ? resolvePointValue() ?? null : null,
|
|
|
|
|
groovyScriptId: formModel.ruleMode === "GROOVY" ? formModel.groovyScriptId : undefined,
|
|
|
|
|
beginAt: resolveTimestamp(formModel.beginAtText),
|
|
|
|
|
endAt: resolveTimestamp(formModel.endAtText),
|
|
|
|
|
exclusive: formModel.exclusive,
|
|
|
|
@@ -469,17 +569,23 @@ async function saveRule(): Promise<void> {
|
|
|
|
|
|
|
|
|
|
async function fillForm(): Promise<void> {
|
|
|
|
|
await prepareStoreScope();
|
|
|
|
|
await loadScriptList();
|
|
|
|
|
if (!isCreateMode.value) {
|
|
|
|
|
const current = await PointAPI.ruleDetail(routeId.value);
|
|
|
|
|
if (current.groovyScript?.id && !scriptList.value.some(item => item.id === current.groovyScript?.id)) {
|
|
|
|
|
scriptList.value.push(current.groovyScript);
|
|
|
|
|
}
|
|
|
|
|
Object.assign(formModel, {
|
|
|
|
|
id: current.id || "",
|
|
|
|
|
storeId: current.storeId || formModel.storeId,
|
|
|
|
|
ruleMode: current.ruleMode || "SIMPLE",
|
|
|
|
|
triggerSourceType: current.triggerSourceType || "EVENT_RECORD",
|
|
|
|
|
triggerRefId: current.triggerRefId || "",
|
|
|
|
|
name: current.name || "",
|
|
|
|
|
status: current.status === "ACTIVE" ? "ACTIVE" : current.status === "INACTIVE" ? "INACTIVE" : "DRAFT",
|
|
|
|
|
pointAction: current.pointValue !== undefined && current.pointValue !== null && current.pointValue < 0 ? "CONSUME" : "EARN",
|
|
|
|
|
pointValueText: current.pointValue === undefined || current.pointValue === null ? "" : String(Math.abs(current.pointValue)),
|
|
|
|
|
groovyScriptId: current.groovyScriptId || "",
|
|
|
|
|
beginAtText: current.beginAt ? Time.toDateTime(current.beginAt) : "",
|
|
|
|
|
endAtText: current.endAt ? Time.toDateTime(current.endAt) : "",
|
|
|
|
|
exclusive: current.exclusive ?? false,
|
|
|
|
@@ -543,6 +649,13 @@ onUnmounted(() => navBarStore.clearRight());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.script-collapse {
|
|
|
|
|
|
|
|
|
|
:deep(.t-cell) {
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(html.theme-dark) {
|
|
|
|
|
.point-rule-edit {
|
|
|
|
|
.form {
|
|
|
|
@@ -554,6 +667,10 @@ onUnmounted(() => navBarStore.clearRight());
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.script-description {
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|