• AppBoxFuture实战: 如何实现一对多表单的增删改查


      本篇通过完整示例介绍如何实现一对多关系表单的相应服务及视图。

    一、准备数据结构

      示例所采用的数据结构为“物资需求”一对多“物资清单”,通过IDE的实体设计器如下所示:

    1. 物资(DonateItem)

      主键为Id(Guid)

    2. 物资需求(Requirement)

      主键为Id(Guid)

    3. 物资清单(RequireItem)

      主键为Req(Requirement)+Item(DonateItem)

    添加实体成员时选择类型EntityRef(一对一)或EntitySet(一对多)可设置相应的导航属性

    二、实现需求列表显示功能

    1. 新建RequirementService服务实现加载列表数据

    using System;
    using System.Threading.Tasks;
    
    namespace dns.ServiceLogic
    {
    	public class RequirementService
    	{
            /// <summary>
            /// 分页加载需求记录
            /// </summary>
            public async Task<object> Load(int pageIndex, int pageSize)
            {
                var q = new SqlQuery<Entities.Requirement>();
                q.Skip(pageSize * pageIndex).Take(pageSize);
                q.OrderByDesc(t => t.Time);
                return await q.ToListAsync();
            }
        }
    }
    

    2. 新建RequireList视图

    2.1 模版

    <div>
    	<el-button-group>
    		<el-button type="primary" icon="fas fa-plus-square fa-fw">新建</el-button>
    		<el-button type="primary" icon="fas fa-edit fa-fw">修改</el-button>
    		<el-button type="primary" icon="fas fa-trash fa-fw">删除</el-button>
    		<el-button @click="load" type="primary" icon="fas fa-search fa-fw">刷新</el-button>
    	</el-button-group>
    	<br/><br/>
    	<el-table :data="items" v-loading="loading" border stripe highlight-current-row
    		readonly>
    		<el-table-column prop="Donee" label="需求方"></el-table-column>
    		<el-table-column prop="Time" label="时间"></el-table-column>
    		<el-table-column prop="Contact" label="联系人" width="80"></el-table-column>
    		<el-table-column prop="Phone" label="电话"></el-table-column>
    		<el-table-column prop="Address" label="地址"></el-table-column>
    		<el-table-column prop="PostCode" label="邮编" width="80"></el-table-column>
    	</el-table>
    	<el-pagination background layout="prev, pager, next" :total="1000">
    	</el-pagination>
    </div>
    

    2.2 脚本

    @Component
    export default class RequireList extends Vue {
        items = [] //需求列表
        loading = false
        pageIndex = 0
        pageSize = 20
    
        load() {
            this.loading = true
            dns.Services.RequirementService.Load(this.pageIndex, this.pageSize).then(res => {
                this.$set(this, 'items', $runtime.parseEntity(res))
                this.loading = false
            }).catch(err => {
                this.loading = false
                this.$message.error('加载需求列表失败: ' + err)
            })
        }
    
        mounted() {
            this.load()
        }
    }
    

    系统函数$runtime.parseEntity()用于将调用服务的结果内的数据对象如{ID:xxx,Name:xxx}转换为前端的Entity对象,另如果调用服务的结果只用作展示可以不用转换。

    2.3 预览

      点击“Preview”预览,当然当前没有任何数据。

    三、实现新建需求功能

    3.1 修改RequirementService实现保存方法

    /// <summary>
    /// 保存需求
    /// </summary>
    public async Task Save(Entities.Requirement req)
    {
        //TODO:验证
        using var conn = await DataStore.Default.OpenConnectionAsync();
        using var txn = conn.BeginTransaction();
        //保存主记录
        await DataStore.Default.SaveAsync(req, txn);
        //保存子记录
        foreach (var item in req.Items)
        {
            await DataStore.Default.SaveAsync(item, txn);
        }
        //处理已删除的物资
        if (req.PersistentState != PersistentState.Detached)
        {
            foreach (var item in req.Items.DeletedItems)
            {
                await DataStore.Default.DeleteAsync(item, txn);
            }
        }
        //递交事务
        txn.Commit();
    }
    

    3.2 新建ItemService实现用于绑定的加载方法

    using System;
    using System.Threading.Tasks;
    
    namespace dns.ServiceLogic
    {
        public class ItemService
        {
            /// <summary>
            /// 加载用于前端选择绑定
            /// </summary>
            public async Task<object> LoadForSelect()
            {
                var q = new SqlQuery<Entities.DonateItem>();
                return await q.ToListAsync(t => new { t.Id, t.Name, t.Spec });
            }
        }
    }
    

    3.3 新建RequireView编辑视图

    3.3.1 模版

    <div>
    	<!-- 表单头 -->
    	<el-row :gutter="20" type="flex" align="middle">
    		<el-col :span="3">需求方:</el-col>
    		<el-col :span="9">
    			<el-input v-model="req.Donee"></el-input>
    		</el-col>
    		<el-col :span="3">时间:</el-col>
    		<el-col :span="9">
    			<el-date-picker v-model="req.Time" type="date" placeholder="选择日期" style="100%">
    			</el-date-picker>
    		</el-col>
    	</el-row>
    	<br/>
    	<el-row :gutter="20" type="flex" align="middle">
    		<el-col :span="3">联系人:</el-col>
    		<el-col :span="9">
    			<el-input v-model="req.Contact"></el-input>
    		</el-col>
    		<el-col :span="3">电话:</el-col>
    		<el-col :span="9">
    			<el-input v-model="req.Phone"></el-input>
    		</el-col>
    	</el-row>
    	<br/>
    	<el-row :gutter="20" type="flex" align="middle">
    		<el-col :span="3">地址:</el-col>
    		<el-col :span="9">
    			<el-input v-model="req.Address"></el-input>
    		</el-col>
    		<el-col :span="3">邮编:</el-col>
    		<el-col :span="9">
    			<el-input v-model="req.PostCode"></el-input>
    		</el-col>
    	</el-row>
    	<br/>
    	<!-- 物资列表 -->
    	<el-table :data="items" border highlight-current-row readonly>
    		<el-table-column type="index"></el-table-column>
    		<el-table-column label="物资" width="280px">
    			<template slot-scope="scope">
    				<el-select v-model="scope.row.ItemId" value-key="Id" style="100%" placeholder="请选择">
    					<el-option v-for="item in optItems" :key="item.Id" :label="item.Name+' '+item.Spec"
    						:value="item.Id">
    					</el-option>
    				</el-select>
    			</template>
    		</el-table-column>
    		<el-table-column label="数量" width="130px">
    			<template slot-scope="scope">
    				<el-input-number v-model="scope.row.Quantity" controls-position="right" :min="1" style="100%">
    				</el-input-number>
    			</template>
    		</el-table-column>
    		<el-table-column label="备注">
    			<template slot-scope="scope">
    				<el-input v-model="scope.row.Comment"></el-input>
    			</template>
    		</el-table-column>
    		<el-table-column align="right" width="100px">
    			<template slot="header" slot-scope="scope">
    				<el-button @click="onAddItem" icon="fa fa-plus fa-fw" size="mini">添加</el-button>
    			</template>
    			<template slot-scope="scope">
    				<el-button @click="onDeleteItem(scope.$index)" icon="fa fa-times fa-fw" size="mini">删除</el-button>
    			</template>
    		</el-table-column>
    	</el-table>
    	<br/>
    	<div style="text-align:center">
    		<el-button @click="onSave" type="primary" icon="fas fa-save fa-fw">保存</el-button>
    	</div>
    </div>
    

    3.3.2 脚本

    @Component
    export default class RequireView extends Vue {
        @Prop({ type: Object, default: {} }) req: dns.Entities.Requirement
    
        optItems = [] //物资选择列表
    
        /** 用于Table绑定,过滤已标为删除的 */
        get items() {
            if (this.req.Items) {
                return this.req.Items.filter(t => !t.isDeleted())
            }
            return null
        }
    
        /** 加载用于绑定下拉选择的物资列表 */
        loadItems() {
            dns.Services.ItemService.LoadForSelect().then(res => {
                this.$set(this, 'optItems', res)
            }).catch(err => {
                this.$message.error("加载物资列表失败: " + err)
            })
        }
    
        /** 添加物资 */
        onAddItem() {
            if (!this.req.Items) { //仅测试
                this.$set(this.req, 'Items', [])
            }
            this.req.Items.push(new dns.Entities.RequireItem())
        }
    
        /** 删除物资 */
        onDeleteItem(index) {
            //新建的直接删除,旧的标为删除
            if (this.req.Items[index].isNew()) {
                this.req.Items.splice(index, 1)
            } else {
                this.req.Items[index].markDeleted()
            }
        }
    
        onSave() {
            dns.Services.RequirementService.Save(this.req).then(res => {
                this.req.acceptChanges()
                this.$message.success("保存成功")
            }).catch(err => {
                this.$message.error("保存错误: " + err)
            })
        }
    
        mounted() {
            this.loadItems()
        }
    }
    

    3.4 修改RequireList视图,实现新建功能

    3.4.1 模版

    	<el-button @click="onCreate" type="primary" icon="fas fa-plus-square fa-fw">新建</el-button>
    <!-- 省略 -->
        <el-dialog title="需求记录" :visible.sync="dlgVisible" width="800px">
    		<require-view :req="current"></require-view>
        </el-dialog>
    <!-- 省略 -->
    

    3.4.2 脚本

    @Component({
        components: { RequireView: dns.Views.RequireView }
    })
    export default class RequireList extends Vue {
        //----省略----
        dlgVisible = false
        current = null
        //----省略----
        onCreate() { 
            this.current = new dns.Entities.Requirement()
            this.dlgVisible = true
        }
        //----省略----
    

    现在可以在预览内尝试添加些数据了!

    四、实现需求列表Pop显示物资清单

      主列表使用懒加载方式加载子表数据。

    4.1 修改RequirementService服务实现加载子表数据方法

    /// <summary>
    /// 加载需求物资列表
    /// </summary>
    public async Task<object> LoadItems(Guid reqId)
    {
        var q = new SqlQuery<Entities.RequireItem>();
        q.Where(t => t.ReqId == reqId);
        q.Include(t => t.Item.Name);
        q.Include(t => t.Item.Spec);
        return await q.ToListAsync();
    }
    

    4.2 修改RequireList视图

    4.2.1 模版

    <!-- 省略 -->
    <el-table-column label="需求物资" width="80">
        <template slot-scope="scope">
            <!-- 物资清单列表 -->
            <el-popover @show="loadItems(scope.row)" trigger="hover" placement="left" width="400">
                <el-row v-for="(item,index) in scope.row.Items" :gutter="20">
                    <el-col :span="2">{{index+1}}.</el-col>
                    <el-col :span="4">{{item.ItemName}}</el-col>
                    <el-col :span="14">{{item.ItemSpec}}</el-col>
                    <el-col :span="4">{{item.Quantity}}</el-col>
                </el-row>
                <div slot="reference">
                    <el-tag size="medium">物资清单</el-tag>
                </div>
            </el-popover>
        </template>
    </el-table-column>
    <!-- 省略 -->
    

    4.2.2 脚本

    /** 加载需求物资清单 */
    loadItems(row: dns.Entities.Requirement) {
        if (row.Items) { return }
        dns.Services.RequirementService.LoadItems(row.Id).then(res => {
            this.$set(row, 'Items', $runtime.parseEntity(res))
        }).catch(err => {
            this.$message.error('加载物资列表失败: ' + err)
        })
    }
    

    4.2.3 预览

    五、实现修改与删除功能

    5.1 修改RequirementService服务实现删除方法

    public async Task Delete(Entities.Requirement req)
    {
        //TODO:判断状态,已发放物资的不能删除
        using var conn = await DataStore.Default.OpenConnectionAsync();
        using var txn = conn.BeginTransaction();
        //先删除子表记录
        var deleteItems = new SqlDeleteCommand<Entities.RequireItem>();
        deleteItems.Where(t => t.ReqId == req.Id);
        await DataStore.Default.ExecCommandAsync(deleteItems, txn);
        //再删除主表记录
        await DataStore.Default.DeleteAsync(req, txn);
        //递交事务
        txn.Commit();
    }
    

    5.2 修改RequireList视图

    5.2.1 模版

    <!-- 省略 -->
    <el-button @click="onEdit" type="primary" icon="fas fa-edit fa-fw">修改</el-button>
    <el-button @click="onDelete" type="primary" icon="fas fa-trash fa-fw">删除</el-button>
    <!-- 省略 -->
    <el-table :data="items" v-loading="loading" @current-change="onCurrentChanged" border stripe highlight-current-row
    		readonly>
    
    

    5.2.2 脚本

    //----省略----
    export default class RequireList extends Vue {
        //----省略----
        currentRow = null
        //----省略----
        onCurrentChanged(row) {
            this.currentRow = row
        }
        onEdit() { 
            if (!this.currentRow) {
                this.$message.warning("请先选择记录")
                return
            }
            this.loadItems(this.currentRow)
            this.current = this.currentRow
            this.dlgVisible = true
        }
        onDelete() {
            if (!this.currentRow) {
                this.$message.warning("请先选择记录")
                return
            }
            this.$confirm('确认删除选择的记录吗?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
                dns.Services.RequirementService.Delete(this.currentRow).then(res => {
                    this.$message.success("删除成功");
                }).catch(err => {
                    this.$message.error("删除失败: " + err)
                })
            }).catch(() => { })
        }
        //----省略----
    

    5.2.3 预览

    六、本篇小结

      作者上篇提到实现独立的不依赖内置存储的版本,本篇示例即是基于此版本,下一步重点是针对此版本的测试与Bug修复。另一边码代码一边码文实属不易,作者需要您的支持请您多多点赞推荐!

  • 相关阅读:
    幻方~未完待续
    过河(DP)
    生日蛋糕(DFS)
    n皇后问题<dfs>
    POJ 1182_食物链
    POJ 2431 Expedition【贪心】
    POJ 3268_Silver Cow Party
    POJ 1061 青蛙的约会【扩欧】
    【数学】扩展欧几里得算法
    Codeforces 404D Minesweeper 1D
  • 原文地址:https://www.cnblogs.com/BaiCai/p/12327351.html
Copyright © 2020-2023  润新知