zoukankan      html  css  js  c++  java
  • 在线数据库设计 初稿 想法简单验证

    缘起

    数据库设计一直在使用 powerdesign,很好用。
    但在分享交流时,对方必须安装,比较麻烦。在线的数据库设计没找到合适好用。
    就想能不能自己试着做个看看。

    已验证的功能:

    添加表、添加文字提示、拖动、绘制表之间的关系线。

    技术难点

    拖动直接使用的 draggabilly 组件,很好用。
    绘制连接线,使用svg 的PATH自己实现。
    从一个表连接到另一个表实现思路:
    获取两个表中心点,使用PATH连线。
    但要绘制箭头时,箭头会被遮住。箭头应该在表边缘外。
    技术难点,获取线段与四边形相交位置,最终问题变成获取指定两条线段的相交位置。
    最终在网上找到现成处理方案。

    效果如下:

    最终目标

    可是实现在线方便的设计数据库的表,可以描述表之间的关系。
    可生成SQL创建脚本(mysql),可对表信息备注,生成数据库文档方便交流。

    实现代码如下:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>DB 设计</title>
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <link href="https://cdn.bootcss.com/normalize/7.0.0/normalize.css" rel="stylesheet">
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <link href="https://cdn.bootcss.com/jquery-contextmenu/3.0.0-beta.1/jquery.contextMenu.min.css" rel="stylesheet">
    <style>
    #divMenu{
        position: absolute;
        top:0;
        left:0;
        height:30px;
        font-size: 12px;
    }
    #divMain{
        position: absolute;
        top:30px;
        left:0;
        right:0;
        bottom:0;
        overflow: auto;
        font-size: 14px;
        font-family:consolas,"Courier New",微软雅黑;
        background:repeating-linear-gradient(45deg, #fff 0,#fff 5px,#f6f6f6 5px,#f6f6f6 10px);
    }
    #divMainObj{
        position: absolute;
        top:0;
        left:0;
        z-index: 2;
    }
    #divMain .divObj{
        position: absolute;
        border: double 3px #999;
        box-shadow: 1px 1px 5px #999;
        background: #fff;
        z-index:1;
    }
    #divMain .divObj:hover{
        box-shadow: 1px 1px 10px #333;
    }
    #divMain .divObj.active{
        position: absolute;
        border: solid 3px #3366CC;
        box-shadow: none;
    }
    #divMain .divObj:hover{
        box-shadow: 1px 1px 10px #333;
    }
    #divMain .divObj .title{
        padding:5px;
        line-height: 1.3;
        text-align: center;
        border-bottom:solid 1px #999;
        font-weight: bold;
        cursor: move;
        background: #ddd;
    }
    #divMain .db-text{
        line-height: 1.3;
        resize: both;
        overflow: auto;
        width:200px;
        height:100px;
    }
    #divMain .db-text .title{
        text-align: left;
    }
    #divMain .db-text .content{
        padding:5px;
        /*
        -webkit-user-modify: read-write-plaintext-only;
        -webkit-user-modify: read-write;
        */
    }
    #divMain .db-table{
        min-width: 200px;
        min-height:100px;
    }
    #divMain .db-table .field_list{
        padding:5px;
        line-height: 1.1;
    }
    #divMain .db-table .field_list .field{
        padding-right:10px;
    }
    #divMain .db-table .field_list .type{
        font-size:12px;
        color:#666;
    }
    #divTemplate{
        display: none;
    }
    .svgObj .svg-line{
        fill: transparent;
        stroke-width: 2px;
        stroke: #999;
    }
    .svgObj .svg-line:hover{
        stroke: #333;
    }
    #svgArrow{
        position: absolute;
        left:0;
        right:0;
        z-index: 1;
    }
    </style>
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/draggabilly/2.1.1/draggabilly.pkgd.min.js"></script>
    </head>
    <body>
        <div id="divInfo"></div>
        <div id="divMenu">
            <button id="btnAddTable">添加表</button>
            <button id="btnAddText">添加文本</button>
            <button id="btnAddLine">连线</button>
            <button id="btnDel">删除</button>
            <button id="btnSave">保存</button>
        </div>
        <div id="divTemplate">
            <div id="divTemplate_table" class="db-table divObj">
                <div class="title">表名</div>
                <div class="field_list">
                    <table>
                        <tr>
                            <td class="field">ID</td>
                            <td class="type">int</td>
                        </tr>
                        <tr>
                            <td class="field">名称</td>
                            <td class="type">varchar(255)</td>
                        </tr>
                        <tr>
                            <td class="field">备注</td>
                            <td class="type">varchar(2048)</td>
                        </tr>
                    </table>
                </div>
            </div>
            <div id="divTemplate_text" class="db-text divObj">
                <div class="title">标题</div>
                <div class="content">文本内容<br>hello world</div>
            </div>
            <textarea id="svgTemplate_line">
                <svg id="svgArrow" class="svgObj" width="__width__" height="__height__" xmlns="http://www.w3.org/2000/svg">
                    <defs>
                    <marker id="markerArrow" markerWidth="8" markerHeight="8" refx="2" refy="5" orient="auto">  
                        <path d="M2,2 L2,8 L8,5 L2,2" style="fill: #999;" />  
                    </marker>
                    </defs>
                    __path_list__
                </svg>
            </textarea>
        </div>
        <div id="divMain">
            <div id="divMainObj"></div>
        </div>
        <script>
        
        var db = {
            divObjMaxId:0,
            divObjZIndex:1,
            curId:'',
            init: function(){
                $('#btnAddTable').click(function(){
                    var divObj = $('#divTemplate_table').clone();
                    divObj.data('type', 'table');
                    db.addObj(divObj);
                });
                
                $('#btnAddText').click(function(){
                    var divObj = $('#divTemplate_text').clone();
                    divObj.data('type', 'text');
                    db.addObj(divObj);
                });
    
                $('#btnDel').click(function(){
                    if (!db.curId)
                    {
                        return;
                    }
                    var divObj = $('#' + db.curId);
                    divObj.remove();
                    db.curId = '';
                });
    
                var timerLine = null;
                $('#btnAddLine').click(function(){
                    if (timerLine) return;
                    timerLine = window.setTimeout(function(){
                        var svgHtml = $('#svgTemplate_line').val();
                        svgHtml = svgHtml.replace(/__width__/g, '' + Math.max($('#divMain').width(), $('#divMainObj').width()));
                        svgHtml = svgHtml.replace(/__height__/g, '' + Math.max($('#divMain').height(), $('#divMainObj').height()));
                        svgHtml = svgHtml.replace(/__path_list__/g, db.getLinePath());
                        $('#svgArrow').remove();
                        //console.debug(svgHtml);
                        $('#divMain').append(svgHtml);
                        timerLine = null;
                    },20);
                });
    
                $('#divMain').click(function(e){
                    var id = $(e.target).attr('id');
                    if (id != 'divMain' && id != 'divMainObj' && id != 'svgArrow')
                    {
                        return;
                    }
                    this.curId = '';
                    $('#divMain .divObj').removeClass('active');
                });
    
                $('#btnSave').click(db.save);
    
                this.load();
            },
            addObj: function(obj){
                this.divObjMaxId++;
                var id = 'dbObj_' + this.divObjMaxId;
                obj.attr('id', id);
                var obj = $('#divMainObj').append(obj);
                var drag = $('#'+id).draggabilly({
                    //containment:'#divMain',
                    handle: '.title',
                });
                
                $('#'+id).mousedown(function(){
                    db.select($(this).attr('id'));
                });
                drag.on('dragMove', function(event, pointer, moveVector) {
                    var id = event.currentTarget.id;
                    $('#' + id).data('xf', moveVector.x);
                    $('#' + id).data('yf', moveVector.y);
                    $('#btnAddLine').click();
                });
                drag.on('dragEnd', function(event, pointer) {
                    $('#' + id).data('xf', 0);
                    $('#' + id).data('yf', 0);
                    $('#btnAddLine').click();
                });
            },
            select: function(id){
                if (id == this.curId)
                {
                    return;
                }
                db.divObjZIndex++;
                var obj = $('#' + id);
                $(obj).css('z-index', db.divObjZIndex);
                $('#divMain .divObj').removeClass('active');
                $(obj).addClass('active');
                this.curId = id;
            },
            save: function(){
                var dbObj = {
                    divObjMaxId:db.divObjMaxId,
                    divObjZIndex:db.divObjZIndex,
                    divObjList:[],
                };
                $('#divMain .divObj').each(function(){
                    dbObj.divObjList.push({
                        id:$(this).attr('id'),
                        zindex:$(this).css('z-index'),
                        top:$(this).css('top'),
                        left:$(this).css('left'),
                        $(this).css('width'),
                        height:$(this).css('height'),
                        title:$(this).find('.title').html(),
                        type:$(this).data('type'),
                    });
                });
                window.localStorage.setItem('dbObj', JSON.stringify(dbObj));
            },
            load: function(){
                var dbStr = window.localStorage.getItem('dbObj');
                if (!dbStr)
                {
                    return;
                }
    
                var dbObj = JSON.parse(dbStr);
                this.divObjMaxId = dbObj.divObjMaxId;
                this.divObjZIndex = dbObj.divObjZIndex;
    
                for (var divObj of dbObj.divObjList)
                {
                    var obj = $('#divTemplate_' + divObj.type).clone();
                    obj.attr('id', divObj.id),
                    obj.css({
                        'z-index': divObj.zindex,
                        'top':divObj.top,
                        'left':divObj.left,
                        'width':divObj.width,
                        'height':divObj.height,
                    });
    
                    obj.find('.title').html(divObj.title);
                    obj.data('type', divObj.type);
                    db.addObj(obj);
                }
            },
    
            // 判断两条线段是否相交
            segmentsIntr: function(a, b, c, d){
                /** 1 解线性方程组, 求线段交点. **/
                // 如果分母为0 则平行或共线, 不相交
                var denominator = (b.y - a.y) * (d.x - c.x) - (a.x - b.x) * (c.y - d.y);
                if (denominator == 0)
                {
                    return false;
                }
    
                // 线段所在直线的交点坐标 (x , y)
                var x = ( (b.x - a.x) * (d.x - c.x) * (c.y - a.y)
                        + (b.y - a.y) * (d.x - c.x) * a.x
                        - (d.y - c.y) * (b.x - a.x) * c.x ) / denominator ;
                var y = -( (b.y - a.y) * (d.y - c.y) * (c.x - a.x)
                        + (b.x - a.x) * (d.y - c.y) * a.y
                        - (d.x - c.x) * (b.y - a.y) * c.y ) / denominator;
    
                /** 2 判断交点是否在两条线段上 **/    
                if ((x - a.x) * (x - b.x) <= 0 
                    && (y - a.y) * (y - b.y) <= 0
                    && (x - c.x) * (x - d.x) <= 0 
                    && (y - c.y) * (y - d.y) <= 0
                )
                {
                    // 返回交点p    
                    return {x:x, y:y};
                }
    
                // 否则不相交
                return false;
            },
                
            getLinePath:function ()
            {
                // <path class="svg-line" d="M15,85 l120,140" marker-end="url(#markerArrow)"/>
                var html = '';
                var x=null, y=null;
                $('#divMain .db-table').each(function(){
                    var xf = $(this).data('xf');
                    var yf = $(this).data('yf');
                    xf = xf || 0;
                    yf = yf || 0;
                    var left = parseInt($(this).css('left')) + xf;
                    var top = parseInt($(this).css('top')) + yf;
                    var width = $(this).outerWidth(true);
                    var height = $(this).outerHeight(true);
                    x1 = left + width / 2;
                    y1 = top + height / 2;
                    if (x !== null)
                    {
                        // 判断边缘
                        var a = {x:x,y:y}, b = {x:x1, y:y1};
                        var lines = [];
                        lines.push([{x:left, y:top}, {x:left+width, y:top}]);
                        lines.push([{x:left+width, y:top}, {x:left+width, y:top+height}]);
                        lines.push([{x:left+width, y:top+height}, {x:left, y:top+height}]);
                        lines.push([{x:left, y:top+height}, {x:left, y:top}]);
                        var point = null;
                        for (var line of lines)
                        {
                            var bo = db.segmentsIntr(a, b, line[0], line[1]);
                            if (bo !== false)
                            {
                                point = bo;
                                break;
                            }
                        }
                        
                        if (point)
                        {
                            // 获取两点间角度
                            var diff_x = point.x - x;
                            var diff_y = point.y - y;
                            var d = Math.atan(diff_y/diff_x);
                            var deg = 180/Math.PI * d;
    
                            var x2 = point.x + Math.cos(d) * 15 * (x < x1 ? -1 : 1);
                            var y2 = point.y + Math.sin(d) * 15 * (x < x1 ? -1 : 1);
                            html += '<path class="svg-line" d="';
                            html += 'M' + x + ',' + y + ' L' + x2 + ',' + y2;
                            html += '" marker-end="url(#markerArrow)"/>';
                        }
                    }
                    x = x1;
                    y = y1;
                    
                    //console.debug(x,y);
                });
                
                return html;
            }
    
        };
    
        $(function(){
            db.init();
        });
        
        </script>
    </body>
    </html>
  • 相关阅读:
    窗体控件随窗体大小改变(包括字体大小)
    Silverlight数据加载时,等待图标显示与隐藏(Loading)
    鼠标经过时,地图上的每个城市变颜色并且有提示框
    开始博客生活
    光纤
    静态路由配置(Static Routing)
    对称加密与非对称加密
    RIP Debug 过程
    WORD 固定表头自动生成/在Word表格接续页加上重复表格标题
    RIP路由
  • 原文地址:https://www.cnblogs.com/zjfree/p/8549663.html
Copyright © 2011-2022 走看看