mirror of https://github.com/certd/certd
chore: suite
parent
8814ffeda6
commit
45839f227a
|
@ -49,4 +49,10 @@ export abstract class CrudController<T> extends BaseController {
|
||||||
await this.getService().delete([id]);
|
await this.getService().delete([id]);
|
||||||
return this.ok(null);
|
return this.ok(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/deleteByIds')
|
||||||
|
async deleteByIds(@Body('ids') ids: number[]) {
|
||||||
|
await this.getService().delete(ids);
|
||||||
|
return this.ok(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,16 @@ export const certdResources = [
|
||||||
auth: true
|
auth: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "套餐购买",
|
||||||
|
name: "SuiteProductBuy",
|
||||||
|
path: "/certd/suite/buy",
|
||||||
|
component: "/certd/suite/buy.vue",
|
||||||
|
meta: {
|
||||||
|
icon: "mdi:format-list-group",
|
||||||
|
auth: true
|
||||||
|
}
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// title: "邮箱设置",
|
// title: "邮箱设置",
|
||||||
// name: "EmailSetting",
|
// name: "EmailSetting",
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { request } from "/@/api/service";
|
||||||
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
|
export const durationDict = dict({
|
||||||
|
data: [
|
||||||
|
{ label: "1年", value: 365 },
|
||||||
|
{ label: "2年", value: 730 },
|
||||||
|
{ label: "3年", value: 1095 },
|
||||||
|
{ label: "4年", value: 1460 },
|
||||||
|
{ label: "5年", value: 1825 },
|
||||||
|
{ label: "6年", value: 2190 },
|
||||||
|
{ label: "7年", value: 2555 },
|
||||||
|
{ label: "8年", value: 2920 },
|
||||||
|
{ label: "9年", value: 3285 },
|
||||||
|
{ label: "10年", value: 3650 },
|
||||||
|
{ label: "永久", value: -1 }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function ProductList() {
|
||||||
|
return await request({
|
||||||
|
url: "/suite/product/list",
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
<template>
|
||||||
|
<fs-page class="page-suite-buy">
|
||||||
|
<template #header>
|
||||||
|
<div class="title">套餐购买</div>
|
||||||
|
</template>
|
||||||
|
<div class="suite-buy-content">
|
||||||
|
<a-row :gutter="12">
|
||||||
|
<a-col v-for="item of suites" :key="item.id" class="mb-10" :xs="12" :sm="12" :md="8" :lg="6" :xl="6" :xxl="4">
|
||||||
|
<a-card :title="item.title">
|
||||||
|
<template #extra>
|
||||||
|
<a-tag>{{ item.type }}</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div>{{ item.intro }}</div>
|
||||||
|
<div class="hr">
|
||||||
|
<div class="flex-between mt-5">流水线条数:<suite-value :model-value="item.content.maxPipelineCount" /></div>
|
||||||
|
<div class="flex-between mt-5">域名数量: <suite-value :model-value="item.content.maxDomainCount" /></div>
|
||||||
|
<div class="flex-between mt-5">部署次数: <suite-value :model-value="item.content.maxDeployCount" /></div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
证书监控:
|
||||||
|
<span v-if="item.content.siteMonitor">支持</span>
|
||||||
|
<span v-else>不支持</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="duration flex-between mt-5 hr">
|
||||||
|
<div class="flex-o">时长</div>
|
||||||
|
<div class="duration-list">
|
||||||
|
<div
|
||||||
|
v-for="dp of item.durationPrices"
|
||||||
|
:key="dp.duration"
|
||||||
|
class="duration-item"
|
||||||
|
:class="{ active: item._selected.duration === dp.duration }"
|
||||||
|
@click="item._selected = dp"
|
||||||
|
>
|
||||||
|
{{ durationDict.dataMap[dp.duration]?.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="price flex-between mt-5 hr">
|
||||||
|
<div class="flex-o">价格</div>
|
||||||
|
<div class="flex-o price-text">
|
||||||
|
<price-input style="font-size: 18px; color: red" :model-value="item._selected?.price" :edit="false" />
|
||||||
|
<span class="ml-5" style="font-size: 12px"> / {{ durationDict.dataMap[item._selected.duration]?.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<setting-outlined key="setting" />
|
||||||
|
<a-button type="primary">立即购买</a-button>
|
||||||
|
</template>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import * as api from "./api";
|
||||||
|
import { durationDict } from "./api";
|
||||||
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
|
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||||
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
|
const suites = ref([]);
|
||||||
|
|
||||||
|
const optionsDict = dict({
|
||||||
|
data: []
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadSuites() {
|
||||||
|
suites.value = await api.ProductList();
|
||||||
|
|
||||||
|
for (const item of suites.value) {
|
||||||
|
item._selected = item.durationPrices[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSuites();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.page-suite-buy {
|
||||||
|
.title {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
background: #f0f2f5;
|
||||||
|
.suite-buy-content {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.duration-list {
|
||||||
|
display: flex;
|
||||||
|
.duration-item {
|
||||||
|
width: 50px;
|
||||||
|
border: 1px solid #cdcdcd;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
border-color: #a6fba3;
|
||||||
|
background-color: #c1eafb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr {
|
||||||
|
border-top: 1px solid #cdcdcd;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-text {
|
||||||
|
align-items: baseline;
|
||||||
|
font-family: "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
}
|
||||||
|
|
||||||
|
.prices {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
margin-top: 20px;
|
||||||
|
.price-item {
|
||||||
|
border: 1px solid #c6c6c6;
|
||||||
|
background-color: #f8ebda;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100px;
|
||||||
|
&:hover {
|
||||||
|
border-color: #38a0fb;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -1,6 +1,9 @@
|
||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
const apiPrefix = "/sys/suite/product";
|
const apiPrefix = "/sys/suite/product";
|
||||||
|
export type PriceItem = {
|
||||||
|
duration: number;
|
||||||
|
price: number;
|
||||||
|
};
|
||||||
|
|
||||||
export async function GetList(query: any) {
|
export async function GetList(query: any) {
|
||||||
return await request({
|
return await request({
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { useUserStore } from "/@/store/modules/user";
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import SuiteValue from "./suite-value.vue";
|
import SuiteValue from "./suite-value.vue";
|
||||||
import SuiteValueEdit from "./suite-value-edit.vue";
|
import SuiteValueEdit from "./suite-value-edit.vue";
|
||||||
|
import PriceEdit from "./price-edit.vue";
|
||||||
|
|
||||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -24,9 +25,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
};
|
};
|
||||||
|
|
||||||
const addRequest = async ({ form }: AddReq) => {
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
form.content = JSON.stringify({
|
|
||||||
title: form.title
|
|
||||||
});
|
|
||||||
const res = await api.AddObj(form);
|
const res = await api.AddObj(form);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
@ -69,11 +67,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
groups: {
|
groups: {
|
||||||
base: {
|
base: {
|
||||||
header: "基础信息",
|
header: "基础信息",
|
||||||
columns: ["title", "type", "price", "originPrice", "duration", "isBootstrap", "intro"]
|
columns: ["title", "type", "isBootstrap", "disabled", "order", "intro"]
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
header: "套餐内容",
|
header: "套餐内容",
|
||||||
columns: ["maxDomainCount", "maxPipelineCount", "maxDeployCount", "siteMonitor"]
|
columns: ["content.maxDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.siteMonitor"]
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
header: "价格",
|
||||||
|
columns: ["durationPrices"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +98,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
search: {
|
search: {
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 200
|
width: 200
|
||||||
}
|
}
|
||||||
|
@ -114,19 +119,41 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
{ label: "加量包", value: "addon" }
|
{ label: "加量包", value: "addon" }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 100
|
width: 100
|
||||||
|
},
|
||||||
|
valueBuilder: ({ row }) => {
|
||||||
|
if (row.content) {
|
||||||
|
row.content = JSON.parse(row.content);
|
||||||
|
}
|
||||||
|
if (row.durationPrices) {
|
||||||
|
row.durationPrices = JSON.parse(row.durationPrices);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valueResolve: ({ form }) => {
|
||||||
|
debugger;
|
||||||
|
if (form.content) {
|
||||||
|
form.content = JSON.stringify(form.content);
|
||||||
|
}
|
||||||
|
if (form.durationPrices) {
|
||||||
|
form.durationPrices = JSON.stringify(form.durationPrices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxDomainCount: {
|
"content.maxDomainCount": {
|
||||||
title: "域名数量",
|
title: "域名数量",
|
||||||
type: "number",
|
type: "number",
|
||||||
form: {
|
form: {
|
||||||
|
key: ["content", "maxDomainCount"],
|
||||||
component: {
|
component: {
|
||||||
name: SuiteValueEdit,
|
name: SuiteValueEdit,
|
||||||
vModel: "modelValue",
|
vModel: "modelValue",
|
||||||
unit: "个"
|
unit: "个"
|
||||||
}
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 100,
|
width: 100,
|
||||||
|
@ -135,15 +162,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxPipelineCount: {
|
"content.maxPipelineCount": {
|
||||||
title: "流水线数量",
|
title: "流水线数量",
|
||||||
type: "number",
|
type: "number",
|
||||||
form: {
|
form: {
|
||||||
|
key: ["content", "maxPipelineCount"],
|
||||||
component: {
|
component: {
|
||||||
name: SuiteValueEdit,
|
name: SuiteValueEdit,
|
||||||
vModel: "modelValue",
|
vModel: "modelValue",
|
||||||
unit: "条"
|
unit: "条"
|
||||||
}
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 100,
|
width: 100,
|
||||||
|
@ -152,15 +181,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxDeployCount: {
|
"content.maxDeployCount": {
|
||||||
title: "部署次数",
|
title: "部署次数",
|
||||||
type: "number",
|
type: "number",
|
||||||
form: {
|
form: {
|
||||||
|
key: ["content", "maxDeployCount"],
|
||||||
component: {
|
component: {
|
||||||
name: SuiteValueEdit,
|
name: SuiteValueEdit,
|
||||||
vModel: "modelValue",
|
vModel: "modelValue",
|
||||||
unit: "次"
|
unit: "次"
|
||||||
}
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 100,
|
width: 100,
|
||||||
|
@ -169,7 +200,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
siteMonitor: {
|
"content.siteMonitor": {
|
||||||
title: "支持证书监控",
|
title: "支持证书监控",
|
||||||
type: "dict-switch",
|
type: "dict-switch",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
|
@ -178,10 +209,40 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
{ label: "否", value: false, color: "error" }
|
{ label: "否", value: false, color: "error" }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
form: {
|
||||||
|
key: ["content", "siteMonitor"],
|
||||||
|
value: false
|
||||||
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 120
|
width: 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
durationPrices: {
|
||||||
|
title: "时长及价格",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
title: "选择时长",
|
||||||
|
component: {
|
||||||
|
name: PriceEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
edit: true,
|
||||||
|
style: {
|
||||||
|
minHeight: "120px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
component: {
|
||||||
|
name: PriceEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
edit: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
isBootstrap: {
|
isBootstrap: {
|
||||||
title: "是否初始套餐",
|
title: "是否初始套餐",
|
||||||
type: "dict-switch",
|
type: "dict-switch",
|
||||||
|
@ -191,27 +252,36 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
{ label: "否", value: false, color: "error" }
|
{ label: "否", value: false, color: "error" }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
form: {
|
||||||
|
value: false
|
||||||
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 120
|
width: 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
price: {
|
disabled: {
|
||||||
title: "单价",
|
title: "上下架",
|
||||||
type: "number",
|
type: "dict-radio",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ value: false, label: "上架" },
|
||||||
|
{ value: true, label: "下架" }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
form: {
|
||||||
|
value: false
|
||||||
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 100
|
width: 100
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
originPrice: {
|
order: {
|
||||||
title: "原价",
|
title: "排序",
|
||||||
type: "number",
|
type: "number",
|
||||||
column: {
|
form: {
|
||||||
width: 100
|
helper: "越小越靠前",
|
||||||
}
|
value: 0
|
||||||
},
|
},
|
||||||
duration: {
|
|
||||||
title: "有效时长",
|
|
||||||
type: "dict-select",
|
|
||||||
column: {
|
column: {
|
||||||
width: 100
|
width: 100
|
||||||
}
|
}
|
||||||
|
@ -223,16 +293,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
width: 200
|
width: 200
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
order: {
|
|
||||||
title: "排序",
|
|
||||||
type: "number",
|
|
||||||
form: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createTime: {
|
createTime: {
|
||||||
title: "创建时间",
|
title: "创建时间",
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="cd-suite-value-edit flex-o">
|
|
||||||
<div class="flex- 1"><a-checkbox :checked="modelValue === -1" @update:checked="onCheckedChange">无限制</a-checkbox><span class="ml-5"></span></div>
|
|
||||||
<div class="ml-10 w-50%">
|
|
||||||
<a-input-number v-if="modelValue >= 0" :value="modelValue" class="ml-5" @update:value="onValueChange">
|
|
||||||
<template v-if="unit" #addonAfter>{{ unit }}</template>
|
|
||||||
</a-input-number>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Form } from "ant-design-vue";
|
|
||||||
import { dict } from "@fast-crud/fast-crud";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
modelValue?: number;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const durationDict = dict({
|
|
||||||
data: [
|
|
||||||
{ label: "1年", value: 365 },
|
|
||||||
{ label: "2年", value: 730 },
|
|
||||||
{ label: "3年", value: 1095 },
|
|
||||||
{ label: "4年", value: 1460 },
|
|
||||||
{ label: "5年", value: 1825 },
|
|
||||||
{ label: "6年", value: 2190 },
|
|
||||||
{ label: "7年", value: 2555 },
|
|
||||||
{ label: "8年", value: 2920 },
|
|
||||||
{ label: "9年", value: 3285 },
|
|
||||||
{ label: "10年", value: 3650 },
|
|
||||||
{ label: "永久", value: -1 }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
const formItemContext = Form.useInjectFormItemContext();
|
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue"]);
|
|
||||||
|
|
||||||
const onCheckedChange = (checked: boolean) => {
|
|
||||||
const value = checked ? -1 : 1;
|
|
||||||
onValueChange(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function onValueChange(value: number) {
|
|
||||||
emit("update:modelValue", value);
|
|
||||||
formItemContext.onFieldChange();
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<template>
|
||||||
|
<div class="cd-price-edit">
|
||||||
|
<div v-if="edit" class="duration-list flex-o">
|
||||||
|
<div v-for="item of durationDict.data" :key="item.value" :class="{ active: isActive(item) }" class="duration-item" @click="onDurationClicked(item)">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-group-list">
|
||||||
|
<div v-for="item of modelValue" :key="item.duration" class="flex-o price-group-item">
|
||||||
|
<div style="width: 50px">{{ durationDict.dataMap[item.duration]?.label }}:</div>
|
||||||
|
<price-input v-model="item.price" style="width: 150px" :edit="edit" class="mr-10" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Form } from "ant-design-vue";
|
||||||
|
import { PriceItem } from "./api";
|
||||||
|
import PriceInput from "./price-input.vue";
|
||||||
|
import { durationDict } from "../../../certd/suite/api";
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue?: PriceItem[];
|
||||||
|
edit: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
modelValue: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
function doEmit(value: PriceItem[]) {
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
formItemContext.onFieldChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActive(item: any) {
|
||||||
|
return props.modelValue.some((v) => v.duration === item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDurationClicked(item: any) {
|
||||||
|
const has = props.modelValue.some((v) => v.duration === item.value);
|
||||||
|
if (has) {
|
||||||
|
// remove
|
||||||
|
const newValue = props.modelValue.filter((v) => v.duration !== item.value);
|
||||||
|
doEmit(newValue);
|
||||||
|
} else {
|
||||||
|
// add
|
||||||
|
const newValue = [...props.modelValue, { duration: item.value, price: 0.0 }];
|
||||||
|
//sort
|
||||||
|
newValue.sort((a, b) => a.duration - b.duration);
|
||||||
|
if (newValue.length > 0) {
|
||||||
|
const first = newValue[0];
|
||||||
|
if (first.duration === -1) {
|
||||||
|
//挪到后面去
|
||||||
|
newValue.shift();
|
||||||
|
newValue.push(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doEmit(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.cd-price-edit {
|
||||||
|
.duration-item {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
padding: 2px;
|
||||||
|
width: 35px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-group-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
.price-group-item {
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex-o">
|
||||||
|
<a-input-number v-if="edit" prefix="¥" :value="priceValue" :precision="2" class="ml-5" @update:value="onPriceChange"> </a-input-number>
|
||||||
|
<span v-else>{{ priceLabel }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue?: number;
|
||||||
|
edit: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const priceValue = computed(() => {
|
||||||
|
if (props.modelValue == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (props.modelValue / 100.0).toFixed(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
const priceLabel = computed(() => {
|
||||||
|
if (priceValue.value === 0 || priceValue.value === "0.00") {
|
||||||
|
return "免费";
|
||||||
|
}
|
||||||
|
return `¥${priceValue.value}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
const onPriceChange = (price: number) => {
|
||||||
|
emit("update:modelValue", price * 100);
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="cd-suite-value-edit flex-o">
|
<div class="cd-suite-value-edit flex-o">
|
||||||
<div class="flex- 1"><a-checkbox :checked="modelValue === -1" @update:checked="onCheckedChange">无限制</a-checkbox><span class="ml-5"></span></div>
|
<div class="flex- 1"><a-checkbox :checked="modelValue === -1" @update:checked="onCheckedChange">无限制</a-checkbox><span class="ml-5"></span></div>
|
||||||
<div class="ml-10 w-50%">
|
<div class="ml-10 w-50%">
|
||||||
<a-input-number v-if="modelValue >= 0" :value="modelValue" class="ml-5" @update:value="onValueChange">
|
<a-input-number v-if="modelValue == null || modelValue >= 0" :value="modelValue" class="ml-5" @update:value="onValueChange">
|
||||||
<template v-if="unit" #addonAfter>{{ unit }}</template>
|
<template v-if="unit" #addonAfter>{{ unit }}</template>
|
||||||
</a-input-number>
|
</a-input-number>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
const apiPrefix = "/sys/suite/order";
|
const apiPrefix = "/sys/suite/trade";
|
||||||
|
|
||||||
export async function GetList(query: any) {
|
export async function GetList(query: any) {
|
||||||
return await request({
|
return await request({
|
|
@ -55,8 +55,8 @@ export class CnameRecordController extends CrudController<CnameProviderService>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/deleteByIds', { summary: 'sys:settings:edit' })
|
@Post('/deleteByIds', { summary: 'sys:settings:edit' })
|
||||||
async deleteByIds(@Body(ALL) body: { ids: number[] }) {
|
async deleteByIds(@Body('ids') ids: number[]) {
|
||||||
const res = await this.service.delete(body.ids);
|
const res = await this.service.delete(ids);
|
||||||
return this.ok(res);
|
return this.ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,8 +57,8 @@ export class PluginController extends CrudController<PluginService> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/deleteByIds', { summary: 'sys:settings:edit' })
|
@Post('/deleteByIds', { summary: 'sys:settings:edit' })
|
||||||
async deleteByIds(@Body(ALL) body: { ids: number[] }) {
|
async deleteByIds(@Body('ids') ids: number[]) {
|
||||||
const res = await this.service.delete(body.ids);
|
const res = await this.service.delete(ids);
|
||||||
return this.ok(res);
|
return this.ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue