精华内容
下载资源
问答
  • electron-vue 实践 2 —— excel 合并

    千次阅读 2018-06-28 10:21:33
    之前使用 vue-cli 和 electron-vue 创建了工程,接下来就开始实现具体的逻辑,我们的目标很简单,就是将一张或多张表中的所有 sheet 页内容都垂直或水平合并在一个 sheet 中,并生成一张新的表。   UI 布局文件 ...

    前言

    之前使用 vue-clielectron-vue 创建了工程,接下来就开始实现具体的逻辑,我们的目标很简单,就是将一张或多张表中的所有 sheet 页内容都垂直或水平合并在一个 sheet 中,并生成一张新的表。

     

    UI 布局文件

    新加一个 .vue 后缀的文件,vue 的 UI 文件格式大致如下,不了解的可以查看 官方入门文档

    <template>
        <!--html布局内容-->
    </template>
    <script src="xxx.js"></script>
    <style>
        /* 布局组件的个性化样式设置 */
    </style>template>
        <!--html布局内容-->
    </template>
    <script src="xxx.js"></script>
    <style>
        /* 布局组件的个性化样式设置 */
    </style>

    我们最终应用的 UI 布局文件内容如下:

    
    <template>
        <div class="excel-merge">
            <div class="upload">
            <div class="upload_warp">
                <div class="upload_warp_left" @click="fileClick">
                <img src="static/imgs/ms-excel.png">
                
                </div>
                <div class="upload_warp_right" @drop="drop($event)" @dragenter="dragenter($event)" @dragover="dragover($event)">
                <p>或将文件夹拖到此处</p>
                </div>
            </div>
            <div class="upload_warp_text">
                <p>选中 【{{xlsList.length}}】 张表</p><!-- ,共 {{bytesToSize(this.size)}} -->
            </div>
            <input @change="fileChange($event)" type="file" accept=".xls, .xlsx" id="upload_file" multiple style="display: none"/>
            <div class="upload_warp_img" v-show="xlsList.length!=0">
                <div class="upload_warp_img_div" v-for="(item,index) of xlsList">
                    <div class="upload_warp_img_div_top">
                        <div class="upload_warp_img_div_text">
                        {{item.file.name}}
                        </div>
                        <img src="static/imgs/close.png" class="upload_warp_img_div_del" @click="fileDel(index)">
                    </div>
                    <img :src="item.file.src">
                </div>
            </div>
            </div>
            <transition>
              <button id="VMBtn" @click="verticalMergeClick()">垂直合并</button>
            </transition>
            <button id="HMBtn" @click="horizontalMergeClick()">水平合并</button>
            <button id="CABtn" @click="cleanAllClick()">清空重来</button>
            
            <!--结果输出栏-->
            <div class="result" v-show="resLogs.length!=0">
                <p>结果:</p>   
                <div class="" v-for="(item,index) of resLogs">
                    <div class="">
                        <div>
                            <!-- <img src="../assets/arrow.png"> -->
                            => {{item.content}}
                        </div>
                    </div>
                </div>
                <button id="OpenResult" @click="openExportDir()" v-show=OpenResVisible >打开输出目录</button>
            </div>
        </div>
    </template>
    <!-- 业务脚本 -->
    <script src="../logic/excel_merge.js"></script>
    <style scoped>
      ...
    </style><template>
        <div class="excel-merge">
            <div class="upload">
            <div class="upload_warp">
                <div class="upload_warp_left" @click="fileClick">
                <img src="static/imgs/ms-excel.png">
                
                </div>
                <div class="upload_warp_right" @drop="drop($event)" @dragenter="dragenter($event)" @dragover="dragover($event)">
                <p>或将文件夹拖到此处</p>
                </div>
            </div>
            <div class="upload_warp_text">
                <p>选中 【{{xlsList.length}}】 张表</p><!-- ,共 {{bytesToSize(this.size)}} -->
            </div>
            <input @change="fileChange($event)" type="file" accept=".xls, .xlsx" id="upload_file" multiple style="display: none"/>
            <div class="upload_warp_img" v-show="xlsList.length!=0">
                <div class="upload_warp_img_div" v-for="(item,index) of xlsList">
                    <div class="upload_warp_img_div_top">
                        <div class="upload_warp_img_div_text">
                        {{item.file.name}}
                        </div>
                        <img src="static/imgs/close.png" class="upload_warp_img_div_del" @click="fileDel(index)">
                    </div>
                    <img :src="item.file.src">
                </div>
            </div>
            </div>
            <transition>
              <button id="VMBtn" @click="verticalMergeClick()">垂直合并</button>
            </transition>
            <button id="HMBtn" @click="horizontalMergeClick()">水平合并</button>
            <button id="CABtn" @click="cleanAllClick()">清空重来</button>
            
            <!--结果输出栏-->
            <div class="result" v-show="resLogs.length!=0">
                <p>结果:</p>   
                <div class="" v-for="(item,index) of resLogs">
                    <div class="">
                        <div>
                            <!-- <img src="../assets/arrow.png"> -->
                            => {{item.content}}
                        </div>
                    </div>
                </div>
                <button id="OpenResult" @click="openExportDir()" v-show=OpenResVisible >打开输出目录</button>
            </div>
        </div>
    </template>
    <!-- 业务脚本 -->
    <script src="../logic/excel_merge.js"></script>
    <style scoped>
      ...
    </style>

    引入表格之前的初始界面:

    引入表格之后的表格列表展示界面:

    合并成功界面如下:

     

    获取文件

    可以通过点击按钮从文件管理器中选择文件列表,也可以通过选中多个文件然后拖动到应用的指定区域来获取文件列表。

    • 通过文本编辑器输入的方式,限制文件后缀名为 .xls.xlsx

      
      <input @change="fileChange($event)" type="file" accept=".xls, .xlsx" id="upload_file" multiple style="display: none"/><input @change="fileChange($event)" type="file" accept=".xls, .xlsx" id="upload_file" multiple style="display: none"/>

      类型 multiple 表示可以输入多个文件。在脚本中重写 fileChange(el) 方法,在输入文件时会自动调用此接口:

      
      // 添加
      fileChange(el) {
          let filePath = el.target.files[0].path;
          let xlsxData = XLSX.readFile(filePath);
          // 空表过滤
          if (xlsxData.Sheets.length == 0){
              gLog("------------------ 引入一个空表");
              return;
          } 
          // 传入文件列表处理
          this.fileList(el.target);
          // 释放内存
          el.target.value = ''
      },// 添加
      fileChange(el) {
          let filePath = el.target.files[0].path;
          let xlsxData = XLSX.readFile(filePath);
          // 空表过滤
          if (xlsxData.Sheets.length == 0){
              gLog("------------------ 引入一个空表");
              return;
          } 
          // 传入文件列表处理
          this.fileList(el.target);
          // 释放内存
          el.target.value = ''
      },

    • 通过拖拽方式输入文件列表:

      
      <div class="upload_warp_right" @drop="drop($event)" @dragenter="dragenter($event)" @dragover="dragover($event)">
        <p>或将文件夹拖到此处</p>
      </div><div class="upload_warp_right" @drop="drop($event)" @dragenter="dragenter($event)" @dragover="dragover($event)">
        <p>或将文件夹拖到此处</p>
      </div>

      然后在脚本中实现 dropdragenterdragover 三个方法:

      需要引入文件处理模块 fs

      
      const fs = require("fs");const fs = require("fs");
      // 拖拽相关
      dragenter(el) {
          el.stopPropagation();
          el.preventDefault();
      },
      dragover(el) {
          el.stopPropagation();
          el.preventDefault();
      },
      drop(el) {
          el.stopPropagation();
          el.preventDefault();
          this.fileList(el.dataTransfer);
      }
      // 拖拽输入的文件列表处理
      fileList(fileList) {
          let files = fileList.files;
          for (let i = 0; i < files.length; i++) {
              console.log("--------------------- files[" + i + "].name = "+ files[i].name);
              //判断是否为文件夹
              if (!fs.lstatSync(files[i].path).isDirectory()) {
                  this.fileAdd(files[i]);
              } else {
                  //文件夹处理
                  this.folders(fileList.items[i]);
              }
          }
      },
      //文件夹处理
      folders(files) {
          let _this = this;
          //判断是否为原生file
          if (files.kind) {
              files = files.webkitGetAsEntry();
          }
          files.createReader().readEntries(function (file) {
              for (let i = 0; i < file.length; i++) {
                  if (file[i].isFile) {
                      _this.foldersAdd(file[i]);
                  } else {
                      _this.folders(file[i]);
                  }
              }
          })
      },
      foldersAdd(entry) {
          let _this = this;
          entry.file(function (file) {
              _this.fileAdd(file)
          })
      },
      dragenter(el) {
          el.stopPropagation();
          el.preventDefault();
      },
      dragover(el) {
          el.stopPropagation();
          el.preventDefault();
      },
      drop(el) {
          el.stopPropagation();
          el.preventDefault();
          this.fileList(el.dataTransfer);
      }
      // 拖拽输入的文件列表处理
      fileList(fileList) {
          let files = fileList.files;
          for (let i = 0; i < files.length; i++) {
              console.log("--------------------- files[" + i + "].name = "+ files[i].name);
              //判断是否为文件夹
              if (!fs.lstatSync(files[i].path).isDirectory()) {
                  this.fileAdd(files[i]);
              } else {
                  //文件夹处理
                  this.folders(fileList.items[i]);
              }
          }
      },
      //文件夹处理
      folders(files) {
          let _this = this;
          //判断是否为原生file
          if (files.kind) {
              files = files.webkitGetAsEntry();
          }
          files.createReader().readEntries(function (file) {
              for (let i = 0; i < file.length; i++) {
                  if (file[i].isFile) {
                      _this.foldersAdd(file[i]);
                  } else {
                      _this.folders(file[i]);
                  }
              }
          })
      },
      foldersAdd(entry) {
          let _this = this;
          entry.file(function (file) {
              _this.fileAdd(file)
          })
      },

    获取到的文件信息存放在两个数组中:

    
    data() {
        return {
            xlsList: [],    // 表格名称表
            size: 0,        // 表格文件总大小
            xlsPathList: [],// 表格路径表
        }
    },data() {
        return {
            xlsList: [],    // 表格名称表
            size: 0,        // 表格文件总大小
            xlsPathList: [],// 表格路径表
        }
    },
    • xlsList 表格名称列表,仅用于 UI 上展示使用;

    • xlsPathList 表格完整路径列表,用于后续获取表格数据使用。

    
    // 文件添加到管理列表
    fileAdd(file) {
        //总大小
        this.size = this.size + file.size;
        //判断是否为表格
        if (file.name.indexOf('.xlsx') == -1 && file.name.indexOf('.xls') == -1) {
            console.log("--------------- 添加的不是表格文件");
        } else {
            let xlsxPath = file.path;
            console.log("--------------- 添加表格文件:" + xlsxPath);
            let _this=this;
            file.width=50;
            file.height=50;
            // 表格图标
            file.src = "./src/renderer/assets/xls.png";
            _this.xlsList.push({
                file
            });
            _this.xlsPathList.push({
                xlsxPath
            });
        }
    },// 文件添加到管理列表
    fileAdd(file) {
        //总大小
        this.size = this.size + file.size;
        //判断是否为表格
        if (file.name.indexOf('.xlsx') == -1 && file.name.indexOf('.xls') == -1) {
            console.log("--------------- 添加的不是表格文件");
        } else {
            let xlsxPath = file.path;
            console.log("--------------- 添加表格文件:" + xlsxPath);
            let _this=this;
            file.width=50;
            file.height=50;
            // 表格图标
            file.src = "./src/renderer/assets/xls.png";
            _this.xlsList.push({
                file
            });
            _this.xlsPathList.push({
                xlsxPath
            });
        }
    },

    也可以在列表中移除某个表格文件,调用 fileDel 方法:

    // 删除表格
    fileDel(index) {
        this.size = this.size - this.xlsList[index].file.size;//总大小
        this.xlsList.splice(index, 1);
        this.xlsPathList.splice(index, 1);
        if(this.size == 0){
            this.cleanAllClick();
        }
    },
    fileDel(index) {
        this.size = this.size - this.xlsList[index].file.size;//总大小
        this.xlsList.splice(index, 1);
        this.xlsPathList.splice(index, 1);
        if(this.size == 0){
            this.cleanAllClick();
        }
    },

    一键清空重来功能其实就是清除数据结构中的数据而已:

    // 清空重来
    cleanAllClick(){
        if(lockBtn){
            return;
        }
        this.OpenResVisible = false;
        this.addResultLog(null, true);
        if(this.xlsPathList.length > 0){
            for(var i=0; i< this.xlsList.length; i ++){
              this.xlsList.splice(i, 1);
            }
            this.xlsList.length = 0;
            this.xlsPathList.length = 0;
        }
    },
    cleanAllClick(){
        if(lockBtn){
            return;
        }
        this.OpenResVisible = false;
        this.addResultLog(null, true);
        if(this.xlsPathList.length > 0){
            for(var i=0; i< this.xlsList.length; i ++){
              this.xlsList.splice(i, 1);
            }
            this.xlsList.length = 0;
            this.xlsPathList.length = 0;
        }
    },

     

    Excel 读写库

    目前支持 Excel 读写的 Node.js 模块大致有:

    • js-xlsx: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格 XLSX / XLSM / XLSB / XLS / CSV,解析采用纯 js 实现,写入需要依赖 Node.js 或者 FileSaver.js 实现生成写入Excel,可以生成子表 Excel ,功能强大,但上手难度稍大。不提供基础设置 Excel 表格 API 例单元格宽度,文档有些乱,不适合快速上手;

    • node-xlsx: 基于 Node.js 解析 Excel 文件数据及生成 excel 文件,仅支持 xlsx 格式文件;

    • excel-parser: 基于 Node.js 解析 Excel 文件数据,支持 xlsxlsx 格式文件,需要依赖 python ,太重不太实用;

    • excel-export : 基于 Node.js 将数据生成导出 Excel 文件,生成文件格式为 xlsx ,可以设置单元格宽度, API 容易上手,无法生成 worksheet 字表,比较单一,基本功能可以基本满足;

    • node-xlrd: 基于 Node.js 从 Excel 文件中提取数据,仅支持 xls 格式文件,不支持 xlsx,有点过时。

    结合我们的需求,最终选择了 js-xlsx 作为我们项目的核心工具库,提交了解此库关于表格数据的封装对象:

    • workbook 对象,指的是整份 Excel 文档。我们在使用 js-xlsx 读取 Excel 文档之后就会获得 workbook 对象。

    • worksheet 对象,指的是 Excel 文档中的表。我们知道一份 Excel 文档中可以包含很多张表,而每张表对应的就是 worksheet 对象。

    • cell 对象,指的就是 worksheet 中的单元格,一个单元格就是一个 cell 对象。

    格式如下:

    
    // workbook
    {
        SheetNames: ['sheet1', 'sheet2'],
        Sheets: {
            // worksheet
            'sheet1': {
                // cell
                'A1': { ... },
                // cell
                'A2': { ... },
                ...
            },
            // worksheet
            'sheet2': {
                // cell
                'A1': { ... },
                // cell
                'A2': { ... },
                ...
            }
        }
    }// workbook
    {
        SheetNames: ['sheet1', 'sheet2'],
        Sheets: {
            // worksheet
            'sheet1': {
                // cell
                'A1': { ... },
                // cell
                'A2': { ... },
                ...
            },
            // worksheet
            'sheet2': {
                // cell
                'A1': { ... },
                // cell
                'A2': { ... },
                ...
            }
        }
    }

    SheetNames 是字符串数组,而 Sheets 是一个 Map 表。

     

    接下来,先了解一下库的引入步骤:

    • npm 安装:

      
      $ npm install --save xlsx$ npm install --save xlsx
    • js 引用:

      
      const XLSX = require("xlsx");const XLSX = require("xlsx");

    表格合并

    合并分为水平合并和垂直合并:

            // 垂直合并
            verticalMergeClick(){
                if(lockBtn){
                    return;
                }
                if(this.size > 0){
                    // 发给主线程,分发给 background 渲染线程去完成融合
                    // ipcRenderer.send('Start-Ver-Merge', this.xlsPathList, exportPathRoot);
                    
                    this.addResultLog('开始垂直合并表格 ...', true);
                    lockBtn = true;
                    this.OpenResVisible = false;
                    // 延迟 0.5s
                    setTimeout(()=>{
                        exportPathRoot = exportPathRoot.replace('/', '\\');
                        resultStr = verticalMerge(this.xlsPathList, exportPathRoot);
                        this.addResultLog('【垂直】合并表格结束,表格输出路径:'+exportPathRoot);
                        // 显示打开输出目录的按钮
                        this.OpenResVisible = true;
                        lockBtn = false;
                    }, 500);
                    
                }else{
                    this.addResultLog('未选择用于【垂直】合并的表格!', true);
                    gLog("------ 传入的表是空的")
                }
            },
    ​
            // 水平合并
            horizontalMergeClick(){
                if(lockBtn){
                    return;
                }
                if(this.size > 0){
                    this.addResultLog('开始水平合并表格 ...', true);
                    lockBtn = true;
                    this.OpenResVisible = false;
                    // 延迟 0.5s
                    setTimeout(()=>{
                        exportPathRoot = exportPathRoot.replace('/', '\\');
                        horizontalMerge(this.xlsPathList, exportPathRoot);
                        this.addResultLog('【水平】合并表格结束,表格输出路径:'+exportPathRoot);
                        // 显示打开输出目录的按钮
                        this.OpenResVisible = true;
                        lockBtn = false;
                    }, 500);
                }else{
                    this.addResultLog('未选择用于【水平】合并的表格!', true);
                    gLog("------ 传入的表是空的")
                }
            },    // 垂直合并
            verticalMergeClick(){
                if(lockBtn){
                    return;
                }
                if(this.size > 0){
                    // 发给主线程,分发给 background 渲染线程去完成融合
                    // ipcRenderer.send('Start-Ver-Merge', this.xlsPathList, exportPathRoot);
                    
                    this.addResultLog('开始垂直合并表格 ...', true);
                    lockBtn = true;
                    this.OpenResVisible = false;
                    // 延迟 0.5s
                    setTimeout(()=>{
                        exportPathRoot = exportPathRoot.replace('/', '\\');
                        resultStr = verticalMerge(this.xlsPathList, exportPathRoot);
                        this.addResultLog('【垂直】合并表格结束,表格输出路径:'+exportPathRoot);
                        // 显示打开输出目录的按钮
                        this.OpenResVisible = true;
                        lockBtn = false;
                    }, 500);
                    
                }else{
                    this.addResultLog('未选择用于【垂直】合并的表格!', true);
                    gLog("------ 传入的表是空的")
                }
            },
    ​
            // 水平合并
            horizontalMergeClick(){
                if(lockBtn){
                    return;
                }
                if(this.size > 0){
                    this.addResultLog('开始水平合并表格 ...', true);
                    lockBtn = true;
                    this.OpenResVisible = false;
                    // 延迟 0.5s
                    setTimeout(()=>{
                        exportPathRoot = exportPathRoot.replace('/', '\\');
                        horizontalMerge(this.xlsPathList, exportPathRoot);
                        this.addResultLog('【水平】合并表格结束,表格输出路径:'+exportPathRoot);
                        // 显示打开输出目录的按钮
                        this.OpenResVisible = true;
                        lockBtn = false;
                    }, 500);
                }else{
                    this.addResultLog('未选择用于【水平】合并的表格!', true);
                    gLog("------ 传入的表是空的")
                }
            },

    在前面的文件获取中,我们已经得到了表格文件的路径列表 this.xlsPathList ,接下来我们就通过路径来获取表格的数据:

    let _workbook = null;
    let _xlsx_path = "";
    let jsons = null;
    xlsPathList.forEach(element => {
        _xlsx_path = element.xlsxPath;
        console.log("---------------- 开始读取表格:" + _xlsx_path);
        _workbook = XLSX.readFile(_xlsx_path);
    ​
        let _worksheet = null;
        _workbook.SheetNames.forEach(sheetName => {
            console.log("----------------- sheetName:" + sheetName);
            _worksheet = _workbook.Sheets[sheetName];
            let json = XLSX.utils.sheet_to_json(_worksheet)
            // console.log(json.length);
            if(jsons == null){
                jsons = json;
            }else{
                // 合并数据
                jsons = Array.prototype.concat.apply(jsons, json);
                // 优化内存
                json.length = 0;
                json = null;
            }
            // console.log("【 total count 】: "+jsons.length);
        });
    }); _workbook = null;
    let _xlsx_path = "";
    let jsons = null;
    xlsPathList.forEach(element => {
        _xlsx_path = element.xlsxPath;
        console.log("---------------- 开始读取表格:" + _xlsx_path);
        _workbook = XLSX.readFile(_xlsx_path);
    ​
        let _worksheet = null;
        _workbook.SheetNames.forEach(sheetName => {
            console.log("----------------- sheetName:" + sheetName);
            _worksheet = _workbook.Sheets[sheetName];
            let json = XLSX.utils.sheet_to_json(_worksheet)
            // console.log(json.length);
            if(jsons == null){
                jsons = json;
            }else{
                // 合并数据
                jsons = Array.prototype.concat.apply(jsons, json);
                // 优化内存
                json.length = 0;
                json = null;
            }
            // console.log("【 total count 】: "+jsons.length);
        });
    });
    • SheetNames 是一个字符串数组,是一个 WorkBook 中所有 sheet 名称的数组;

    • Sheets 是 sheet 名称与数据映射表,格式为 [{sheetName: WorkSheet},{sheetName: WorkSheet},...]

    先通过 XLSX.utils.sheet_to_json 将数据转为 json 格式,然后用 Array.prototype.concat.apply 来合并数组,最终所有表格的 sheet 数据都存放在 jsons 中,接下来我们就将此数据写入到一张新建的 Excel 表中:

    
    // 创建新的表格
    function createResultXlsx(sheetData, xlsxPath){
        var workbook = {
            SheetNames: ['total'],
            Sheets: {
                'total': sheetData
            }
        };
        var index = xlsxPath.lastIndexOf('\\');
        var pathRoot = xlsxPath.substring(0, index);
        let result_str = '';
        // gLog('-----------输出目录:'+pathRoot);
        fs.exists(pathRoot, function(exists) {  
            if(!exists){
                gLog('----------- 创建目录');
                fs.mkdir(pathRoot)
                result_str = WriteXLSXFile(workbook, xlsxPath);
            }else{
                
                result_str = WriteXLSXFile(workbook, xlsxPath);
            }
        });
        gLog('-----------输出result_str:'+result_str);
        return result_str
    }
    // 导出表格
    function WriteXLSXFile(workbook, xlsxPath){
        try {
            XLSX.writeFile(workbook, xlsxPath, {cellStyles: true});
            gLog("表格生成成功,路径:"+xlsxPath);
        } catch (error) {
            if(String(error).includes('Error: EBUSY: resource busy or locked, open')){
                gLog('先关闭文件:' + xlsxPath);
                showDialog('合并失败!需要先关闭 Excel 中打开的文件:' + xlsxPath);
            }else{
                gLog(error)
                showDialog('合并失败!失败原因:'+error);
            }
        }
    }// 创建新的表格
    function createResultXlsx(sheetData, xlsxPath){
        var workbook = {
            SheetNames: ['total'],
            Sheets: {
                'total': sheetData
            }
        };
        var index = xlsxPath.lastIndexOf('\\');
        var pathRoot = xlsxPath.substring(0, index);
        let result_str = '';
        // gLog('-----------输出目录:'+pathRoot);
        fs.exists(pathRoot, function(exists) {  
            if(!exists){
                gLog('----------- 创建目录');
                fs.mkdir(pathRoot)
                result_str = WriteXLSXFile(workbook, xlsxPath);
            }else{
                
                result_str = WriteXLSXFile(workbook, xlsxPath);
            }
        });
        gLog('-----------输出result_str:'+result_str);
        return result_str
    }
    // 导出表格
    function WriteXLSXFile(workbook, xlsxPath){
        try {
            XLSX.writeFile(workbook, xlsxPath, {cellStyles: true});
            gLog("表格生成成功,路径:"+xlsxPath);
        } catch (error) {
            if(String(error).includes('Error: EBUSY: resource busy or locked, open')){
                gLog('先关闭文件:' + xlsxPath);
                showDialog('合并失败!需要先关闭 Excel 中打开的文件:' + xlsxPath);
            }else{
                gLog(error)
                showDialog('合并失败!失败原因:'+error);
            }
        }
    }

    通过 XLSX.utils.json_to_sheet 将上面合并得到的 json 数据转回 WorkSheet 数据类型,然后构建一个 WorkBook 对象,通过 XLSX.writeFile 创建表格。

     

    通用弹窗

    使用 eletron 提供的 dialog.showMessageBox 接口来打开系统提示框:

    
    // 打开通用提示
    function showDialog(content){
        remote.dialog.showMessageBox({
            type: 'info',
            // buttons: ['确定'],
            defaultId: 0,
            title: '提示',
            message: content
        })
    }// 打开通用提示
    function showDialog(content){
        remote.dialog.showMessageBox({
            type: 'info',
            // buttons: ['确定'],
            defaultId: 0,
            title: '提示',
            message: content
        })
    }

    使用时直接调用:

    
    showDialog('合并失败!失败原因:'+error);showDialog('合并失败!失败原因:'+error);

     

    打开资源目录

    合并的表格这里直接生成到桌面的 LMExcel 目录下:

    // 文件输出目录
    let exportPathRoot = remote.app.getPath('desktop')+"/LMExcel\\";
    // 打开目录
    showExport(exportPathRoot);
    let exportPathRoot = remote.app.getPath('desktop')+"/LMExcel\\";
    // 打开目录
    showExport(exportPathRoot);

    然后通过 electron.shell 工具实现打开资源目录:

    // 打开输出文件夹
    function showExport(path){
        // 判断目录是否存在
        fs.exists(path, function(exists) {  
            if(!exists){
                gLog('----------- 目录不存在');
            }
        });
        shell.openExternal(path);
    }
    function showExport(path){
        // 判断目录是否存在
        fs.exists(path, function(exists) {  
            if(!exists){
                gLog('----------- 目录不存在');
            }
        });
        shell.openExternal(path);
    }

     

    性能优化

    参考 XCel 项目总结:Electron 与 Vue 的性能优化 对当前项目做一些优化:

    1. 读表速度慢

    读取表格数据速度慢,要等待几秒才响应,测试了一下才发现 XLSX.utils.sheet_to_jsonXLSX.utils.json_to_sheet 两个接口都很耗时(每个 sheet 转化需要消耗长达 6s 的时间),因为直接使用 WorkSheet 数据来完成数据拼接:

    // 24 个字母,列号(A-Z,AA-AZ,BA-BZ,...ZA-ZZ,AAA-AAZ,ABA-ABZ,..AZA-AZZ,BAA-BAZ,..)以此规律类推
    let Signals = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    ​
    function verticalMerge(xlsPathList, exportPath){
        let _workbook = null;
        let _xlsx_path = "";
        // gLog(xlsPathList);
        let jsons = null;
        let sheetsData = null;
    ​
        xlsPathList.forEach(element => {
            _xlsx_path = element.xlsxPath;
            gLog("---------------- 开始读取表格:" + _xlsx_path + "," +  new Date().getTime());
            _workbook = XLSX.readFile(_xlsx_path);
    ​
            let _worksheet = null;
            // 最后一行行号
            let _lastRowNum = 0;
            // 最后一列列号
            let _lastColNum = 0;
            _workbook.SheetNames.forEach(sheetName => {
                gLog("----------------- sheetName:" + sheetName);
                _worksheet = _workbook.Sheets[sheetName];
                // 非 json 格式
                gLog( '范围:'+_worksheet['!ref']);
    ​
                let ref = _worksheet['!ref'];
                let cellKeys = ref.split(':');
                let startCell = cellKeys[0];
                let endCell = cellKeys[1];
                gLog( '起点:'+startCell+', 终点:'+endCell);
    ​
                //分离出列号和行号(正则表达式)
                let s_col = startCell.match(/\D+/);
                let s_low = startCell.substring(String(s_col).length, startCell.length);
                // gLog( '行号:'+s_col+', 列号:'+s_low);
                let e_col = endCell.match(/\D+/);
                let e_low = endCell.substring(String(e_col).length, endCell.length);
                // gLog( '行号:'+e_low+', 列号:'+e_col);
    ​
                if(sheetsData == null){
                    sheetsData = _worksheet;
                    // 记录第一行(用于后续对应匹配),并记录最好一行的行号
                    _lastRowNum = Number(e_low);
                    _lastColNum = getColNumByColName(e_col);
                }else{
                    let e_col_num = getColNumByColName(e_col);
                    let col_name = "";
                    let cell_name = "";
                    let cell_data = null;
                    let newRowNum = 0;
                    // 过滤空白行使用
                    let noEmptyRowNum = 0;
                    // 写入写表中的单元格名称
                    let newCell_name = "";
                    let e_low_num = Number(e_low);
                    for(let i=1; i <= e_col_num; i++){
                        col_name = getColNameByColNum(i);
                        for(let j=1; j <= e_low_num; j++){
                            cell_name = col_name + j;
                            // 有数据则拷贝到新表中
                            cell_data = _worksheet[cell_name]
                            if(cell_data){
                                newRowNum = j+_lastRowNum;
                                newCell_name = col_name + newRowNum;
                                sheetsData[newCell_name] = cell_data;
                                gLog('-------------- 单元格名称:' + newCell_name);
                            }
                        }
                    }
                    // 刷新行列号
                    if(e_col_num > _lastColNum){
                        _lastColNum = e_col_num;
                    }
                    _lastRowNum += e_low_num;
    ​
                    // 刷新新表的让范围值
                    let newRef = 'A1:'+getColNameByColNum(_lastColNum)+_lastRowNum;
                    sheetsData['!ref'] = newRef;
                    // 优化内存
                    _worksheet.length = 0;
                    _worksheet = null;
                }
                // gLog(sheetsData)
                gLog("----------------- "+sheetName+" 数据读完了" + "," +  new Date().getTime());
            });
        });
        createResultXlsx(sheetsData, exportPath+getFinalExcelName(xlsPathList));
    }
    ​
    // 通过列序号获取列名称
    function getColNameByColNum(colNum){
        let colName = "";
        let temNum = colNum;
        let remain = 0;
        while(temNum >= 26){
            remain = temNum%26;
            temNum = (temNum - remain)/26;
            if(remain>0){
                colName = Signals[remain-1] + colName;
            }else if(remain == 0){
                temNum -= 1;
                colName = Signals[25] + colName;
            }
        }
        if(temNum > 0){
            colName = Signals[temNum-1] + colName;
        }
        return colName;
    }
    // 通过列符获取列序号
    function getColNumByColName(colName){
        let magnif = 1;
        let _char = "";
        let index = 0;
        let resNum = 0;
        let len = String(colName).length;
        for(let i=0; i< len; i++){
            _char = String(colName).substring(len-1-i, len-i);
            index = getSignalIndex(_char);
            if(index > 0){
                resNum += index * magnif;
                magnif *= 26;
            }
        }
        return resNum;
    }
    // A-Z序号
    function getSignalIndex(signal){
        for(let i=0;i<Signals.length;i++){
            if(Signals[i] == signal){
                return i+1;
            }
        }
        return 0;
    }
    let Signals = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    ​
    function verticalMerge(xlsPathList, exportPath){
        let _workbook = null;
        let _xlsx_path = "";
        // gLog(xlsPathList);
        let jsons = null;
        let sheetsData = null;
    ​
        xlsPathList.forEach(element => {
            _xlsx_path = element.xlsxPath;
            gLog("---------------- 开始读取表格:" + _xlsx_path + "," +  new Date().getTime());
            _workbook = XLSX.readFile(_xlsx_path);
    ​
            let _worksheet = null;
            // 最后一行行号
            let _lastRowNum = 0;
            // 最后一列列号
            let _lastColNum = 0;
            _workbook.SheetNames.forEach(sheetName => {
                gLog("----------------- sheetName:" + sheetName);
                _worksheet = _workbook.Sheets[sheetName];
                // 非 json 格式
                gLog( '范围:'+_worksheet['!ref']);
    ​
                let ref = _worksheet['!ref'];
                let cellKeys = ref.split(':');
                let startCell = cellKeys[0];
                let endCell = cellKeys[1];
                gLog( '起点:'+startCell+', 终点:'+endCell);
    ​
                //分离出列号和行号(正则表达式)
                let s_col = startCell.match(/\D+/);
                let s_low = startCell.substring(String(s_col).length, startCell.length);
                // gLog( '行号:'+s_col+', 列号:'+s_low);
                let e_col = endCell.match(/\D+/);
                let e_low = endCell.substring(String(e_col).length, endCell.length);
                // gLog( '行号:'+e_low+', 列号:'+e_col);
    ​
                if(sheetsData == null){
                    sheetsData = _worksheet;
                    // 记录第一行(用于后续对应匹配),并记录最好一行的行号
                    _lastRowNum = Number(e_low);
                    _lastColNum = getColNumByColName(e_col);
                }else{
                    let e_col_num = getColNumByColName(e_col);
                    let col_name = "";
                    let cell_name = "";
                    let cell_data = null;
                    let newRowNum = 0;
                    // 过滤空白行使用
                    let noEmptyRowNum = 0;
                    // 写入写表中的单元格名称
                    let newCell_name = "";
                    let e_low_num = Number(e_low);
                    for(let i=1; i <= e_col_num; i++){
                        col_name = getColNameByColNum(i);
                        for(let j=1; j <= e_low_num; j++){
                            cell_name = col_name + j;
                            // 有数据则拷贝到新表中
                            cell_data = _worksheet[cell_name]
                            if(cell_data){
                                newRowNum = j+_lastRowNum;
                                newCell_name = col_name + newRowNum;
                                sheetsData[newCell_name] = cell_data;
                                gLog('-------------- 单元格名称:' + newCell_name);
                            }
                        }
                    }
                    // 刷新行列号
                    if(e_col_num > _lastColNum){
                        _lastColNum = e_col_num;
                    }
                    _lastRowNum += e_low_num;
    ​
                    // 刷新新表的让范围值
                    let newRef = 'A1:'+getColNameByColNum(_lastColNum)+_lastRowNum;
                    sheetsData['!ref'] = newRef;
                    // 优化内存
                    _worksheet.length = 0;
                    _worksheet = null;
                }
                // gLog(sheetsData)
                gLog("----------------- "+sheetName+" 数据读完了" + "," +  new Date().getTime());
            });
        });
        createResultXlsx(sheetsData, exportPath+getFinalExcelName(xlsPathList));
    }
    ​
    // 通过列序号获取列名称
    function getColNameByColNum(colNum){
        let colName = "";
        let temNum = colNum;
        let remain = 0;
        while(temNum >= 26){
            remain = temNum%26;
            temNum = (temNum - remain)/26;
            if(remain>0){
                colName = Signals[remain-1] + colName;
            }else if(remain == 0){
                temNum -= 1;
                colName = Signals[25] + colName;
            }
        }
        if(temNum > 0){
            colName = Signals[temNum-1] + colName;
        }
        return colName;
    }
    // 通过列符获取列序号
    function getColNumByColName(colName){
        let magnif = 1;
        let _char = "";
        let index = 0;
        let resNum = 0;
        let len = String(colName).length;
        for(let i=0; i< len; i++){
            _char = String(colName).substring(len-1-i, len-i);
            index = getSignalIndex(_char);
            if(index > 0){
                resNum += index * magnif;
                magnif *= 26;
            }
        }
        return resNum;
    }
    // A-Z序号
    function getSignalIndex(signal){
        for(let i=0;i<Signals.length;i++){
            if(Signals[i] == signal){
                return i+1;
            }
        }
        return 0;
    }

    修改之后合并的耗时直接提升了好几倍。

    将数据转为 json 除了速度较慢之外,由于 json 数据需要以每列的第一行数据作为 key 以该列数据作为 value。所以,假如有同名的 key 还会导致第一行数据被修改(被加上 "_X" 格式的后缀)。最终使用遍历后续每个 sheet 中每个单元格数据的方式,逐个将数据写入新表中,效率上也是最高的,而且可以做各种自定义的优化,例如:剔除空白的行或者列,多张字表第一行 key 一致的存在同一列中。

     

    2. 界面 UI 响应延迟

    耗时操作会导致页面响应式内容无法实时更新,解决办法就是启动一个没有页面的后台子线程(另一个渲染线程),当前渲染线程将消息发给主线程,主线程再透传给后台子线程,由后台子线程完成任务再通过消息告知主线程,主线程再回传给当前显示的渲染线程。主线程担任消息传递媒介。目前, demo 还没写完这部分。

     

    3. 样式引入

    Sheet/js-xlsx 库并不支持样式的添加,参考 js-xlsx纯前端excle文件导出实践(vuedemo)xSirrioNx/js-xlsx设置基本样式输出excel文件 ,使用改版后的库 xSirrioNx/js-xlsx ,此库融合了 protobi/js-xlsx ,可以实现样式的引入,下面是实现步骤:

    • 替换库:

      使用 npm 移除已安装的原版 js-xlsx 库,替换成改版的版本:

      
      $ npm rm xlsx
      $ npm i --save git+https://git@github.com/xSirrioNx/js-xlsx$ npm rm xlsx
      $ npm i --save git+https://git@github.com/xSirrioNx/js-xlsx

       

    • 写入样式信息:

      在每个 cell 数据中,会有一个 s 字段用于存储样式信息,格式如下:

      
      excelCell.s = {
          fill: {
              patternType: "none", // none / solid
              fgColor: {rgb: "FF000000"},
              bgColor: {rgb: "FFFFFFFF"}
          },
          font: {
              name: 'Times New Roman',
              sz: 16,
              color: {rgb: "#FF000000"},
              bold: false,
              italic: false,
              underline: false
          },
          alignment: {
              vertical: "center",
              horizontal: "center",
              indent:0,
              wrapText: true
          },
          border: {
              top: {style: "thin", color: {auto: 1}},
              right: {style: "thin", color: {auto: 1}},
              bottom: {style: "thin", color: {auto: 1}},
              left: {style: "thin", color: {auto: 1}}
          }
      };excelCell.s = {
          fill: {
              patternType: "none", // none / solid
              fgColor: {rgb: "FF000000"},
              bgColor: {rgb: "FFFFFFFF"}
          },
          font: {
              name: 'Times New Roman',
              sz: 16,
              color: {rgb: "#FF000000"},
              bold: false,
              italic: false,
              underline: false
          },
          alignment: {
              vertical: "center",
              horizontal: "center",
              indent:0,
              wrapText: true
          },
          border: {
              top: {style: "thin", color: {auto: 1}},
              right: {style: "thin", color: {auto: 1}},
              bottom: {style: "thin", color: {auto: 1}},
              left: {style: "thin", color: {auto: 1}}
          }
      };

      可以设置单元格的背景色,对齐方式和文字格式等信息。例如,标题栏文字居中且设置为红色:

      
      // 标题栏格式
      let head_style = {
          font: {                 // 字体
              color : {rgb: "FFFF0000"}
          },
          alignment: {            // 对齐方式
              vertical: "center",
              horizontal: "center",
          }
      }
      ​
      // 标题栏
      if(j == 1){ 
          cell_data.s = head_style;
      }// 标题栏格式
      let head_style = {
          font: {                 // 字体
              color : {rgb: "FFFF0000"}
          },
          alignment: {            // 对齐方式
              vertical: "center",
              horizontal: "center",
          }
      }
      ​
      // 标题栏
      if(j == 1){ 
          cell_data.s = head_style;
      }

      写入时设置为带样式的写入方式:

      
      XLSX.writeFile(workbook, xlsxPath, {cellStyles: true});XLSX.writeFile(workbook, xlsxPath, {cellStyles: true});

    • 读取样式信息:

      上面只能在写入的时候进入设置,假如需要读取到原表的样式设置,可以在读取表格信息的时候设置读取参数:

      
      _workbook = XLSX.readFile(_xlsx_path, {cellStyles:true});   // 读取样式_workbook = XLSX.readFile(_xlsx_path, {cellStyles:true});   // 读取样式

      但似乎不起作用,官方说是 xlsx-style 插件的问题。最近发现,原来原版的库也支持了写入时的样式属性设置,但读取时无法读取到样式信息,Note on extended features (styles, PivotTables, etc) 中指出了只有 pro 版本才有提供读取样式信息的接口。

     

    其他

    • 使用现成的表格合并库 excel-merge ,使用其 'mutiple' 模式进行融合。

    • npm run build 打包时出现报错:

      
      Error: C:\Users\Administrator\AppData\Local\electron-builder\cache\nsis\nsis-3.0.1.13\Bin\makensis.exe exited with code 1
      Output:
      ...Error: C:\Users\Administrator\AppData\Local\electron-builder\cache\nsis\nsis-3.0.1.13\Bin\makensis.exe exited with code 1
      Output:
      ...

      发现是因为之前打出过 Setup 包,并安装到本机中,通过控制台卸载掉再重新打包即正常。

     

    参考:

    展开全文
  • 企业云+端应用开发 就用BeX5BeX5企业快速开发平台 ⇒ 云+端时代企业应用开发利器BeX5 = WeX5开源快速开发框架+ 强大的工作流/报表/组织权限/文档/门户能力WeX5:开源、免费的应用快速开发框架BeX5:专业、强大的...

    BeX5企业快速开发平台,提供工作流,bpm,workflow,报表,协同门户,组织权限服务,一次开发,跨前端发布运行,云+端时代企业应用开发利器.

    企业云+端应用开发  就用BeX5

    bex5

    BeX5企业快速开发平台 ⇒ 云+端时代企业应用开发利器

    BeX5 = WeX5开源快速开发框架+ 强大的工作流/报表/组织权限/文档/门户能力

    WeX5:开源免费的应用快速开发框架

    BeX5:专业强大的企业快速开发平台

    • 定位:开发面向消费者和公众的开放应用系统
    • 适用:一般app、电商app、客服app、会员app、微店微商等
    • 前端:安卓app/苹果app/微信服务号/PC web app
    • 后端:后端数据处理组件,对接各主流技术平台
    • 费用:完全开源,彻底免费,无任何限制
    • 定位:开发面向企业和政务的信息化系统和管理软件系统
    • 适用:OA/HR/CRM等各类管理软件,电子政务及各行业管理软件
    • 前端:安卓app/苹果app/微信服务号/PC web app
    • 后端:工作流、报表、组织机构、业务逻辑等能力,j2ee框架
    • 费用:按企业用户数收费,20用户免费,商业版收费

    企业应用快速开发 ⇒ 天下开发,唯快不破!

    成熟的应用框架、强大的业务模型、丰富的业务模板、完全可视化拖拽开发、开放的组件体系

    拥抱企业应用“多端时代”  每一种前端都不能少

    企业web app、安卓app、苹果app、微信服务号、微信企业号、其它轻应用,一个都不能少

    外卖案例
    起步软件

    企业应用能力  企业应用必须“十项全能”

    组织机构、权限、工作流、报表、业务概念、业务规则、用户界面、业务门户、即时通讯、消息推送、文档服务“全能”支撑

    性能极致、操控顺滑、界面炫酷  企业应用体验,必须是享受

    性能极致优化,操控爽滑顺畅,界面时尚酷炫。更有风格任意换、多屏自适应,表现完美

    真实运行,可直接操作

    扫一下体验微信运行效果

    app 案例

    扫一下体验Android app

    扫一下体验ios app

    强大的工作流(workflow)  别玩概念,好用才是硬道理!

    真正好用的工作流体系,支持全面的流程定义和扩展,与业务系统高度整合

    中式报表、复杂报表和图表  企业报表轻松完成

    完全所见即所得的报表设计,强大的报表能力和丰富的展现,PC、平板和手机全支持

    组织、分工、权限管理  严格还是灵活?尽由你定

    强大的组织、分工、权限管理,严格还是灵活,一切由你轻松设定

    组织管理

    支持多机构、矩阵型组织、部门、岗位、角色、人员、分工、职权、代理。

    权限管理

    支持各类复杂的权限管理,包括管理权限、业务权限、操作权限、数据权限、三员管理等多种权限模式。

    分级授权

    支持分级授权,使业务主导型分支机构和阿米巴式管理轻而易举

    全局权限控制,业务无缝集成

    为所有业务模块提供统一的后端服务和全局权限控制,并与工作流、业务规则、业务数据访问控制无缝集成

    业务协同门户(portal)  企业业务一站完成

    最佳业务协同和集成环境,所有业务一站完成,为PC、平板、手机、微信企业号提供专用业务门户

    业务协同和应用集成

    单点登录、任务推送、业务处理、流程协作、委托代理。。。最佳业务协同和应用集成环境,所有业务一站完成

    集成专业的企业即时通讯

    与专业的企业通讯集成,无论PC还是移动设备,无论组织机构还是工作任务,全部同步推送。

    多种风格,个性化定制

    多种门户风格,且每个用户能轻松定制符合自己工作习惯的工作桌面。

    app

    PC/移动/微信等多种业务门户

    为PC、平板、手机、微信企业号等提供专用的业务门户

    文档和非结构化支持  文档处理轻而易举

    专业可靠的文档服务器,完备的文档处理能力,支持在线编辑、版本历史、全文检索、修订留痕

    专业的文档服务器

    专业的文档服务器,支持文档、报表、图纸、照片、视频具备完备的文档权限管理和版本历史管理

    完备的文档处理能力

    完备的文档处理能力,支持在线编辑、修订留痕。

    文档全文检索

    支持全文检索、目录分类、归档等能力。

    轻松集成第三方电子签章

    与各类第三方电子签章轻松集成,满足电子签章各类要求

    标准+主流+开放  企业开发主流之道

    主流开发模式、主流技术标准、开放技术体系

    企业App主流开发之道:hybrid app

    使app应用具备原生native app的所有能力,又彻底获得html5开发的方便性和标准性

    html5

    前端UI:恪守HTML5 + CSS3+JS

    前端UI完全基于html5+css3+js,干净纯洁,标准就是移动和互联网世界的通行证

    部署:支持各种云服务和技术

    支持公有云和私有云部署方式,支持或提供PAAS/BAAS服务,支持SAAS方式运行。

    后端:JavaEE技术体系,主流可靠

    后端采用JavaEE标准体系,主流可靠。可用Java扩展任意业务逻辑、业务规则

    开放架构、海量资源、任意扩展  海纳百川,有容乃大

    门户主题、风格样式、UI组件、后端服务组件、向导、模板、多语言均可任意扩展

    Bootstrap

    主题、样式:海量bootstrap资源引入和定制

    Web世界主流的样式组件和模板库,BootStrap海量资源任意引入和定制扩展

    UI组件:轻松扩展和引入各类UI组件

    基于纯H5框架,轻松引入第三方UI组件,并实现定制和可视化

    各类第三方插件轻松对接

    以web、本机、服务调用等方式,轻松对接即时通讯、消息推送、支付等各类插件

    向导、模板:轻松自定义和扩展

    提供了丰富的向导和模板,开发者只需简单定义,即可轻松制作自己的向导和模板

    国际化扩展:多语言支持

    底层统一采用UTF-8,国际化支持全面,轻松开发多语言应用

    门户主题:大量资源可引入和扩展

    基于bootstrap的门户主题,大量门户主题资源可利用,轻松扩展引入

    国产化支撑  核高基国产化平台体系的重要参与者

    起步软件是核高基国产化平台体系的重要参与者,对国产化平台提供全面和深度支持

    大规模开发者群体和生态圈 ⇒ 好平台必然人气爆棚

    依托开源免费的WeX5快速开发框架,以及能力强大的BeX5企业快速开发平台,我们凝聚了广泛的开发者,形成了大规模的开发者群体和活跃的生态圈。

    看看典型开发商

    大规模开发者人群

    桃李不言下自成蹊,程序员只相信好产品!WeX5以极致的产品、开放的架构、彻底的开源,凝聚了大规模的开发者群体
    看看典型客户

    遍及各行业用户

    用户遍及政府、军队、军工、金融、电信、航天、航空、船舶、交通、建设、电力、石油石化、建筑、制造、教育等
    看看应用中心

    大量经典应用和案例

    各类基于WeX5/BeX5的App和企业应用,从免费到商业应用,从面向公众、面向企业内部和电子政务应用,尽在应用展厅
    逛逛供需中心

    生机勃勃的生态圈

    好平台必然有生机勃勃的生态圈,各类供需合作每天都在火热进行中,找工作、接项目、招人才。。。就在供需中心
    参加每周培训

    每周线上和线下免费培训

    面向所有WeX5/BeX5开发者,快速入门,立足实战,一站到底,dev to dev。每周三、四官方培训,实地、远程任选!
    逛逛技术论坛

    人气爆满的技术论坛

    数千人同时在线,人气爆满,交流切磋的好舞台。小白也好,高手也好,都欢迎入内,答疑解惑,交流分享
    浏览开发者博客

    开发者博客

    每一个开发者都是知识创造者、信息的传播者。业界新闻、技术动态、开发秘籍、技术精髓、感悟心得。。。想写就写!
    加入多样的QQ群

    官方、民间、地方qq群

    WeX5和BeX5粉丝众多,官方、民间、地方群有数十个,欢迎与技术大牛一起论道。请加技术10群:537579119(新开放)

    展开全文
  • 两者也在InfoQ及其组织的大会上进行过多次分享,我们将对美团和大众点评使用的技术进行回顾,来看看这家电商巨头的技术实力。 美团和大众点评都是国内O2O领域的主要参与者。除了团购外,美团还有自营的...

    美团网和大众点评网在10月8日中午联合发布声明,宣布达成战略合作,两者将共同成立一家新公司。两者也在InfoQ及其组织的大会上进行过多次分享,我们将对美团和大众点评使用的技术进行回顾,来看看这两家电商巨头的技术实力。


    美团和大众点评都是国内O2O领域的主要参与者。除了团购外,美团还有自营的外卖、电影、酒店预订等业务。大众点评网最开始主要为用户提供各种生活信息服务,基于这些信息开发了团购等业务模式,近期它也开始涉足电影、酒店预订等业务。


    由此可见,两者在业务上存在相当的重合,那么他们在公司成长的过程中又分别选取了什么样的技术发展路线?下面我们将从技术角度来看两者的异同,包括云架构,数据架构,前端,移动端,个性化推荐。

    云架构


    大众点评容器私有云

    大众点评在2014年7月基于Docker搭建了私有云平台。2015年8月份大众点评云平台首席架构师盛延敏在InfoQ主办的CNUTCon容器技术峰会上分享了它们容器私有云的实战内容。


    Docker的应用场景之一就是构建企业私有云平台,它具有简单轻量的特点,可以降低私有云的构建难度,也能更高效的利用物理资源。大众点评的私有云平台承担了大部分线上业务。实例数2800个左右,Docker物理集群300多台。


    大众点评原来的应用部署在KVM上,需要耗费很大运维时间和精力,特别对于扩容和缩容。搭建Docker私有云之后实现了应用标准化和运维自动化,从而节省了运维成本。


    大众点评的Docker云底层使用Docker,通过Dockerfile,Docker Registry统一管理应用的标准化运行环境。组件之间使用NATS,降低耦合。接入层使用Nginx和Zookeeper。对于WEB应用,通过与Nginx的Restful接口交互;对于服务类应用,通过Zookeeper上注册和注销服务IP和端口,便于客户端发现和更新该服务。

    美团云

    美团网于2013年上半年发布了其公有云服务美团云。它们也在InfoQ上分享了公有云的研发经验


    美团云最初版本起步于2012年7月,一开始构建的是私有云计算平台。2013年5月,美团云对外提供公有云服务。美团云架构设计使用OpenStack的框架,网络架构参考CloudStack,主要组件由自己开发,部分组件在OpenStack原生组件上进行了二次开发。


    核心云主机管理为自研,采用Region-Zone-Cluster三层架构,支持跨地域,多数据中心的大规模集群部署。采用基于KVM的主机虚拟化和基于OpenVSwitch+OpenFlow的网络虚拟化技术,降低了系统的开发和维护成本。镜像管理使用Glance;身份管理使用Keystone;对象存储使用Swift。


    美团云在2013年5月刚上线时单月交易额10亿左右,2015年7月单月突破111亿,对美团云系统和网络稳定性要求比较高,目前美团云支撑了超过两亿用户的高并发访问。并采用多个数据中心,数据中心之间使用光纤高速互联,确保美团云的可靠性。


    数据架构

    大众点评

    大众点评在起步阶段,为了抢占市场,快速推出产品,采用方案比较简单。访问量大起来后,针对系统做了改进。大众点评的DBA经理苗发平曾在InfoQ分享大众点评的数据架构之道。新版本中引入了Ehcache作为一级缓存,Memcached作为二级缓存,缓解数据库读压力,分布式文件系统MogileFS作为分布式图片存储服务解决海量图片存储,搜索引擎Lucene,Nosql数据库MongoDB作为K-V数据服务。后期引入MySQL数据库,并采用Hadoop集群管理。最新版本中为解决服务治理问题,引入“泳道架构与容错隔离”方案,来提供系统的高可用性。架构中使用MySQL高可用方案。通过MMM实现HA高可用,实现秒级故障转移。

    美团

    关于美团网的架构,美团网的技术委员会主席夏华夏在InfoQ主办的全球架构师峰会上分享了一些美团架构实践经验。美团初期使用MySql+Apache/PHP,随着业务增加,在开源软件的基础上做集成和优化,使用了Memcache和Varnish,随后增加了Redis。

    前端开发


    大众点评

    大众点评是国内领先的城市生活消费指南网站,浏览量为500M(GA)/每月,高峰3000次动态请求/每秒,180万动态访问量/天。前端早期使用ASP.NET+SQL server,独立的web和Database服务器。后来引进了Cortex,基于CommonJS的Web开发环境

    美团

    美团网采用JS框架,基于YUI3来构建,使用了YUI内部的Loader、模块、组件框架等基本思路和工具,围绕YUI开发了自己的公共底层库、Combo以及发布工具等。2014年底,基于React开发页面组件,使用NPM进行分享,用NPM+Reduce构成新的资源管理方案。其后在 Node 框架的基础上,提供了一系列中间件和开发工具,逐步构建对组件友好的前端工程化方案 Turbo。

    移动客户端


    大众点评

    大众点评月综合浏览量(网站及移动设备)超过150亿,其中移动客户端的浏览量超过85%,移动客户端累计独立用户数超过2亿。移动端是大众的业务重点。我们可以通过大众点评移动应用架构师屠毅敏在全球架构师峰会上关于大众点评移动应用的架构演进的演讲来一窥大众点评的移动开发技术。大众点评的移动架构主要关注三个方面:速度,灵活性,耦合。在速度方面优化Http协议减短响应时间,并进行了缓存优化。引入NDK以及Hybrid App进行开发。利用Android DexClassLoader进行动态加载。

    美团

    美团手机移动端业务种类多差异大,为解决早期架构上的耦合场景问题,引入了Portal开源框架。并引入Subtree代码管理和CocoaPods第三方库依赖管理工具。并利用Jenkins进行持续集成。美团技术专家陈晓亮曾在InfoQ主办的QCon全球软件开发大会上针对美团移动平台背后的技术进行了演讲。


    美团手机移动端架构图

    个性化推荐


    大众点评

    大众点评网因应对大数据查询分析功能,从2011年开始使用Hadoop,有60多个节点,700TB容量,还有2个HBase线上集群。使用Puppet管理集群,之后由于业务需要,又自行开发了Taurus调度系统。并于2012年数据库仓库转向Hadoop/Hive。在2013年建立主要的大数据架构后,大众点评上线了HBase应用,并引入Spark/Shark以提高Ad Hoc Query执行时间。整个大数据架构采用开源+自研的形式。

    美团

    为解决信息过载,帮助用户快速找到所需,美团推出大数据推荐系统。美团推荐系统框架分为:数据层,触发层,融合过滤层和排序层。数据层用Hadoop/Hive,HBase,MySql和Redis作为数据存储,使用了ETL系统,Map/Reduce,Spark,Storm,Flume作为数据产生。触发层利用各种触发策略如用户行为产生推荐的候选集,使用传统的user based,item based协同过滤算法进行融合筛选。排序层主要是利用机器学习的模型对触发层筛选出来的候选集进行重排序。


    随着美团交易规模的逐步增大,积累下来的业务数据和交易数据越来越多。目前在美团的团购系统中大量地应用到了机器学习和数据挖掘技术。并且研究了基于机器学习方法的POI品类推荐算法。美团网的技术总监王栋曾以美团的智能化推荐为主题接受过InfoQ的采访。

    总结

    上面对云架构,前端,移动端,数据库,大数据的概况来看,在云架构上大众点评使用的是Docker,而美团是采用OpenStack+自研。前端和移动端因功能不同而不一致。数据库两者在前期都采用简单架构以满足初创公司快速发展,后因业务需要而不断采用新的开源技术。在大数据方面,均采用了Hadoop,HBase,Spark,Storm等来保证数据的准确快速产生,而因推荐业务需要,美团还大量使用了机器学习。可见美团和大众点评均具有实力强大的技术团队,从云架构和大数据来看,美团似乎更甚一筹,两家公司合并实为强强联合,至于后续会如何发展,还将拭目以待。

    展开全文
  • SharePoint定制开发个性皮肤

    千次阅读 2013-07-25 10:06:31
    Adventure Works Travel 网站的设计旨在迎合喜欢个性化外观的年轻用户群。这些用户喜欢使用顶部和左侧导航以及 SharePoint Server 2010 搜索在网站中导航。用户访问网站的主要原因是了解探险目的地和预订假期。该...

    使用 SharePoint 2010 发布网站实际打造品牌 

    摘要:了解一些基本概念,以帮助您在 Microsoft SharePoint Server 2010 发布网站中创建引人注目的用户界面设计。

    适用范围: Microsoft SharePoint Server 2010

    供稿人:Andrew Connell,Critical Path Training LLC(该链接可能指向英文页面) (SharePoint MVP) | Randy Drisgill,SharePoint911(该链接可能指向英文页面) (SharePoint MVP)

    目录

    • 使用 SharePoint 2010 发布网站实际打造品牌简介 
    • 收集 SharePoint 发布网站的设计要求 
    • 规划 SharePoint 品牌打造任务 
    • 为 SharePoint 网站设计创建实际设计组合 
    • 创建 Adventure Works Travel HTML 
    • 为 Adventure Works Travel 创建 .css 文件 
    • 在多个浏览器中测试 SharePoint 网页设计 
    • 在 SharePoint 中创建品牌 
    • 向 SharePoint 添加 .css 和图像文件 
    • 使用 HTML 构建母版页 
    • 为 SharePoint 网站设计制定 .css 规则 
    • 创建自定义页面布局 
    • 使用 SharePoint Designer 编辑页面布局 
    • 打包和部署 SharePoint 品牌 
    • 结论 
    • 其他资源 

    下载代码(该链接可能指向英文页面)

    使用 SharePoint 2010 发布网站实际打造品牌简介

    Microsoft SharePoint Server 2010 发布网站使用“发布功能”提供创建引人注目的 Web 内容管理 (WCM) 网站的功能。这些网站通常用作面向 Internet 的网站,它们需要使用定制的用户界面 (UI) 建立联机公司标识。创建定制 UI(无论是在传统 HTML 页面中还是在 Microsoft SharePoint Server 2010 中)的过程称为打造网站品牌。发布网站使用母版页、页面布局Web 部件级联样式表(.css 文件),从而使设计人员和开发人员可以创建品牌化的网站,这些网站的设计可以与当下许多最新热门网站相媲美。本文重点介绍使用发布网站为面向 Internet 的外部网站合理规划和创建设计的机制,如图 1 所示。本文以虚构的旅行社 Adventure Works Travel 为例,该公司希望创建高度品牌化的 SharePoint 网站。
    图 1. Adventure Works Travel 网站品牌
    Adventure Works Travel 网站品牌

    收集SharePoint发布网站的设计要求

    在您准备为 SharePoint 网站创建精美的设计时,首先需要花一些时间精心规划网站。在规划阶段,要收集网站元素(如母版页和页面布局)的设计要求。通过在开始编写代码之前正确理解业务目标,可以避免在项目生命周期的后期艰难而耗时地重新编写代码。

    收集设计要求时,首先要召开正式的要求收集会议。无论您设计的网站是面向 10 名用户还是 100,000 名用户,都必须满足一些要求,才能将项目视为取得成功。根据网站的复杂性,调整收集的要求的详细程度。例如,在收集要求时,大型网站(无论是具有许多页面还是面向许多用户)可能要比小型的简单网站花费更长时间。在要求收集过程中征求关键业务、市场营销和 IT 利益干系人的意见,务必考虑他们的想法,并确保所有关键利益干系人都完全认可项目。对于品牌打造项目来说,要求收集通常是一项非常困难的任务,有时需要委托给市场营销部门,甚至是外包给外部顾问。尽管征求关键利益干系人的意见很重要,但还要考虑在决策制定过程中征求更多人的意见是否会增加收集要求所需的时间以及是否会增加项目的整体复杂程度。因此,在考虑要征求哪些利益干系人的意见时,要认真考虑谁会提供针对性最强的意见。

    以下几节介绍开始任何 SharePoint 品牌打造项目之前要了解的一些更重要的概念。

    SharePoint Server 2010 发布网站与 SharePoint Foundation 2010 网站

    要求收集完成后,首先要确定网站是以 Microsoft SharePoint Foundation 2010 为基础,还是以运行 Microsoft SharePoint Server 2010(启用了发布功能)的服务器为基础。发布网站基于 SharePoint Foundation 构建,使用发布网站构建面向 Internet 的引人注目的网站有许多优势。与使用 SharePoint Foundation 网站相比,使用 SharePoint Server 发布网站创建品牌的一些优势包括:

    • 允许内容作者使用比 SharePoint Foundation 网站提供的更可靠的格式文本编辑体验创建网页。
    • 包含母版页,母版页面向发布网站并使用可充分利用发布功能的特定代码程序集。
    • 更轻松地控制通过 Web UI 进行的 Web 导航,并且为设计人员提供了更多选项。
    • 使用 Web UI 可以轻松地更改母版页,并且可以将母版页应用于当前网站下的所有子网站。
    • 使用页面布局可在页面级别创建模板。使用文字版式可完成简单的页面布局表单。文字版式不可配置。
    • 使用 $SPUrl 标记通过相对于网站集 ($SPUrl;~sitecollection/) 或网站根目录 ($SPUrl:~site/) 的 URL 确定 HTML 资产的目标。
    Gg430141.note(zh-cn,office.14).gif注释:

    在本文中,发布网站是一个在顶级(根)目录中具有网站集并且该网站集启用了发布功能的 SharePoint Server 2010 Web 应用程序。为了简单起见,默认发布模板(“企业 Wiki”和“发布门户”)已经启用了发布功能。本文在 Adventure Works Travel 示例中使用“企业 Wiki”模板。

    要了解有关设置 Web 应用程序和网站集的详细信息,请参阅适用于 SharePoint Server 2007 Web 内容管理网站的规范性指南 (该链接可能指向英文页面)。

    面向 SharePoint 发布网站设计的浏览器和平台

    开始设计网站和编写网站代码之前,请尽早确定该设计将支持的浏览器和操作系统平台。尽管您应该努力创建尽可能在每个浏览器和每个操作系统中都能够完美呈现的网站设计,但即便是针对这一级别的浏览器兼容性对设计进行成功测试,通常也是不可能且不切实际的。通常,最好选择一组浏览器和操作系统以专门针对它们进行测试,并相应地编写代码以便在打造网站品牌时支持它们。

    选择浏览器和操作系统支持级别的一种不错的做法是参考研究并提供 Web 流量分析的专题网站。应用程序的净市场份额(该链接可能指向英文页面)列出了 2010 年 6 月总市场份额排在前 10 位的 Web 浏览器,如表 1 所示。

    表 1. 浏览器版本和总市场份额

    浏览器版本 总市场份额百分比

    Internet Explorer 8

    25.18%

    Internet Explorer 6

    17.16%

    Firefox 3.6

    15.67%

    Internet Explorer 7

    12.04%

    Firefox 3.5

    5.24%

    Chrome 4.1

    5.16%

    Safari 4.0

    3.83%

    Internet Explorer 8 兼容模式

    3.35%

    Firefox 3.0

    2.65%

    Opera 10.x

    1.88%

    Microsoft 指明了各浏览器在 SharePoint 中的支持级别。这些级别包括:

    • 支持  支持的 Web 浏览器可以与 SharePoint Server 2010 协同工作,并且所有特性和功能都能按预期方式工作。
    • 支持但具有已知限制  支持但具有已知限制的 Web 浏览器可以与 SharePoint Server 2010 协同工作,但存在一些已知限制。大多数特性和功能都能正常工作,但如果存在无法正常工作或根据设计被禁用的特性或功能,则会提供有关如何解决这些问题的文档。 
    • 未测试:未测试的 Web 浏览器是指它与 SharePoint Server 2010 的兼容性未经过测试,使用这类 Web 浏览器时可能会遇到问题。

    有关 SharePoint 中的浏览器支持级别的详细信息,请参阅规划浏览器支持 (Office SharePoint Server)。

    Gg430141.note(zh-cn,office.14).gif注释:

    SharePoint 2010 不支持 Internet Explorer 6.0。尽管您可以创建能够在 Internet Explorer 6.0 中正常显示 Web 内容的母版页,但是它与 SharePoint 2010 的创作体验不兼容,后者需要基于新式标准的浏览器。

    本文中的 Adventure Works Travel 示例重点介绍如何在 Internet Explorer 7、Internet Explorer 8 和 Firefox 3 中实现尽可能完美的最终用户浏览体验,这样可以确保其他一些新式浏览器(包括 Google Chrome 和 Apple Safari)也能够正常呈现。

    适用于 SharePoint 网站设计的屏幕大小

    要考虑的另一问题是新设计应该面向的屏幕分辨率。多年前,显示器仅支持有限的一组分辨率,如 640 x 480。随着显示器价格的降低,网站访问者以 1920 x 1200 甚至更高分辨率进行浏览的情况越来越常见。大多数网站设计人员认为 1024 x 768 是最常见的屏幕分辨率,紧接着是 1280 x 800。创建要在 SharePoint 网站中显示的设计时,请记住 SharePoint 以用户的典型屏幕分辨率一次呈现大量信息。如果考虑到浏览器工具栏和滚动条也占用屏幕上的一些可用显示区域,则可用于呈现内容的空间就更小了。

    在 Adventure Works Travel 示例中,最低屏幕分辨率是 1024 x 768。设计留出了一些边距以供滚动条使用。因为存在边距,所以网站的宽度不能超过 960 像素。

    定义 SharePoint 网站设计的访问群体和成功标准

    为了帮助确保成功完成品牌打造计划,请设定一些主观性较强的设计目标。哪些访问群体将使用该网站?网站的典型用户希望或需要完成哪些任务?用户希望如何在网站中导航?用户希望与形象较为传统的公司开展业务还是希望与较为现代的公司开展业务?与软件开发过程不同,设计过程对于每个业务环境来说都是主观的。在确定品牌标识之前,利息干系人之间对设计决策通常存在争议。因为品牌创意可能很难评估,所以最好同时确定新品牌的成功标准。成功标准可以非常简单,例如吸引更多访问者;也可以非常复杂,例如计算在主要受众人口统计中销量的增加情况。成功标准越容易量化和衡量,确定品牌打造工作的相对成功程度就越容易。

    Adventure Works Travel 网站的设计旨在迎合喜欢个性化外观的年轻用户群。这些用户喜欢使用顶部和左侧导航以及 SharePoint Server 2010 搜索在网站中导航。用户访问网站的主要原因是了解探险目的地和预订假期。该品牌适合这样的旅行社:迎合的受众希望在假期参加更具冒险性的活动,而不是像往常一样呆在海滩上的旅馆中。

    规划 SharePoint 品牌打造任务

    实际为 SharePoint 网站的品牌打造编写代码的过程涉及若干步骤,如创建母版页、页面布局和级联样式表(.css 文件)。打造 SharePoint 品牌的规划过程也可以分为若干步骤,如创建黑白框架图、创建全彩的网站设计组合(简称组合)和创建可正常运行的主要页面的 HTML 和 .css 文件版本。以下各节介绍这些活动,因为它们与创建品牌化的 SharePoint UI 有关。

    创建简单的 SharePoint 网站设计框架图

    框架图通常是一组黑白框图,形象地描述网站的整体结构及其布局、导航和功能,有时甚至描述其内容。由于 Web 设计(或者广义上的设计)的主观特性,最好以框架图的形式讨论这些主题,而不是陷入选择颜色和照片的困境。正确完成后,框架图可以为开发人员和设计人员提供功能和布局方面的相关指导,以便在品牌打造过程的后期阶段应用。

    创建框架图有许多方式,从使用简单的笔和纸绘制到使用专用软件工具(如 Microsoft Visio 2010)建模。创建框架图时使用专用软件工具可能非常有帮助,因为您可以利用映射到特定应用程序(如 SharePoint)的特定功能的预建模具。您可以找到许多免费的 模板和模具,可使用它们来创建 SharePoint 网站的框架图。

    创建框架图时,要确定品牌支持的 SharePoint 功能。SharePoint 默认情况下显示的内容中有一些并非适合每个面向 Internet 的网站。图 2 标出了 SharePoint 界面的主要功能区域,表 2 介绍了这些功能区域。
    图 2. SharePoint 界面的主要功能区域
    SharePoint 界面的主要功能区域

    表 2. SharePoint 界面的主要功能区域

    图中的标签 功能区域 功能描述

    A

    服务器功能区

    UI 的整个顶部区域是功能区的一部分。显示的内容取决于用户的当前上下文。

    B

    网站操作

    用于与 SharePoint 交互的主菜单,主要由内容作者和管理员使用。

    C

    全局痕迹导航控件

    在 Microsoft Office SharePoint Server 2007 中首次引入的全局痕迹导航控件的新型实现。单击时,该图标显示的动态 HTML 呈现网站的分层视图。使用它可从层次结构中的当前位置导航到上面各级。

    D

    “页面状态操作”按钮

    该按钮用于控制页面状态,通常显示用于编辑或保存当前页面的快捷方式。

    E

    功能区上下文选项卡

    此类选项卡提供特定于 SharePoint 网站功能的菜单。显示的内容会根据用户与其交互的页面对象而异。有些选项卡并非适用于每个网站。

    F

    “欢迎”菜单

    此菜单显示欢迎消息,并且允许用户查看他们的配置文件、注销和以其他用户身份登录。如果安装了其他语言包,则此处还会提供用于更改用户语言的功能。用户未登录时,“欢迎”菜单还会显示“登录”链接。

    G

    “开发人员仪表板”按钮

    该按钮打开通常显示在屏幕底部的开发人员仪表板。开发人员仪表板包含有关页面呈现和查询的统计信息。该图标在开发人员仪表板的显示级别设置为OnDemand(其他选项包括 On 和 Off)时显示。管理员可以使用 Windows PowerShell 或使用 SharePoint API 设置开发人员仪表板的显示级别。

    H

    标题徽标

    有时称为网站图标。它通常显示 SharePoint 网站图标,但也可以显示用户定义的徽标。

    I

    痕迹导航

    这是特定于 v4.master 母版页的痕迹导航性质的控件。它包括“网站标题”标题区中的标题 的占位符(通常包含“页面标题”)。“网站标题”链接到网站的顶层。

    J

    社交按钮

    用于“顶”某个项以及向内容添加标记和备注。

    K

    全局导航

    有时称为“顶部链接栏”或“顶部导航栏”,它是网站的水平方向上的主要导航机制。

    L

    “搜索”区域

    搜索框用于输入搜索字词以在网站上执行搜索。

    M

    帮助按钮

    帮助按钮链接到 SharePoint 2010 帮助文档。

    N

    快速启动

    提供当前导航。有时称为“左侧导航”。它是与当前位置相关的页面的辅助或垂直方面导航机制。

    O

    树视图

    以 Windows 资源管理器的形式显示网站。由于树视图的外观,它通常非常适合 Intranet 网站。

    P

    回收站

    提供指向网站的“回收站”的链接,回收站是用于存储已删除项的区域。通常,这非常适合 Intranet 网站。

    Q

    所有网站内容

    指向“所有网站内容”页面的链接。它是 Office SharePoint Server 2007 中的“查看所有网站内容”链接。通常,这非常适合 Intranet 网站。

    R

    正文区域

    表示主要内容占位符,其中包括特定于页面的所有内容。如果要呈现页面内容,则必须使用正文区域。

    为 SharePoint 网站创建框架图时,一定要考虑 SharePoint 支持的几种页面类型。SharePoint 网站中可以包含的页面类型包括主页、登录页面、搜索结果页面、文章和 Wiki 页面等等。

    图 3 显示了 Adventure Works Travel 网站的 Microsoft Visio 2010 框架图。
    图 3. Adventure Works Travel 网站的 Visio 2010 框架图
    用于 Adventure Works Travel 的 Visio 2010 线框

    从框架图页面中可以看出,Adventure Works Travel 网站支持一些而非全部 SharePoint 功能。例如,UI 中会去掉“帮助”按钮、树视图和回收站等一些元素。通过在框架图阶段做出这些决策,开发人员不必构建不必要的功能。

    为SharePoint网站设计创建实际设计组合

    尽管规划新 SharePoint 网站时创建框架图肯定会对完成重要的品牌打造工作有所帮助,但是在开始编写代码之前,您应该创建完整的设计组合或原型。与框架图不同,大多数 Web 设计组合旨在尽可能真实地模拟实际网站的外观和行为,但不实际创建任何代码。组合包括静态版本的实际照片、徽标、颜色、字体、表单元素以及页面上可能显示的其他设计或结构项目。对于 SharePoint 网站,模拟页面内容意味着模拟 SharePoint 用户界面的许多功能区域。

    尽管您可以使用任何图形应用程序(甚至使用笔和纸)创建设计组合,但是 Adobe Photoshop 或 Microsoft Expression Design 等应用程序可以大大简化该任务。使用这些应用程序可以为 SharePoint 网站创建易于维护和可重用的设计组合。

    Gg430141.note(zh-cn,office.14).gif注释:

    尽管本文并不涉及具体的 Adobe Photoshop 或 Microsoft Expression Design 功能,但是介绍了一些常规概念和过程,这些和类似设计应用程序中可能提供了类似的功能。

    以下各节介绍用于创建设计组合的应用程序常见的功能。

    使用设计应用程序中的图层和图层组将各元素分开

    使用图层和图层组可将设计元素分开放置在特定单元中。图层不会在一个“平面”文件中创建设计元素,它的工作原理是每个新图层都位于前一图层的上面。设计人员可以隐藏、显示、操纵、移动各个图层,还可以对各个图层应用投影和边框等效果,这些都不影响其他设计元素。使用设计工具创建设计组合时,最好为设计中的每个元素创建一个新图层。

    使用设计应用程序创建可编辑的文字

    使用各种字体、字号和样式创建可编辑的文字。如果没有此功能,在基本设计程序中创建的文字将是静态的,并且必须清除才能进行各种更改。通过使用新式设计工具,您可以调整文字大小、以粗体显示文字、设置文字颜色或更改字体等等,而不需要清除先前的状态。

    使用设计应用程序创建可在 Web 中安全使用的图像

    以可在 Web 中安全使用的文件格式(如 .jpg, .gif 和 .png)轻松保存图像。许多设计程序可以帮助您以适合 Web 的小型文件创建图像,而不影响它们的质量。

    使用设计应用程序创建实际设计组合

    创建设计组合时,人们都很乐意使用功能强大的设计工具创建极其完美或精致的设计。但请注意,创建的设计不能太过精致,否则浏览器可能无法在 SharePoint 页面中将其真实呈现出来。文字就属于此类限制。在 Adobe Photoshop 中,每段文字都可以使用不同的抗锯齿技术。抗锯齿是一种降低图像在以较低分辨率显示时的失真程度的方法。特别是,小型文字在 Photoshop 中的显示效果比在浏览器中平滑得多。为了防止设定的期望值太高,最好避免对小型文字使用抗锯齿。

    除了文字抗锯齿,还要考虑 SharePoint 的外观和行为。要在设计组合中准确重现 SharePoint 功能,请截取每项 SharePoint 功能的屏幕截图,并将它们粘贴到设计中。

    例如,创建 Adventure Works Travel 设计组合时,要敲定各种颜色和样式。必须获取素材照片、选择字体并创建徽标。在单独的图层中创建每个元素,作为图层效果创建渐变和边框等效果以便日后更改起来更轻松。捕获 SharePoint 元素(如服务器功能区或搜索框)并将它们粘贴到设计工具中,最后以引人注目的方式布置这些元素。图 4 显示最终的 Adventure Works Travel 设计组合。



    图 4. Adventure Works 设计组合 

    Adventure Works 设计组合

    创建设计组合时,要确定如何重现 SharePoint 中的概念。图 5 显示同一设计组合,并且添加了标签以突出显示每个功能区域。表 3 介绍了这些功能区域。
    图 5. 设计组合中的 SharePoint 功能区域
    设计组合中的 SharePoint 功能区域

    表 3. SharePoint 网站设计组合中的主要功能区域

    标签 功能区域 说明

    A

    功能区

    包含所有标准功能区元素,如“网站操作”菜单和“欢迎”菜单。

    B

    标题徽标

     

    C

    “搜索”区域

     

    D

    全局导航

     

    E

    当前导航

     

    F

    痕迹导航

    使用 SiteMapPath 控件。

    G

    字段控件

     

    H

    字段控件

     

    I

    Web 部件

     

    J

    Web 部件

     

    将设计组合转换为 HTML 和 .CSS 代码

    将设计组合转换为正常运行的 HTML 页面。对于简单的设计,您可以跳过此步骤,但是对于复杂的设计,完成此步骤可使设计人员在熟悉的环境中工作。HTML 代码稍后可用于在 Microsoft SharePoint Designer 2010 等工具中创建母版页。通过首先创建正常运行的 HTML 版本,您可以针对母版页微调 HTML,而不必处理 SharePoint 添加到显示内容中的代码。此步骤完成后,应该会具有功能完备的网站主要页面的 HTML 版本。基本布局的所有级联样式表代码都已完成,所有图像都已从设计组合中分离出来,并且已保存为单独的文件。

    可供设计人员用来创建 HTML 的工具集有许多,从记事本或其他用于编写简单 HTML 代码的文本编辑器,到专业的网页开发工具(如 Adobe Dreamweaver 或 Microsoft Expression Web),种类繁多。下面列出了对于设计人员来说,专业网页开发应用程序具有的一些优势:

    • 对完成 HTML 和级联样式表代码的支持
    • WYSIWIG(所见即所得)设计视图
    • 有助于创建兼容多种浏览器的网页的工具

    DOCTYPES 和 SharePoint

    创建兼容多种浏览器的 HTML 时,务必了解 HTML DOCTYPE 声明的工作原理。DOCTYPE 是一种声明,指示浏览器或验证程序使用特定语言解释它描述的 HTML 或 XML 代码。尽管可以创建不声明 DOCTYPE 的 HTML(甚至母版页),但是如果没有它,浏览器可能会以意想不到的方式呈现 HTML 代码。例如,如果不声明有效的 DOCTYPE,则 Internet Explorer 8 将在 Quirks(兼容)模式下呈现 HTML 页面(这与 Internet Explorer 5.5 呈现页面的方式类似)。

    目前使用的一些 DOCTYPE 声明可使浏览器以可预测的方式呈现内容。最常见的 DOCTYPE 声明包括以下几种:

    • HTML 4.01 Strict   允许使用所有 HTML 元素,但不允许使用已弃用的元素,如标记。
    • HTML 4.01 Transitional   允许使用所有 HTML 元素,包括已弃用的元素。
    • XHTML 1.0 Strict   与 HTML 4.01 Strict 类似,但是所有标记都必须是格式标准的 XML(例如,必须正确地结束标记)。所有已弃用的元素都将被忽略。 
    • XHTML 1.0 Transitional   与 HTML 4.01 Transitional 类似,但是所有标记必须是格式标准的 XML。允许使用已弃用的元素(但是也必须是格式标准的 XML)。 

    因为 SharePoint 2010 在默认母版页中使用 XHMTL 1.0 Strict DOCTYPE 声明,所以创建要在 SharePoint 2010 中使用的 HTML 时,请使用 XHTML 1.0 Strict DOCTYPE

    Gg430141.note(zh-cn,office.14).gif注释:

    默认情况下,通过任何万维网联合会 (W3C) 验证检查器检查时,SharePoint 2010 网站可能都不是完全有效的 XHTML 1.0 Strict。SharePoint 2010 中仍使用一些旧控件。尽管不会验证全部页面,但是如果使用 XHTML 1.0 Strict 编写 SharePoint HTML 代码,设计体验会更加可靠。本文中的示例使用 XHTML 1.0 Strict DOCTYPE

    要在 HTML 编辑器工具中创建 XHTML 1.0 Strict 文档,务必创建一个将 DOCTYPE 指定为 XHTML 1.0 Strict 的新空白 HTML 文档。(有关 XHTML 1.0 Strict DOCTYPE 的详细信息,请参阅 W3C XHTML 1.0 Strict 规范。)工具创建的 HTML 页面在打开后会显示以下标记。

    HTML
    <!DOCTYPE html PUBLIC "=//W3C//DTD XHTML 1.0 Strict//EN"" 
    http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd><html 
    xmlns=http://www.w3.org/1999/xhtml><head><meta http-equiv="Content-Type" 
    content="text/html; charset=utf-8" /><title>Untitled Document</title></head> 
    <body></body></html>

    在这里,创建 HTML 的其余部分。务必遵循 W3C 指南以创建有效的 XHTML 1.0 Strict 代码。有关 XHTML 1.0 Strict DOCTYPE 的详细信息,请参阅 W3C XHTML 1.0 Strict 规范。本节的其余部分重点介绍为 SharePoint 设计创建 HTML 时需要了解的具体要点。有关创建 HTML 代码的详细信息,请参阅 MSDN HTML 和 DHTML 概述及教程

    是否使用表设计 SharePoint 网站

    另一经常存在争议的设计选择是 HTML 设计布局是否应该使用表,或者是否应该使用标记设置 .css 样式。以前,所有 HTML 布局都是使用表创建的,以提供丰富的 UI,但是随着浏览器的演变,对基于级联样式表的布局的支持也有所变化。因为 HTML 表原本用于显示表格式信息,而不是创建布局,所以正逐渐被 Web 设计人员淘汰。

    您应该考虑到,默认情况下 SharePoint 2010 包含的表比以前的版本要少,在 SharePoint 2010 中,基本上只有在显示表格式数据时才使用表。Adventure Works Travel HTML 代码不使用表,而是使用级联样式表确定其整个布局。

    HTML 和未来的 Internet Explorer 与 SharePoint 的兼容性

    随着 Internet Explorer 新版本的发布,浏览器呈现 HTML 的方式可能会随着时间的推移而变化。为了应对各种可能的变化,Microsoft 使用 X-UA-Compatible META 标记,该标记可使特定 Internet Explorer 版本成为 HTML 标记的目标浏览器。默认 SharePoint 2010 母版页设置为强制 Internet Explorer 的当前或未来版本在 Internet Explorer 8 模式下呈现 HTML,如以下标记所示:

    <meta http-equiv="X-UA-Compatibile" content="IE=IE8" />

    Adventure Works Travel HTML 包含 META 标记,以帮助确保未来的 Internet Explorer 版本能够正确显示 SharePoint HTML。

    有关 Internet Explorer 标准模式的详细信息,请参阅未来兼容性中的 META 标记和锁定。

    将设计组合切割成 Web 图像

    创建设计组合对于理解网页的外观很有用,不仅如此,还可以使用设计组合创建 HTML 将加载的所有单个图像。将大型图像分成单个 Web 图像的一种有效方式是使用设计应用程序(如 PhotoShop 或 Expression Blend)中的“切割”工具。

    要基于设计组合创建 Web 图像,请从设计应用程序的相应菜单中打开“切割”工具。在需要转换为 Web 图像的所有区域周围创建矩形选择范围,务必隐藏最终图像中不需要的所有图层(如 SharePoint 创建的模型文字)。单击每个切块并选择相应的 Web 图像文件格式。对于不应该转换为图像的切块,可以选择不将切块与任何图像关联。通常,应该对具有许多颜色的照片使用 .jpg 文件,对于需要透明背景的艺术作品以及文字或图像,应该使用 .gif 文件或 .png 文件。.png 格式的文件能够使用渐变的透明度,而 .gif 文件只有 100% 透明的区域。

    创建 Adventure Works Travel HTML

    现在已经创建了所有单独的 Web 图像,下一步骤是为 Adventure Works Travel 的 HTML 和 .css 文件编写代码。我使用 Adobe Dreamweaver CS3 创建了 XHTML 1.0 Strict HTML 文件。HTML 标记的其余部分可在与本文相关联的可下载文件中找到(请参阅 MSDN 代码库中的 MSDN 示例 - 实际 SharePoint 品牌打造(该链接可能指向英文页面))。

    Gg430141.note(zh-cn,office.14).gif注释:

    本示例中的 HTML 不使用表创建布局,而是频繁地使用标记划分页面的逻辑区域。此 HTML 已接受 W3C 标记验证服务(该链接可能指向英文页面)的检查,符合 XHTML 1.0 Strict。

     

     

    为 Adventure Works Travel 创建 .css 文件

    由于所有布局设计都使用了 .css 代码,因此只使用 HTML 标记将无法创建有吸引力的网页。在与本文相关联的可下载文件中,找到创建的 .css 代码,这些代码用于设置 HTML 元素的所有颜色、字体、图像和位置(请参阅 MSDN 代码库中的 MSDN 示例 - 实际 SharePoint 品牌打造(该链接可能指向英文页面))。在 Adventure Works Travel HTML 文件中,已通过 <head> 部分中的以下代码关联此 .css 文件。

    HTML
     
    <link rel="stylesheet" href="style.css" type="text/css">
    

    有关创建 .css 代码以设置 HTML 网页样式的详细信息,请参阅 MSDN CSS 参考。

    在多个浏览器中测试 SharePoint 网页设计

    现在所有 HTML、图像和 .css 文件都已创建完毕,您可以测试网页,以确保它看起来与设计组合尽可能相似。图 6 显示完成的 Adventure Works Travel 网页在 Internet Explorer 中的效果。



    图 6. 完成的 Adventure Works Travel 网页在 Internet Explorer 中的效果

    IE 中已完成的 Adventure Works Travel 网页

    在将 HTML 设计转换为正常运行的 SharePoint 网站之前,在尽可能多的浏览器中测试设计。除了 Internet Explorer 之外,通过安装 Mozilla Firefox、Google Chrome 和 Apple 的 Safari for Windows,您可以针对许多不同的浏览方案测试 Web 设计。在多个浏览器中进行测试的另一选择是使用 Expression Web Super Preview(该链接可能指向英文页面)。此应用程序在 Expression Web 3 中提供,也可以免费下载,免费下载版本只能测试 Internet Explorer 版本。完整版本可以测试非 Microsoft 创建的浏览器,如 Firefox。两个版本都可以使用不同的呈现引擎并排显示页面,并且都可以对最细微的差异进行非常精细的检查。

    在 SharePoint 中创建品牌

    现在,我将重点介绍如何在发布网站中创建品牌。您将了解如何使用简易母版页,以及如何添加自定义 HTML 标记和 .css 代码以创建与原 Adventure Works Travel HTML 页面极其相似的母版页。最后,您将了解页面布局的相关信息,包括如何为 Adventure Works Travel 创建页面布局。本节将帮助您完成 Adventure Works Travel 的 SharePoint 品牌打造。

    构建自定义 SharePoint 母版页

    打造 SharePoint 网站的品牌时,母版页是最重要的。SharePoint 中的每个页面都使用母版页确定 SharePoint 网站包含的功能和内容的布局。使用 SharePoint 网站创建品牌鲜明的网站的一个关键步骤是创建设计合理的母版页。因为您已经创建了设计组合并且在 HTML 中完成了设计,所以您可以使用它创建自定义母版页。

    在 SharePoint 中使用内容占位符

    除了引用和使用所有特定 SharePoint 控件之外,SharePoint 中的母版页还需要一组特定的内容占位符。如果从母版页中删除这些必需的内容占位符,则 SharePoint 会在浏览器中显示一个错误。许多时候,特定网站设计中不使用必需的内容占位符;这种情况下,如果有办法隐藏必需的内容占位符会很有帮助。通过在隐藏的 面板控件中嵌套内容占位符,可以在不导致错误的情况下将它们从呈现的页面中删除。以下代码显示放置在隐藏面板中的内容占位符。

     
     
    <asp:Panel visible="false" runat=server>
      <asp:ContentPlaceholder ID=PlaceHolderNavSpacer"> runat="server" />
    </asp:Panel>
    

    有关如何在 SharePoint 默认母版页中使用内容占位符的详细信息,请参阅 Windows SharePoint Services 3.0 网站中 Default.Master 上的默认内容占位符。

    SharePoint 简易母版页

    因为 SharePoint 需要许多特定内容占位符,所有从头开始创建自定义母版页可能很有挑战性。尽管可以从任何默认母版页开始创建新自定义母版页,但是它们包含在开始之前必须删除的大量品牌打造代码。更好的方法是从简易母版页开始,简易母版页是预配置的母版页框架,仅包含在 SharePoint 中创建正常运行的页面所必需的功能。有关 SharePoint Server 2010 母版页中使用的内容占位符的列表,请参阅将现有母版页升级到 SharePoint Foundation 母版页。

    本文的下载内容中包含一个适用于面向 Internet 的发布网站的简易母版页,其中包含的注释浅显易懂。总体来说,这是 SharePoint 的一个传统简易母版页,但是它使用了一些专用于发布的元素,最值得注意的是导航控件。该简易母版页应该适用于大多数默认 SharePoint 2010 页面,包括应用程序页面(如“网站设置”)、列表和文档。

    简易母版页的每个部分都包含注释,这些注释标明了所表示的 SharePoint 功能区域。以下各节介绍在 SharePoint 2010 中使用母版页时需要注意的一些要点,特别是与简易母版页相关的要点。

    使用 SharePoint 功能区

    简易母版页的布局与默认母版页非常相似,所以它的功能区同样“固定”在可视页面的顶部。启用功能区定位系统后,SharePoint 可管理页面滚动,在允许大型页面滚动的同时仍始终在浏览器窗口的顶部显示功能区。要实现此效果,请在 .css 代码和标记中禁用页面滚动,并将正文内容(功能区下的所有内容)放置在两个特定标记中,如下所示。

    HTML
     
    <div id="s4-workspace"> 
    <div id=s4-bodyContainer">
    . . . 
    </div>
    </div>
    

    SharePoint 会查找这些标记,然后仅向该区域而不是功能区添加滚动效果。由于功能区定位系统管理滚动效果和功能区位置的方式,在使用非常复杂的 .css 布局时,可能需要禁用它并使用较为传统的滚动方法。要详细了解功能区定位系统如何工作,或者如何更改它以使用较为传统的滚动方法,请参阅 SharePoint 2010 母版页中的自定义功能区定位(该链接可能指向英文页面)。

    处理固定宽度的 SharePoint 网页设计

    SharePoint 2010 中功能区定位系统的部分功能涉及基于浏览器窗口的大小自动设置页面宽度和高度。默认 SharePoint 品牌对其布局使用整个浏览器宽度;使用固定宽度(通常在页面中间居中)的自定义品牌必须将名为 s4-nosetwidth 的特殊 .css 类应用于 Workspace 元素。简易母版页设置为使用此 s4-nosetwidth 类实例;对于必须占满整个浏览器宽度的设计,应该删除该类。

    在 SharePoint 网页设计中使用 .css 代码

    在 SharePoint 中打造品牌的一个主要方面是 .css 文件中样式表的级联特性。如果两个 .css 规则具有相同的特征,则后加载的 .css 规则是应用于元素的样式。有关此概念的详细信息,请参阅 W3C 的分配属性值、级联和继承。

    Microsoft 已充分利用级联并使用它作为使用自定义样式覆盖默认样式的主要方式。在 SharePoint 中默认加载的大量 .css 样式来自 Corev4.css 文件以及 SharePoint 2010 根据特定页面的需要即时加载的其他一些相关 .css 文件。Corev4 和其他默认 .css 文件从 [..]\14\TEMPLATE\LAYOUTS\1033\STYLES 文件夹加载,该文件夹位于 SharePoint 根文件夹中,其中包含大多数 SharePoint 安装文件。

    有关在 SharePoint 2010 中默认加载的所有样式的列表,请参阅SharePoint Foundation 中级联样式表类的用法。

    品牌打造的一项主要任务是使用自定义 .css 覆盖默认样式,自定义 .css 将重新设置 SharePoint 功能的样式以符合整体网站品牌。在 SharePoint 2010 中,Microsoft 添加了 After 属性以允许自定义 .css 始终在特定 .css 文件(如默认 CoreV4.css 文件)之后加载。以下代码显示用于加载自定义级联样式表的 After 属性。

    HTML
     
    <SharePoint:CssRegistration name="/Style Library/sitename/style.css" After="corev4.css" runat="server"/>
    
    Gg430141.note(zh-cn,office.14).gif注释:

    After 属性需要较完整的路径才能在其他自定义 .css 文件之后加载某一 .css 文件。例如,要在自定义 style.css 文件之后加载另一 .css 文件,请使用以下代码。

    <SharePoint:CssRegistration name="/Style Library/sitename/morestyles.css" After="/Style Library/sitename/style.css" runat="server"/> 

     

    简易母版页中的 CssRegistration 设置为在发布网站的样式库中的 SiteName 子文件夹下查找自定义 .css。您应该将简易母版页中引用的 SiteName 文件夹替换为实际网站的名称。

    Gg430141.note(zh-cn,office.14).gif注释:

    引用 Web 文件(如自定义样式表)时,SharePoint Server 2010 提供了 $SPUrl 标记以表示相对于网站集根目录的 URL 或相对于网站根目录的 URL。可以将简易母版页中的样式表引用编写为使用此功能,如下所示:<SharePoint:CssRegistration name”<% $SPUrl:-sitecollection/Style Library/sitename/style.css %>” After=”corev4.css” runat=”server”/>

    在将品牌部署到并非位于 Web 应用程序根目录的网站集时,使用该方法的优点便会突显出来。使用相对于网站集的 URL 可确保样式从网站集自身的样式库加载,而不是从根网站集的样式库加载。使用该方法的缺点是以这种方式引用时 设计视图无法显示某些资产。为了简单起见,本文不在 URL 中使用 $SPURL 变量。

     

    考虑品牌对 SharePoint 对话框的影响

    SharePoint 2010 中一项强大的新功能是对话框框架。许多菜单页在位于主要页面内容之上的模式对话框中加载。这会影响品牌效果,因为默认情况下所有自定义品牌(包括徽标、页眉、导航和页脚)都显示在对话框内。为了防止品牌元素显示在对话框中,SharePoint 2010 提供了一个名为 s4-notdlg 的级联样式表类。如果将此类应用于某个元素,SharePoint 2010 将自动在对话框中隐藏该元素。在简易母版页中,我使用此类在对话框中隐藏了品牌元素。图 7 显示应用于对话框的自定义品牌。



    图 7. 对话框中的自定义品牌

    对话框中的自定义品牌

    处理 Name.dll ActiveX 控件

    显示面向 Internet 的发布网站时,Internet Explorer 浏览器会在未将 SharePoint 2010 服务器添加到受信任的网站列表时显示一条烦人的消息。此消息要求用户添加 Name.dll ActiveX 控件。

    通常,SharePoint 的匿名用户不使用此控件,如果要求加载此控件,则很可能会使不熟悉 SharePoint 的用户放弃使用。您可以在“管理中心”的“管理 Web 应用程序”部分的“常规设置”页上禁用该消息。请将“对成员启用人名智能标记和联机状态显示”设置为“否”

    您可以通过向母版页添加 ECMAScript(JavaScript、JScript)代码禁止显示该消息。简易母版页包含以下 JavaScript 代码,这些代码将隐藏该消息。

    JavaScript
     
    <script type="text/javascript"> 
    function ProcessImn(){}
    function ProcessImnMarkers () {}
    </script>
    

    有关联机状态的详细信息,请参阅 SharePoint 2010 中的联机状态(该链接可能指向英文页面)。

    处理旧式浏览器

    大多数情况下,由于 SharePoint 2010 不支持 Internet Explorer 6 浏览器,Microsoft 建议警告 Internet Explorer 6 用户,他们的体验可能会受影响。Microsoft 提供了一个可在母版页中使用的 WarnOnUnsupportedBrowsers 控件,以警告用户他们的浏览器不受支持,如以下示例所示。

    HTML
     
    <SharePoint:WarnOnUnsupportedBrowsers runat="server"/>
    

    简易母版页在靠近代码底部的位置使用 WarnOnUnsupportedBrowsers 控件;要禁用该通知,请从母版页中删除该控件。

    使用 SharePoint Designer 创建母版页

    在简易母版页的代码准备就绪后,将母版页添加到 SharePoint 中。Microsoft SharePoint Designer 2010 非常适合执行此任务。

    使用 SharePoint Designer 2010 向 SharePoint 添加简易母版页

    1. 在 Microsoft SharePoint Designer 2010 中打开 SharePoint Server 2010 发布网站。

    2. 在“网站对象”面板中,单击“母版页”。这会打开母版页样式库,所有母版页和页面布局都在这里创建。

    3. 在功能区上,单击“空白母版页”,然后将它命名为 AdventureWorks.master

    4. 单击名为 AdventureWorks.master 的文件,然后在功能区上单击“编辑文件”。SharePoint 会打开包含默认内容的新母版页。

    5. 选择所有内容,然后按 Delete 以删除。接下来,复制“StarterPublishing.master”(随文章下载内容提供)的内容并粘贴到 AdventureWorks.master。

    6. 要保存更改,请在 SharePoint Designer 2010 中单击“保存”

    7. 在“网站对象”菜单中,单击“母版页”,右键单击“AdventureWorks.master”,然后单击“签入”。在“签入”菜单上,选择“发布主要版本”,然后单击“确定”

    8. 由于对母版页样式库应用了审批工作流,所以会显示一条警告:“此文档要求内容审批。是否要查看或修改其审批状态?”。单击“是”

    9. SharePoint Web 界面会在浏览器中打开。如果要求您进行身份验证,请使用您的用户名和密码登录。

    10. “母版页样式库”将会打开,并显示按“审批状态”分组的视图。在“AdventureWorks.master”的右侧单击,然后单击“批准/拒绝”

    11. 对于“审批状态”,请选择“已批准”,然后单击“确定”

      Gg430141.note(zh-cn,office.14).gif注释:

      要将母版页添加到 SharePoint,请将它们作为主要版本签入,然后在除签出文件的用户以外的其他用户之前发布并批准它们,以允许用户访问应用了该母版页的网站。这同样适用于对母版页所做的所有更改:只有在作为主要版本签入、发布并批准更改后,其他用户才能看到更新。

       

    在 SharePoint Designer 2010 中处理 SharePoint 文件时,请注意,SharePoint 会将它们置于自定义状态,这可能会影响网站维护。本文的最后一节介绍以非自定义状态将品牌文件部署到 SharePoint 的过程。由于会进行自定义,因此最好仅在开发环境中通过 SharePoint Designer 处理品牌文件,而不是在运行 SharePoint 的生产服务器上处理文件的最终版本。有关在 SharePoint 中创建非自定义文件的详细信息,请参阅了解并在 Windows SharePoint Services 3.0 中创建自定义和非自定义文件 (该链接可能指向英文页面)。尽管本文提到的是 SharePoint 的早期版本,但是所有概念和代码也适用于 SharePoint 2010。

    应用母版页

    签入并批准母版页后,下一步骤是将母版页应用于 SharePoint 网站。

    将母版页应用于 SharePoint 网站

    1. 依次单击“网站操作”和“网站设置”,然后在“外观”部分单击“母版页”

    2. 对于“网站母版页”和“系统母版页”,选择“AdventureWorks.master”,然后单击“重置所有子网站以继承此网站母版页设置”

    3. 确保“备用 CSS URL”设置为“使用 Microsoft SharePoint Foundation 默认样式”。单击“确定”

    通过将母版页应用于“网站母版页”和“系统母版页”,所有发布页面和应用程序页面都会使用自定义品牌设置样式。这是 SharePoint 2010 中的新功能;默认情况下,在 Office SharePoint Server 2007 中,自定义母版页不应用于应用程序页面(如“网站设置”菜单)。应用设置了丰富样式的母版页(如 Adventure Works Travel)作为“系统母版页”的一个潜在缺点是,需要进行更多测试以确保所有设置页和列表都呈现正确的自定义品牌。对“系统母版页”应用自定义母版页的决策纯粹是业务决策。

    Gg430141.note(zh-cn,office.14).gif注释:

    应用于应用程序页面的自定义母版页有时具有特定的用户界面需求。例如,在“网站设置”中,“用户”和“权限”菜单必须具有在自定义母版页中可见的 PlaceHolderLeftNavBar 内容占位符,以显示人员和组。此外,有时如果缺少元素(如必需的内容占位符),应用程序页面不显示错误,而是恢复为显示标准 v4.master 页。

     

    应用简易母版页后,网站的外观为空,可以应用品牌。简易母版页当然不太具有吸引力,但是下面几节会解决这一问题。



    图 8. 应用于发布网站的简易母版页

    应用于发布网站的初学者母版页

    向 SharePoint 添加 .css 和图像文件

    Adventure Works Travel 的品牌需要 .css 文件和图像才能正常工作。之前已经为 HTML 模型创建了这些文件和图像,它们随与 MSDN 示例 - 实际 SharePoint 品牌打造(该链接可能指向英文页面)相关联的可下载代码提供。

    向样式库添加品牌文件

    1. 在“网站对象”菜单中,单击“所有文件”。在主窗口中的“所有文件”列表中,单击“样式库”

    2. 在功能区上,单击“文件夹”以创建一个新文件夹,并将它命名为 AdventureWorks

    3. 单击新的“AdventureWorks”文件夹,然后拖动 MSDN 示例 - 实际 SharePoint 品牌打造(该链接可能指向英文页面)文章下载内容中 HTML Branding 文件夹内的所有图像、favicon.ico 和 style.css。

    4. 选择已添加到“样式库”的所有文件,右键单击,然后选择“签入”

    5. 在“签入”菜单上,单击“发布主要版本”,然后单击“确定”。因为样式库未应用任何审批工作流,所以无需审批文件。

    使用 HTML 构建母版页

    在将所有品牌文件添加到 SharePoint 网站后,下一步是开始将 HTML 设计中的代码添加到简易母版页中。添加 HTML 时,也是执行以下操作的最佳时机:开始在整体布局中四处移动简易母版页的各个区域并进行任何其他特定于网站的更改。确认 Adventure Works.master 已在 SharePoint Designer 2010 中打开,并且已签出以进行编辑。要签出文件,请单击“网站对象”菜单上的“母版页”。在主窗口中,如果 AdventureWorks.master 旁边没有绿色的复选标记,请右键单击该文件,然后单击“签出”

    对于 Adventure Works Travel 网站,该过程首先处理简易母版页的部分。该部分的三个区域包含的 Site Name 字样可以更改为 Adventure Works,包括PlaceHolderPageTitleSPShortcutIcon 和 CssRegistration 占位符。

    C#
     
    <title runat="server"><asp:ContentPlaceHolder id="PlaceHolderPageTitle" 
      runat="server">Adventure Works</asp:ContentPlaceHolder></title>
    
    C#
     
    <SharePoint:SPShortcutIcon runat="server" IconUrl="/Style Library/AdventureWorks/favicon.ico"/>
    
    C#
     
    <SharePoint:CssRegistration name="/Style Library/AdventureWorks/style.css" After="corev4.css" runat="server"/>
    

    Adventure Works 具有自己的自定义样式表,因此简易母版页的部分中包含的内嵌 .css 代码可以移动到以下路径:Style Library/AdventureWorks/style.css

    Gg430141.note(zh-cn,office.14).gif注释:

    您可以忽略代码的整个功能区部分。除非出现特殊情况,否则大多数母版页都可以使用默认功能区代码。

     

    接下来,复制原 HTML 设计中 <form> 和 </form> 标记之间的所有内容并将其粘贴到母版页中的 <div id="MSO_ContentDiv" runat="server"> 标记后面。接下来的几节介绍哪些 SharePoint 功能区域将从简易母版页中较靠下的部分上移到粘贴的 HTML 代码中。

    Gg430141.note(zh-cn,office.14).gif注释:

    下面的某些信息可能很难理解,因此打开 Adventure Works 母版页的最终版本(随文章下载内容提供)并参考它来理解所述内容可能会有帮助。

     

    使用 HTML 构建母版页

    1. Adventure Works 是面向公众的 Internet 网站,设计者决定对匿名用户隐藏功能区,改为显示简单的“User Login”(用户登录)链接。对用户进行身份验证后,“User Login”(用户登录)链接消失,完整的功能区显示在顶部。默认情况下,简易母版页中不包含代码。使用了 <asp:Loginview> 标记,以便对匿名用户和登录用户显示不同的 HTML 代码。新的自定义 <div> 标记包含这段代码。

      HTML
       
      <div class="customTopLeft"> 
      <asp:LoginView id="LoginView1" runat="server"> 
      <AnonymousTemplate>
      <div class="customLogin"<a href="/_layouts/authenticate.aspx">User Login</a></div>
      <style type="text/css" 
      body #s4-ribbonrow {
      display: none;  }
      </style>
      </AnonymousTemplate>
      <LoggedInTemplate>
      <style type="text/css"> 
      .customLogin {
      display: none;  }
      </style>
      </LoggedInTemplate>
      <asp:LoginView>
      </div>
      
      
    2. 由于在 SharePoint Server 2010 中,customTop <DIV> 标记不应该显示在对话框中,因此必须添加 s4-notdlg .css 类。

      HTML
       
      <div class="customTop s4-notdlg">
      
    3. 静态搜索 HTML 替换为 PlaceHolderSearchArea 占位符和 SmallSearchInputBox 委托控件。

      HTML
       
      <div class="customSearch">
       <asp:ContentPlaceHolder id="PlaceHolderSearchArea" runat="server">
        <SharePoint:DelegateControl runat="server" ControlId="SmallSearchInputBox" Version="4"/>
       </asp:ContentPlaceHolder>
      </div>
      
      
    4. 在 SharePoint 2010 中,customHeader <DIV> 标记不应该显示在对话框中,因此必须添加 s4-notdlg .css 类。

    5. 指向主页的静态链接 (<a class="customLogo" href="#"><img src="logo.png" alt="Back to Home" title="Back to Home" /></a>) 替换为自定义徽标 (<div class="customLogo">) 标记,简易母版页中的 SharePoint 链接按钮 <SharePoint:SPLinkButton> 和 <SharePoint:SiteLogoImage> 标记移动到其中。此外,LogoImageUrl 标记从 sitename 更改为AdventureWorks。这些更改显示在以下标记中。

      HTML
       
      <div class="customLogo">
        <SharePoint:SPLinkButton runat="server"
        NavigateUrl="~sitecollection/">
        <SharePoint:SiteLogoImage LogoImageUrl="/Style Library/AdventureWorks/logo.png" AlternateText="Back to Home" ToolTip="Back to Home" runat="server"/>
        </SharePoint:SPLinkButton>
        </div>
      
      

       

    6. 静态导航替换为 SharePoint 全局导航控件以及相应数据源。还可以从 <div class="menu horizontal customTopNavHolder"> 删除菜单以及水平方向的 .css 类,因为 SharePoint 现在将处理此 .css 代码。

      HTML
       
      <div class="customTopNavHolder">
      <PublishingNavigation:PortalSiteMapDataSource
        ID="topSiteMap"
        runat="server"
        EnableViewState="false"
        SiteMapProvider="GlobalNavigation"
        StartFromCurrentNode="true"
        StartingNodeOffset="0"
        ShowStartingNode="false"
        TrimNonCurrentTypes="Heading"/>
      
       <SharePoint:AspMenu
         ID="TopNavigationMenuV4"
         Runat="server"
         EnableViewState="false"
         DataSourceID="topSiteMap"
         AccessKey="<%$Resources:wss,navigation_accesskey%>"
         UseSimpleRendering="true"
         UseSeparateCss="false"
         Orientation="Horizontal"
         StaticDisplayLevels="1"
         MaximumDynamicDisplayLevels="1" 
         SkipLinkText=""
         CssClass="s4-tn">
       </SharePoint:AspMenu> 
      </div>
      
      
    7. 在 customHeader 结束 </DIV> 标记和 customMain <DIV> 标记之间添加默认 SharePoint 2010 状态栏 <DIV> 标记,如以下标记所示。

      HTML
       
      </div>
      
      <div class="s4-notdlg">
       <div id="s4-statusbarcontainer">
        <div id="pageStatusBar" class="s4-status-s1"></div>
       </div>
      </div>      
      <div class="customMain">
      
      
    8. 接下来将添加左侧导航。但是由于 Adventure Works 品牌具有样式独特的导航,因此最好仅在创建 Adventure Works 发布页面时显示品牌化的导航,而不是在所有应用程序页面上或其他任何位置显示。仅使用 PlaceHolderLeftNavBar 的内容占位符,删除它的任何常见内容,如 AspMenu 和数据源占位符。删除这些占位符可使 Adventure Works 页面布局使用品牌化的导航覆盖该内容占位符,需要左侧导航的任何其他页面也可以使用各自的导航覆盖该占位符。对于不包含左侧导航的页面,将占位符设置为完全隐藏左侧面板,以便界面左侧没有空白区域。另外请注意,外部的 <DIV> ID 标记和 Class 与 HTML 模型中的 customMainLeft 类合并在一起。这种合并可使默认 SharePoint .css 文件应用于左侧导航以及 Adventure Works 的任何自定义品牌。

      HTML
       
      <div id="s4-leftpanel" class="customMainLeft s4-notdlg">
       
       <asp:ContentPlaceHolder id="PlaceHolderLeftNavBar" runat="server">
      
        <style type="text/css"> 
         #s4-leftpanel {
          display: none;
         }
         .customMainRight {
          width: inherit;
          padding-left: 10px;
         }
        </style>
       </asp:ContentPlaceHolder>
        
      </div>
      
      
    9. 在 HTML 模型中,左侧导航下面显示一个旅行计划工具。在 SharePoint 2010 中,这是适合放置 Web 部件区域的位置。您可以在页面布局中添加 Web 部件区域,但不要在母版页中添加。因此,要添加 Web 部件区域,请在 PlaceHolderLeftNavBar 内容占位符的下方添加 PlaceHolderLeftActions 内容占位符。Adventure Works 页面布局将覆盖PlaceHolderLeftActions 内容占位符,不覆盖此占位符的任何页面都不会在此母版页区域中显示任何内容。

      C#
       
      <asp:ContentPlaceHolder id="PlaceHolderLeftActions" runat ="server"/>
      
      
    10. 大部分页面内容都包含在 customMainRight <DIV> 标记中。添加 s4-ca 类以便 SharePoint 可以使用自己的级联样式表控制该区域。

      HTML
       
      <div class="s4ca customMainRight">
      
    11. 接下来,将痕迹导航、页面标题和页面说明放置在各自的 <DIV> 部分并应用 s4-notdlg .css 类,以便不在对话框中显示。对于页面标题和说明来说,这与添加PlaceHolderPageTitleInTitleArea 和 PlaceHolderPageDescription 内容占位符一样简单。痕迹导航需要另外完成一些工作,因为 SharePoint 2010 的默认痕迹导航菜单是页面左上角的弹出菜单。此弹出菜单非常适合 Intranet 网站,但在面向公众的 Internet 网站上无法对匿名用户正常显示。要实现与较传统的痕迹导航相同的功能,请使用 SiteMapPath类:<asp:SiteMapPath runat="server" />

      HTML
       
      <div class="customMainContent">
       <div class="s4-notdlg">
        <div class="customBreadcrumbs">
         <asp:SiteMapPath runat="server"/>
        </div>     
      
        <h1 class="customPageTitle"><asp:ContentPlaceHolder id="PlaceHolderPageTitleInTitleArea" runat="server" /></h1>
        
        <asp:ContentPlaceHolder id="PlaceHolderPageDescription" runat="server" />
       </div>
      
    12. customMainContent 部分中的其余 HTML 模型内容由 PlaceHolderMain 内容占位符处理,并且最终由页面布局提供。此代码包含副标题、页面内容和“热门活动”(将成为 Web 部件)。直接删除本部分的所有内容并替换为占位符,如以下示例所示。

      HTML
       
      <asp:ContentPlaceHolder id="PlaceHolderPageDescription" runat="server" />
        </div>
      
        <asp:ContentPlaceHolder id="PlaceHolderMain" runat="server"/>
       </div>
      </div>
      
      
    13. 由于在 SharePoint 2010 中 customFooter <DIV> 部分不应该出现在对话框中,请添加 s4-notdlg .css 类 <div class="customFooter s4-notdlg">

    14. 上移简易母版页代码中的开发人员仪表板代码并将这些代码放置在 customFooter 结束 </DIV> 标记的后面。

      HTML
       
      </div>
      <div id="DeveloperDashboard" class="ms-developerdashboard">
      <SharePoint:DeveloperDashboard runat="server"/>
      </div>
      
      
    15. 删除位于开发人员仪表板结束 </DIV> 标记之后、三个结束 </DIV> 标记和 PlaceholderFormDigest 占位符之前的其余所有简易母版页代码。

    此时,Adventure Works Travel 母版页已完成。您应该签入母版页、作为主要版本发布它,然后批准它,以便用户可以看到变化。尽管此时母版页已完成,但是网站看起来仍不像最终设计。网站需要向 style.css 文件添加更多自定义 .css 代码才能完成外观的设计。

    为 SharePoint 网站设计制定 .css 规则

    将所有 .css 文件和图像添加到“样式库”之后,您可以看到 style.css 文件,该文件包含确定 HTML 设计的外观的所有样式。为了使级联样式表与其他 SharePoint 功能协同工作,需要对 .css 代码进行一些更改。本节首先更改 HTML 设计区域的必须更新的 .css 代码,然后添加一大段 .css 代码,这些代码用于设置 SharePoint 功能元素的样式。

    Gg430141.note(zh-cn,office.14).gif注释:

    在 SharePoint 中处理 .css 代码可能很有挑战性,因为应用的 .css 规则数量巨大。一次使用超过 5,000 行 .css 代码时,设计人员和开发人员通常求助于工具,来帮助他们处理 SharePoint 中的 .css 文件。Internet Explorer 8 开发人员工具和 Firebug FireFox 插件就是两个这样的工具。这两个工具都可用于检查和处理应用于网页(包括 SharePoint 页面)的 .css 代码。这两个工具共有的一项重要功能是,通过指向页面区域,便可更好地了解应用于该区域的所有 .css 代码并查看 .css 级联覆盖了哪些规则。

     

    更新 SharePoint 网站设计的 .css 代码

    1. 向 a:hover 样式添加颜色以确保链接悬停颜色与 SharePoint 中的其余链接相符。

      HTML
       
      a:hover {
       color: #0077b4;
       text-decoration: underline;
      
      
    2. 向主要内容区域添加自动滚动效果 (overflow:auto)。

      Gg430141.note(zh-cn,office.14).gif注释:

      品牌元素将在整个 SharePoint 中使用(包括应用程序页面和列表),所以向主要内容区域添加自动滚动效果可能会有帮助。添加自动滚动效果可使非常宽的页面在品牌内滚动,而不是在品牌外和背景上显示。

       

      HTML
       
      .customMain {
       width: 100%;
       background-color: white;
       min-height: 400px;
       padding:8px 20px;
       width:937px;
       overflow:auto;
      } 
      
      
    3. 调整 .customMainRight 类的宽度。.customMainRight 的宽度默认情况下为 760 像素。如果隐藏左侧导航,母版页或页面布局会调整宽度,以展开并填满整个中间区域。

      HTML
       
      .customMainRight { 
       width:760px;
       padding-bottom:15px;
       float: left;
      }
      
    4. 从区域的 HTML 模型中删除若干现有样式,这些区域稍后将添加特定 SharePoint 样式,包括搜索、导航、顶部导航和左侧导航的样式。您可以删除下面的每个类以及所有相应的 .css 代码。

      HTML
       
      .menu ul
      .menu ul, .menu li
      .horizontal li
      .customSearch input
      .customSearchGo
      .customSearchGo:hover
      .customTopNavHolder li
      .customTopNavHolder li:hover
      .customTopNavHolder li a
      .customLeftNavHolder li 
      
      
    5. 添加若干样式以打造搜索区域的品牌,包括隐藏默认搜索按钮、添加具有悬停效果的品牌化按钮以及为搜索框添加样式。

      HTML
       
      /* search button hider */
      .customSearch .ms-sbgo img {
       display: none;
      }
      
      /* fancy search button */
      .customSearch .ms-sbgo a {
       display: block;
       height:17px;
       width:32px;
       background:transparent url('but_go.gif') no-repeat scroll left top;
       margin: 0px;
       padding: 0px;
       position: relative;
       top: 0px; 
      }
      
      /* search button hover */
      .customSearch .ms-sbgo a:hover {
       background-image: url('but_go_on.gif');
      }
      
      /* search box style */
      .customSearch input.ms-sbplain {
       font-size:1em;
       height:15px;
       margin-right: 5px;
       background-image: none;
       color: #999999;
      }
      
      
    6. 添加若干样式以处理各种顶部导航元素,包括隐藏默认箭头、项目样式和悬停状态、动态弹出项目容器以及弹出项目和悬停状态。

      HTML
       
      /* arrow for flyouts */
      .menu-horizontal a.dynamic-children span.additional-background,
      .menu-horizontal span.dynamic-children span.additional-background {
       padding-right:0px;
       background-image:none;
      }
      
      /* item style */
      .s4-tn li.static > .menu-item {
       white-space:nowrap;
       border:0px none transparent;
       padding:12px 10px 5px;
       display:inline-block;
       vertical-align:middle;
       color:white;
       font-family:arial,helvetica,sans-serif;
       font-size: 105%;
       font-weight: bold;
       background-image:url('dottedline.gif');
       background-position:right top;
       background-repeat:no-repeat;
       background-color:transparent;
      }
      
      /* item style hover */
      .s4-tn li.static > a:hover {
       color: white; 
       text-decoration: none;
       background-image:url('nav_hover.gif');
       background-position:right top;
       background-repeat: repeat-x;
      }
      
      /* flyout holder */
      .s4-tn ul.dynamic {
       background-color:#1e4b68;
       border:0px none;
      }
      
      /* flyout item */
      .s4-tn li.dynamic > .menu-item {
       display:block;
       white-space:nowrap;
       font-weight:normal;
       background-color: #1E4B68;
       background-repeat: repeat-x;
       padding:4px 8px 4px 10px;
       font-family:arial,helvetica,sans-serif;
       border-top: 0px;
       color: #ffffff;
      }
      
      /* flyout item hover */
      .s4-tn li.dynamic > a:hover {
       font-weight:normal;
       text-decoration:none;
       background-color: #b5d8ee;
       color: #222222;
      }
      
      
    7. 左侧导航仅将样式应用于导航中的项目,不应用于设计。由于 Adventure Works Travel 中的左侧导航不显示弹出项目,因此没有为这些状态添加样式。

      HTML
       
      /* left nav item style */
      .customLeftNavHolder li > .menu-item {
       background-image:url('arrow.gif');
       background-position:left center;
       background-repeat:no-repeat;
       border-bottom:1px solid #ECF0EF;
       padding:4px 0 4px 14px;
      }
      
      
    8. 左侧栏中的 Web 部件需要特殊的样式,以便标题包含品牌元素并减少一些空白和边距。

      HTML
       
      /* Web Part title for left column */
      .customLeftWPHolder .ms-WPTitle {
       color:inherit;
       padding:0px;
       font-family: Arial,sans-serif;
       font-weight: bold;
       font-size: 1.2em;
       margin-bottom: 0;
       text-transform: uppercase;
       background-image:url('ticket_bg.gif');
       background-position:left top;
       background-repeat:no-repeat;
       height:30px;
       line-height:34px;
       padding-left:4px;
      }
      
      /* Web Part padding for left column */
      .customLeftWPHolder .ms-wpContentDivSpace {
       padding: 0px;
      }
      
      /* Remove some white space from Web Parts in left column */
      .customLeftWPHolder .ms-WPHeader .ms-wpTdSpace {
       display:none;
      }
      
      /* remove border from bottom of Web Parts in left column */
      .customLeftWPHolder .ms-WPHeader td {
       border-bottom: none;
      }
      
      
    9. 在所有 HTML 设计样式后面,添加若干特定于 SharePoint 的 .css 样式。本部分中每个样式规则的开头都是描述其特定用途的注释。前几个是以内嵌方式包含在简易母版页中的样式。

      HTML
       
      /* hide body scrolling (SharePoint will handle) */ 
      body { 
      height:100%; 
      overflow:hidden; 
      width:100%; 
      } 
      /* Pop-out breadcrumb menu needs background color for Firefox */ 
      .s4-breadcrumb-menu { 
      background:#F2F2F2; 
      } 
      /* If you want to change the left navigation width, change this and the margin-left in .s4-ca */ 
      body #s4-leftpanel { 
      padding-right:20px; 
      } 
      /* body area */ 
      .s4-ca { 
      margin-left:auto; 
      } 
      /* Fix scrolling on list pages */ 
      #s4-bodyContainer { 
      position: relative; 
      } 
      /* Fix the font on some built-in menus */ 
      .propertysheet, .ms-authoringcontrols { 
      font-family: Verdana,Arial,sans-serif;; 
      line-height: normal; 
      } 
      /* Nicer border between top bar and page */ 
      .ms-cui-topBar2 { 
      border-bottom: 1px solid #666666; 
      } 
      /* Hide the hover state for the ribbon links */ 
      #s4-ribbonrow a:hover { 
      text-decoration: none; 
      } 
      /* Fix ribbon line height */ 
      #s4-ribbonrow { 
      line-height: normal; 
      } 
      /* Make site settings links look normal */ 
      .ms-linksection-level1 ul li a { 
      font-weight:normal; 
      } 
      /* Hide the left margin when dialog is up */ 
      .ms-dialog .customCentered, .ms-dialog .customMain, .ms-dialog .customMainRight { 
      margin-left:0 !important; 
      margin-right:0 !important; 
      min-height:0 !important; 
      min-width:0 !important; 
      width:auto !important; 
      height:auto !important; 
      background-color: white !important; 
      background-image: none !important; 
      padding: 0px !important; 
      overflow:inherit; 
      } 
      /* Dialog bg */ 
      .ms-dialog body { 
      background-color: white; 
      background-image: none; 
      } 
      /* Fix dialog padding */ 
      .ms-dialog .s4-wpcell-plain { 
      padding: 4px; 
      } 
      
      

    将最后几个样式规则添加到 style.css 后,Adventure Works Travel 品牌的 .css 代码就完成了。签入 style.css 文件并将它作为主要版本发布,以便最终用户可以看到变化。图 9 显示有了很大改观的 SharePoint 品牌。



    图 9. 即将完成的 SharePoint 品牌作业

    即将完成的 SharePoint 品牌作业
    Gg430141.note(zh-cn,office.14).gif注释:

    页面的内容部分看起来仍不像设计模型。此区域将使用自定义页面布局打造品牌。

     

    创建自定义页面布局

    通过使用页面布局作为发布网站中的一种页面模板,设计人员和开发人员可创建将在母版页设计中使用的不同类型的页面设计。除了覆盖母版页的内容占位符外,页面布局还使用字段控件、Web 部件和 Web 部件区域定义所有可编辑的页面内容区域。要了解有关字段控件与 Web 部件的差异的详细信息,请参阅了解 SharePoint Server 2007 发布网站中的字段控件和 Web 部件 (该链接可能指向英文页面)。尽管本文面向 Office SharePoint Server 2007,但概念和功能同样适用于 SharePoint 2010。

    SharePoint 中的每个页面布局都是基于一种特定 SharePoint 内容类型创建的。内容类型定义可用于存储页面数据的所有网站栏。这些网站栏组成可在页面布局中使用的可用字段控件。为了简单起见,Adventure Works Travel 页面布局将使用现有的默认欢迎页面内容类型。此内容类型具有创建 Adventure Works Travel 页面所需的足够多的网站栏,并且现有主页布局可轻松替换为新页面布局。

    创建 Adventure Works Travel 页面布局

    1. 在“网站操作”菜单上,单击“页面布局”

    2. 在功能区上,单击“新建页面布局”

    3. 在“新建页面布局”窗口中,执行以下操作:

      • 对于“内容类型组”,选择“页面布局内容类型”

      • 对于“内容类型名称”,选择“欢迎页面”

      • 对于“URL 名称”,键入 AW_Layout.aspx

      • 对于“标题”,键入 Adventure Works Page

    4. 单击“确定”

    SharePoint Designer 会打开创建了 PlaceHolderPageTitle 和 PlaceHolderMain 内容占位符的新页面布局。

    C#
     
    <asp:Content ContentPlaceholderID="PlaceHolderPageTitle" runat="server">
    <SharePointWebControls:FieldValue id="PageTitle" FieldName="Title" runat="server"/>
    </asp:Content>
    <asp:Content ContentPlaceholderID="PlaceHolderMain" runat="server">
    </asp:Content>
    

    使用 SharePoint Designer 编辑页面布局

    接下来,将通过添加字段控件和 Web 部件区域来编辑 Adventure Works Travel 页面布局。您可以从 SharePoint Designer 中的特定任务窗格轻松添加这些元素。

    Gg430141.note(zh-cn,office.14).gif注释:

    必须使用 SharePoint Designer 2010 中的高级模式编辑页面布局。如果尝试在正常模式下编辑页面布局,所有内容都将以黄色突出显示,以指示内容不可编辑。新页面布局自动在高级模式下打开;打开现有页面布局时,在功能区上指向“编辑文件”,然后单击“在高级模式下编辑文件”

     

    字段控件

    使用 SharePoint Designer 中的“工具箱”窗格可向页面布局添加字段控件。将要使用的字段控件直接从“工具箱”窗格拖到将包含它们的内容控件。

    Web 部件区域

    要向内容控件添加 Web 部件区域,请使用“设计视图”或“拆分视图”在 SharePoint Designer 中选择内容控件,然后在功能区上单击“Web 部件区域”。添加 Web 部件区域会创建一个空白 Web 部件区域,可为该区域指定描述性更强的标题以帮助内容作者在编辑页面时识别它。

    完成 Adventure Works Travel 页面布局

    通过添加控件、内嵌样式和其他元素完成 Adventure Works Travel 的页面布局。

    完成 Adventure Works Travel 页面布局

    1. 添加 PlaceHolderAdditionalPageHead 内容控件和一些内嵌样式,以控制页面左侧和右侧区域的宽度。

      C#
       
      <asp:Content ContentPlaceholderID="PlaceHolderLeftActions" runat="server">
       <div class="customLeftWPHolder">
        <WebPartPages:WebPartZone id="LeftZone" runat="server" title="Left Zone"><ZoneTemplate></ZoneTemplate></WebPartPages:WebPartZone>
       </div>
      </asp:Content>
      
      
    2. 向页面布局添加 PlaceHolderPageTitle 内容控件,并在 PageTitle 字段控件前面添加文本 Adventure Works -。这些操作会在页面标题前面插入该文本,并将所有文本放置在 HTML 页面标题中。

      C#
       
      <asp:Content ContentPlaceholderID="PlaceHolderPageTitle" runat="server">
       Adventure Works - <SharePointWebControls:FieldValue id="PageTitle" FieldName="Title" runat="server"/>
      </asp:Content>
      
    3. 添加包含 TitleField 字段控件的 PlaceHolderPageTitleInTitleArea 内容控件。这些控件会在页面内容前面添加页面标题。

      C#
       
      <asp:Content ContentPlaceholderID="PlaceHolderPageTitleInTitleArea" runat="server">
       <SharePointWebControls:TextField runat="server" id="TitleField" FieldName="Title"/>
      </asp:Content>
      
    4. 向页面添加 PlaceHolderLeftNavBar 以添加 HTML 模型中的“相关链接”标题,然后添加从简易母版页中删除的左侧导航 AspMenu 和数据源。添加这些内容会使基于此页面布局创建的页面显示品牌化的左侧导航。

      C#
       
      <asp:Content ContentPlaceholderID="PlaceHolderLeftNavBar" runat="server">
       <div class="customTicketTitle">
        <h1>RELATED LINKS</h1>
       </div>
      
       <PublishingNavigation:PortalSiteMapDataSource
        ID="SiteMapDS" runat="server" EnableViewState="false" SiteMapProvider="CurrentNavigation" StartFromCurrentNode="true"
        StartingNodeOffset="0" ShowStartingNode="false" TrimNonCurrentTypes="Heading"/>  
       
       <SharePointWebControls:AspMenu
        ID="CurrentNav" runat="server" EnableViewState="false" DataSourceID="SiteMapDS" UseSeparateCSS="false"
        UseSimpleRendering="true" Orientation="Vertical" StaticDisplayLevels="1" MaximumDynamicDisplayLevels="0"
        CssClass="customLeftNavHolder" SkipLinkText="<%$Resources:cms,masterpages_skiplinktext%>"/>
      </asp:Content>
      
      
    5. PlaceHolderMain 内容占位符的以 <WebPartPages:SPProxyWebPartManager /> 控件开头,该控件是在页面布局中使用 Web 部件区域时由 SharePoint Designer 自动添加的。接下来,添加 Comments 字段控件以允许内容作者编辑页面的副标题。然后添加 PublishingPageContent 字段控件。此控件包含页面的主要发布 HTML 内容。

      C#
       
      <asp:Content ContentPlaceholderID="PlaceHolderMain" runat="server">
       <WebPartPages:SPProxyWebPartManager runat="server" id="ProxyWebPartManager"></WebPartPages:SPProxyWebPartManager>
      
       <div class="customSubTitle">
        <SharePointWebControls:NoteField FieldName="Comments" InputFieldLabel="SubTitle" DisplaySize="50" runat="server"></SharePointWebControls:NoteField>
       </div>
          
       <PublishingWebControls:RichHtmlField FieldName="PublishingPageContent" runat="server"/>
      </asp:Content>
      
      

    这是创建 Adventure Works Travel 页面布局所需的全部代码。必须首先签入该页面布局,作为主要版本发布并批准该页面布局,然后内容作者才能基于它创建页面。

    更改页面的页面布局

    自定义页面布局完成后,您可以基于它创建新页面。这对于使用新内容填充网站当然很有用,但是仍必须完成一个步骤才能使主页看起来像初始设计组合。因为主页使用默认页面布局,所以您需要使用自定义 Adventure Works Travel 页面布局替换主页。

    Gg430141.note(zh-cn,office.14).gif注释:

    用于创建发布网站的网站模板确定哪些页面布局可供选择。根据创建发布网站时所用的页面布局,您可能需要首先更改可用的页面布局,然后才能选择新页面布局。要更改可用的页面布局,请单击“网站操作”,依次指向“网站设置”和“外观”,然后单击“页面布局和网站模板”。此设置页具有“页面布局”选项,可从中选择可用的页面布局。尝试一些页面布局(包括新自定义页面布局)的最简单方法是选择“此网站中的页面可以使用任何布局”,然后单击“确定”

     

    从主页切换到新页面布局

    1. 单击“网站操作”,然后单击“编辑网页”

    2. 在功能区上,单击“页面”,然后单击“页面布局”。在“欢迎页面”组下的下拉列表中,选择“Adventure Works Page”

    3. 页面将刷新,新页面布局应用于该页面,如图 10 所示。



    图 10. 编辑模式中的 Adventure Works Travel 主页

    编辑模式中的 Adventure Works Travel 主页

    在这里,可以编辑页面以包含任何内容,包括副标题、页面内容和任何 Web 部件。在 SharePoint Server 2010 中,Web 部件不一定要添加到 Web 部件区域;它们还可以添加到发布页面和 Wiki 页中的丰富 HTML 内容区域。图 11 显示添加了 HTML 模型中的所有内容的最终页面。



    图 11. 编辑模式中添加了所有内容的 Adventure Works Travel 主页

    包含所有内容的 Adventure Works Travel 主页

    完成所有更改后,只需单击功能区左上角的小“保存”图标,或者单击“页面”,然后单击“保存并关闭”。如果对页面库激活了发布工作流,则必须首先发布并批准页面,然后最终用户才能看到新内容。

    打包和部署 SharePoint 品牌

    此时,Adventure Works Travel 网站品牌已创建,并且已应用于现有 SharePoint 发布网站。尽管以这种方式创建和应用品牌非常适合测试演示用途,但下一步还要通过以下操作完成品牌打造工作:打包品牌文件(包括图像, .css 文件、母版页和页面布局中的 JavaScript 及标记),以便可以将品牌文件添加到其他环境中。最终生产的包将使网站设计人员可以轻松分发品牌文件。以下是完成此步骤的一些方法。

    品牌部署选项

    要部署自定义品牌文件,第一种选择是直接使用网站集备份和还原功能。这种方式在面向 Internet 的方案中不太理想,因为所有文件仍然是自定义文件。有关 SharePoint 中自定义和非自定义文件的差异和含义的详细信息,请参阅了解并在 Windows SharePoint Services 3.0 中创建自定义和非自定义文件 (该链接可能指向英文页面)。如果将品牌文件作为自定义文件部署和管理,重新打造网站品牌的活动可能会变得非常复杂。因此,非自定义品牌打造和管理过程是不错的选择,特别是对于流量较高的网站和填充了大量内容的网站。

    部署非自定义品牌文件时,发布网站实施者在确定文件的部署位置时有几种不同的选择。以下是三种最常见的选择:

    • 将品牌文件部署到网站的顶级文件夹。

    • 将品牌文件部署到网站的 _layouts 目录。

    • 将品牌文件部署到网站集的内容数据库。

    上述每种选择都有各自的优缺点,MSDN 文章在 SharePoint Server 2007 发布网站中实施品牌 (该链接可能指向英文页面)中介绍了这些优缺点。本文其余的内容假定采用最后一种选择:将品牌文件部署到网站集的内容数据库。通过将品牌文件部署到网站集的内容数据库,维护和将来可能需要的重新打造品牌活动更容易开展。所有文件都将部署到网站集的母版页样式库和“样式库”中,这两个库均位于所有 SharePoint 发布网站集的根网站中。

    要将品牌文件部署到网站集的内容数据库中,必须使用 SharePoint 功能框架将文件设置到网站集的内容数据库中,具体说就是“母版页样式库”和“样式库”中。通过功能框架,可在 SharePoint 内容数据库中创建文件的自定义和非自定义实例。源文件(包括图像, .css 文件、JavaScript 库、母版页和页面布局)作为功能的一部分部署并保留在文件系统中。激活功能后,它将源文件的非自定义实例设置到指定位置。

    将品牌文件部署到网站集的内容数据库的过程通常由开发人员和管理员处理,因为它涉及将文件放置在文件系统中以及创建功能和 SharePoint 解决方案包(.wsp 文件)。

    SharePoint 品牌打包和部署过程概述

    本文其余部分所选择和演示的打包和部署方法使负责自己专业领域的人员可以只关注该领域。这样,为发布网站实施和部署新品牌的过程可以更加清晰、更加顺利地完成。例如,打造自定义 SharePoint 品牌的过程共分三部分,到目前为止本文已介绍了其中两个:

    • 在 SharePoint 中创建品牌时将“欢迎”菜单和“网站操作”控件等考虑在内。

    • 在 SharePoint 中通过以下方式实施品牌:使用母版页和页面布局、覆盖 SharePoint 默认 .css 代码、添加某些 JavaScript 代码以解决面向 Internet 的网站特有的一些问题。

    具有网站和品牌设计者角色的用户负责这两部分。第三部分通常由网站开发人员角色完成,该角色创建 SharePoint 功能和 WSP,它们用于将文件部署和设置到网站集。设计人员需要将品牌化的发布网站移交给开发人员进行打包。网站开发人员从网站集中提取文件并将它们添加到新功能中,然后将该功能添加到解决方案包(.wsp 文件)中,部署 .wsp 文件并通过激活该功能测试品牌文件。如果网站设计人员和开发人员在不同的环境中工作(品牌打造外包出去时就属于这种情况),对于设计人员来说最简单的方法是使用 Windows PowerShell 备份网站集、将备份文件发送给开发人员,让开发人员将网站集还原到新 SharePoint Web 应用程序中。

    Gg430141.note(zh-cn,office.14).gif注释:

    本文其余部分假定读者创建了两个 Web 应用程序:http://test.adventure-works.com 和 http://test1.adventure-works.comhttp://test.adventure-works.com Web 应用程序应该为空,不包含网站集。http://test1.adventure-works.com Web 应用程序应该在根目录中包含一个基于发布门户模板的网站集。

     

    将品牌化网站集从设计人员移交给开发人员

    如果设计人员和开发人员在同一共享环境中工作,则无需备份以便将网站集从一个环境转移到另一环境。然而,如果品牌打造工作外包给外部供应商(如代理机构),则开发人员需要通过简单的方法获取所实施品牌的副本。这相当容易。

    网站设计人员可以备份网站集,并将备份文件发送给开发人员。备份甚至可以通过电子邮件发送,因为网站集可能不是特别大并且未包含任何内容。使用 Windows PowerShell 备份网站,如以下示例所示。

    Windows PowerShell
     
    PS C:\> $siteCollection = Get-SPSite | Where-Object {$_.Url -eq [URL USED WHEN CREATING AND TESTING THE BRAND]}
    PS C:\> Backup-SPSite -Identity $siteCollection -Path "C:\AdventureWorksBranded.dat"
    
    

    将 c:\AdventureWorksBranded.dat 文件交给开发人员。开发人员可以将网站还原到他们的环境中。Microsoft 建议将备份的网站集还原到不包含其他网站集的新 SharePoint Web 应用程序的根目录。以这种方式还原备份可确保不会意外引用其他网站中的文件。要将网站集还原到 http://test.adventure-works.com 网站,请再次使用 Windows PowerShell,如以下代码所示。

    Windows PowerShell
     
    PS C:\> Restore-SPSite "http://test.adventure-works.com" -Path "C:\AdventureWorksBranded.dat"
    
    

    Windows PowerShell 会提示您确认要还原网站。

    使用备份/还原方法将网站集从一个环境移到另一环境时,大多数开发人员需要另外执行一个步骤。因为两个环境可能来自不同的域,所以网站集主管理员在还原后的环境中不再是有效的帐户。通过在“应用程序管理”页上选择“网站集管理员”链接,可在“管理中心”中快速更改这种情况。选择备份的网站还原到的网站集,并将网站集主管理员更改为用于登录网站并从中提取文件的帐户。

    开发人员现在即可拥有品牌化发布网站的本地副本。

    创建 Visual Studio 项目以包含并打包品牌文件功能

    现在开发人员已经拥有品牌化发布网站的本地副本,下一步骤是创建 Visual Studio 2010 项目,该项目将包含功能并将用于创建解决方案包。在 Microsoft Visual Studio 2010 中,Microsoft 引入了可靠的 SharePoint 开发工具以简化该任务。Visual Studio 2010 中的 SharePoint 开发工具包含在安装的 Visual Studio 2010 中。

    从品牌化示例发布网站集中提取品牌文件并将它们添加到 Visual Studio 项目中

    1. 在 Visual Studio 2010 中使用“空白 SharePoint 项目”模板创建新的 SharePoint 2010 项目,务必选择 .NET Framework 3.5 作为目标框架版本。

    2. 根据 Visual Studio 2010 在 SharePoint 自定义向导中的提示,传递测试项目时要使用的网站集 URL (http://test1.adventure-works.com) 并将它指定为场解决方案。

    3. 向 Visual Studio 项目中添加文件的容器。

    4. 将发布网站中的文件复制到 Visual Studio 项目。

    5. 修改项目文件,以包含所有添加的文件。

    6. 添加“样式库”文件。

    7. 添加“母版页样式库”中的文件。

    添加“样式库”文件。

    将样式库文件添加到 Visual Studio 中

    将发布网站的“样式库”中的文件复制到项目中。在浏览器中打开“样式库”,切换到“资源管理器”视图,将文件复制并粘贴到 Visual Studio。

    将 SharePoint“样式库”中的文件添加到 Visual Studio 中

    1. 为“样式库”中的文件设置 Visual Studio 项目容器或模块。右键单击项目名称,单击“添加”,然后单击“新建项”

    2. 在“添加新项”对话框中,从“SharePoint/2010”类别中选择“模块”,并将它命名为 StyleLibraryModule

    3. 在解决方案资源管理器中,从“StyleLibraryModule”中删除 sample.txt 文件,因为它只是占位符文件。

    将“样式库”中的文件复制到项目中

    1. 在浏览器中,打开 http://test.adventure-works.com

    2. 在“网站操作”菜单上,单击“管理网站内容和结构”

    3. 在左侧文件夹视图中,悬停在“样式库”上,并使用“编辑控制块(ECB)”菜单选择“在新窗口中打开链接”

    4. 双击“样式库”中的“AdventureWorks”节点,然后在功能区的“库工具”菜单上单击“库”。在“连接和导出”组中,使用 Windows 资源管理器打开项目。

    5. 在 Windows 资源管理器中复制“AdventureWorks”节点中的所有文件并将它们粘贴到 Visual Studio 项目的“StyleLibraryModule”中。

     

    XML
     
    <Module Name="StyleLibraryModule"
        Url="/Style Library/AdventureWorks"
        RootWebOnly="TRUE">
    
    

    现在,将设置到“样式库”中的每个文件必须作为子 File 元素添加到 Module 元素中。每个条目都应该指定要设置的文件名称和文件 Type。两个 Type 选项是 Ghostable 和GhostableInLibrary。这两个选项都会将非自定义实例设置到网站集中,但是因为这些文件必须注册为“样式库”中的内容,所以应将类型设置为 GhostableInLibrary。此外,每个 File元素的 Url 属性必须进行更新,以便将文件放置在 Module 元素指定的位置。要更新 Url 属性,请通过将 Module 元素更新为以下标记来删除在 Url 属性中指定的子文件夹。

    XML
     
    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
      <Module Name="StyleLibraryModule"
              Url="Style Library/AdventureWorks"
              RootWebOnly="TRUE">
        <File Path="StyleLibraryModule\bg.gif" Url="bg.gif" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\but_go.gif" Url="but_go.gif" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\but_go_on.gif" Url="but_go_on.gif" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\dottedline.gif" Url="dottedline.gif" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\favicon.ico" Url="favicon.ico" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\footer_bg.png" Url="footer_bg.png" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\glory.jpg" Url="glory.jpg" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\logo.png" Url="logo.png" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\microsoft_logo.gif" Url="microsoft_logo.gif" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\nav_hover.gif" Url="nav_hover.gif" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\style.css" Url="style.css" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\ticket_bg.gif" Url="ticket_bg.gif" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\wp_topactivities.jpg" Url="wp_topactivities.jpg" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\wp_tripplanner.jpg" Url="wp_tripplanner.jpg" Type="GhostableInLibrary" />
        <File Path="StyleLibraryModule\arrow.gif" Url="arrow.gif" Type="GhostableInLibrary" />
      </Module>
    </Elements>
    
    

    接下来,添加母版页样式库中的文件。

    添加母版页样式库中的文件

    部署 Adventure Works Travel 自定义品牌所需的其他文件位于母版页样式库中。需要移动该库中的两个文件:

    • AdventureWorks.master,它是用于实施品牌的自定义母版页。

    • AW_layout.aspx,它是页面布局,基于网站主页使用的默认“欢迎页面”内容类型。

    将 SharePoint 母版页样式库中的文件添加到 Visual Studio 中

    1. 为“母版页样式库”中的文件设置 Visual Studio 项目容器或模块。右键单击项目名称,单击“添加”,然后选择“新建项”

    2. 在“添加新项”对话框中,从“SharePoint/2010”类别中选择“模块”,并将它命名为 MasterPageGalleryModule

    3. 在解决方案资源管理器中,从“MasterPageGalleryModule”中删除 sample.txt 文件,因为它只是占位符文件。

    现在可以从网站集检索文件,并将它们添加到 Visual Studio 项目中。

    下载母版页和页面布局

    1. 在浏览器中,打开 http://test.adventure-works.com

    2. 在“网站操作”菜单上,单击“网站设置”。在“库”部分,选择“母版页和页面布局”

    3. 下载 AdventureWorks.master 和 AW_layout.aspx 两个文件的副本。为此,请选择相应项,并在下拉菜单中指向“发送至”,然后单击“下载副本”。将两个文件保存到桌面。

    4. 使用 Windows 资源管理器,将两个文件复制到 Visual Studio 项目的 StyleLibraryModule 中。

    5. 要将三个文件都设置到母版页样式库中,请修改 MasterPageGalleryModule\Elements.xml 路径中的 Elements.xml 文件。

      XML
       
      <?xml version="1.0" encoding="utf-8"?>
      <Elements Id="94022f3a-580a-4745-9d9c-42c21f79fdfe" xmlns="http://schemas.microsoft.com/sharepoint/">
        <Module Name="MasterPageGalleryModule"
                Url="_catalogs/masterpage" RootWebOnly="TRUE">
        </Module>
      </Elements>
      
      
    6. 在 <Module> 开始标记后面添加以下标记,以设置母版页。

      Gg430141.note(zh-cn,office.14).gif注释:

      设置母版页时需要该模块指定其他字段,例如,设置时哪种内容类型要与母版页文件相关联。

       

      XML
       
      <File Url="AdventureWorks.master"
            Path="MasterPageGalleryModule\AdventureWorks.master" Type="GhostableInLibrary">
        <Property Name="ContentType"
                  Value="$Resources:cmscore,contenttype_masterpage_name;" />
        <Property Name="Title" 
                  Value="Adventure Works Travel Custom Branding" />
      </File>
      
      
    7. 添加页面布局。与母版页一样,必须设置其他属性。然而,页面布局使用的内容类型与母版页不同,所以还需要使用 PublishingAssociatedContentType 属性指定与页面布局相关联的内容类型。

    8. 在母版页的 <File /> 标记后面添加以下标记。

      XML
       
      <File Url="AW_layout.aspx"
            Path="MasterPageGalleryModule\AW_layout.aspx" Type="GhostableInLibrary">
        <Property Name="ContentType"
                  Value="$Resources:cmscore,contenttype_pagelayout_name;" />
        <Property Name="PublishingAssociatedContentType"
                  Value=";#Welcome Page;#0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF390064DEA0F50FC8C147B0B6EA0636C4A7D4;#" />
        <Property Name="Title" Value="Adventure Works Travel Branded Welcome Page" />
      </File>
      

    此时,Visual Studio 项目已完成,并且包含应该设置到“母版页样式库”和“样式库”中的所有文件。

    打包、部署和测试品牌打造功能

    确认正确配置了 Microsoft Visual Studio 2010 中的 SharePoint 开发工具自动创建的功能。在 vsstudio2010short 的解决方案资源管理器中,双击 AdventureWorksBranding\Features\Feature1\Feature1.feature 文件以打开功能设计器。请注意,“范围”当前设置为“Web”。将“范围”更改为“网站”,因为此功能必须显示在“管理网站集功能”页上。另外请注意,功能中有两项是您创建的两个模块。

    现在 Visual Studio 项目已完成,您可以打包、部署和测试新的品牌打造功能。通过在新 Web 应用程序的根目录下创建干净的发布门户网站集来测试品牌打造功能。创建新 Web 应用程序和发布门户网站集的原因是旨在消除任何文件引用还原的网站集中的文件(如图像或 .css 文件)的可能性。在以下测试中,使用发布门户模板创建了新 Web 应用程序http://test1.adventure-works.com,并且在 Web 应用程序的根目录下创建了新的网站集。

    测试新的品牌打造功能

    1. 在 Visual Studio 2010 中,按 F5 编译、打包、部署并激活功能。

    2. 导航到 http://test1.adventure-works.com,依次指向“网站操作”和“网站设置”,然后单击“修改所有网站设置”

    3. 通过在“外观”组中选择“母版页”来切换母版页,并将“网站母版页”设置为“AdventureWorks.master”

    4. 返回网站的主页并确认使用的是新品牌。与之前一样,页面看起来有点奇怪,因为它未使用品牌打造功能部署的页面布局。

    5. 导航到“网站操作”菜单并单击“编辑页面”来更新页面,以便它使用正确的页面布局。

    6. 使用功能区切换页面布局:指向“页面”,从“页面操作”组中选择“页面布局”,然后从“欢迎页面”部分选择品牌化的 Adventure Works Travel 页面布局。

    7. 要在其他环境中部署品牌,请在 Visual Studio 项目的 \bin\debug 文件夹中查找 *.wsp 文件。这是解决方案包文件,它包含用于将所有品牌文件设置到发布网站集的功能。

    结论

    本文介绍使用自定义设计打造 Microsoft SharePoint Server 2010 发布网站的品牌的整个过程。首先,本文分步向网站设计人员介绍了为新 SharePoint 发布网站创建品牌的过程,包括分析发布网站和特定于 SharePoint 的控件所特有的问题。接下来,本文介绍了如何将原型中的设计组合转换为使用母版页、页面布局、CSS 和图像的品牌化发布网站的实际实现。最后,本文介绍了如何将品牌化的发布网站转换为功能,以便日后更轻松地维护品牌。为此,需要使用 Visual Studio 2010 中的 SharePoint 开发工具并创建一个新项目,该项目设置自定义品牌涉及的所有文件。

    其他资源

    有关详细信息,请参阅以下资源:

    • MSDN 示例 - 实际 SharePoint 品牌打造(该链接可能指向英文页面) 

    • Microsoft Office Online:SharePoint Designer 2007 帮助和操作方法:修改默认母版页 

    • 了解并在 Windows SharePoint Services 3.0 中创建自定义文件和未自定义文件 (该链接可能指向英文页面) 

    • 在 SharePoint Server 2007 中创建母版页和页面布局的方法 (该链接可能指向英文页面) 

    • 在 SharePoint 2007 发布网站中实施品牌 (该链接可能指向英文页面) 

    • 了解 SharePoint Server 2007 发布网站中的字段控件和 Web 部件 (该链接可能指向英文页面) 

    • 在 SharePoint Server 2007 中向母版页和页面布局添加代码隐藏文件 

    • WROX:Professional SharePoint 2010 Branding and User Interface Design(《专业级 SharePoint 2010 品牌打造和用户界面设计》)(该链接可能指向英文页面) 

    • WROX:Professional SharePoint 2007 Web Content Management Development(《专业级 SharePoint 2007 Web 内容管理开发》)(该链接可能指向英文页面) 

    • 企业内容管理资源中心 | SharePoint 2010 

    • SharePoint 开发中心 
    展开全文
  • 个性化推荐算法总结

    万次阅读 多人点赞 2019-04-11 23:24:58
    读书笔记 |《推荐系统实践》- 个性化推荐系统总结 对于推荐系统,本文总结内容,如下图所示: 一、什么是推荐系统 1. 为什么需要推荐系统 为了解决互联网时代下的信息超载问题。 2. 搜索引擎与推荐系统 ...
  • 个性篮球队名_你的篮球个性是什么

    千次阅读 2020-09-13 13:22:22
    个性篮球队名The game of basketball continues to evolve as time passes. It is hard to imagine, but the game began without dribbling and the three point shot is a ‘modern’ invention many people alive ...
  • 2020年10月12日斗鱼虎牙合并的瓜

    千次阅读 2020-10-13 18:42:36
    今天看知乎的时候 ,看到了斗鱼被转让企鹅电竞业务并退市,斗鱼、虎牙将按 1:1 合并,直播行业将迎来哪些变化?。好奇之下看了看。里面的知乎网友说了许多有意思的观点。可惜我没有专业知识不好分析这起事件,只能...
  • Sublime Text 3 个性设置和快捷键总结

    千次阅读 2017-01-21 16:57:15
    另发表于Levi.Blog和oschina Sublime Test 3 个性设置和快捷键总结
  • 导语:业务快速增长给搜索带来什么样的挑战?针对类似场景如何设计通用的平台?本文详细讲述Airbnb大型搜索服务的演进之路。 去年,Airbnb到了需要可扩展、分布式存储系统的时候了。例如,搜索个性化数据超过了...
  • 个性化搜索架构设计探索

    千次阅读 2017-02-03 18:01:58
    基于种种急需解决的痛点,需要平台级项目支持商品搜索实现个性化,需求期望细化为以下四点: 1.根据积累的用户数据,在搜索排序中引入个性化因子,打破千人一面的排序格局。精排序时,除通用的商品精排分数,还...
  • RDIFramework.NET,基于.NET的快速信息化系统开发、整合框架,为企业或个人在.NET环境下快速开发系统提供了强大的支持,开发人员不需要开发系统的基础功能和公共模块,框架自身提供了强大的函数库和开发包,开发人员...
  • 微博社区发现在舆情分析,个性化推荐等方面具有重要的应用价值。该方法通过高度重叠社区的合并和划分误差的修正,进一步提高了微博社区发现的准确率。为了提高微博社区发现的效率,利用开源云计算平台Hadoop所提供的...
  • 本次内部结构调整,与地图部门一样,苹果将继续建立其条线的 AI/ML 团队,专注于云计算以及设备本地的个性化敏感计算。TechCrunch 指出苹果拥有超过 10 亿台相关设备,目前正在构建有史以来最大的人工智能边缘计算...
  • 个性化联邦学习综述

    2021-06-15 19:56:40
    步走的个性化方法存在的问题 《Improving federated learning personalization via model agnostic meta learning》这篇文章提出,第一步一味地对全局模型进行优化,得到的最优全局模型并不利于后续的个性化处理...
  • 本文从构建用户模型到个性化推荐,介绍了达观数据的一些实践...个性化推荐系统能有效解决信息过载和长尾物品个方面的问题,不仅提供了极佳的用户体检,满足了用户的信息需求,也帮助了企业挖掘其中蕴含的无限商机。
  • 个性化智能推荐系统分析与调研

    千次阅读 2018-04-09 15:09:57
    1. 系统简介从市场用户调研到互联网电商平台产品设计,再到上线运营推广,覆盖的面很宽,但最为关键和难度最高的是个性化推荐系统、搜索底层和大数据系统。其中,个性化推荐系统和搜索底层都是基于大数据,所以最终...
  • 定制Hexo-NexT的个性化博客

    千次阅读 2019-07-28 18:47:55
    前几天,我写了一篇关于如何使用GitHub+Hexo搭建个人博客的博文,接下来我会以我的博客为例,讲讲如何对NexT主题的个性化定制和一些细节的优化。 前言 这篇博客主要是next主题的个性化定制,还有一些细节的优化。...
  • 个性化推荐系统实践

    千次阅读 2016-11-21 15:41:43
    这三种兴趣是离线计算的,还要考虑用户的实时兴趣,我们通过很短的时间间隔进行近线挖掘分析,从而快速适应用户当前的信息需求。 通过上述过程,最终就为每个用户生成了各个维度上的偏好和兴趣点数据。 ...
  • 一、券商实施个性化推荐项目的必要性1.1 个性化推荐技术发展背景目前,随着用户接收到的信息量爆炸般增长,传统的推荐以及服务方式的边际收益正在不断减少,用户个性化的需求变得越来越多。大数据实时个性化服务,...
  • JFinal快速入门笔记

    千次阅读 2017-02-21 17:00:47
    JFinal快速入门笔记(尚不完整)
  • 官方的默认有个快速符号的分组,然后里面都是一些数学符号,其格式是 输入码=候选位,输入内容 然后每个分组以[分组名]开始,值得一提的是 个性短语的导入是覆盖的,不像词库的导入是合并的.其次是个性短语的优先级是最高...
  • 不能在建项目的时间直接引入,参考了start.spring.io,尝试搭建自己的spring initializr服务,同时整合自己的一些package,提供个性化服务,快速开发。 目标 基本框架 Spring Initializr 提供核心REST API,可以整合...
  • 深度解析京东个性化推荐系统

    千次阅读 2019-09-30 19:20:08
    个性化推荐系统的主要优势体现为支持多类型推荐和多屏产品形态,支持算法模型A/B实验快速迭代,支持系统架构与算法解耦,支持存储资源与推荐引擎计算的解耦,支持预测召回与推荐引擎计算的解耦,支持自定义埋点功能...
  • 千人千面,撩拨你的个性化广告

    千次阅读 2018-04-09 00:00:00
    一,题记58同城智能推荐系统大约诞生于2014年(C++实现),该套系统先后经历了招聘、房产、二手车、黄页和二手物品等产品线的推荐业务迭代,但该系统耦合性高,难以适应推荐策略的快速迭代。58同城APP猜你喜欢推荐和...
  • 知识图谱辅助的个性化推荐系统

    千次阅读 2020-03-13 19:35:36
    常见的知识合并需求有个,一个是合并外部知识库,另一个是合并关系数据库。 将外部知识库融合到本地知识库需要处理个层面的问题: 数据层的融合,包括实体的指称、属性、关系以及所属类别等,主要的问题是如何...
  • 随着互联网技术以及软硬件技术的快速发展,网络已经成为人们生活中不可或缺的一部分,在长期的互联网冲浪中,网民对网络信息的辨识度日益增进,网络信息提供方也必须与时俱进,抓住用户的要害。 就拿我们淘宝的...
  • JEPF软件快速开发平台【免费发布】

    千次阅读 2016-03-01 13:42:15
    把企业级应用移至云端满足小企业的低成本需求同时兼顾大中型企业的个性化定制、大数据的处理和分析、人工智能和机器学习是JEPF架构师团队的终极目标。 强大的工作流引擎 引擎...
  • 作者 | fisherman、Davidxiaozhi“ 本文摘自《决战618:探秘京东技术取胜之道》,位作者时任京东推荐系统负责人和系统架构师。”在电商领域,推荐的价值在于挖掘用户潜在购买需求,缩短用户到商品的距离,提升用户...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,220
精华内容 5,688
关键字:

怎么快速合并两个行