build: trident-sync prepare

This commit is contained in:
xiaojunnuo
2023-01-29 13:44:19 +08:00
parent dcd1023a39
commit 07a45b4530
589 changed files with 36886 additions and 2 deletions
@@ -0,0 +1,112 @@
import * as api from "/@/views/certd/access/api";
import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common";
export default function ({ expose, props, ctx }) {
const { crudBinding } = expose;
const lastResRef = ref();
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
form.type = props.type;
const res = await api.UpdateObj(form);
lastResRef.value = res;
return res;
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
form.type = props.type;
const res = await api.AddObj(form);
lastResRef.value = res;
return res;
};
const selectedRowKey = ref([props.modelValue]);
// watch(
// () => {
// return props.modelValue;
// },
// (value) => {
// selectedRowKey.value = [value];
// },
// {
// immediate: true
// }
// );
const onSelectChange = (changed) => {
selectedRowKey.value = changed;
ctx.emit("update:modelValue", changed[0]);
};
const typeRef = ref("aliyun");
const commonColumnsDefine = getCommonColumnDefine(crudBinding, typeRef);
commonColumnsDefine.type.form.component.disabled = true;
return {
typeRef,
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
toolbar: {
show: false
},
search: {
show: false
},
form: {
wrapper: {
width: "1050px"
}
},
rowHandle: {
width: "150px"
},
table: {
rowSelection: {
type: "radio",
selectedRowKeys: selectedRowKey,
onChange: onSelectChange
},
customRow: (record) => {
return {
onClick: () => {
onSelectChange([record.id]);
} // 点击行
};
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
name: {
title: "名称",
search: {
show: true
},
type: ["text"],
form: {
rules: [{ required: true, message: "请填写名称" }]
}
},
...commonColumnsDefine
}
}
};
}
@@ -0,0 +1,64 @@
<template>
<fs-page class="page-cert-access-modal">
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, onMounted, ref, watch } from "vue";
import { useCrud, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
export default defineComponent({
name: "CertAccessModal",
props: {
type: {
type: String,
default: "aliyun"
},
modelValue: {}
},
emits: ["update:modelValue"],
setup(props, ctx) {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions, typeRef } = createCrudOptions({ expose, props, ctx });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
function onTypeChanged(value) {
typeRef.value = value;
expose.setSearchFormData({ form: { type: value }, mergeForm: true });
expose.doRefresh();
}
watch(
() => {
return props.type;
},
(value) => {
console.log("access type changed:", value);
onTypeChanged(value);
}
);
// 页面打开后获取列表数据
onMounted(() => {
onTypeChanged(props.type);
});
return {
crudBinding,
crudRef
};
}
});
</script>
<style lang="less">
.page-cert-access {
}
</style>
@@ -0,0 +1,103 @@
<template>
<div class="pi-access-selector">
<span v-if="target.name" class="mlr-5">{{ target.name }}</span>
<span v-else class="mlr-5 gray">请选择</span>
<a-button @click="chooseForm.open">选择</a-button>
<a-form-item-rest v-if="chooseForm.show">
<a-modal v-model:visible="chooseForm.show" title="选择授权提供者" width="700px" @ok="chooseForm.ok">
<div style="height: 400px; position: relative">
<cert-access-modal v-model="selectedId" :type="type"></cert-access-modal>
</div>
</a-modal>
</a-form-item-rest>
</div>
</template>
<script>
import { defineComponent, reactive, ref, watch } from "vue";
import * as api from "../api";
import CertAccessModal from "./access/index.vue";
export default defineComponent({
name: "PiAccessSelector",
components: { CertAccessModal },
props: {
modelValue: {
type: [Number, String],
default: null
},
type: {
type: String,
default: "aliyun"
}
},
emits: ["update:modelValue"],
setup(props, ctx) {
const target = ref({});
const selectedId = ref();
async function refreshTarget(value) {
selectedId.value = value;
if (value > 0) {
target.value = await api.GetObj(value);
}
}
watch(
() => {
return props.modelValue;
},
async (value) => {
selectedId.value = null;
target.value = {};
if (value == null) {
return;
}
await refreshTarget(value);
},
{
immediate: true
}
);
const providerDefine = ref({});
async function refreshProviderDefine(type) {
providerDefine.value = await api.GetProviderDefine(type);
}
watch(
() => {
return props.type;
},
async (value) => {
await refreshProviderDefine(value);
},
{
immediate: true
}
);
const chooseForm = reactive({
show: false,
open() {
chooseForm.show = true;
},
ok: () => {
chooseForm.show = false;
console.log("choose ok:", selectedId.value);
refreshTarget(selectedId.value);
ctx.emit("update:modelValue", selectedId.value);
}
});
return {
target,
selectedId,
providerDefine,
chooseForm
};
}
});
</script>
<style lang="less">
.access-selector {
}
</style>
@@ -0,0 +1,49 @@
import { request } from "/src/api/service";
const apiPrefix = "/pi/access";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "post",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
}
export function GetProviderDefine(type) {
return request({
url: apiPrefix + "/define",
method: "post",
params: { type }
});
}
@@ -0,0 +1,85 @@
import { dict } from "@fast-crud/fast-crud";
import * as api from "./api";
import _ from "lodash-es";
export function getCommonColumnDefine(crudBinding, typeRef) {
const AccessTypeDictRef = dict({
url: "/pi/access/accessTypeDict"
});
const defaultPluginConfig = {
component: {
name: "a-input",
vModel: "value"
}
};
function buildDefineFields(define, mode) {
const columns = crudBinding.value[mode + "Form"].columns;
for (const key in columns) {
if (key.indexOf(".") >= 0) {
delete columns[key];
}
}
console.log('crudBinding.value[mode + "Form"].columns', columns);
_.forEach(define.input, (value, mapKey) => {
const key = "access." + mapKey;
const field = {
...value,
key
};
columns[key] = _.merge({ title: key }, defaultPluginConfig, field);
console.log("form", crudBinding.value[mode + "Form"]);
});
}
return {
type: {
title: "类型",
type: "dict-select",
dict: AccessTypeDictRef,
search: {
show: false
},
form: {
component: {
disabled: false
},
rules: [{ required: true, message: "请选择类型" }],
valueChange: {
immediate: true,
async handle({ value, mode, form }) {
if (value == null) {
return;
}
const define = await api.GetProviderDefine(value);
console.log("define", define);
buildDefineFields(define, mode);
}
}
},
addForm: {
value: typeRef
}
},
setting: {
column: { show: false },
form: {
show: false,
valueBuilder({ value, form }) {
form.access = {};
if (!value) {
return;
}
const setting = JSON.parse(value);
for (const key in setting) {
form.access[key] = setting[key];
}
},
valueResolve({ form }) {
const setting = form.access;
form.setting = JSON.stringify(setting);
}
}
}
};
}
@@ -0,0 +1,61 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common";
export default function ({ expose }) {
const { t } = useI18n();
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
const typeRef = ref();
const { crudBinding } = expose;
const commonColumnsDefine = getCommonColumnDefine(crudBinding, typeRef);
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
form: {
labelCol: {
span: 6
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
name: {
title: "名称",
type: "text",
form: {
rules: [{ required: true, message: "必填项" }]
}
},
...commonColumnsDefine
}
}
};
}
@@ -0,0 +1,44 @@
<template>
<fs-page>
<template #header>
<div class="title">授权管理</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
import { message } from "ant-design-vue";
export default defineComponent({
name: "CertdAccess",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
@@ -0,0 +1,35 @@
import { request } from "/src/api/service";
import { RunHistory } from "/@/views/certd/pipeline/pipeline/type";
const apiPrefix = "/pi/history";
export async function GetList(query) {
const list = await request({
url: apiPrefix + "/list",
method: "post",
data: query
});
for (const item of list) {
if (item.pipeline) {
item.pipeline = JSON.parse(item.pipeline);
}
}
console.log("history", list);
return list;
}
export async function GetDetail(query): Promise<RunHistory> {
const detail = await request({
url: apiPrefix + "/detail",
method: "post",
params: query
});
const pipeline = JSON.parse(detail.history?.pipeline || "{}");
const logs = JSON.parse(detail.log?.logs || "{}");
return {
id: detail.history.id,
pipeline,
logs
} as RunHistory;
}
@@ -0,0 +1,38 @@
import { request } from "/src/api/service";
import _ from "lodash-es";
const apiPrefix = "/pi/plugin";
const defaultInputDefine = {
component: {
name: "a-input",
vModel: "modelValue"
}
};
export async function GetList(query) {
const plugins = await request({
url: apiPrefix + "/list",
method: "post",
params: query
});
for (const plugin of plugins) {
for (const key in plugin.input) {
const field = _.merge({}, defaultInputDefine, plugin.input[key]);
if (field.component.name === "a-input" || field.component.name === "a-select") {
field.component.vModel = "value";
}
//嵌套对象
field.key = ["input", key];
if (field.required) {
delete field.required;
if (field.rules == null) {
field.rules = [];
}
field.rules.push({ required: true, message: "此项必填" });
}
plugin.input[key] = field;
}
}
console.log("plugins", plugins);
return plugins;
}
@@ -0,0 +1,66 @@
import { request } from "/src/api/service";
const apiPrefix = "/pi/pipeline";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "post",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
}
export function GetDetail(id) {
return request({
url: apiPrefix + "/detail",
method: "post",
params: { id }
});
}
export function Save(pipelineEntity) {
return request({
url: apiPrefix + "/save",
method: "post",
data: pipelineEntity
});
}
export function Trigger(id) {
return request({
url: apiPrefix + "/trigger",
method: "post",
params: { id }
});
}
@@ -0,0 +1,129 @@
import { compute } from "@fast-crud/fast-crud";
import { Dicts } from "./dicts";
export default function () {
return {
crudOptions: {
form: {
wrapper: {
width: "1150px"
}
},
columns: {
domains: {
title: "域名",
type: ["dict-select"],
search: {
show: true,
component: {
name: "a-input"
}
},
form: {
col: {
span: 24
},
wrapperCol: {
span: null
},
component: {
mode: "tags",
open: false
},
helper: {
render: () => {
return (
<div>
<div>支持通配符域名例如 *.foo.com *.test.handsfree.work</div>
<div>支持多个域名多个子域名多个通配符域名打到一个证书上域名必须是在同一个DNS提供商解析</div>
<div>多级子域名要分成多个域名输入*.foo.com的证书不能用于xxx.yyy.foo.com</div>
<div>输入一个回车之后再输入下一个</div>
</div>
);
}
},
valueResolve({ form }) {
if (form.domains instanceof String) {
form.domains = form.domains?.join(",");
}
},
rules: [{ required: true, message: "请填写域名" }]
}
},
email: {
title: "邮箱",
type: "text",
search: { show: false },
form: {
rules: [{ required: true, type: "email", message: "请填写邮箱" }]
}
},
dnsProviderType: {
title: "DNS提供商",
type: "dict-select",
dict: Dicts.dnsProviderTypeDict,
form: {
value: "aliyun",
rules: [{ required: true, message: "请选择DNS提供商" }],
valueChange({ form }) {
form.dnsProviderAccess = null;
}
}
},
dnsProviderAccess: {
title: "DNS授权",
type: "text",
form: {
component: {
name: "PiAccessSelector",
type: compute(({ form }) => {
return form.dnsProviderType;
}),
vModel: "modelValue"
},
rules: [{ required: true, message: "请选择DNS授权" }]
}
}
// country: {
// title: "国家",
// type: "text",
// form: {
// value: "China"
// }
// },
// state: {
// title: "省份",
// type: "text",
// form: {
// value: "GuangDong"
// }
// },
// locality: {
// title: "市区",
// type: "text",
// form: {
// value: "NanShan"
// }
// },
// organization: {
// title: "单位",
// type: "text",
// form: {
// value: "CertD"
// }
// },
// organizationUnit: {
// title: "部门",
// type: "text",
// form: {
// value: "IT Dept"
// }
// },
// remark: {
// title: "备注",
// type: "text"
// }
}
}
};
}
@@ -0,0 +1,9 @@
import { dict } from "@fast-crud/fast-crud";
export const Dicts = {
certIssuerDict: dict({ data: [{ value: "letencrypt", label: "LetEncrypt" }] }),
challengeTypeDict: dict({ data: [{ value: "dns", label: "DNS校验" }] }),
dnsProviderTypeDict: dict({
url: "pi/dnsProvider/dnsProviderTypeDict"
})
};
@@ -0,0 +1,46 @@
<template>
<fs-form-wrapper ref="formWrapperRef" />
</template>
<script lang="ts">
import { useColumns, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud.jsx";
import { ref } from "vue";
import _ from "lodash-es";
export default {
name: "PiCertdForm",
setup(props, ctx) {
// 自定义表单配置
const { buildFormOptions } = useColumns();
//使用crudOptions结构来构建自定义表单配置
let { crudOptions } = createCrudOptions();
const doSubmitRef = ref();
const formOptions = buildFormOptions(
_.merge(crudOptions, {
form: {
doSubmit({ form }) {
// 创建certd 的pipeline
doSubmitRef.value({ form });
}
}
})
);
const formWrapperRef = ref();
const formWrapperOptions = ref();
formWrapperOptions.value = formOptions;
function open(doSubmit) {
doSubmitRef.value = doSubmit;
formWrapperRef.value.open(formWrapperOptions.value);
}
return {
formWrapperRef,
open,
formWrapperOptions
};
}
};
</script>
<style scoped></style>
@@ -0,0 +1,225 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { ref, shallowRef } from "vue";
import { useRouter } from "vue-router";
import { dict } from "@fast-crud/fast-crud";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { nanoid } from "nanoid";
import { message } from "ant-design-vue";
export default function ({ expose, certdFormRef }) {
const router = useRouter();
const { t } = useI18n();
const lastResRef = ref();
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
const res = await api.UpdateObj(form);
lastResRef.value = res;
return res;
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
form.content = JSON.stringify({
title: form.title
});
const res = await api.AddObj(form);
lastResRef.value = res;
return res;
};
function addCertdPipeline() {
certdFormRef.value.open(async ({ form }) => {
// 添加certd pipeline
const pipeline = {
title: form.domains[0] + "证书自动化",
stages: [
{
id: nanoid(),
title: "证书申请阶段",
tasks: [
{
id: nanoid(),
title: "证书申请任务",
steps: [
{
id: nanoid(),
title: "申请证书",
input: {
renewDays: 20,
...form
},
strategy: {
runStrategy: 0 // 正常执行
},
type: "CertApply"
}
]
}
]
}
]
};
const id = await api.Save({
content: JSON.stringify(pipeline),
keepHistoryCount: 30
});
message.success("创建成功,请添加证书部署任务");
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
});
}
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
actionbar: {
buttons: {
add: {
order: 5,
text: "自定义流水线"
},
addCertd: {
order: 1,
text: "添加证书流水线",
type: "primary",
click() {
addCertdPipeline();
}
}
}
},
form: {
afterSubmit({ form, res, mode }) {
if (mode === "add") {
router.push({ path: "/certd/pipeline/detail", query: { id: res.id, editMode: "true" } });
}
}
},
rowHandle: {
buttons: {
view: {
click({ row }) {
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
}
},
config: {
order: 1,
title: null,
type: "link",
icon: "ant-design:edit-outlined",
click({ row }) {
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "true" } });
}
},
edit: {
order: 2,
icon: "ant-design:setting-outlined"
},
remove: {
order: 5
}
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
title: {
title: "流水线名称",
type: "text",
search: {
show: true,
component: {
name: "a-input"
}
},
column: {
width: 300
}
},
lastHistoryTime: {
title: "最后运行",
type: "datetime",
form: {
show: false
}
},
status: {
title: "状态",
type: "dict-select",
dict: dict({
data: statusUtil.getOptions()
}),
form: {
show: false
}
},
disabled: {
title: "启用",
type: "dict-switch",
dict: dict({
data: [
{ value: true, label: "禁用" },
{ value: false, label: "启用" }
]
}),
form: {
value: false,
show: false
},
column: {
component: {
name: "fs-dict-switch",
vModel: "checked"
},
async valueChange({ row, key, value }) {
return await api.UpdateObj({
id: row.id,
disabled: row[key]
});
}
}
},
keepHistoryCount: {
title: "历史记录保持数",
type: "number",
form: {
value: 30,
helper: "历史记录保持条数,多余的会被删除"
}
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false
}
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false
}
}
}
}
};
}
@@ -0,0 +1 @@
https://stackoverflow.com/questions/28365839/dashed-border-animation-in-css3-animation
@@ -0,0 +1,86 @@
<template>
<fs-page class="fs-pipeline-detail">
<pipeline-edit v-model:edit-mode="editMode" :pipeline-id="pipelineId" :options="pipelineOptions"></pipeline-edit>
</fs-page>
</template>
<script lang="ts">
import { defineComponent, Ref, ref } from "vue";
import PipelineEdit from "./pipeline/index.vue";
import * as pluginApi from "./api.plugin";
import * as historyApi from "./api.history";
import * as api from "./api";
import { useRoute } from "vue-router";
import { Pipeline, PipelineDetail, PipelineOptions, RunHistory } from "/@/views/certd/pipeline/pipeline/type";
import { PluginDefine } from "@certd/pipeline/src";
export default defineComponent({
name: "PipelineDetail",
components: { PipelineEdit },
setup() {
const route = useRoute();
const pipelineId = ref(route.query.id);
const getPipelineDetail = async ({ pipelineId }) => {
const detail = await api.GetDetail(pipelineId);
return {
pipeline: {
id: detail.pipeline.id,
stages: [],
triggers: [],
...JSON.parse(detail.pipeline.content || "{}")
}
} as PipelineDetail;
};
const getHistoryList = async ({ pipelineId }) => {
const list: RunHistory[] = await historyApi.GetList({ pipelineId });
return list;
};
const getHistoryDetail = async ({ historyId }): Promise<RunHistory> => {
const detail = await historyApi.GetDetail({ id: historyId });
return detail;
};
const getPlugins = async () => {
const plugins = await pluginApi.GetList({});
return plugins as PluginDefine[];
};
async function doSave(pipelineConfig: Pipeline) {
await api.Save({
id: pipelineConfig.id,
content: JSON.stringify(pipelineConfig)
});
}
async function doTrigger({ pipelineId }) {
await api.Trigger(pipelineId);
}
const pipelineOptions: Ref<PipelineOptions> = ref({
doTrigger,
doSave,
getPlugins,
getHistoryList,
getHistoryDetail,
getPipelineDetail
});
const editMode = ref(false);
if (route.query.editMode !== "false") {
editMode.value = true;
}
return {
pipelineOptions,
pipelineId,
editMode
};
}
});
</script>
<style lang="less">
.page-pipeline-detail {
}
</style>
@@ -0,0 +1,52 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">我的流水线</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<pi-certd-form ref="certdFormRef"></pi-certd-form>
</fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
import PiCertdForm from "./certd-form/index.vue";
export default defineComponent({
name: "PipelineManager",
components: { PiCertdForm },
setup() {
const certdFormRef = ref();
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose, certdFormRef });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef,
certdFormRef
};
}
});
</script>
<style lang="less"></style>
@@ -0,0 +1,66 @@
<template>
<a-timeline-item v-if="status && runnable" class="pi-history-timeline-item" :color="status.color">
<template #dot>
<fs-icon v-bind="status" />
</template>
<p>
<fs-date-format :model-value="runnable.status?.startTime"></fs-date-format>
<a-tag class="ml-10" :color="status.color">{{ status.label }}</a-tag>
<a-tag v-if="isCurrent" class="pointer" color="green" :closable="true" @close="cancel">当前</a-tag>
<a-tag v-else-if="!editMode" class="pointer" color="blue" @click="view">查看</a-tag>
</p>
</a-timeline-item>
</template>
<script lang="ts">
import { defineComponent, ref, provide, Ref, watch, computed } from "vue";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
export default defineComponent({
name: "PiHistoryTimelineItem",
props: {
runnable: {
type: Object,
default() {
return {};
}
},
type: {
type: String,
default: "icon"
},
isCurrent: {
type: Boolean
},
editMode: {
type: Boolean,
default: false
}
},
emits: ["view", "cancel"],
setup(props, ctx) {
const status = computed(() => {
return statusUtil.get(props.runnable?.status?.result);
});
function view() {
ctx.emit("view");
}
function cancel() {
ctx.emit("cancel");
}
return {
status,
cancel,
view
};
}
});
</script>
<style lang="less">
.pi-history-timeline-item {
.ant-tag.pointer {
cursor: pointer;
}
}
</style>
@@ -0,0 +1,61 @@
<template>
<a-select class="pi-output-selector" :value="modelValue" :options="options" @update:value="onChanged"> </a-select>
</template>
<script lang="ts">
import { inject, onMounted, Ref, ref, watch } from "vue";
import { pluginManager } from "../../plugin";
export default {
name: "PiOutputSelector",
props: {
modelValue: {
type: String,
default: undefined
}
},
emits: ["update:modelValue"],
setup(props, ctx) {
const options = ref<any[]>([]);
const pipeline = inject("pipeline") as Ref<any>;
const currentStageIndex = inject("currentStageIndex") as Ref<number>;
const currentStepIndex = inject("currentStepIndex") as Ref<number>;
const currentTask = inject("currentTask") as Ref<any>;
function onCreate() {
options.value = pluginManager.getPreStepOutputOptions({
pipeline: pipeline.value,
currentStageIndex: currentStageIndex.value,
currentStepIndex: currentStepIndex.value,
currentTask: currentTask.value
});
if (props.modelValue == null && options.value.length > 0) {
ctx.emit("update:modelValue", options.value[0].value);
}
}
onMounted(() => {
onCreate();
});
watch(
() => {
return pluginManager.map;
},
() => {
onCreate();
}
);
function onChanged(value) {
ctx.emit("update:modelValue", value);
}
return {
options,
onChanged
};
}
};
</script>
<style lang="less"></style>
@@ -0,0 +1,46 @@
<template>
<span v-if="statusRef" class="pi-status-show">
<template v-if="type === 'icon'">
<fs-icon class="status-icon" v-bind="statusRef" :style="{ color: statusRef.color }" />
</template>
<template v-if="type === 'tag'">
<a-tag :color="statusRef.color">{{ statusRef.label }}</a-tag>
</template>
</span>
</template>
<script lang="ts">
import { defineComponent, ref, provide, Ref, watch, computed } from "vue";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
export default defineComponent({
name: "PiStatusShow",
props: {
status: {
type: String,
default: ""
},
type: {
type: String,
default: "icon"
}
},
setup(props, ctx) {
const statusRef = computed(() => {
return statusUtil.get(props.status);
});
return {
statusRef
};
}
});
</script>
<style lang="less">
.pi-status-show {
.status-icon {
font-size: 16px;
margin-left: 3px;
margin-right: 3px;
}
}
</style>
@@ -0,0 +1,314 @@
<template>
<a-drawer v-model:visible="stepDrawerVisible" placement="right" :closable="true" width="600px" :after-visible-change="stepDrawerOnAfterVisibleChange">
<template #title>
编辑任务
<a-button v-if="editMode" @click="stepDelete()">
<template #icon><DeleteOutlined /></template>
</a-button>
</template>
<template v-if="currentStep">
<pi-container v-if="currentStep._isAdd" class="pi-step-form">
<a-row :gutter="10">
<a-col v-for="(item, index) of stepPluginDefineList" :key="index" class="step-plugin" :span="12">
<a-card
hoverable
:class="{ current: item.name === currentStep.type }"
@click="stepTypeSelected(item)"
@dblclick="
stepTypeSelected(item);
stepTypeSave();
"
>
<a-card-meta>
<template #title>
<a-avatar :src="item.icon || '/images/plugin.png'" />
<span class="title">{{ item.title }}</span>
</template>
<template #description>
<span :title="item.desc">{{ item.desc }}</span>
</template>
</a-card-meta>
</a-card>
</a-col>
</a-row>
<a-button v-if="editMode" type="primary" @click="stepTypeSave"> 确定 </a-button>
</pi-container>
<pi-container v-else class="pi-step-form">
<a-form ref="stepFormRef" class="step-form" :model="currentStep" :label-col="labelCol" :wrapper-col="wrapperCol">
<div class="mb-10">
<a-alert type="info" :message="currentPlugin.title" :description="currentPlugin.desc"> </a-alert>
</div>
<fs-form-item
v-model="currentStep.title"
:item="{
title: '任务名称',
key: 'title',
component: {
name: 'a-input',
vModel: 'value'
},
rules: [{ required: true, message: '此项必填' }]
}"
:get-context-fn="blankFn"
/>
<template v-for="(item, key) in currentPlugin.input" :key="key">
<fs-form-item v-model="currentStep.input[key]" :item="item" :get-context-fn="blankFn" />
</template>
<fs-form-item
v-model="currentStep.strategy.runStrategy"
:item="{
title: '运行策略',
key: 'strategy.runStrategy',
component: {
name: 'a-select',
vModel: 'value',
options: [
{ value: 0, label: '正常运行' },
{ value: 1, label: '成功后跳过' }
]
},
rules: [{ required: true, message: '此项必填' }]
}"
:get-context-fn="blankFn"
/>
</a-form>
<template #footer>
<a-form-item v-if="editMode" :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="stepSave"> 确定 </a-button>
</a-form-item>
</template>
</pi-container>
</template>
</a-drawer>
</template>
<script lang="jsx">
import { message, Modal } from "ant-design-vue";
import { inject, ref } from "vue";
import _ from "lodash-es";
import { nanoid } from "nanoid";
export default {
name: "PiStepForm",
props: {
editMode: {
type: Boolean,
default: true
}
},
emits: ["update"],
setup(props, context) {
/**
* step drawer
* @returns
*/
function useStepForm() {
const stepPluginDefineList = inject("plugins");
const mode = ref("add");
const callback = ref();
const currentStep = ref({ title: undefined, input: {} });
const currentPlugin = ref({});
const stepFormRef = ref(null);
const stepDrawerVisible = ref(false);
const rules = ref({
name: [
{
type: "string",
required: true,
message: "请输入名称"
}
]
});
const stepTypeSelected = (item) => {
currentStep.value.type = item.name;
currentStep.value.title = item.title;
console.log("currentStepTypeChanged:", currentStep.value);
};
const stepTypeSave = () => {
currentStep.value._isAdd = false;
if (currentStep.value.type == null) {
message.warn("请先选择类型");
return;
}
// 给step的input设置默认值
changeCurrentPlugin(currentStep.value);
//赋初始值
_.merge(currentStep.value, { input: {}, strategy: { runStrategy: 0 } }, currentPlugin.value.default, currentStep.value);
for (const key in currentPlugin.value.input) {
const input = currentPlugin.value.input[key];
if (input.default != null) {
currentStep.value.input[key] = input.default ?? input.value;
}
}
};
const stepDrawerShow = () => {
stepDrawerVisible.value = true;
};
const stepDrawerClose = () => {
stepDrawerVisible.value = false;
};
const stepDrawerOnAfterVisibleChange = (val) => {
console.log("stepDrawerOnAfterVisibleChange", val);
};
const stepOpen = (step, emit) => {
callback.value = emit;
currentStep.value = _.merge({ input: {}, strategy: {} }, step);
console.log("currentStepOpen", currentStep.value);
if (step.type) {
changeCurrentPlugin(currentStep.value);
}
stepDrawerShow();
};
const stepAdd = (emit) => {
mode.value = "add";
const step = {
id: nanoid(),
title: "新任务",
type: undefined,
_isAdd: true,
input: {},
status: null
};
stepOpen(step, emit);
};
const stepEdit = (step, emit) => {
mode.value = "edit";
stepOpen(step, emit);
};
const stepView = (step, emit) => {
mode.value = "view";
stepOpen(step, emit);
};
const changeCurrentPlugin = (step) => {
const stepType = step.type;
const pluginDefine = stepPluginDefineList.value.find((p) => {
return p.name === stepType;
});
if (pluginDefine) {
step.type = stepType;
step._isAdd = false;
currentPlugin.value = pluginDefine;
}
console.log("currentStepTypeChanged:", currentStep.value);
console.log("currentStepPlugin:", currentPlugin.value);
};
const stepSave = async (e) => {
console.log("currentStepSave", currentStep.value);
try {
await stepFormRef.value.validate();
} catch (e) {
console.error("表单验证失败:", e);
return;
}
callback.value("save", currentStep.value);
stepDrawerClose();
};
const stepDelete = () => {
Modal.confirm({
title: "确认",
content: `确定要删除此步骤吗?`,
async onOk() {
callback.value("delete");
stepDrawerClose();
}
});
};
const blankFn = () => {
return {};
};
return {
stepTypeSelected,
stepTypeSave,
stepPluginDefineList,
stepFormRef,
mode,
stepAdd,
stepEdit,
stepView,
stepDrawerShow,
stepDrawerVisible,
stepDrawerOnAfterVisibleChange,
currentStep,
currentPlugin,
stepSave,
stepDelete,
rules,
blankFn
};
}
return {
...useStepForm(),
labelCol: { span: 6 },
wrapperCol: { span: 16 }
};
}
};
</script>
<style lang="less">
.pi-step-form {
.body {
padding: 10px;
.ant-card {
margin-bottom: 10px;
&.current {
border-color: #00b7ff;
}
.ant-card-meta-title {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.ant-avatar {
width: 24px;
height: 24px;
flex-shrink: 0;
}
.title {
margin-left: 5px;
white-space: nowrap;
flex: 1;
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
}
.ant-card-body {
padding: 14px;
height: 100px;
overflow-y: hidden;
.ant-card-meta-description {
font-size: 10px;
line-height: 20px;
height: 40px;
color: #7f7f7f;
}
}
}
}
</style>
@@ -0,0 +1,265 @@
<template>
<a-drawer
v-model:visible="taskDrawerVisible"
placement="right"
:closable="true"
width="600px"
class="pi-task-form"
:after-visible-change="taskDrawerOnAfterVisibleChange"
>
<template #title>
编辑任务
<a-button v-if="editMode" @click="taskDelete()">
<template #icon><DeleteOutlined /></template>
</a-button>
</template>
<template v-if="currentTask">
<pi-container>
<a-form
ref="taskFormRef"
class="task-form"
:model="currentTask"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<fs-form-item
v-model="currentTask.title"
:item="{
title: '任务名称',
key: 'title',
component: {
name: 'a-input',
vModel: 'value'
},
rules: [{ required: true, message: '此项必填' }]
}"
:get-context-fn="blankFn"
/>
<div class="steps">
<a-form-item
:value="currentTask.steps"
name="steps"
label=""
:wrapper-col="{ span: 24 }"
:rules="[{ required: true, message: '至少需要一个步骤,或者你可以点击标题右边删除按钮删除此任务' }]"
>
<a-descriptions title="任务步骤" size="small">
<template #extra>
<a-button type="primary" @click="stepAdd(currentTask)">添加步骤</a-button>
</template>
</a-descriptions>
<a-list class="step-list" item-layout="horizontal" :data-source="currentTask.steps">
<template #renderItem="{ item, index }">
<a-list-item>
<template #actions>
<a key="edit" @click="stepEdit(currentTask, item, index)">编辑</a>
<a key="remove" @click="stepDelete(currentTask, index)">删除</a>
</template>
<a-list-item-meta>
<template #title>
{{ item.title }}
</template>
<template #avatar>
<fs-icon icon="ion:flash"></fs-icon>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-form-item>
</div>
</a-form>
<pi-step-form ref="stepFormRef" :edit-mode="editMode"></pi-step-form>
<template #footer>
<a-form-item v-if="editMode" :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="taskSave"> 确定 </a-button>
</a-form-item>
</template>
</pi-container>
</template>
</a-drawer>
</template>
<script lang="ts">
import { provide, Ref, ref } from "vue";
import _ from "lodash-es";
import { nanoid } from "nanoid";
import PiStepForm from "../step-form/index.vue";
import { message, Modal } from "ant-design-vue";
export default {
name: "PiTaskForm",
components: { PiStepForm },
props: {
editMode: {
type: Boolean,
default: true
}
},
emits: ["update"],
setup(props, ctx) {
function useStep() {
const stepFormRef: Ref<any> = ref(null);
const currentStepIndex = ref(0);
provide("currentStepIndex", currentStepIndex);
const stepAdd = (task) => {
currentStepIndex.value = task.steps.length;
stepFormRef.value.stepAdd((type, value) => {
if (type === "save") {
task.steps.push(value);
if (!task.title || task.title === "新任务") {
task.title = value.title;
}
}
});
};
const stepEdit = (task, step, stepIndex) => {
currentStepIndex.value = stepIndex;
console.log("step.edit start", task, step, props.editMode);
if (props.editMode) {
console.log("step.edit", task, step);
stepFormRef.value.stepEdit(step, (type, value) => {
console.log("step.save", step, type, value);
if (type === "delete") {
task.steps.splice(stepIndex, 1);
} else if (type === "save") {
task.steps[stepIndex] = { ...value };
}
console.log("task.steps", task.steps);
});
} else {
stepFormRef.value.stepView(step, (type, value) => {});
}
};
const stepDelete = (task, stepIndex) => {
Modal.confirm({
title: "确认",
content: `确定要删除此步骤吗?`,
async onOk() {
task.steps.splice(stepIndex, 1);
}
});
};
return { stepAdd, stepEdit, stepDelete, stepFormRef };
}
/**
* task drawer
* @returns
*/
function useTaskForm() {
const mode = ref("add");
const callback = ref();
const currentTask = ref({ title: undefined, steps: [] });
provide("currentTask", currentTask);
const taskFormRef: Ref<any> = ref(null);
const taskDrawerVisible = ref(false);
const rules = ref({
name: [
{
type: "string",
required: true,
message: "请输入名称"
}
]
});
const taskDrawerShow = () => {
taskDrawerVisible.value = true;
};
const taskDrawerClose = () => {
taskDrawerVisible.value = false;
};
const taskDrawerOnAfterVisibleChange = (val) => {
console.log("taskDrawerOnAfterVisibleChange", val);
};
const taskOpen = (task, emit) => {
callback.value = emit;
currentTask.value = _.merge({ steps: {} }, task);
console.log("currentTaskOpen", currentTask.value);
taskDrawerShow();
};
const taskAdd = (emit) => {
mode.value = "add";
const task = { id: nanoid(), title: "新任务", steps: [], status: null };
taskOpen(task, emit);
};
const taskEdit = (task, emit) => {
mode.value = "edit";
taskOpen(task, emit);
};
const taskView = (task, emit) => {
mode.value = "view";
taskOpen(task, emit);
};
const taskSave = async (e) => {
console.log("currentTaskSave", currentTask.value);
try {
await taskFormRef.value.validate();
} catch (e) {
console.error("表单验证失败:", e);
return;
}
callback.value("save", currentTask.value);
taskDrawerClose();
};
const taskDelete = () => {
Modal.confirm({
title: "确认",
content: `确定要删除此任务吗?`,
async onOk() {
callback.value("delete");
taskDrawerClose();
}
});
};
const blankFn = () => {
return {};
};
return {
taskFormRef,
mode,
taskAdd,
taskEdit,
taskView,
taskDrawerShow,
taskDrawerVisible,
taskDrawerOnAfterVisibleChange,
currentTask,
taskSave,
taskDelete,
rules,
blankFn
};
}
return {
labelCol: { span: 6 },
wrapperCol: { span: 16 },
...useTaskForm(),
...useStep()
};
}
};
</script>
<style lang="less">
.pi-task-form {
.steps {
margin: 0 50px 0 50px;
}
}
</style>
@@ -0,0 +1,116 @@
<template>
<a-modal
v-model:visible="taskModal.visible"
class="pi-task-view"
title="任务日志"
style="width: 80%"
v-bind="taskModal"
>
<a-tabs v-model:activeKey="activeKey" tab-position="left" animated>
<a-tab-pane v-for="item of detail.nodes" :key="item.node.id">
<template #tab>
<div class="tab-title" :title="item.node.title">
<span class="tab-title-text">{{ item.type }} {{ item.node.title }}</span>
<pi-status-show :status="item.node.status?.result" type="icon"></pi-status-show>
</div>
</template>
<pre
class="pi-task-view-logs"
><template v-for="(text, index) of item.logs" :key="index">{{ text }}</template></pre>
</a-tab-pane>
</a-tabs>
</a-modal>
</template>
<script lang="ts">
import { inject, provide, Ref, ref } from "vue";
import { RunHistory } from "/@/views/certd/pipeline/pipeline/type";
import PiStatusShow from "/@/views/certd/pipeline/pipeline/component/status-show.vue";
export default {
name: "PiTaskView",
components: { PiStatusShow },
props: {},
setup(props, ctx) {
const taskModal = ref({
visible: false,
onOk() {
taskViewClose();
},
cancelText: "关闭"
});
const detail = ref({ nodes: [] });
const activeKey = ref();
const currentHistory: Ref<RunHistory> | undefined = inject("currentHistory");
const taskViewOpen = (task) => {
taskModal.value.visible = true;
const nodes: any = [];
// nodes.push({
// node: task,
// type: "任务",
// tab: 0,
// logs: [],
// result: {}
// });
for (let step of task.steps) {
nodes.push({
node: step,
type: "步骤",
tab: 2,
logs: []
});
}
for (let node of nodes) {
if (currentHistory?.value?.logs != null) {
node.logs = currentHistory.value.logs[node.node.id] || [];
}
}
if (task.steps.length > 0) {
activeKey.value = task.steps[0].id;
}
detail.value = { nodes };
console.log("nodes", nodes);
};
const taskViewClose = () => {
taskModal.value.visible = false;
};
return {
detail,
taskModal,
activeKey,
taskViewOpen,
taskViewClose
};
}
};
</script>
<style lang="less">
.pi-task-view {
.tab-title {
display: flex;
.tab-title-text {
display: inline-block;
width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.pi-task-view-logs {
background-color: #000c17;
color: #fafafa;
min-height: 300px;
max-height: 580px;
white-space: pre-wrap;
word-wrap: break-word;
}
}
</style>
@@ -0,0 +1,208 @@
<template>
<a-drawer
v-model:visible="triggerDrawerVisible"
placement="right"
:closable="true"
width="600px"
class="pi-trigger-form"
:after-visible-change="triggerDrawerOnAfterVisibleChange"
>
<template #title>
编辑触发器
<a-button v-if="mode === 'edit'" @click="triggerDelete()">
<template #icon><DeleteOutlined /></template>
</a-button>
</template>
<template v-if="currentTrigger">
<pi-container>
<a-form
ref="triggerFormRef"
class="trigger-form"
:model="currentTrigger"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<fs-form-item
v-model="currentTrigger.title"
:item="{
title: '触发器名称',
key: 'title',
component: {
name: 'a-input',
vModel: 'value',
disabled: !editMode
},
rules: [{ required: true, message: '此项必填' }]
}"
/>
<fs-form-item
v-model="currentTrigger.type"
:item="{
title: '类型',
key: 'type',
value: 'timer',
component: {
name: 'a-select',
vModel: 'value',
disabled: !editMode,
options: [{ value: 'timer', label: '定时' }]
},
rules: [{ required: true, message: '此项必填' }]
}"
/>
<fs-form-item
v-model="currentTrigger.props.cron"
:item="{
title: '定时脚本',
key: 'props.cron',
component: {
disabled: !editMode,
name: 'a-input',
vModel: 'value'
},
helper: 'cron表达式,例如: * * 3 * * * ,表示每天凌晨3点触发',
rules: [{ required: true, message: '此项必填' }]
}"
/>
</a-form>
<template #footer>
<a-form-item v-if="editMode" :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="triggerSave"> 确定 </a-button>
</a-form-item>
</template>
</pi-container>
</template>
</a-drawer>
</template>
<script>
import { message, Modal } from "ant-design-vue";
import { inject, ref } from "vue";
import _ from "lodash-es";
import { nanoid } from "nanoid";
export default {
name: "PiTriggerForm",
props: {
editMode: {
type: Boolean,
default: true
}
},
emits: ["update"],
setup(props, context) {
/**
* trigger drawer
* @returns
*/
function useTriggerForm() {
const mode = ref("add");
const callback = ref();
const currentTrigger = ref({ title: undefined, input: {} });
const currentPlugin = ref({});
const triggerFormRef = ref(null);
const triggerDrawerVisible = ref(false);
const rules = ref({
name: [
{
type: "string",
required: true,
message: "请输入名称"
}
]
});
const triggerDrawerShow = () => {
triggerDrawerVisible.value = true;
};
const triggerDrawerClose = () => {
triggerDrawerVisible.value = false;
};
const triggerDrawerOnAfterVisibleChange = (val) => {
console.log("triggerDrawerOnAfterVisibleChange", val);
};
const triggerOpen = (trigger, emit) => {
callback.value = emit;
currentTrigger.value = _.cloneDeep(trigger);
console.log("currentTriggerOpen", currentTrigger.value);
triggerDrawerShow();
};
const triggerAdd = (emit) => {
mode.value = "add";
const trigger = { id: nanoid(), title: "定时触发", type: "timer", props: {} };
triggerOpen(trigger, emit);
};
const triggerEdit = (trigger, emit) => {
mode.value = "edit";
triggerOpen(trigger, emit);
};
const triggerView = (trigger, emit) => {
mode.value = "view";
triggerOpen(trigger, emit);
};
const triggerSave = async (e) => {
console.log("currentTriggerSave", currentTrigger.value);
try {
await triggerFormRef.value.validate();
} catch (e) {
console.error("表单验证失败:", e);
return;
}
callback.value("save", currentTrigger.value);
triggerDrawerClose();
};
const triggerDelete = () => {
Modal.confirm({
title: "确认",
content: `确定要删除此触发器吗?`,
async onOk() {
callback.value("delete");
triggerDrawerClose();
}
});
};
const blankFn = () => {
return {};
};
return {
triggerFormRef,
mode,
triggerAdd,
triggerEdit,
triggerView,
triggerDrawerShow,
triggerDrawerVisible,
triggerDrawerOnAfterVisibleChange,
currentTrigger,
currentPlugin,
triggerSave,
triggerDelete,
rules,
blankFn
};
}
return {
...useTriggerForm(),
labelCol: { span: 6 },
wrapperCol: { span: 16 }
};
}
};
</script>
<style lang="less">
.pi-trigger-form {
}
</style>
@@ -0,0 +1,652 @@
<template>
<fs-page v-if="pipeline" class="page-pipeline-edit">
<template #header>
<div class="title">
<pi-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></pi-editable>
</div>
<div class="more">
<template v-if="editMode">
<a-button type="primary" :loading="saveLoading" @click="save">保存</a-button>
<a-button class="ml-5" @click="cancel">取消</a-button>
</template>
<template v-else>
<a-button type="primary" @click="edit">编辑</a-button>
</template>
</div>
</template>
<div class="layout">
<div class="layout-left">
<div class="pipeline-container">
<div class="pipeline">
<div class="stages">
<div class="stage first-stage">
<div class="title">
<pi-editable model-value="触发源" :disabled="true" />
</div>
<div class="tasks">
<div class="task-container first-task">
<div class="line">
<div class="flow-line"></div>
</div>
<div class="task">
<a-button shape="round" type="primary" @click="run">
<fs-icon icon="ion:play"></fs-icon>
手动触发
</a-button>
</div>
</div>
<div v-for="(trigger, index) of pipeline.triggers" :key="trigger.id" class="task-container">
<div class="line">
<div class="flow-line"></div>
</div>
<div class="task">
<a-button shape="round" @click="triggerEdit(trigger, index)">
<fs-icon icon="ion:time"></fs-icon>
{{ trigger.title }}
</a-button>
</div>
</div>
<div v-if="editMode" class="task-container is-add">
<div class="line">
<div class="flow-line"></div>
</div>
<div class="task">
<a-button shape="round" type="dashed" @click="triggerAdd">
<fs-icon icon="ion:add-circle-outline"></fs-icon>
触发源定时
</a-button>
</div>
</div>
</div>
</div>
<div v-for="(stage, index) of pipeline.stages" :key="stage.id" class="stage" :class="{ 'last-stage': !editMode && index === pipeline.stages.length - 1 }">
<div class="title">
<pi-editable v-model="stage.title" :disabled="!editMode"></pi-editable>
</div>
<div class="tasks">
<div
v-for="(task, taskIndex) of stage.tasks"
:key="task.id"
class="task-container"
:class="{
'first-task': taskIndex === 0
}"
>
<div class="line">
<div class="flow-line"></div>
<fs-icon v-if="editMode" class="add-stage-btn" title="添加新阶段" icon="ion:add-circle" @click="stageAdd(index)"></fs-icon>
</div>
<div class="task">
<a-button shape="round" @click="taskEdit(stage, index, task, taskIndex)">
{{ task.title }}
<pi-status-show :status="task.status?.result"></pi-status-show>
</a-button>
</div>
</div>
<div v-if="editMode" class="task-container is-add">
<div class="line">
<div class="flow-line"></div>
</div>
<div class="task">
<a-button type="dashed" shape="round" @click="taskAdd(stage, index)">
<fs-icon class="font-20" icon="ion:add-circle-outline"></fs-icon>
并行任务
</a-button>
</div>
</div>
</div>
</div>
<div v-if="editMode" class="stage last-stage">
<div class="title">
<pi-editable model-value="新阶段" :disabled="true" />
</div>
<div class="tasks">
<div class="task-container first-task">
<div class="line">
<div class="flow-line"></div>
<fs-icon class="add-stage-btn" title="添加新阶段" icon="ion:add-circle" @click="stageAdd()"></fs-icon>
</div>
<div class="task">
<a-button shape="round" type="dashed" @click="stageAdd()">
<fs-icon icon="ion:add-circle-outline"></fs-icon>
新任务
</a-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="layout-right">
<a-page-header title="运行历史" sub-title="点任务可查看日志" class="logs-block">
<a-timeline class="mt-10">
<template v-for="item of histories" :key="item.id">
<pi-history-timeline-item :runnable="item.pipeline" :is-current="currentHistory?.id === item.id" :edit-mode="editMode" @view="historyView(item)" @cancel="historyCancel()"></pi-history-timeline-item>
</template>
<a-empty v-if="histories.length === 0"> </a-empty>
</a-timeline>
</a-page-header>
</div>
</div>
<pi-task-form ref="taskFormRef" :edit-mode="editMode"></pi-task-form>
<pi-trigger-form ref="triggerFormRef" :edit-mode="editMode"></pi-trigger-form>
<pi-task-view ref="taskViewRef"></pi-task-view>
</fs-page>
</template>
<script lang="ts">
import { defineComponent, ref, provide, Ref, watch } from "vue";
import PiTaskForm from "./component/task-form/index.vue";
import PiTriggerForm from "./component/trigger-form/index.vue";
import PiTaskView from "./component/task-view/index.vue";
import PiStatusShow from "./component/status-show.vue";
import _ from "lodash-es";
import { message, Modal, notification } from "ant-design-vue";
import { pluginManager } from "/@/views/certd/pipeline/pipeline/plugin";
import { nanoid } from "nanoid";
import { PipelineDetail, PipelineOptions, RunHistory, Runnable } from "/@/views/certd/pipeline/pipeline/type";
import { statusUtil } from "./utils/util.status";
import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/history-timeline-item.vue";
export default defineComponent({
name: "PipelineEdit",
// eslint-disable-next-line vue/no-unused-components
components: { PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow },
props: {
pipelineId: {
type: [Number, String],
default: 0
},
editMode: {
type: Boolean,
default: false
},
options: {
type: Object as PropType<PipelineOptions>,
default() {
return {};
}
}
},
emits: ["update:modelValue", "update:editMode"],
setup(props, ctx) {
const currentPipeline: Ref<any> = ref({});
const pipeline: Ref<any> = ref({});
const histories: Ref<RunHistory[]> = ref([]);
const currentHistory: Ref<any> = ref({});
const loadCurrentHistoryDetail = async () => {
console.log("load history logs");
const detail: RunHistory = await props.options?.getHistoryDetail({ historyId: currentHistory.value.id });
currentHistory.value.logs = detail.logs;
_.merge(currentHistory.value.pipeline, detail.pipeline);
};
const changeCurrentHistory = async (history?: RunHistory) => {
if (!history) {
//取消历史记录查看模式
currentHistory.value = null;
pipeline.value = currentPipeline.value;
return;
}
currentHistory.value = history;
pipeline.value = history.pipeline;
await loadCurrentHistoryDetail();
console.log("currentHistory:", currentHistory);
};
async function loadHistoryList(reload = false) {
if (props.editMode) {
return;
}
if (reload) {
histories.value = [];
}
console.log("load history list");
const historyList = await props.options.getHistoryList({ pipelineId: pipeline.value.id });
if (!historyList) {
return;
}
if (histories.value.length > 0 && histories.value[0].id === historyList[0].id) {
return;
}
histories.value = historyList;
if (historyList.length > 0) {
if (historyList[0].pipeline?.version === pipeline.value.version) {
await changeCurrentHistory(historyList[0]);
}
}
return true;
}
const intervalLoadHistoryRef = ref();
function watchNewHistoryList() {
intervalLoadHistoryRef.value = setInterval(async () => {
if (currentHistory.value == null) {
await loadHistoryList();
} else if (currentHistory.value.pipeline?.status?.status === "start") {
await loadCurrentHistoryDetail();
} else {
clearInterval(intervalLoadHistoryRef.value);
}
}, 3000);
}
watch(
() => {
return props.editMode;
},
(editMode) => {
if (editMode) {
changeCurrentHistory();
} else if (histories.value.length > 0) {
if (histories.value[0].pipeline.version === pipeline.value.version) {
changeCurrentHistory(histories.value[0]);
}
}
}
);
watch(
() => {
return props.pipelineId;
},
async (value) => {
if (!value) {
return;
}
const detail: PipelineDetail = await props.options.getPipelineDetail({ pipelineId: value });
currentPipeline.value = _.merge({ title: "新管道流程", stages: [], triggers: [] }, detail.pipeline);
pipeline.value = currentPipeline.value;
await loadHistoryList(true);
},
{
immediate: true
}
);
const plugins: Ref<any> = ref([]);
const fetchPlugins = async () => {
const list = await props.options.getPlugins();
plugins.value = list;
pluginManager.init(list);
};
fetchPlugins();
provide("pipeline", pipeline);
provide("plugins", plugins);
provide("currentHistory", currentHistory);
function useTask() {
const taskFormRef: Ref<any> = ref(null);
const currentStageIndex = ref(0);
const currentTaskIndex = ref(0);
provide("currentStageIndex", currentStageIndex);
provide("currentTaskIndex", currentTaskIndex);
function useTaskView() {
const taskViewRef: Ref<any> = ref(null);
const taskViewOpen = (task) => {
taskViewRef.value.open(task);
};
return {
taskViewOpen,
taskViewRef
};
}
const taskView = useTaskView();
const taskAdd = (stage: any, stageIndex: number, onSuccess?) => {
currentStageIndex.value = stageIndex;
currentTaskIndex.value = stage.tasks.length;
taskFormRef.value.taskAdd((type, value) => {
if (type === "save") {
stage.tasks.push(value);
if (onSuccess) {
onSuccess();
}
}
});
};
const taskEdit = (stage, stageIndex, task, taskIndex, onSuccess?) => {
currentStageIndex.value = stageIndex;
currentTaskIndex.value = taskIndex;
if (taskFormRef.value == null) {
return;
}
if (props.editMode) {
taskFormRef.value.taskEdit(task, (type, value) => {
if (type === "delete") {
stage.tasks.splice(taskIndex, 1);
if (stage.tasks.length === 0) {
_.remove(pipeline.value.stages, (item: Runnable) => {
return item.id === stage.id;
});
}
} else if (type === "save") {
stage.tasks[taskIndex] = value;
}
if (onSuccess) {
onSuccess(type);
}
});
} else {
taskView.taskViewRef.value.taskViewOpen(task);
}
};
return { taskAdd, taskEdit, taskFormRef, ...taskView };
}
function useStage(useTaskRet) {
const stageAdd = (stageIndex = pipeline.value.stages.length) => {
const stage = {
id: nanoid(),
title: "新阶段",
tasks: [],
status: null
};
//stage: any, stageIndex: number, onSuccess
useTaskRet.taskAdd(stage, stageIndex, () => {
let task = stage.tasks[0] as any;
stage.title = task.title + "阶段";
//插入阶段
pipeline.value.stages.splice(stageIndex, 0, stage);
});
};
return {
stageAdd
};
}
function useTrigger() {
const triggerFormRef: Ref<any> = ref(null);
const triggerAdd = () => {
triggerFormRef.value.triggerAdd((type, value) => {
if (type === "save") {
pipeline.value.triggers.push(value);
}
});
};
const triggerEdit = (trigger, index) => {
if (triggerFormRef.value == null) {
return;
}
if (props.editMode) {
triggerFormRef.value.triggerEdit(trigger, (type, value) => {
if (type === "delete") {
pipeline.value.triggers.splice(index, 1);
} else if (type === "save") {
pipeline.value.triggers[index] = value;
}
});
} else {
triggerFormRef.value.triggerView(trigger, (type, value) => {});
}
};
return {
triggerAdd,
triggerEdit,
triggerFormRef
};
}
function useActions() {
const saveLoading = ref();
const run = async () => {
if (props.editMode) {
message.warn("请先保存,再运行管道");
return;
}
if (!props.options.doTrigger) {
message.warn("暂不支持运行");
return;
}
if (pipeline.value.stages == null || pipeline.value.stages.length === 0) {
message.warn("请先添加阶段和任务");
return;
}
Modal.confirm({
title: "确认",
content: `确定要手动触发运行吗?`,
async onOk() {
//@ts-ignore
await changeCurrentHistory(null);
watchNewHistoryList();
await props.options.doTrigger({ pipelineId: pipeline.value.id });
notification.success({ message: "管道已经开始运行" });
}
});
};
function toggleEditMode(editMode: boolean) {
ctx.emit("update:editMode", editMode);
}
const save = async () => {
saveLoading.value = true;
try {
if (props.options.doSave) {
pipeline.value.version++;
currentPipeline.value = pipeline.value;
await props.options.doSave(pipeline.value);
}
toggleEditMode(false);
} finally {
saveLoading.value = false;
}
};
const edit = () => {
pipeline.value = _.cloneDeep(currentPipeline.value);
currentHistory.value = null;
toggleEditMode(true);
};
const cancel = () => {
pipeline.value = currentPipeline.value;
toggleEditMode(false);
};
return {
run,
save,
edit,
cancel,
saveLoading
};
}
function useHistory() {
const historyView = (history) => {
changeCurrentHistory(history);
console.log("currentPipeline", pipeline);
};
const historyCancel = () => {
changeCurrentHistory();
console.log("currentPipeline", pipeline);
};
return {
historyView,
historyCancel
};
}
const useTaskRet = useTask();
const useStageRet = useStage(useTaskRet);
return {
pipeline,
currentHistory,
histories,
...useTaskRet,
...useStageRet,
...useTrigger(),
...useActions(),
...useHistory()
};
}
});
</script>
<style lang="less">
.page-pipeline-edit {
.fs-page-header {
.title {
.pi-editable {
width: 300px;
}
}
}
.pi-status-show {
display: inline-flex;
}
.layout {
width: 100%;
height: 100%;
position: relative;
display: flex;
.layout-left {
flex: 1;
height: 100%;
}
.layout-right {
width: 350px;
height: 100%;
}
}
.pipeline-container {
width: 100%;
height: 100%;
position: relative;
background-color: #f0f0f0;
overflow: auto;
}
.pipeline {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: #f0f0f0;
.stages {
display: flex;
overflow: auto;
min-width: 100%;
height: 100%;
.stage {
width: 300px;
border-right: 1px solid #c7c7c7;
.is-add {
visibility: hidden;
color: gray;
}
&:hover .is-add {
visibility: visible;
}
.title {
padding: 20px;
color: gray;
}
&.first-stage {
.line {
width: 50% !important;
.flow-line {
border-left: 0;
}
}
}
&.last-stage {
.line {
width: 50% !important;
left: 0;
right: auto;
.flow-line {
border-right: 0;
}
.add-stage-btn {
visibility: hidden;
}
}
}
.line {
height: 50px;
position: absolute;
top: -25px;
right: 0;
width: 100%;
.flow-line {
height: 100%;
margin-left: 28px;
margin-right: 28px;
border: 1px solid #c7c7c7;
border-top: 0;
}
.add-stage-btn {
display: inline-flex;
visibility: hidden;
font-size: 24px;
cursor: pointer;
position: absolute;
bottom: -12px;
left: -12px;
z-index: 100;
&:hover {
color: #1890ff;
}
}
}
.tasks {
.task-container {
width: 100%;
height: 50px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
&.first-task {
.line {
.flow-line {
margin: 0;
border-left: 0;
border-right: 0;
}
.add-stage-btn {
visibility: visible;
}
}
}
.task {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
z-index: 2;
.ant-btn {
width: 200px;
}
}
}
}
}
}
}
.logs-block {
height: 100%;
overflow-y: auto;
}
}
</style>
@@ -0,0 +1,65 @@
export class PluginManager {
// @ts-ignore
map: {
[key: string]: any;
} = {};
/**
* 初始化plugins
* @param plugins
*/
init(plugins) {
const list = plugins;
const map = {};
for (const plugin of list) {
map[plugin.key] = plugin;
}
this.map = map;
}
get(name: string) {
return this.map[name];
}
getPreStepOutputOptions({ pipeline, currentStageIndex, currentStepIndex, currentTask }) {
const steps = this.collectionPreStepOutputs({
pipeline,
currentStageIndex,
currentStepIndex,
currentTask
});
const options: any[] = [];
for (const step of steps) {
const stepDefine = this.get(step.type);
for (const key in stepDefine?.output) {
options.push({
value: `step.${step.id}.${key}`,
label: `${stepDefine.output[key].title}【from${step.title}`,
type: step.type
});
}
}
return options;
}
collectionPreStepOutputs({ pipeline, currentStageIndex, currentStepIndex, currentTask }) {
const steps: any[] = [];
// 开始放step
for (let i = 0; i < currentStageIndex; i++) {
const stage = pipeline.stages[i];
for (const task of stage.tasks) {
for (const step of task.steps) {
steps.push(step);
}
}
}
//放当前任务下的step
for (let i = 0; i < currentStepIndex; i++) {
const step = currentTask.steps[i];
steps.push(step);
}
return steps;
}
}
export const pluginManager = new PluginManager();
@@ -0,0 +1,22 @@
import { PluginDefine, Pipeline } from "@certd/pipeline/src";
export * from "@certd/pipeline/src";
export type PipelineDetail = {
pipeline: Pipeline;
};
export type RunHistory = {
id: any;
pipeline: Pipeline;
logs?: {
[id: string]: string[];
};
};
export type PipelineOptions = {
doTrigger(options: { pipelineId }): Promise<void>;
doSave(pipelineConfig: PipelineDefile): Promise<void>;
getPipelineDetail(query: { pipelineId }): Promise<PipelineDetail>;
getHistoryList(query: { pipelineId }): Promise<RunHistory[]>;
getHistoryDetail(query: { historyId }): Promise<RunHistory>;
getPlugins(): Promise<PluginDefine[]>;
};
@@ -0,0 +1,49 @@
const StatusEnum = {
success: {
value: "success",
label: "成功",
color: "green",
icon: "ant-design:check-circle-outlined"
},
error: {
value: "error",
label: "错误",
color: "red",
icon: "ant-design:info-circle-outlined"
},
skip: {
value: "skip",
label: "跳过",
color: "blue",
icon: "fluent:arrow-step-over-20-filled"
},
start: {
value: "start",
label: "运行中",
color: "blue",
spin: true,
icon: "ant-design:sync-outlined"
},
none: {
value: "none",
label: "未运行",
color: "blue",
icon: "ant-design:minus-circle-twotone"
}
};
export const statusUtil = {
getColor(status = "none") {
return StatusEnum[status].color;
},
get(status = "none") {
return StatusEnum[status];
},
getOptions() {
const options: any[] = [];
for (const key of Object.keys(StatusEnum)) {
options.push(StatusEnum[key]);
}
return options;
}
};
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/AdvancedBigData";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,170 @@
import * as api from "./api";
import { message } from "ant-design-vue";
import { dict } from "@fast-crud/fast-crud";
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
output: {},
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
table: {
scroll: {
//启用横向滚动条,设置一个大于所有列宽之和的值,一般大于表格宽度
x: 2400
}
},
pagination: {
pageSize: 100
},
rowHandle: {
fixed: "right"
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
text: {
title: "文本",
type: "text"
},
dict1: {
title: "字典1",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict1"
})
},
dict2: {
title: "字典2",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict2"
})
},
dict3: {
title: "字典3",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict3"
})
},
dict4: {
title: "字典4",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict4"
})
},
dict5: {
title: "字典5",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict5"
})
},
dict6: {
title: "字典6",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict6"
})
},
dict7: {
title: "字典7",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict7"
})
},
dict8: {
title: "字典8",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict8"
})
},
dict9: {
title: "字典9",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict9"
})
},
dict10: {
title: "字典10",
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?from=dict10"
})
},
text1: {
title: "文本",
type: "text"
},
text2: {
title: "文本",
type: "text"
},
text3: {
title: "文本",
type: "text"
},
text4: {
title: "文本",
type: "text"
},
text5: {
title: "文本",
type: "text"
},
text6: {
title: "文本",
type: "text"
},
text7: {
title: "文本",
type: "text"
},
text8: {
title: "文本",
type: "text"
},
text9: {
title: "文本",
type: "text"
},
text10: {
title: "文本",
type: "text"
}
}
}
};
}
@@ -0,0 +1,43 @@
<template>
<fs-page>
<template #header>
<div class="title">大量数据</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
export default defineComponent({
name: "AdvancedBigData",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions, output } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef,
...output
};
}
});
</script>
@@ -0,0 +1,126 @@
import mockUtil from "/src/mock/base";
const options = {
name: "AdvancedBigData",
idGenerator: 0,
copyTimes: 1000
};
const list = [
{
text: "测试文本",
dict1: "1",
dict2: "1",
dict3: "2",
dict4: "1",
dict5: "2",
dict6: "1",
dict7: "1",
dict8: "1",
text1: "测试文本1",
text2: "测试文本2",
text3: "测试文本3",
text4: "测试文本4",
text5: "测试文本5",
text6: "测试文本6",
text7: "测试文本7",
text8: "测试文本8",
dict9: "2",
dict10: "1",
dict11: "2",
dict12: "1"
},
{
text: "测试文本",
dict1: "1",
dict2: "1",
dict3: "2",
dict4: "1",
dict5: "2",
dict6: "1",
dict7: "1",
dict8: "1",
text1: "测试文本1",
text2: "测试文本2",
text3: "测试文本3",
text4: "测试文本4",
text5: "测试文本5",
text6: "测试文本6",
text7: "测试文本7",
text8: "测试文本8",
dict9: "2",
dict10: "1",
dict11: "2",
dict12: "1"
},
{
text: "测试文本",
dict1: "1",
dict2: "1",
dict3: "2",
dict4: "1",
dict5: "2",
dict6: "1",
dict7: "1",
dict8: "1",
text1: "测试文本1",
text2: "测试文本2",
text3: "测试文本3",
text4: "测试文本4",
text5: "测试文本5",
text6: "测试文本6",
text7: "测试文本7",
text8: "测试文本8",
dict9: "2",
dict10: "1",
dict11: "2",
dict12: "1"
},
{
text: "测试文本",
dict1: "1",
dict2: "1",
dict3: "2",
dict4: "1",
dict5: "2",
dict6: "1",
dict7: "1",
dict8: "1",
text1: "测试文本1",
text2: "测试文本2",
text3: "测试文本3",
text4: "测试文本4",
text5: "测试文本5",
text6: "测试文本6",
text7: "测试文本7",
text8: "测试文本8",
dict9: "2",
dict10: "1",
dict11: "2",
dict12: "1"
},
{
text: "测试文本",
dict1: "1",
dict2: "1",
dict3: "2",
dict4: "1",
dict5: "2",
dict6: "1",
dict7: "1",
dict8: "1",
text1: "测试文本1",
text2: "测试文本2",
text3: "测试文本3",
text4: "测试文本4",
text5: "测试文本5",
text6: "测试文本6",
text7: "测试文本7",
text8: "测试文本8",
dict9: "2",
dict10: "1",
dict11: "2",
dict12: "1"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,48 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/AdvancedFromBackend";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
export function GetCrud() {
return request({
url: apiPrefix + "/crud",
method: "get"
});
}
@@ -0,0 +1,29 @@
export const crudOptions = `
({expose,dict}) => {
return {
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
radio: {
title: "状态",
search: { show: true },
type: "dict-radio",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?single"
})
}
}
}
}
`;
@@ -0,0 +1,27 @@
import * as api from "./api";
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
}
}
};
}
@@ -0,0 +1,45 @@
<template>
<fs-crud v-if="crudBinding" ref="crudRef" v-bind="crudBinding" />
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud, dict, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { GetCrud } from "./api";
import _ from "lodash-es";
export default defineComponent({
name: "AdvancedFromBackend",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// 页面打开后获取列表数据
onMounted(async () => {
// 从后台获取crud配置
const ret = await GetCrud();
// 编译
const crudBackend = eval(ret);
// 本示例返回的是一个方法字符串,所以要先执行这个方法,获取options
const crudOptionsFromBackend = crudBackend({ expose, dict });
// 与本地options合并
_.merge(crudOptions, crudOptionsFromBackend);
// useCrud
useCrud({ expose, crudOptions });
// 刷新数据
expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
@@ -0,0 +1,35 @@
import mockUtil from "/src/mock/base";
import { crudOptions } from "./crud-backend";
const options = {
name: "AdvancedFromBackend",
idGenerator: 0
};
const list = [
{
radio: "1"
},
{
radio: "2"
},
{
radio: "0"
}
];
options.list = list;
options.copyTimes = 1000;
const mock = mockUtil.buildMock(options);
mock.push({
path: "/AdvancedFromBackend/crud",
method: "get",
handle(req) {
return {
code: 0,
msg: "success",
data: crudOptions
};
}
});
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/AdvancedInDialog";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,106 @@
import * as api from "./api";
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
columns: {
name: {
title: "姓名",
type: "text", //虽然不写也能正确显示组件,但不建议省略它
search: { show: true },
form: {
component: {
maxlength: 20
}
}
},
search: {
title: "搜索",
type: "text",
form: {
component: {
addonAfter: "后置",
suffix: "suffix",
children: {
addonBefore() {
return <SearchOutlined />;
}
}
}
}
},
password: {
title: "密码",
type: "password",
column: {
//一般密码不显示在列里面
show: false
}
},
intro: {
title: "简介",
type: "textarea",
form: {
component: { showWordLimit: true, maxlength: 200 }
},
column: {
ellipsis: true
}
},
render: {
title: "复杂输入(render)",
form: {
title: "复杂输入",
component: {
render(context) {
console.log("context scope", context);
return (
<a-input-group compact>
<a-input
placeholder={"render1 input"}
style="width: 50%"
v-model={[context.form.render, "value"]}
/>
<a-input
placeholder={"render2 input"}
style="width: 50%"
v-model={[context.form.render2, "value"]}
/>
</a-input-group>
);
}
}
}
},
render2: {
title: "我的值是由复杂输入列输入的",
column: {
width: "300px"
},
form: {
show: false
}
}
}
}
};
}
@@ -0,0 +1,40 @@
<template>
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding" />
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
export default defineComponent({
name: "fs-in-dialog",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
@@ -0,0 +1,40 @@
import mockUtil from "/src/mock/base";
const options = {
name: "AdvancedInDialog",
idGenerator: 0
};
const list = [
{
name: "王小虎",
date: "2016-05-02",
status: "0",
province: "1",
avatar: "https://alicdn.antdv.com/vue.png",
show: true,
city: "sz",
address: "123123",
zip: "518000",
intro: "王小虎是element-plus的table示例出现的名字"
},
{
name: "张三",
date: "2016-05-04",
status: "1",
province: "2"
},
{
name: "李四",
date: 2232433534511,
status: "1",
province: "0"
},
{
name: "王五",
date: "2016-05-03",
status: "2",
province: "wh,gz"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,37 @@
<template>
<fs-page>
<template #header>
<div class="title">
对话框中显示crud
</div>
</template>
<div style="padding:50px">
<a-button type="primary" @click="openDialog">打开对话框</a-button>
</div>
<a-modal v-model:visible="dialogShow" width="80%" title="fs-crud in dialog">
<div style="height: 400px;position: relative">
<fs-in-dialog ></fs-in-dialog>
</div>
</a-modal>
</fs-page>
</template>
<script>
import { defineComponent, ref } from "vue";
import FsInDialog from './crud/index.vue'
export default defineComponent({
name: "InDialog",
components:{FsInDialog},
setup() {
const dialogShow = ref(false)
function openDialog(){
dialogShow.value=true
}
return {
dialogShow,
openDialog
};
}
});
</script>
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/FormLinkage";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,118 @@
import * as api from "./api";
import { dict } from "@fast-crud/fast-crud";
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
rowHandle: {
align: "center"
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
province: {
title: "省",
type: "dict-select",
search: {
show: true
},
dict: dict({
url: "/mock/linkage/province",
value: "id",
cache: true
}),
form: {
valueChange({ form, value, getComponentRef }) {
form.city = undefined; // 将“city”的值置空
form.county = undefined; // 将“county”的值置空
if (value) {
getComponentRef("city").reloadDict(); // 执行city的select组件的reloadDict()方法,触发“city”重新加载字典
}
}
}
},
city: {
title: "市",
type: "dict-select",
search: {
show: true
},
dict: dict({
cache: true,
prototype: true,
// url() 改成构建url,返回一个url
url({ form }) {
if (form && form.province != null) {
// 本数据字典的url是通过前一个select的选项决定的
return `/mock/linkage/city?province=${form.province}`;
}
return undefined; // 返回undefined 将不加载字典
},
value: "id"
}),
form: {
// 注释同上
valueChange({ value, form, getComponentRef }) {
if (value) {
form.county = undefined; // 将county的value置空
const countySelect = getComponentRef("county");
if (form && form.province && form.city) {
countySelect.reloadDict(); // 重新加载字典项
} else {
countySelect.clearDict(); // 清空选项
}
}
}
}
},
county: {
title: "区",
type: "dict-select",
search: {
show: true
},
dict: dict({
value: "id",
cache: true,
prototype: true,
url({ form }) {
if (form && form.province != null && form.city != null) {
return `/mock/linkage/county?province=${form.province} &city=${form.city}`;
}
return undefined;
}
})
}
}
}
};
}
@@ -0,0 +1,38 @@
<template>
<fs-crud ref="crudRef" v-bind="crudBinding" />
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
export default defineComponent({
name: "FormLinkage",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
@@ -0,0 +1,129 @@
import mockUtil from "/src/mock/base";
import _ from "lodash-es";
const options = {
name: "FormLinkage",
idGenerator: 0
};
const list = [
{
province: 10000,
city: 100003,
county: 100004
},
{
province: 10010,
city: 100113,
county: 100115
}
];
const tree = [
{
id: 10000,
label: "北京市",
children: [
{
id: 100003,
label: "北京市辖区",
children: [
{ id: 100004, label: "东城区" },
{ id: 100005, label: "西城区" }
]
},
{
id: 100103,
label: "北京郊区",
children: [
{ id: 100104, label: "东郊" },
{ id: 100105, label: "西郊" }
]
}
]
},
{
id: 10010,
label: "天津市",
children: [
{
id: 100013,
label: "天津市辖区",
children: [
{ id: 100014, label: "天津湾" },
{ id: 100015, label: "渤海湾" }
]
},
{
id: 100113,
label: "天津市郊区",
children: [
{ id: 100114, label: "天津湾郊区" },
{ id: 100115, label: "渤海湾郊区" }
]
}
]
}
];
options.list = list;
options.copyTimes = 1000;
const mock = mockUtil.buildMock(options);
function omitChildren(orignalListt) {
const list = [];
orignalListt.forEach((item) => {
list.push(_.omit(item, "children"));
});
return list;
}
mock.push({
path: "/mock/linkage/province",
method: "get",
handle() {
const list = omitChildren(tree);
return {
code: 0,
msg: "success",
data: list
};
}
});
mock.push({
path: "/mock/linkage/city",
method: "get",
handle(req) {
const province = parseInt(req.params.province);
const a = tree.filter((item) => {
return item.id === province;
});
const list = omitChildren(a[0].children);
return {
code: 0,
msg: "success",
data: list
};
}
});
mock.push({
path: "/mock/linkage/county",
method: "get",
handle(req) {
const province = parseInt(req.params.province);
const a = tree.filter((item) => {
return item.id === province;
});
const city = parseInt(req.params.city);
const b = a[0].children.filter((item) => {
return item.id === city;
});
const list = omitChildren(b[0].children);
return {
code: 0,
msg: "success",
data: list
};
}
});
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/AdvancedLocalPagination";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,101 @@
import * as api from "./api";
import _ from "lodash-es";
import { dict } from "@fast-crud/fast-crud";
export default function ({ expose, localDataRef }) {
const pageRequest = async ({ page, query }) => {
//总数据
let data = localDataRef.value;
//获取请求参数
const limit = page.limit;
let offset = page.offset;
data = data.filter((item) => {
// 根据你的业务,编写你的本地查询逻辑
// text改成你的查询字段
if (query.status && item.status !== query.status) {
return false;
}
return true;
});
// 本地分页
const start = offset;
let end = offset + limit;
if (data.length < end) {
end = data.length;
}
const records = data.slice(start, end);
// 构造返回结果
return {
offset,
limit,
total: localDataRef.value.length,
records
};
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
await api.UpdateObj(form);
//更新本地数据
const tableData = localDataRef.value;
for (const item of tableData) {
if (item.id === form.id) {
_.merge(item, form);
}
}
};
const addRequest = async ({ form }) => {
const id = await api.AddObj(form);
//本地添加
form.id = id;
localDataRef.value.unshift(form);
return id;
};
const delRequest = async ({ row }) => {
await api.DelObj(row.id);
//本地删除那一条记录
const tableData = localDataRef.value;
let index = 0;
for (const item of tableData) {
if (item.id === row.id) {
localDataRef.value.splice(index, 1);
}
index++;
}
};
return {
output: {},
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
status: {
title: "状态",
search: { show: true },
type: "dict-select",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?single"
})
}
}
}
};
}
@@ -0,0 +1,57 @@
<template>
<fs-page>
<template #header>
<div class="title">本地分页</div>
</template>
<fs-crud v-if="crudBinding" ref="crudRef" v-bind="crudBinding">
<template #actionbar-right>
<a-alert type="warning" class="ml-1" message="先从后台获取全部数据,然后本地分页展示" />
</template>
</fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { GetList } from "./api";
/**
* 本示例演示如何本地分页
* 主要就是将pageRequest修改为从本地获取数据就行了
*/
export default defineComponent({
name: "AdvanceLocalPagination",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
const localDataRef = ref();
onMounted(async () => {
//先加载后台数据
const ret = await GetList({ page: { offset: 0, limit: 99999999 }, query: {}, sort: {} });
localDataRef.value = ret.records;
//然后再初始化crud
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose, localDataRef });
// 初始化crud配置
useCrud({ expose, crudOptions });
// 页面打开后获取列表数据
await expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
@@ -0,0 +1,19 @@
import mockUtil from "/src/mock/base";
const options = {
name: "AdvancedLocalPagination",
idGenerator: 0
};
const list = [
{
status: "1"
},
{
status: "2"
},
{
status: "0"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,50 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/AdvancedNest";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
export function BatchDelete(ids) {
return request({
url: apiPrefix + "/batchDelete",
method: "post",
data: { ids }
});
}
@@ -0,0 +1,50 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/AdvancedAside";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
export function BatchDelete(ids) {
return request({
url: apiPrefix + "/batchDelete",
method: "post",
data: { ids }
});
}
@@ -0,0 +1,56 @@
import * as api from "./api";
import { dict } from "@fast-crud/fast-crud";
export default function ({ expose }) {
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
pagination: {
showSizeChanger: false, // antdv
showQuickJumper: false // antdv
},
request: {
pageRequest: api.GetList,
addRequest,
editRequest,
delRequest
},
toolbar: {
compact: false
},
rowHandle: {
width: "230px"
},
table: {},
columns: {
gradeId: {
title: "年级Id",
search: { show: true },
type: "number",
column: {
width: 80,
align: "center",
sortable: true
}
},
class: {
title: "班级",
search: { show: false },
type: "text",
column: {
sortable: true
}
}
}
}
};
}
@@ -0,0 +1,43 @@
<template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right>
<a-alert type="warning" class="ml-1" message="左侧表格点击行可以触发这里的查询" />
</template>
</fs-crud>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import createCrudOptions from "./crud";
import { useExpose, useCrud } from "@fast-crud/fast-crud";
export default defineComponent({
name: "AsideTable",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions, selectedIds } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef,
setSearchFormData: expose.setSearchFormData,
doRefresh: expose.doRefresh
};
}
});
</script>
@@ -0,0 +1,26 @@
import mockUtil from "/src/mock/base";
const options = {
name: "AdvancedAside",
idGenerator: 0
};
const list = [
{
class: "一班",
gradeId: 1
},
{
class: "二班",
gradeId: 1
},
{
class: "三班",
gradeId: 2
},
{
class: "四班",
gradeId: 2
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,101 @@
import * as api from "./api";
import { ref, shallowRef } from "vue";
import SubTable from "./sub-table/index.vue";
import { compute } from "@fast-crud/fast-crud";
export default function ({ expose, asideTableRef }) {
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
const currentRow = ref();
const onCurrentRowChange = (id) => {
currentRow.value = id;
asideTableRef.value.setSearchFormData({ form: { gradeId: id } });
asideTableRef.value.doRefresh();
};
return {
crudOptions: {
table: {
customRow(record, index) {
const clazz = record.id === currentRow.value ? "fs-current-row" : "";
return {
onClick() {
onCurrentRowChange(record.id);
},
class: clazz
};
}
},
pagination: {
showSizeChanger: false, // antdv
showQuickJumper: false // antdv
},
form: {
wrapper: {
is: "a-drawer"
}
},
request: {
pageRequest: api.GetList,
addRequest,
editRequest,
delRequest
},
rowHandle: {
width: "240px"
},
toolbar: {
compact: false
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
grade: {
title: "年级",
search: { show: true },
type: "text",
column: {
sortable: true
}
},
nestId: {
title: "嵌套表格",
//复合字段类型
type: ["number", "colspan"],
form: {
// 嵌套表格字段
rules: [{ required: true, message: "请选择用户" }],
component: {
//局部引用子表格,要用shallowRef包裹
name: shallowRef(SubTable),
vModel: "modelValue",
gradeId: compute(({ form }) => {
return form.id;
})
}
// antdv 的跨列配置,需要配置如下三个, 可以通过colspan简化
// col: { span: 24 },
// labelCol: { span: 2 },
// wrapperCol: { span: 21 }
}
}
}
}
};
}
@@ -0,0 +1,59 @@
<template>
<a-row class="demo-nest" :gutter="0">
<a-col :span="12">
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right>
<a-alert type="warning" class="ml-1" message="<--对话框内嵌套子表格" />
</template>
</fs-crud>
</a-col>
<a-col :span="12">
<aside-table ref="asideTableRef" />
</a-col>
</a-row>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import createCrudOptions from "./crud";
import { useExpose, useCrud } from "@fast-crud/fast-crud";
import AsideTable from "./aside-table/index.vue";
export default defineComponent({
name: "FeatureNest",
// eslint-disable-next-line vue/no-unused-components
components: { AsideTable },
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const asideTableRef = ref();
const { crudOptions } = createCrudOptions({ expose, asideTableRef });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef,
asideTableRef
};
}
});
</script>
<style lang="less">
.demo-nest {
height: 100%;
width: 100%;
}
</style>
@@ -0,0 +1,22 @@
import mockUtil from "/src/mock/base";
const options = {
name: "AdvancedNest",
idGenerator: 0
};
const list = [
{
grade: "一年级",
nestId: 1
},
{
grade: "二年级",
nestId: 2
},
{
grade: "三年级",
nestId: 3
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,50 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/AdvancedSubTable";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
export function BatchDelete(ids) {
return request({
url: apiPrefix + "/batchDelete",
method: "post",
data: { ids }
});
}
@@ -0,0 +1,62 @@
import * as api from "./api";
export default function ({ expose, props, ctx }) {
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
table: {
customRow(record, index) {
const clazz = record.id === props.modelValue ? "fs-current-row" : "";
return {
onClick() {
ctx.emit("update:modelValue", record.id);
},
class: clazz
};
}
},
request: {
pageRequest: api.GetList,
addRequest,
editRequest,
delRequest
},
search: { show: false },
form: {
wrapper: {
is: "a-drawer"
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
name: {
title: "用户姓名",
search: { show: true },
type: "text",
column: {
sortable: true
}
}
}
}
};
}
@@ -0,0 +1,61 @@
<template>
<div>
<div>年级id{{ gradeId }},当前选中值{{ modelValue }}</div>
<div style="height: 400px">
<fs-crud ref="crudRef" v-bind="crudBinding" />
</div>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, watch } from "vue";
import createCrudOptions from "./crud";
import { useExpose, useCrud } from "@fast-crud/fast-crud";
export default defineComponent({
name: "SubTable",
props: {
modelValue: {},
gradeId: {} //年级id,接收其他参数
},
emits: ["update:modelValue"],
setup(props, ctx) {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose, props, ctx });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
//你的业务代码
watch(
() => {
return props.modelValue;
},
(value) => {
console.log("modelValue changed", value);
}
);
return {
crudBinding,
crudRef
};
}
});
</script>
<style lang="less" scoped>
/deep/.fs-crud-container.compact .el-table--border {
border-left: 1px solid #eee;
}
</style>
@@ -0,0 +1,19 @@
import mockUtil from "/src/mock/base";
const options = {
name: "AdvancedSubTable",
idGenerator: 0
};
const list = [
{
name: "张三"
},
{
name: "李四"
},
{
name: "王五"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,50 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/BasisColumnsSet";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
export function BatchDelete(ids) {
return request({
url: apiPrefix + "/batchDelete",
method: "post",
data: { ids }
});
}
@@ -0,0 +1,69 @@
import * as api from "./api";
import { dict } from "@fast-crud/fast-crud";
import { ref } from "vue";
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
toolbar: {
columnsFilter: {
mode: "default"
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
radio: {
title: "状态",
search: { show: true },
type: "dict-radio",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?single"
})
},
disabled: {
title: "列设置禁用",
type: "text",
column: {
columnSetDisabled: true
}
},
hidden: {
title: "列设置隐藏",
type: "text",
column: {
columnSetShow: false
}
}
}
}
};
}
@@ -0,0 +1,52 @@
<template>
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right>
<a-alert class="ml-1" type="warning" message="列设置可以禁用或者隐藏某字段勾选" />
<a-button @click="columnsSetToggleMode()"> 切换简单模式 </a-button>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, Ref } from "vue";
import createCrudOptions from "./crud.jsx";
import { useExpose, useCrud, CrudBinding } from "@fast-crud/fast-crud";
import { message, Modal, notification } from "ant-design-vue";
export default defineComponent({
name: "BasisColumnsSet",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding: Ref<CrudBinding> = ref({});
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions, selectedRowKeys } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
function columnsSetToggleMode() {
crudBinding.value.toolbar.columnsFilter.mode =
crudBinding.value.toolbar.columnsFilter.mode === "simple" ? "default" : "simple";
message.info("当前列设置组件的模式为:" + crudBinding.value.toolbar.columnsFilter.mode);
}
return {
crudBinding,
crudRef,
columnsSetToggleMode
};
}
});
</script>
@@ -0,0 +1,19 @@
import mockUtil from "/src/mock/base";
const options = {
name: "BasisColumnsSet",
idGenerator: 0
};
const list = [
{
radio: "1"
},
{
radio: "2"
},
{
radio: "0"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/FormComputeMore";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,79 @@
import * as api from "./api";
import { requestForMock } from "/src/api/service";
import { useCompute } from "@fast-crud/fast-crud";
import { message } from "ant-design-vue";
import { ref, computed } from "vue";
const { asyncCompute, compute } = useCompute();
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
//普通的ref引用,可以动态切换配置
const defValueRef = ref("我是动态的默认值");
const defValueComputed = computed(() => {
return defValueRef.value;
});
return {
output: {
defValueRef,
defValueComputed
},
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
table: {
scroll: {
x: 1500
}
},
form: {
labelCol: { span: 8 },
wrapperCol: { span: 14 }
},
rowHandle: {
fixed: "right",
align:'center',
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
defValue: {
title: "默认值",
type: "text",
search: { show: true, value: null },
form: {
// form.value不支持asyncCompute
// 假如你的默认值异步获取的,那么你自己必须保证先异步计算完成之后,才能打开对话框。
// 因为在打开对话框时,默认值就必须得设置好。
value: defValueRef
}
}
}
}
};
}
@@ -0,0 +1,43 @@
<template>
<fs-page>
<template #header>
<div class="title">动态计算-更多测试</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
export default defineComponent({
name: "BasisComputeMore",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions, output } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef,
...output
};
}
});
</script>
@@ -0,0 +1,32 @@
import mockUtil from "/src/mock/base";
const options = {
name: "FormComputeMore",
idGenerator: 0
};
const list = [
{
ref: "根据showRef显示",
compute: true,
status: "1",
remote: "2",
shower: "---> 点右边编辑查看示例效果",
remote2: "2",
editable: true
},
{
compute: false,
status: "2",
remote: "0",
remote2: "2",
editable: false
},
{
compute: true,
status: "0",
remote2: "2",
editable: true
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/FormCompute";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,211 @@
import * as api from "./api";
import { requestForMock } from "/src/api/service";
import { useCompute } from "@fast-crud/fast-crud";
import { message } from "ant-design-vue";
import { ref, computed } from "vue";
const { asyncCompute, compute } = useCompute();
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
//普通的ref引用,可以动态切换配置
const showRef = ref(false);
const showTableRef = ref(true);
const showTableComputed = computed(() => {
return showTableRef.value;
});
const columnComponentShowRef = ref(true);
const columnComponentShowComputed = computed(() => {
return columnComponentShowRef.value;
});
return {
output: {
showRef,
showTableRef,
columnComponentShowRef
},
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
table: {
scroll: {
x: 1500
},
//通过switch动态显隐table
show: showTableComputed //不仅支持computed,直接传showTableRef也是可以的
},
form: {
labelCol: { span: 8 },
wrapperCol: { span: 14 }
},
rowHandle: {
fixed: "right",
buttons: {
edit: {
show: compute(({ row }) => {
return row.editable;
})
},
remove: {
show: compute(({ row }) => {
return row.editable;
})
}
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50,
resizable: true
},
form: {
show: false
}
},
refSwitch: {
title: "ref引用切换",
type: "text",
form: {
helper: "点我切换右边的输入框显示"
}
},
ref: {
title: "根据ref引用显示",
type: ["text"],
form: {
component: {
show: showRef
},
helper: "我会根据showRef进行显隐"
}
},
compute: {
title: "compute",
search: { show: false },
type: "text",
column: {
show: columnComponentShowComputed,
columnSetDisabled: true, //这里采用自定义控制显隐,那么列设置里面就要禁用
// columnSetShow: false, //直接不在列设置里面显示也行
component: {
name: "a-switch",
vModel: "checked"
}
},
form: {
component: {
name: "a-switch",
vModel: "checked"
},
helper: "点我触发动态计算"
}
},
shower: {
title: "根据compute显示",
search: { show: false },
type: "button",
form: {
component: {
show: compute(({ form }) => {
return form.compute;
})
}
},
column: {
width: 250,
resizable: true,
component: {
show: compute(({ row }) => {
return row.compute;
})
}
}
},
remote: {
title: "asyncCompute",
search: { show: true },
type: "text",
form: {
component: {
name: "a-select",
vModel: "value",
placeholder: "异步计算远程获取options",
options: asyncCompute({
async asyncFn(watchValue, context) {
const url = "/mock/dicts/OpenStatusEnum?remote";
return await requestForMock({ url });
}
})
},
helper: "我的options是异步计算远程获取的,只会获取一次"
}
},
remote2: {
title: "监听switch触发异步计算",
search: { show: false },
type: "text",
form: {
component: {
name: "a-select",
vModel: "value",
placeholder: "异步计算远程获取options",
options: asyncCompute({
watch({ form }) {
return form.compute;
},
async asyncFn(watchValue) {
message.info("监听switch,触发远程获取options");
const url = watchValue
? "/mock/dicts/OpenStatusEnum?remote"
: "/mock/dicts/moreOpenStatusEnum?remote";
return await requestForMock({ url });
}
})
},
helper: "监听其他属性修改后,触发重新计算"
},
column: {
width: 200
}
},
editable: {
title: "可编辑",
search: { show: false },
type: "text",
column: {
fixed: "right",
component: {
name: "a-switch",
vModel: "checked"
}
},
form: {
show: false
}
}
}
}
};
}
@@ -0,0 +1,60 @@
<template>
<fs-page>
<template #header>
<div class="title">
动态计算
<fs-icon icon="ion:apps-sharp" :spin="true" />
</div>
<div class="more">
<a target="_blank" href="http://fast-crud.docmirror.cn/guide/advance/compute.html">帮助说明</a>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right>
<a-tooltip title="我能控制表格显隐">
<span class="ml-1">表格显隐:<a-switch v-model:checked="showTableRef"></a-switch></span>
</a-tooltip>
<span class="ml-1">列显隐:<a-switch v-model:checked="columnComponentShowRef"></a-switch></span>
<a-alert class="ml-1" type="info" message="点击下方右边的编辑按钮查看示例效果-----------> ↓↓↓↓↓" />
</template>
<template #form_refSwitch>
<a-switch v-model:checked="showRef"></a-switch>
</template>
</fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
export default defineComponent({
name: "BasisCompute",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions, output } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef,
...output
};
}
});
</script>
@@ -0,0 +1,34 @@
import mockUtil from "/src/mock/base";
const options = {
name: "FormCompute",
idGenerator: 0
};
const list = [
{
ref: "根据showRef显示",
compute: true,
status: "1",
remote: "2",
shower: "---> 点右边编辑查看示例效果",
remote2: "2",
editable: true
},
{
compute: false,
shower: "---> 点右边编辑查看示例效果",
status: "2",
remote: "0",
remote2: "2",
editable: false
},
{
compute: true,
shower: "---> 点右边编辑查看示例效果",
status: "0",
remote2: "2",
editable: true
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,109 @@
<template>
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding" />
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { dict, useCrud } from "@fast-crud/fast-crud";
import { useExpose } from "@fast-crud/fast-crud";
import _ from "lodash-es";
//此处为crudOptions配置
const createCrudOptions = function ({ expose }) {
//本地模拟后台crud接口方法 ----开始
const records = [{ id: 1, name: "Hello World", type: 1 }];
const pageRequest = async (query) => {
return {
records,
currentPage: 1,
pageSize: 20,
total: records.length
};
};
const editRequest = async ({ form, row }) => {
const target = _.find(records, (item) => {
return row.id === item.id;
});
_.merge(target, form);
return target;
};
const delRequest = async ({ row }) => {
_.remove(records, (item) => {
return item.id === row.id;
});
};
const addRequest = async ({ form }) => {
const maxRecord = _.maxBy(records, (item) => {
return item.id;
});
form.id = (maxRecord?.id || 0) + 1;
records.push(form);
return form;
};
//本地模拟后台crud接口方法 -----结束
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
columns: {
name: {
title: "姓名",
type: "text",
search: { show: true },
column: {
resizable: true,
width: 200
}
},
type: {
title: "类型",
type: "dict-select",
dict: dict({
data: [
{ value: 1, label: "开始" },
{ value: 0, label: "停止" }
]
})
}
}
}
};
};
//此处为组件定义
export default defineComponent({
name: "FsCrudFirst",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/BasisI18n";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,72 @@
import * as api from "./api";
import { dict } from "@fast-crud/fast-crud";
import { useI18n } from "vue-i18n";
export default function ({ expose }) {
const { t } = useI18n();
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
name: {
title: t("app.crud.i18n.name"),
type: "text",
search: { show: true }
},
city: {
title: t("app.crud.i18n.city"),
type: "dict-select",
search: { show: true },
dict: dict({
value: "id",
label: "text",
data: [
{ id: "sz", text: "深圳", color: "success" },
{ id: "gz", text: "广州", color: "blue" },
{ id: "bj", text: "北京" },
{ id: "wh", text: "武汉" },
{ id: "sh", text: "上海" }
]
})
},
radio: {
title: t("app.crud.i18n.status"),
search: { show: true },
type: "dict-radio",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?single"
})
}
}
}
};
}
@@ -0,0 +1,53 @@
<template>
<fs-page>
<template #header>
<div class="title">CRUD示例国际化</div>
<div class="more"><a-button @click="showDemo">更多</a-button></div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right>
<a-alert class="ml-1" type="warning" message="右上角切换语言查看效果" />
</template>
</fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
import { message } from "ant-design-vue";
export default defineComponent({
name: "BasisI18n",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
function showDemo() {
message("演示按钮");
}
return {
crudBinding,
crudRef,
showDemo
};
}
});
</script>
@@ -0,0 +1,25 @@
import mockUtil from "/src/mock/base";
const options = {
name: "BasisI18n",
idGenerator: 0
};
const list = [
{
radio: "1",
name: "张三",
city: "sz"
},
{
radio: "2",
name: "李四",
city: "gz"
},
{
radio: "0",
name: "王五",
city: "sh"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/BasisLayoutCard";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,73 @@
import * as api from "./api";
import { dict } from "@fast-crud/fast-crud";
export default function ({ crudExpose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
container: {
is: "fs-layout-card"
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
name: {
title: "姓名",
type: "text",
search: { show: true }
},
city: {
title: "城市",
type: "dict-select",
search: { show: true },
dict: dict({
value: "id",
label: "text",
data: [
{ id: "sz", text: "深圳", color: "success" },
{ id: "gz", text: "广州", color: "blue" },
{ id: "bj", text: "北京" },
{ id: "wh", text: "武汉" },
{ id: "sh", text: "上海" }
]
})
},
radio: {
title: "单选",
search: { show: true },
type: "dict-radio",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?single"
})
}
}
}
};
}
@@ -0,0 +1,46 @@
<template>
<fs-page class="page-layout-card">
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
export default defineComponent({
name: "BasisLayoutCard",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { crudExpose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ crudExpose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
<style lang="less">
.page-layout-card {
background-color: #eee;
}
</style>
@@ -0,0 +1,25 @@
import mockUtil from "/src/mock/base";
const options = {
name: "BasisLayoutCard",
idGenerator: 0
};
const list = [
{
radio: "1",
name: "张三",
city: "sz"
},
{
radio: "2",
name: "李四",
city: "gz"
},
{
radio: "0",
name: "王五",
city: "sh"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/BasisLayoutCustom";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,75 @@
import * as api from "./api";
import { dict } from "@fast-crud/fast-crud";
import CustomLayout from "./custom-layout.vue";
import { shallowRef } from "vue";
export default function ({ crudExpose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
container: {
is: shallowRef(CustomLayout) //可以将自定义布局组件全局注册,这里只需要配置name即可
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
name: {
title: "姓名",
type: "text",
search: { show: true }
},
city: {
title: "城市",
type: "dict-select",
search: { show: false },
dict: dict({
value: "id",
label: "text",
data: [
{ id: "sz", text: "深圳", color: "success" },
{ id: "gz", text: "广州", color: "blue" },
{ id: "bj", text: "北京" },
{ id: "wh", text: "武汉" },
{ id: "sh", text: "上海" }
]
})
},
radio: {
title: "单选",
search: { show: false },
type: "dict-radio",
dict: dict({
url: "/mock/dicts/OpenStatusEnum?single"
})
}
}
}
};
}
@@ -0,0 +1,74 @@
<template>
<div class="custom-layout">
<div class="layout-header">
<!-- 关键插槽查询 -->
<slot name="search"></slot>
<!-- 关键插槽工具条 -->
<slot name="toolbar"></slot>
</div>
<div class="layout-top">
<!-- 关键插槽动作条 -->
<slot name="actionbar"></slot>
<!-- 上翻页条 -->
<slot name="pagination"></slot>
</div>
<!-- 高度需要自适应撑开可以通过flex:1 -->
<div class="layout-body">
<!-- 默认插槽 -->
<slot></slot>
<!-- 关键插槽表格 -->
<slot name="table"></slot>
<!-- 关键插槽表单 -->
<slot name="form"></slot>
</div>
<div class="layout-footer">
<!-- 关键插槽分页条 -->
<slot name="pagination"></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
/**
* 自定义布局
*/
export default defineComponent({
name: "CustomLayout"
});
</script>
<style lang="less">
.custom-layout {
height: 100%;
display: flex;
flex-direction: column;
.layout-header {
padding: 10px 10px 5px 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.layout-top {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 10px 5px 10px;
}
.layout-body {
flex: 1; //重要,自适应撑开高度,表格固定表头必须
overflow-y: auto;
}
.fs-crud-actionbar {
display: flex;
align-items: center;
}
.fs-crud-pagination {
text-align: right;
padding: 5px 10px 5px 10px;
}
}
</style>
@@ -0,0 +1,56 @@
<template>
<fs-page class="page-layout-custom">
<template #header>
<div class="title">自定义布局</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right>
<a-alert
class="ml-1"
type="info"
message="通过自定义container.is可以自定义布局,甚至可以支持上下两个翻页条 -------->"
/>
</template>
</fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
export default defineComponent({
name: "BasisLayoutCustom",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { crudExpose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ crudExpose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
<style lang="less">
.page-layout-custom {
}
</style>
@@ -0,0 +1,25 @@
import mockUtil from "/src/mock/base";
const options = {
name: "BasisLayoutCustom",
idGenerator: 0
};
const list = [
{
radio: "1",
name: "张三",
city: "sz"
},
{
radio: "2",
name: "李四",
city: "gz"
},
{
radio: "0",
name: "王五",
city: "sh"
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/BasisValueChange";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,95 @@
import * as api from "./api";
import { message } from "ant-design-vue";
import { dict } from "@fast-crud/fast-crud";
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
output: {},
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
switch: {
title: "开关",
type: "dict-switch",
dict: dict({
data: [
{ value: true, label: "开启" },
{ value: false, label: "关闭" }
]
}),
column: {
component: {
name: "fs-dict-switch",
vModel: "checked"
},
valueChange(context) {
console.log("column value changed:", context);
}
},
form: {
valueChange({ value, key, form }) {
console.log("valueChanged,", key, value, form);
message.info(`valueChanged:${key}=${value}`);
}
}
},
normal: {
title: "value-change",
type: "text",
form: {
valueChange({ value, key, form }) {
console.log("valueChanged,", key, value, form);
message.info(`valueChanged:${key}=${value}`);
}
}
},
immediate: {
title: "immediate",
type: "text",
search: {
show: true
},
form: {
valueChange: {
handle({ value, key, form, immediate }) {
console.log("valueChange,", key, value, "isImmediate=", immediate);
message.info(`valueChanged:${key}=${value},isImmediate=${immediate}`);
},
immediate: true
}
}
}
}
}
};
}
@@ -0,0 +1,43 @@
<template>
<fs-page>
<template #header>
<div class="title">ValueChange</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
export default defineComponent({
name: "BasisValueChange",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions, output } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef,
...output
};
}
});
</script>
@@ -0,0 +1,32 @@
import mockUtil from "/src/mock/base";
const options = {
name: "BasisValueChange",
idGenerator: 0
};
const list = [
{
ref: "根据showRef显示",
compute: true,
status: "1",
remote: "2",
shower: "---> 点右边编辑查看示例效果",
remote2: "2",
editable: true
},
{
compute: false,
status: "2",
remote: "0",
remote2: "2",
editable: false
},
{
compute: true,
status: "0",
remote2: "2",
editable: true
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/ComponentButton";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,115 @@
import * as api from "./api";
import { requestForMock } from "/src/api/service";
import { dict, compute } from "@fast-crud/fast-crud";
import { message } from "ant-design-vue";
export default function ({ expose }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
form: {
//配置表单label的宽度
labelCol: { span: 6 }
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
button: {
title: "按钮",
search: { show: true },
type: "button",
column: {
component: {
show: compute(({ value }) => {
//当value为null时,不显示
return value != null;
}),
on: {
// 注意:必须要on前缀
onClick({ row }) {
message.success("按钮点击:" + row.button);
}
}
}
}
},
url: {
title: "url",
search: { show: true },
type: "text",
column: {
show: false
}
},
link: {
title: "链接",
search: { show: true },
type: "link",
column: {
component: {
on: {
// 注意:必须要on前缀
onClick({ row }) {
if (row.url) {
window.open(row.url);
}
}
}
}
},
form: {
title: "按钮文字"
}
},
link2: {
title: "手写link配置",
search: { show: true },
type: "text", //form组件用input
column: {
component: {
name: "fs-button", //列展示组件为button
vModel: "text", // 将row.link2的值赋值给text属性
type: "link", // 按钮展示为链接样式
on: {
//注册点击事件
// 注意:必须要on前缀
onClick({ row }) {
if (row.url) {
window.open(row.url);
}
}
}
}
}
}
}
}
};
}
@@ -0,0 +1,42 @@
<template>
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding" />
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
export default defineComponent({
name: "ComponentButton",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
<style lang="less"></style>
@@ -0,0 +1,23 @@
import mockUtil from "/src/mock/base";
const options = {
name: "ComponentButton",
idGenerator: 0
};
const list = [
{
button: "张三",
link: "百度",
url: "https://www.baidu.com",
link2: "手写配置"
},
{
button: "李四",
link: "百度",
url: "https://www.baidu.com",
link2: "手写配置"
},
{}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;
@@ -0,0 +1,42 @@
import { requestForMock } from "/src/api/service";
const request = requestForMock;
const apiPrefix = "/mock/ComponentCascader";
export function GetList(query) {
return request({
url: apiPrefix + "/page",
method: "get",
data: query
});
}
export function AddObj(obj) {
return request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export function UpdateObj(obj) {
return request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export function DelObj(id) {
return request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export function GetObj(id) {
return request({
url: apiPrefix + "/info",
method: "get",
params: { id }
});
}
@@ -0,0 +1,137 @@
import * as api from "./api";
import { requestForMock } from "/src/api/service";
import { dict } from "@fast-crud/fast-crud";
export default function ({ crudRef }) {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }) => {
return await api.AddObj(form);
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
form: {
// 单列布局
col: { span: 24 },
labelCol: { span: 4 },
wrapperCol: { span: 18 }
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50
},
form: {
show: false
}
},
cascader: {
title: "级联",
search: { show: true },
type: "dict-cascader",
dict: dict({
cloneable: false,
isTree: true,
url: "/mock/dicts/cascaderData?single"
})
},
lazyLoad: {
title: "懒加载",
type: "dict-cascader",
dict: dict({
url: "/mock/tree/GetTreeChildrenByParentId?lazyLoad",
value: "code",
label: "name",
isTree: true,
prototype: true,
getNodesByValues(values) {
//给cell展示组件调用,根据value值获取节点,每行都会请求一次
if (values == null) {
return [];
}
return requestForMock({
url: "/mock/tree/GetNodesByValues",
params: { values }
});
}
}),
form: {
component: {
vModel: "value",
options: [
{
code: "11",
name: "北京",
isLeaf: false
},
{
code: "12",
name: "天津",
isLeaf: false
}
],
loadData: async (selectedOptions) => {
console.log("lazyLoad", selectedOptions);
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const ret = await requestForMock({
url: "/mock/tree/GetTreeChildrenByParentId",
params: { parentId: targetOption.code }
});
targetOption.loading = false;
const list = [];
for (const item of ret) {
list.push({
code: item.code,
name: item.name,
isLeaf: item.leaf === true
});
}
targetOption.children = list;
//options.value = [...options.value];
},
changeOnSelect: true
}
}
},
multiple: {
title: "可搜索,可只选父节点",
type: "dict-cascader",
dict: dict({
isTree: true,
url: "/mock/dicts/cascaderData?multiple"
}),
form: {
component: {
showSearch: {
filter: (inputValue, path) => {
return path.some((option) => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
}
},
"change-on-select": true
},
helper: "antd cascader 不支持级联多选"
}
}
}
}
};
}
@@ -0,0 +1,40 @@
<template>
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding" />
</fs-page>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCrud } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
export default defineComponent({
name: "ComponentCascader",
setup() {
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { expose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ expose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ expose, crudOptions });
// 你可以调用此方法,重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
expose.doRefresh();
});
return {
crudBinding,
crudRef
};
}
});
</script>
@@ -0,0 +1,23 @@
import mockUtil from "/src/mock/base";
const options = {
name: "ComponentCascader",
idGenerator: 0
};
const list = [
{
cascader: ["zhinan", "shejiyuanze", "yizhi"],
lazyLoad: ["11", "1101", "110101", "110101001"],
multiple: ["antdv cascader不支持多选"]
},
{
cascader: ["zhinan", "shejiyuanze", "yizhi"],
multiple: ["antdv cascader不支持多选"]
},
{
cascader: ["zhinan", "shejiyuanze", "yizhi"],
multiple: ["antdv cascader不支持多选"]
}
];
options.list = list;
const mock = mockUtil.buildMock(options);
export default mock;

Some files were not shown because too many files have changed in this diff Show More