chore: suite

pull/330/head
xiaojunnuo 2024-12-20 01:00:13 +08:00
parent 08111f1418
commit 5d568efac3
15 changed files with 175 additions and 110 deletions

View File

@ -96,6 +96,9 @@ h1, h2, h3, h4, h5, h6 {
overflow-y: auto; overflow-y: auto;
} }
.m-0{
margin:0
}
.m-2{ .m-2{
margin:2px margin:2px
} }

View File

@ -23,9 +23,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;
}; };

View File

@ -22,9 +22,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;
}; };

View File

@ -17,7 +17,7 @@ export const durationDict = dict({
}); });
export type OrderModalOpenReq = { export type OrderModalOpenReq = {
productId: number; product: any;
duration: number; duration: number;
num?: number; num?: number;
}; };
@ -41,6 +41,7 @@ export type TradeCreateReq = {
productId: number; productId: number;
duration: number; duration: number;
num: number; num: number;
payType: string;
}; };
export async function TradeCreate(form: TradeCreateReq) { export async function TradeCreate(form: TradeCreateReq) {

View File

@ -6,10 +6,12 @@
<div class="suite-buy-content"> <div class="suite-buy-content">
<a-row :gutter="12"> <a-row :gutter="12">
<a-col v-for="item of products" :key="item.id" class="mb-10" :xs="12" :sm="12" :md="8" :lg="6" :xl="6" :xxl="4"> <a-col v-for="item of products" :key="item.id" class="mb-10" :xs="12" :sm="12" :md="8" :lg="6" :xl="6" :xxl="4">
<product-info product="item"></product-info> <product-info :product="item" @order="doOrder" />
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
<order-modal ref="orderModalRef" />
</fs-page> </fs-page>
</template> </template>
@ -17,14 +19,21 @@
import { ref } from "vue"; import { ref } from "vue";
import * as api from "./api"; import * as api from "./api";
import ProductInfo from "/@/views/certd/suite/product-info.vue"; import ProductInfo from "/@/views/certd/suite/product-info.vue";
import OrderModal from "/@/views/certd/suite/order-modal.vue";
const products = ref([]); const products = ref([]);
async function loadSuites() { async function loadProducts() {
products.value = await api.ProductList(); products.value = await api.ProductList();
} }
loadSuites(); loadProducts();
const orderModalRef = ref<any>(null);
async function doOrder(req: any) {
await orderModalRef.value.open({
...req
});
}
</script> </script>
<style lang="less"> <style lang="less">
@ -36,26 +45,6 @@ loadSuites();
.suite-buy-content { .suite-buy-content {
padding: 20px; 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 { .hr {
border-top: 1px solid #cdcdcd; border-top: 1px solid #cdcdcd;
margin-top: 5px; margin-top: 5px;

View File

@ -0,0 +1,67 @@
<template>
<a-modal v-model:open="openRef" class="order-modal" title="订单确认" @ok="orderCreate">
<div v-if="product" class="order-box">
<div class="flex-o mt-5">套餐{{ product.title }}</div>
<div class="flex-o mt-5">说明{{ product.intro }}</div>
<div class="flex-o mt-5">
<span class="flex-o">规格</span>
<span class="flex-o">流水线<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" unit="条" /></span>
<span class="flex-o">域名<suite-value class="ml-5" :model-value="product.content.maxDomainCount" unit="个" /></span>
<span class="flex-o">部署次数<suite-value class="ml-5" :model-value="product.content.maxDeployCount" unit="次" /></span>
</div>
<div class="flex-o mt-5">
时长
<a-tag color="green"> {{ durationDict.dataMap[formRef.duration]?.label }}</a-tag>
</div>
<div class="flex-o mt-5">价格 <price-input :edit="false" :model-value="durationSelected.price"></price-input></div>
<div class="flex-o mt-5">
支付方式
<a-select v-model:value="formRef.payType">
<a-select-option value="alipay">支付宝</a-select-option>
</a-select>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { durationDict, OrderModalOpenReq, TradeCreate, TradeCreateReq } from "/@/views/certd/suite/api";
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
const openRef = ref(false);
const product = ref<any>(null);
const formRef = ref<any>({});
const durationSelected = ref<any>(null);
async function open(opts: OrderModalOpenReq) {
openRef.value = true;
product.value = opts.product;
durationSelected.value = opts.product.durationPrices.find((dp: any) => dp.duration === opts.duration);
formRef.value.productId = opts.product.id;
formRef.value.duration = opts.duration;
formRef.value.num = opts.num ?? 1;
formRef.value.payType = "alipay";
}
async function orderCreate() {
console.log("orderCreate", formRef.value);
const res = await TradeCreate({
productId: formRef.value.productId,
duration: formRef.value.duration,
num: formRef.value.num ?? 1,
payType: formRef.value.payType
});
//
}
defineExpose({
open
});
</script>

View File

@ -1,22 +0,0 @@
<template>
<a-modal class="order-modal">
<div class="order-box"></div>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { OrderModalOpenReq } from "/@/views/certd/suite/api";
const openRef = ref(false);
async function open(opts: OrderModalOpenReq) {
openRef.value = true;
const productDetail = api.ProductInfo(opts.productId);
}
defineExpose({
open
});
</script>

View File

@ -1,18 +1,27 @@
<template> <template>
<a-card :title="product.title"> <a-card :title="product.title" class="product-card">
<template #extra> <template #extra>
<a-tag>{{ product.type }}</a-tag> <a-tag>{{ product.type }}</a-tag>
</template> </template>
<div>{{ product.intro }}</div> <div>{{ product.intro }}</div>
<div class="hr"> <div class="hr">
<div class="flex-between mt-5">流水线条数<suite-value :model-value="product.content.maxPipelineCount" /></div>
<div class="flex-between mt-5">域名数量 <suite-value :model-value="product.content.maxDomainCount" /></div>
<div class="flex-between mt-5">部署次数 <suite-value :model-value="product.content.maxDeployCount" /></div>
<div class="flex-between mt-5"> <div class="flex-between mt-5">
证书监控 <div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 流水线条数</div>
<span v-if="product.content.sproductonitor"></span> <suite-value :model-value="product.content.maxPipelineCount" unit="条" />
<span v-else></span> </div>
<div class="flex-between mt-5">
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />域名数量</div>
<suite-value :model-value="product.content.maxDomainCount" unit="个" />
</div>
<div class="flex-between mt-5">
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 部署次数</div>
<suite-value :model-value="product.content.maxDeployCount" unit="次" />
</div>
<div class="flex-between mt-5">
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 证书监控</div>
<a-tag v-if="product.content.sproductonitor" color="green" class="m-0"></a-tag>
<a-tag v-else color="gray" class="m-0">不支持</a-tag>
</div> </div>
</div> </div>
@ -22,7 +31,7 @@
<div <div
v-for="dp of product.durationPrices" v-for="dp of product.durationPrices"
:key="dp.duration" :key="dp.duration"
class="duration-product" class="duration-item"
:class="{ active: selected.duration === dp.duration }" :class="{ active: selected.duration === dp.duration }"
@click="selected = dp" @click="selected = dp"
> >
@ -40,7 +49,6 @@
</div> </div>
<template #actions> <template #actions>
<setting-outlined key="setting" />
<a-button type="primary" @click="doOrder"></a-button> <a-button type="primary" @click="doOrder"></a-button>
</template> </template>
</a-card> </a-card>
@ -50,16 +58,38 @@ import { durationDict } from "/@/views/certd/suite/api";
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
import PriceInput from "/@/views/sys/suite/product/price-input.vue"; import PriceInput from "/@/views/sys/suite/product/price-input.vue";
import { ref } from "vue"; import { ref } from "vue";
import * as api from "./api";
const props = defineProps<{ const props = defineProps<{
product: any; product: any;
}>(); }>();
const selected = ref(props.product.durationPrices[0]); const selected = ref(props.product.durationPrices[0]);
const emit = defineEmits(["order"]);
async function doOrder() { async function doOrder() {
console.log("doOrder", selected.value); emit("order", { product: props.product, productId: props.product.id, duration: selected.value.duration });
const res = await api.OrderCreate({
productId: props.product.id,
duration: selected.value.duration
});
} }
</script> </script>
<style lang="less">
.product-card {
.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;
}
}
}
}
</style>

View File

@ -23,9 +23,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;
}; };

View File

@ -23,9 +23,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;
}; };

View File

@ -134,7 +134,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
} }
}, },
valueResolve: ({ form }) => { valueResolve: ({ form }) => {
debugger;
if (form.content) { if (form.content) {
form.content = JSON.stringify(form.content); form.content = JSON.stringify(form.content);
} }
@ -145,7 +144,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
"content.maxDomainCount": { "content.maxDomainCount": {
title: "域名数量", title: "域名数量",
type: "number", type: "text",
form: { form: {
key: ["content", "maxDomainCount"], key: ["content", "maxDomainCount"],
component: { component: {
@ -158,13 +157,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
column: { column: {
width: 100, width: 100,
component: { component: {
name: SuiteValue name: SuiteValue,
vModel: "modelValue",
unit: "个"
} }
} }
}, },
"content.maxPipelineCount": { "content.maxPipelineCount": {
title: "流水线数量", title: "流水线数量",
type: "number", type: "text",
form: { form: {
key: ["content", "maxPipelineCount"], key: ["content", "maxPipelineCount"],
component: { component: {
@ -177,13 +178,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
column: { column: {
width: 100, width: 100,
component: { component: {
name: SuiteValue name: SuiteValue,
vModel: "modelValue",
unit: "条"
} }
} }
}, },
"content.maxDeployCount": { "content.maxDeployCount": {
title: "部署次数", title: "部署次数",
type: "number", type: "text",
form: { form: {
key: ["content", "maxDeployCount"], key: ["content", "maxDeployCount"],
component: { component: {
@ -196,7 +199,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
column: { column: {
width: 100, width: 100,
component: { component: {
name: SuiteValue name: SuiteValue,
vModel: "modelValue",
unit: "次"
} }
} }
}, },
@ -217,6 +222,22 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 120 width: 120
} }
}, },
isBootstrap: {
title: "是否初始套餐",
type: "dict-switch",
dict: dict({
data: [
{ label: "是", value: true, color: "success" },
{ label: "否", value: false, color: "gray" }
]
}),
form: {
value: false
},
column: {
width: 120
}
},
durationPrices: { durationPrices: {
title: "时长及价格", title: "时长及价格",
type: "text", type: "text",
@ -243,29 +264,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
} }
} }
}, },
isBootstrap: {
title: "是否初始套餐",
type: "dict-switch",
dict: dict({
data: [
{ label: "是", value: true, color: "success" },
{ label: "否", value: false, color: "error" }
]
}),
form: {
value: false
},
column: {
width: 120
}
},
disabled: { disabled: {
title: "上下架", title: "上下架",
type: "dict-radio", type: "dict-radio",
dict: dict({ dict: dict({
data: [ data: [
{ value: false, label: "上架" }, { value: false, label: "上架", color: "green" },
{ value: true, label: "下架" } { value: true, label: "下架", color: "gray" }
] ]
}), }),
form: { form: {

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="flex-o"> <div class="flex-o price-input">
<a-input-number v-if="edit" prefix="¥" :value="priceValue" :precision="2" class="ml-5" @update:value="onPriceChange"> </a-input-number> <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> <span v-else class="price-text">{{ priceLabel }}</span>
</div> </div>
</template> </template>
@ -33,3 +33,12 @@ const onPriceChange = (price: number) => {
emit("update:modelValue", price * 100); emit("update:modelValue", price * 100);
}; };
</script> </script>
<style lang="less">
.price-input {
.price-text {
font-size: 18px;
color: red;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-if="target" class="cd-suite-value"> <div v-if="target" class="cd-suite-value">
<a-tag :color="target.color">{{ target.label }}</a-tag> <a-tag :color="target.color" class="m-0">{{ target.label }}</a-tag>
</div> </div>
</template> </template>
@ -9,10 +9,11 @@ import { computed } from "vue";
const props = defineProps<{ const props = defineProps<{
modelValue: number; modelValue: number;
unit?: string;
}>(); }>();
const target = computed(() => { const target = computed(() => {
if (!props.modelValue) { if (props.modelValue == null) {
return {}; return {};
} }
if (props.modelValue === -1) { if (props.modelValue === -1) {
@ -24,13 +25,13 @@ const target = computed(() => {
} else if (props.modelValue === 0) { } else if (props.modelValue === 0) {
return { return {
value: 0, value: 0,
label: "-", label: "0" + (props.unit || ""),
color: "red" color: "red"
}; };
} else { } else {
return { return {
value: props.modelValue, value: props.modelValue,
label: props.modelValue, label: props.modelValue + (props.unit || ""),
color: "blue" color: "blue"
}; };
} }

View File

@ -23,9 +23,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;
}; };

View File

@ -23,9 +23,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;
}; };