zoukankan      html  css  js  c++  java
  • 微项目: 地区导航——知识与技术点总结

     1, index页面(带直接显示数据)

    <%@ page language="java" contentType="text/html; charset=UTF-8"  pageEncoding="UTF-8"%>
    <%@ page import="java.util.*" %>        //刚开始的时候不要忘记引入各种包
    <%@ page import="com.hanqi.Dao.*" %>
    <%@ page import="com.hanqi.*" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>地区列表显示页面</title>
    <%
    String pid = request.getParameter("parentid");   //接收自己传给自己的参数
    if(pid==null||pid.trim().length()<=0) {
          pid = "0";
    }  
    int pid1 = Integer.parseInt(pid);
    //获取数据
    MemberDal md = new MemberDal();
    Memberclass m = md.getMemberclass(pid1);
    int backid = 0;
    if(m!=null) {
        backid = m.getParentId();
    }
    %>
    <script src="js/jquery-1.11.3.min.js"></script>
    <script>      //这里使用jQuery隐藏和显示添加文本框
    $(document).ready(function () {
        $("#i1").click(function (){
            $("#d1").toggle(100);
        })
    });
    $(document).ready($("#d1").hide());    //隐藏
    </script>
    <script>
    function check() {
        var obj = /d{6}/;      //正则表达式判断是否是6位数字以上的
        if(fm.name.value=="") {
            alert("地区不能为空");
            return false;
        }
        else if(!(obj.test(fm.postcode.value)||fm.postcode.value=="")) {
            alert("邮编请符合规则");
            return false;
        }
        else {
            return true;
        }
    }
    function con(){
        var cfm = confirm("确定要删除该地区吗? 删除该地区的同时, 会将该地区下的所有地区都删除! ");
            return cfm;
    }
    /*function show() {
        document.getElementById("fm").style.display="block";
    }*/       
    //样式表中的隐藏属性, display = none或者block
    function backSuper() {
        window.location="index.jsp?parentid=<%=backid %>";
            //window.history.back();     //使页面后退的两种简单方法
        //window.history.go(-1);
    }
    </script>
    </head>
    <body>
    <a href="Finder">显示全部省级地区</a>
    <br>
    <br>
    <input id="i1" type="button" value="在此级别添加地区" />&nbsp;
    <input type="button" value="返回上级地区" onclick="backSuper()" />
    <br>
    <table border="1" cellspacing="1" cellpadding="1">
      <tr>
        <th width="80">地名</th>
        <th width="80">邮编</th>
        <th width="80">管理</th>
        <th width="80">删除</th>
      </tr>
      <%
      ArrayList<Memberclass> al = md.getList(pid1);
      //Memberclass u;
      if(al!=null) {
          for(Memberclass rset : al) {
              out.print("<tr><td>");    //通过输出表格标签的形式组建表格
              out.print(rset.getName());
              out.print("</td><td>");
              out.print(rset.getPostCode()==null?"":rset.getPostCode());     //用三元表达式让其不显示"null"
              out.print("</td><td>");
              out.print("<a href='index.jsp?parentid="+rset.getId()+"'>下级地区</a>");
              out.print("</td><td>");
              out.print("<a onclick='return con()' href='DeleteMember?parentid="+rset.getParentId()+"&id="+rset.getId()+"'>删除</a></td></tr>");
          }
      }
      %>
    </table>
    <br>
    <div id="d1">
    <form id="fm" method="post" action="InsertMember" onSubmit="return check()">
    <input name="parentid" type="hidden" value="<%=pid%>">
    <ul>
    <li>请输入地区: <input name="name" type="text" value="" /></li>
    <li>请输入邮编: <input name="postcode" type="text" value="" maxlength="6"/></li>
    <li><input type="submit" value="添加" /><input type="reset" value="取消" /></li>
    </ul>
    </form>
    </div>
    带刷新的三级联动<br>
    <iframe src="list.jsp" width="500" height="50"></iframe><br><br>
    无刷新三级联动<br>
    <iframe src="ajaxlist1.jsp" width="500" height="50"></iframe>
    </body>
    <script>    //设置开始的隐藏状态, 隐藏添加的文本框
    $("#d1").hide();
    </script>
    </html>
    View Code

     2, Servlet控制层

    录入数据

        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");          //先设置转码的指令
            String name = request.getParameter("name");
            String postcode = request.getParameter("postcode");   //接收参数
            String parentid = request.getParameter("parentid");
            
            if(name!=null&&name.trim().length()>0) {
                //get方法需要在这里在进行一次转码
                //name = new String(name.getBytes("iso-8859-1"),"utf-8");
        
                if(parentid!=null&&parentid.trim().length()>0) {
                    Memberclass m = new Memberclass();
                    m.setName(name);
                    m.setParentId(Integer.parseInt(parentid));
                    m.setPostCode(postcode);
                    MemberDal ma = new MemberDal();
                    try {
                        if(ma.insert(m)>0) {
                            response.sendRedirect("Finder?parentid="+parentid);   //顺便将参数传回去, 控制显示页面
                        }
                        else {
                            response.getWriter().append("添加失败");
                        }
                    } catch (Exception e) {
                        response.getWriter().append(e.getMessage());
                    }
                }
                else {
                    response.getWriter().append("地区parentid不能为空");
                }
            }
            else {
                response.getWriter().append("地区name不能为空");
            }
        }
    View Code

    删除数据

        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/htm; charset=utf-8");
            String id = request.getParameter("id");
            String parentid = request.getParameter("parentid");
            if(id==null||id.trim().length()<=0) {
                response.getWriter().append("id不能为空");
            }
            else {
                MemberDal1 de = new MemberDal1();
                try {
                    if(de.deleteDG(Integer.parseInt(id))>0) {   //判断递归级联删除的返回值为多少
                        response.sendRedirect("Finder?parentid="+(parentid==null?"0":parentid));   //使用三元表达式赋值控制显示数据
                    }
                    else {
                        response.getWriter().append("删除数据失败");
                    }
                }
                catch(Exception e) {
                    response.getWriter().append(e.getMessage());
                }
            }
        }
    View Code

    查询显示数据(使用一个sevrlet), 在index中可以直接获取数据, 两种方法均可

        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html; charset=utf-8");
            try {    //异常包裹处理是非常重要的
                Connection con = Dbhelper.getConnection();
                if(con!=null) {
                    String parentid = null;
                    String sql = "select * from member where parentid ="+parentid;
                    Statement stm = con.createStatement();
                    ResultSet rs = stm.executeQuery(sql);   //在这里混了好几次了, 根据返回值的不同, 执行的语句也是不一样的, 高度注意这里
                    ArrayList<Memberclass> result = new ArrayList<Memberclass>();   //实例化一个集合要标明对象类型Memberclass
                    if(rs!=null) {
                        while(rs.next()) {
                            Memberclass place = new Memberclass();
                            place.setId(rs.getInt("id"));
                            place.setParentId(rs.getInt("parentid"));
                            place.setName(rs.getString("name"));
                            place.setPostCode(rs.getString("postcode"));
                            result.add(place);
                        }
                        request.setAttribute("placelist", result);
                    }
                    else {
                        response.getWriter().append("无结果返回");
                    }
                    //p.close();
                    con.close();
                    stm.close();
                }
                else{
                    response.getWriter().append("连接数据库失败");
                }
            }
            catch(Exception e) {
                response.getWriter().append(e.getMessage());
            }
            request.getRequestDispatcher("index.jsp").forward(request, response);   //注意跳转的方式
        }
    View Code

     3, Dal工具类

    链接数据库, 加载驱动(没什么好说的, 背下来)

    public class Dbhelper {
        public static Connection getConnection() throws Exception {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            String url = "jdbc:oracle:thin:@localhost:1521:orcl";
            Connection conn = DriverManager.getConnection(url, "test1204", "1234");
            return conn;
        }
    }
    View Code

    对象类(当时学的时候觉得很深奥, 现在已经用的比较熟练了, 为什么?)

    package com.hanqi.Dao;
    
    public class Memberclass {
        private int id;
        private int ParentId;
        private String Name;
        private String PostCode;
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public int getParentId() {
            return ParentId;
        }
        public void setParentId(int parentId) {
            ParentId = parentId;
        }
        public String getName() {
            return Name;
        }
        public void setName(String name) {
            Name = name;
        }
        public String getPostCode() {
            return PostCode;
        }
        public void setPostCode(String postCode) {
            PostCode = postCode;
        }
    }
    View Code

    增删查方法体类(重点是递归级联删除, 以后关于地区的问题, 大部分都要使用到这个技术, 还有建立树形文件夹)

    package com.hanqi.Dao;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.util.ArrayList;
    public class MemberDal {
        //插入数据
        public int insert(Memberclass m) throws Exception {
            int row = 0;
            Connection conn = Dbhelper.getConnection();
            PreparedStatement pst = null;
            if(conn!=null) {
                try {   //注意在写占位符的时候尽量不要加空格
                    String sql = "insert into member (id, parentid, name, postcode) values (sq_member.nextval,?,?,?)";
                    pst = conn.prepareStatement(sql);
                    pst.setInt(1, m.getParentId());
                    pst.setString(2, m.getName());
                    pst.setString(3, m.getPostCode());
                    row = pst.executeUpdate();
                }
                catch(Exception ex) {
                    throw ex;
                }
                finally {
                    try{
                        pst.close();                      //如果出现异常也要确保资源能够正常释放
                    }
                    catch(Exception ex1) {
                        throw ex1;
                    }
                    conn.close();
                }
            }
            return row;
        }
        
        //删除数据, 只能删除两级
        public int delete(int id) throws Exception {
            int row = 0;
            Connection con = Dbhelper.getConnection();
            PreparedStatement ps = null;
            if(con!=null) {
                try {   //运用上下级id的关系
                    String sql = "delete member where id = ? or parentid = ?";
                    ps = con.prepareStatement(sql);
                    ps.setInt(1, id);
                    ps.setInt(2, id);
                    row = ps.executeUpdate();
                }
                catch(Exception e) {
                    throw e;
                }
                finally {
                    try {
                        ps.close();
                    }
                    catch(Exception e1) {
                        throw e1;
                    }
                    con.close();
                }
            }
            return row;
        }
    
        
        //构造查询的方法
        public ArrayList<Memberclass> getList(int parentid) throws Exception {
            ArrayList<Memberclass> rst = null;
            Connection con = Dbhelper.getConnection();
            PreparedStatement p = null;
            ResultSet rs = null;
                if(con!=null) {
                    try {
                        String sql = "select * from member where parentid = ?";
                        p = con.prepareStatement(sql);
                        p.setInt(1, parentid);
                        rs = p.executeQuery();
                        rst = new ArrayList<Memberclass>();
                        if(rs!=null) {
                            while(rs.next()) {
                                Memberclass place = new Memberclass();
                                place.setId(rs.getInt("id"));
                                place.setParentId(rs.getInt("parentid"));
                                place.setName(rs.getString("name"));
                                place.setPostCode(rs.getString("postcode"));
                                rst.add(place);
                            }
                        }
                    }
                    catch(Exception e) {
                        System.out.println(e);
                    }
                    finally {
                        try {
                            p.close();
                        }
                        catch(Exception e1) {
                            System.out.println(e1);
                        }
                        finally {
                            try {
                                con.close();
                            }
                            catch(Exception e2) {
                                
                            }
                            finally {
                                rs.close();
                            }
                        }
                    }
                }
            return rst;
        }
        
        
        
        //返回上一级单条查询
        public Memberclass getMemberclass(int id) throws Exception{
            Memberclass rtn = null;
            Connection conn = Dbhelper.getConnection();
            PreparedStatement pst = null;
            ResultSet rs = null;
            if(conn!=null) {
                try {
                    String sql = "select * from member where id=?";
                    pst = conn.prepareStatement(sql);
                    pst.setInt(1, id);
                    rs = pst.executeQuery();
                    if(rs!=null&&rs.next()) {
                            rtn = new Memberclass();
                            rtn.setId(rs.getInt("id"));
                            rtn.setParentId(rs.getInt("parentid"));
                    }
                }
                catch(Exception ex) {
                    throw ex;
                }
                finally {
                    try {
                        pst.close();                      //如果出现异常也要确保资源能够正常释放
                    }
                    catch(Exception ex1) {
                        throw ex1;
                    }
                    conn.close();
                }
            }
            return rtn;
        }
        
        
        
        //递归级联删除
        //递归级联删除
        private void dgDel(Connection con, int id) throws SQLException {
            PreparedStatement pst = null;
            ResultSet rs = null;
            Statement st = null;
            try {
                //找到第一条复合条件的先将其删除
                String sql = "delete member where id = ? ";
                pst = con.prepareStatement(sql);
                pst.setInt(1, id);
                pst.executeUpdate();
                //找到其下一级
                sql = "select * from member where parentid = " + id;
                st = con.createStatement(); 
                rs = st.executeQuery(sql);
              //因为在这个题目当中有一个id==parentid的隐含条件, 所以这里可以使用递归级联删除
                //遍历子节点
                if (rs != null) {
                    while(rs.next()) {
                        dgDel(con, rs.getInt("id"));//递归调用
                    }  
                }
            }
            catch(Exception ex) {
                System.out.println(ex);
            }
            finally {
                try {
                    rs.close();
                }
                catch(Exception ex) { }
                try {
                    pst.close();
                }
                catch(Exception ex) { }
                try {
                    st.close();
                }
                catch(Exception ex) { }
            }
        }
        
        //
        public int deleteDG(int id) throws Exception {
            int rtn = -1;
            Connection conn = Dbhelper.getConnection();
            PreparedStatement pst = null;
            if (conn != null) {                        
                try {
                    //因为牵扯到级联删除, 这个地方一定要用到事务, 设置为手动提交
                    //手动提交
                    conn.setAutoCommit(false);
                    //递归级联删除
                    dgDel(conn, id);
                    conn.commit();
                    //之前一直返回的是一个负数是因为rtn这个一直没被赋值, 这样做一下判断, 如果删除成功了就赋值为1
                    rtn = 1;
                }
                catch(Exception ex) {
                    conn.rollback();
                    throw ex;
                }
                finally {
                    try {
                        pst.close();
                    }
                    catch(Exception ex) { }
                    conn.close();
                }
            } 
            return rtn;
        }
        
        
        
        
        
    }
    View Code

    地区的三级联动

    1, 显示页面, jsp页面

    带刷新的三级联动

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@ page import="java.util.*" %>
    <%@ page import="com.hanqi.Dao.*" %>
    <%@ page import="com.hanqi.*" %>    
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>三级联动</title>
    <%
    String first = request.getParameter("first");   //接收自己传给自己的数据
    String second = request.getParameter("second");
    int ifirst = 0;
    int isecond = 0;
    if(first!=null&&first.trim().length()>0) {
        ifirst = Integer.parseInt(first);
    }
    if(second!=null&&second.trim().length()>0) {
        isecond = Integer.parseInt(second);
    }
    %>  
    <script>
    function firstOnChange() {
        //将第一级的id传进去, 再刷新页面
        var first = document.getElementById("first");
        window.location = "list.jsp?first="+first.value;
    }
    function secondOnChange() {
    //在这里, 当选择第二级和第三级选项是, 会影响第一级的选择, 
    //这时候要把两个参数都传一下, 使页面重新刷新 
        var first = document.getElementById("first");
        var second = document.getElementById("second");
        window.location = "list.jsp?first="+first.value+"&second="+second.value;
    }
    </script>
    </head>
    <body>
    一级地区
    <select id="first" onchange="firstOnChange()">
    <option value="0">未选择</option>
    <%
    MemberDal md = new MemberDal();
    ArrayList<Memberclass> al = md.getList(0);
    if(al!=null) {
        for(Memberclass m:al) {
    //运用三元表达式在页面刷新之后还能选中之前选中的选项
            out.print("<option value='"+m.getId()+"' "+(m.getId()==ifirst?"selected":"")+">"+m.getName()+"</option>");
        }
    }
    %>
    </select>
    
    
    二级地区
    <select id="second" onchange="secondOnChange()">
    <option value="0">未选择</option>
    <%
    ArrayList<Memberclass> al2 = null;
    if(ifirst!=0) {
        al2 = md.getList(ifirst);
        if(al2!=null) {
            for(Memberclass m1:al2) {
                out.print("<option value='"+m1.getId()+"' "+(m1.getId()==isecond?"selected":"")+">"+m1.getName()+"</option>");
            }
        }
    }
    %>
    </select>
    
    
    三级地区
    <select id="third">
    <option value="0">未选择</option>
    <%
    ArrayList<Memberclass> al3 = null;
    if(isecond!=0) {
        al3 = md.getList(isecond);
        //必须判断是否为空, 否则会出现空指令异常
        if(al3!=null) {
            for(Memberclass m2:al3) {
                out.print("<option value='"+m2.getId()+"'>"+m2.getName()+"</option>");
            }
        }
    }
    %>
    </select>
    </body>
    </html>
    View Code

    不带刷新的三级联动(关于onchange事件的调用函数有N种, 包括自己定义一种格式, 手工接收返回的数据, 包括使用jQuery+Ajax写onchange的函数, 包括使用json来接收和编译取到的数据)

    使用jQuery的话就不需要Servlet来控制了, 可以直接返回数据

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ page import="java.util.*" %>
    <%@ page import="com.hanqi.Dao.*" %>
    <%@ page import="com.hanqi.*" %>    
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>无刷新三级联动</title>
    <script src="js/jquery-1.11.3.min.js"></script>
    <script>
    var hx = false;
    //最原始的方法, 直接用ajax, 自己手工接收参数, 在sevrlet控制层自己构造一个格式
    function firstOnChange() {
        if(window.XMLHttpRequest) {
            hx = new XMLHttpRequest;
        }
        else if(window.ActiveXObject) {
            hx = new ActiveXObject(Msxml2.XMLHTTP);
        }
        else {
            hx = new ActiveXObject(Microsoft.XMLHTTP);
        }
        var first = document.getElementById("first");
        hx.open("GET","AjaxGetList1?parentid="+first.value,true);
    //这里使用的GET方法, 注意与POST方法的区别
    //设置回调函数
        hx.onreadystatechange = firstCh;
        hx.send(null);
        function firstCh() {
            if(hx.readyState==4) {
                if(hx.status==200) {
    //接收返回的内容
                    var ts = hx.responseText;
    分割字符串
                    var ar = ts.split(";");
                    var doc = document.getElementById("second");   
    //清除掉之前的下拉选项
                    doc.options.length=1;
                    for(var i = 0; i<ar.length-1; i++) {
                        var rr = ar[i].split(":");
                        doc.options.add(new Option(rr[1],rr[0]));
                    }
                }
            }
        }
    //清除掉第三级下拉菜单
        var doc = document.getElementById("third");
        doc.options.length=1;
        //secondOnChangeJq();
    }
    //同上
    function secondOnChange() {
        if(window.XMLHttpRequest) {
            hx = new XMLHttpRequest;
        }
        else if(window.ActiveXObject) {
            hx = new ActiveXObject(Msxml2.XMLHTTP);
        }
        else {
            hx = new ActiveXObject(Microsoft.XMLHTTP);
        }
        var second = document.getElementById("second");
        hx.open("GET","AjaxGetList1?parentid="+second.value,true);
        hx.onreadystatechange = secCh;
        hx.send(null);
        function secCh() {
            if(hx.readyState==4) {
                if(hx.status==200) {
                    var ts = hx.responseText;
                    var ar = ts.split(";");
                    var doc = document.getElementById("third");
                    doc.options.length=1;
                    for(var i = 0; i<ar.length-1; i++) {
                        var rr = ar[i].split(":");
                        doc.options.add(new Option(rr[1],rr[0]));
                    }
                }
            }
        }
    }
    
    
    //使用jQuery运行ajax
    function secondOnChangeJq() {
        var url="AjaxGetList1?parentid="+$("#second").val();
        var cb = function(data,status) {   //直接返回数据和状态
            //alert("data:"+data+"
    Status:"+status);
            var ar = data.split(";");
            var doc = document.getElementById("third");
            doc.options.length=1;   //必须要等于1, 要保留第一个选项
            for(var i = 0; i<ar.length-1; i++) {
                var rr = ar[i].split(":");
    //设置下拉选项的value
                doc.options.add(new Option(rr[1],rr[0]));
            }
        }
    //直接调用
        $.get(url,cb);
    }
    
    
    //使用JSON来接收字符串
    function jsonChange() {
        var cb = function(data,status) {
                        alert(data);
                        //使用eval()方法解析json
                        var array = eval("("+data+")");
                        var doc = document.getElementById("second");
                        doc.options.length=1;
                        //json返回的是一个数组, 数组的每个单位是一个对象, 每个对象都是一个键值对, 可以在这里直接调用属性值
                        for(var i = 0; i<array.length; i++) {
                            doc.options.add(new Option(array[i].name,array[i].id));
                        }
                    }
        var url = "JsonGetList?parentid="+$("#first").val()+"&nocache="+new Date().getTime();   //添加一个时间消除缓存
        $.get(url,cb);
    }
    </script>
    </head>
    <body>
    一级地区
    <select id="first" onchange="firstOnChange()">
    <option value="0">未选择</option>
    <%
    MemberDal md = new MemberDal();
    ArrayList<Memberclass> al = md.getList(0);
    if(al!=null) {
        for(Memberclass m:al) {
            out.print("<option value='"+m.getId()+"'>"+m.getName()+"</option>");
        }
    }
    %>
    </select>
    
    二级地区
    <select id="second" onchange="secondOnChangeJq()">
    <option value="-1">未选择</option>
    </select>
    
    三级地区
    <select id="third">
    <option value="-1">未选择</option>
    </select>
    </body>
    </html>
    View Code

    2, Servlet控制层

        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //啥事也别干, 先设置字符集
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html; charset=utf-8");
    //第二步, 接收参数
            String parentid = request.getParameter("parentid");
            int pid = 0;
            if(parentid!=null&&parentid.trim().length()>0) {
                pid = Integer.parseInt(parentid);
            }
            else {
                pid = -1;
            }
            String str = "";
            try {
                MemberDal m = new MemberDal();
                ArrayList<Memberclass> array =  m.getList(pid);
                if(array!=null) {
                    for(Memberclass q:array) {
                        str = str+q.getId()+":"+q.getName()+";";   //这里就是自己构造的一个格式
                    }
                }
            } 
            catch (Exception e) {
                //response.getWriter().append(e.getMessage());
            }
    //返回只能返回一个文本或者字符串, 
            response.getWriter().append(str);
        }
    View Code
  • 相关阅读:
    git相关整理
    cookie、sessionStorage和localStorage
    AJAX学习笔记
    json web token
    加密算法
    单点登陆
    给手机网络添加手动代理
    oracle数据库索引
    类加载器
    类加载过程
  • 原文地址:https://www.cnblogs.com/wgbs25673578/p/5067907.html
Copyright © 2011-2022 走看看