mirror of
https://github.com/certd/certd.git
synced 2026-05-18 06:17:31 +08:00
feat: 用户套餐,用户支付功能
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<fs-page class="page-sys-settings page-sys-settings-suite">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
套餐设置
|
||||
<span class="sub"> 需要<router-link to="/sys/settings" :query="{ tab: 'payment' }">开启至少一种支付方式</router-link></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="form-content">
|
||||
<a-form ref="formRef" :model="formState" :label-col="{ style: { width: '150px' } }" :wrapper-col="{ span: 20 }" autocomplete="off">
|
||||
<a-form-item label="开启套餐功能" name="enabled" required>
|
||||
<a-switch v-model:checked="formState.enabled" />
|
||||
</a-form-item>
|
||||
<template v-if="formState.enabled">
|
||||
<a-form-item label="套餐列表" name="enabled">
|
||||
<div style="height: 400px">
|
||||
<ProductManager @refreshed="onTableRefresh"></ProductManager>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="注册赠送套餐" name="registerGift">
|
||||
<suite-duration-selector ref="suiteDurationSelectedRef" v-model="formState.registerGift"></suite-duration-selector>
|
||||
<div class="helper">添加套餐后再选择</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="套餐说明" name="intro">
|
||||
<a-textarea v-model:value="formState.intro" :rows="3"></a-textarea>
|
||||
<div class="helper">将显示在套餐购买页面</div>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item label=" " :colon="false">
|
||||
<loading-button type="primary" html-type="button" :click="onClick">保存</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { request } from "/@/api/service";
|
||||
import SuiteDurationSelector from "/@/views/sys/suite/setting/suite-duration-selector.vue";
|
||||
import ProductManager from "/@/views/sys/suite/product/index.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "SettingsSuite"
|
||||
});
|
||||
|
||||
const api = {
|
||||
async SuiteSettingGet() {
|
||||
return await request({
|
||||
url: "/sys/settings/suite/get",
|
||||
method: "post"
|
||||
});
|
||||
},
|
||||
async SuiteSettingSave(data: any) {
|
||||
return await request({
|
||||
url: "/sys/settings/suite/save",
|
||||
method: "post",
|
||||
data
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const formRef = ref<any>(null);
|
||||
const formState = reactive<
|
||||
Partial<{
|
||||
enabled: boolean;
|
||||
registerGift?: {
|
||||
productId?: number;
|
||||
duration?: number;
|
||||
};
|
||||
intro?: string;
|
||||
}>
|
||||
>({ enabled: false });
|
||||
|
||||
async function loadSettings() {
|
||||
const data: any = await api.SuiteSettingGet();
|
||||
merge(formState, data);
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
const onClick = async () => {
|
||||
const form = await formRef.value.validateFields();
|
||||
await api.SuiteSettingSave(form);
|
||||
await loadSettings();
|
||||
notification.success({
|
||||
message: "保存成功"
|
||||
});
|
||||
};
|
||||
|
||||
const suiteDurationSelectedRef = ref();
|
||||
function onTableRefresh() {
|
||||
suiteDurationSelectedRef.value?.refresh();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-sys-settings-suite {
|
||||
.form-content {
|
||||
padding: 20px;
|
||||
.ant-table-body {
|
||||
height: 400px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<fs-dict-select style="width: 200px" :value="selectedValue" :dict="suiteDictRef" @selected-change="onSelectedChange"></fs-dict-select>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { durationDict } from "/@/views/certd/suite/api";
|
||||
import { ref, watch } from "vue";
|
||||
import { dict } from "@fast-crud/fast-crud";
|
||||
import { request } from "/@/api/service";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: {
|
||||
productId?: number;
|
||||
duration?: number;
|
||||
};
|
||||
}>();
|
||||
|
||||
const suiteDictRef = dict({
|
||||
async getData() {
|
||||
const res = await request({
|
||||
url: "/sys/suite/product/list",
|
||||
method: "post"
|
||||
});
|
||||
const options: any = [
|
||||
{
|
||||
value: "",
|
||||
label: "不赠送"
|
||||
}
|
||||
];
|
||||
res.forEach((item: any) => {
|
||||
const durationPrices = JSON.parse(item.durationPrices);
|
||||
for (const dp of durationPrices) {
|
||||
const value = item.id + "_" + dp.duration;
|
||||
options.push({
|
||||
label: `${item.title}<${durationDict.dataMap[dp.duration]?.label}>`,
|
||||
value: value,
|
||||
target: {
|
||||
productId: item.id,
|
||||
duration: dp.duration
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return options;
|
||||
}
|
||||
});
|
||||
|
||||
const selectedValue = ref();
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
(value) => {
|
||||
if (value && value.productId && value.duration) {
|
||||
selectedValue.value = value.productId + "_" + value.duration;
|
||||
} else {
|
||||
selectedValue.value = "";
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const onSelectedChange = (value: any) => {
|
||||
selectedValue.value = value;
|
||||
if (!value) {
|
||||
emit("update:modelValue", undefined);
|
||||
return;
|
||||
}
|
||||
const arr = value.value.split("_");
|
||||
emit("update:modelValue", {
|
||||
productId: parseInt(arr[0]),
|
||||
duration: parseInt(arr[1])
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
refresh() {
|
||||
suiteDictRef.reloadDict();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
Reference in New Issue
Block a user