個人資料的訪問地址是:user/profile
效果是:

主要分普通信息和頭像裁剪外加第三方登錄綁定。radio選中綁定用了eq標簽。
主要表單代碼:
~~~
<form action="/api.php/member/{$info.id}" method="POST" class="MemberInfo">
<div class="form-group">
<label>郵箱</label>
<input type="email" class="form-control" placeholder="Enter email" name="email" value="{$info.email}">
</div>
<div class="form-group">
<label>生日</label>
<input type="date" class="form-control" placeholder="出生日期" name="birthday" value="{$info.birthday}">
</div>
<div class="checkbox">
<label>
<input type="radio" value="1" name="sex" <eq name="info.sex" value="1">checked</eq>> 男
</label>
<label>
<input type="radio" value="0" name="sex" <eq name="info.sex" value="0">checked</eq>> 女
</label>
</div>
<div class="form-group">
<volist name="oauths" id="oauth">
<eq name="oauth.status" value="1">
<label for="">已經綁定{$oauth.title}</label> <a href="{:U('/user/unbindOauth','id='.$oauth['id'])}">取消綁定</a>
<else/>
<label for="">綁定{$oauth.title}</label> <a href="{:U('/user/oauth','type='.$oauth['type'])}">設置</a>
</eq>
<br>
</volist>
</div>
<div class="form-group">
<input type="file" id="upload_file" name="file" style="width:44px">
<input type="hidden" name="avatar" id="avatar" value="{$info.avatar|default=0}" />
<input type="hidden" name="id" value="{$info.id}">
</div>
<button type="submit" class="btn btn-primary ajax-put" target-form="MemberInfo">保存</button>
</form>
~~~
仍然用api put方式更新。
然后主要是頭像上傳后并支持了裁剪功能。
~~~
<script type="text/javascript">
var uploadify_swf = '__BOWER__/uploadify/uploadify.swf';
<present name="info">
var no_pic = '{$info.avatar|get_cover="path"}' || '__PUBLIC__/images/no-cover.png';
<else />
var no_pic = '__PUBLIC__/images/no-cover.png';
</present>
/* 初始化上傳插件 */
$(function(){
$("#upload_file").uploadify({
"width" : 150,
"height" : 150,
"buttonImage" : no_pic,
"multi": false,
"swf" : uploadify_swf,
"fileObjName" : "file",
"buttonText" : "",
'buttonClass' : "btn_upload",
"formData": {
'session_id': '{:session_id()}',
'ajax': 1,
'model': 'Picture',
'field': 'file',
},
"uploader" : "{:U('Upload/ajaxUpload')}",
"removeCompleted": true,
"removeTimeout" : 1,
"fileSizeLimit": '5MB',
"fileTypeDesc": '圖片',
"fileTypeExts" : '*.jpg; *.png; *.gif;',
"onFallback" : function() {
notify("未檢測到兼容版本的Flash");
},
"onUploadSuccess" : afterUpload,
"onUploadError": function(file, errorCode, errorMsg, errorString) {
var msg = '<a title="' + errorString + '">上傳失敗<a/>';
notify(msg);
}
});
function afterUpload(file, data){
console.log(data);
var data = $.parseJSON(data);
var src = '';
if(data.status){
$("#avatar").val(data.id);
src = data.url || '__ROOT__' + data.path;
var $layer = $('#jcrop_layer');
$('#jcrop_img', $layer).html('<img src="'+src+'" id="jcrop_target"/>');
$('[name="file"]', $layer).val(src);
$layer.on('show.bs.modal', function (event) {
function updateView (c){
$('[name="width"]').val(c.w);
$('[name="height"]').val(c.h);
$('[name="x"]').val(c.x);
$('[name="y"]').val(c.y);
}
var model = $(this);
$('#jcrop_target').Jcrop({
minSize:[100,100],
allowSelect:false,
aspectRatio: 1,
bgColor: 'black',
bgOpacity: .4,
boxWidht: 400,
boxHeight: 400,
setSelect: [ 200, 200, 50, 50 ],
onSelect: updateView
});
$('.cropForm').on('submit', function(){
var $this = $(this);
$.ajax({
url: $this.attr('action'),
type: 'POST',
dataType: 'json',
data: $this.serialize(),
})
.done(function(data) {
console.log(data);
if(data.status){
notify(data.info, 'success');
$("#upload_file").uploadify('settings', 'buttonImage', data.path);
$("#avatar").val(data.id);
$layer.modal('hide');
}else{
notify(data.info, 'error');
}
})
.fail(function() {
console.log("error");
})
.always(function() {
console.log("complete");
});
return false;
});
});
$layer.on('hidden.bs.modal', function (event) {
$(this).removeData('bs.modal');
});
$layer.modal('show');
} else {
notify(data.info);
}
}
});
</script>
~~~
效果是:


主要是用了[jcrop插件](http://deepliquid.com/content/Jcrop_Manual.html)。
首先,uploadify 寫了一個回調函數afterUpload。
然后回調函數里將上傳完的圖片替換 彈窗層里的img。
`$('#jcrop_img', $layer).html('<img src="'+src+'" id="jcrop_target"/>');`
在boostrap彈層 彈出事件里 初始化 裁剪插件:
~~~
function updateView (c){
$('[name="width"]').val(c.w);
$('[name="height"]').val(c.h);
$('[name="x"]').val(c.x);
$('[name="y"]').val(c.y);
}
var model = $(this);
$('#jcrop_target').Jcrop({
minSize:[100,100],
allowSelect:false,
aspectRatio: 1,
bgColor: 'black',
bgOpacity: .4,
boxWidht: 400,
boxHeight: 400,
setSelect: [ 200, 200, 50, 50 ],
onSelect: updateView
});
~~~
指定好各種參數。
~~~
<div id="jcrop_layer" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="gridSystemModalLabel">設置頭像</h4>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<div class="row center-block" id="jcrop_img"></div>
<div class="row">
圖片大小不能超過5M,且只允許為png、gif、jpg格式圖片
</div>
</div>
<div class="col-md-2"></div>
</div>
</div>
</div>
<div class="modal-footer">
<form action="{:U('/upload/crop')}" method="post" class="cropForm">
<input type="hidden" name="file">
<input type="hidden" name="width">
<input type="hidden" name="height">
<input type="hidden" name="x">
<input type="hidden" name="y">
<button type="submit" class="btn btn-primary">更新</button>
</form>
</div>
</div>
</div>
</div>
~~~
預覽后,拖拽選區,選擇要裁剪的 范圍區域,最后交給裁剪表單處理,即向upload/crop提交 file(文件路徑)、width(裁剪區域寬度)、height(裁剪區域高度)、x(裁剪區域x坐標)、y(裁剪區域y坐標)。
我們看上傳控制器里的crop方法:
~~~
public function crop($file, $x, $y, $width, $height){
$image = new \Think\Image();
$image->open('.'.$file);
//將圖片裁剪為$widthx$height并保存為$tmp_file
$tmp_file = date('Y_m_d_h_i_s').'.'.$image->type();
$local_file = './Uploads/tmp/'.$tmp_file;
$image->crop($width, $height,$x,$y)->save($local_file);
if(!is_file($local_file)){
$this->error('生成裁剪文件失敗');
}
$res = local_upload($local_file, 'Picture');
if($res['status'])
$this->success('裁剪成功', '', $res);
else
$this->error($res['error']);
}
~~~
主要就是調用image類的crop方法。裁剪出新的臨時圖片,然后用local_upload函數重新插入到picture表里。然后前臺替換頭像id。
local_upload函數:
~~~
/**
* 網站上文件模擬上傳,避免重復,且可以統一管理
* @param $local_file string 本地文件絕對路徑,
* @param $model string Picture 或 File 走哪個模型 對應圖片 和普通文件
* @param $save_path string 保存的目標路徑,盡量和項目中2種文件一致,當然支持自定義
* @return array 成功返回array('status'=>1, 'id'=>1, 'path'=>'./Uploads/picture/2015_04_05_09_45_00.png') 類似地址, 失敗status=0, error=失敗原因
*/
function local_upload($local_file, $model = 'Pictrue', $saveroot = './Uploads/picture/'){
if (!$local_file)
return array('status'=>0, 'error'=>'文件路徑為空');
$md5 = md5_file($local_file);
if($record = M($model)->where("md5 = '{$md5}'")->find()){
return array('status'=>1, 'id'=>$record['id'], 'path'=>$record['path']);
}
$upload_config = C(strtoupper($model).'_UPLOAD');
$ext_arr = explode(',', $upload_config['exts']);
$ext = strtolower(end(explode('.', $local_file)));
if (!in_array($ext, $ext_arr))
return array('status'=>0, 'error'=>"非法后綴{$ext}");
$filename = uniqid();
if($upload_config['autoSub']){
$rule = $upload_config['subName'];
$name = '';
if(is_array($rule)){ //數組規則
$func = $rule[0];
$param = (array)$rule[1];
foreach ($param as &$value) {
$value = str_replace('__FILE__', $filename, $value);
}
$name = call_user_func_array($func, $param);
} elseif (is_string($rule)){ //字符串規則
if(function_exists($rule)){
$name = call_user_func($rule);
} else {
$name = $rule;
}
}
$savepath .= $name.'/';
}
$savepath = $saveroot. $savepath;
if (!is_dir($savepath)){
if(!mkdir($savepath, 0777))
return array('status'=>0, 'error'=>"創建保存目錄{$savepath}失敗");
}
if (!is_readable($savepath)){
chmod($savepath, 0777);
}
$filename = $savepath . $filename . '.' . $ext;
ob_start();
readfile($local_file);
$file = ob_get_contents();
ob_end_clean();
$size = strlen($file);
slog($filename);
$fp2 = @fopen($filename, "a");
if(false === fwrite($fp2, $file)){
return array('status'=>0, 'error'=>'寫入目標文件失敗');
}
fclose($fp2);
$data = array(
'name' => end(explode('/', $local_file)),
'path' => str_replace('./', '/', $filename),
'md5' => $md5,
'sha1' => sha1($filename),
'status' => 1,
'create_time' => NOW_TIME,
);
$id = M($model)->add($data);
slog($local_file);
@unlink($local_file);
if(false === $id){
@unlink($filename);
return array('status'=>0, 'error'=>'保存上傳文件記錄失敗');
}else{
return array('status'=>1, 'id'=>$id, 'path'=>$data['path']);
}
}
~~~
這個函數是之前有個用OneThink做的內部管理系統項目,有個要求采集原有系統,插入到OneThink開發的新系統文章庫里。我通過采集文章內容,匹配第一個圖片地址,后寫的函數。為了符合onethink文件上傳的不重復的管理風格。

裁剪完完后替換avatar表單項和將uploadify 插件的按鈕背景設置為新圖片。
可能裁剪的圖片大于按鈕背景大小。
css處理了下:
~~~
.uploadify-button{
background-size: cover;
}
~~~
- 序
- 前言
- 內容簡介
- 目錄
- 基礎知識
- 起步
- 控制器
- 模型
- 模板
- 命名空間
- 進階知識
- 路由
- 配置
- 緩存
- 權限
- 擴展
- 國際化
- 安全
- 單元測試
- 拿來主義
- 調試方法
- 調試的步驟
- 調試工具
- 顯示trace信息
- 開啟調試和關閉調試的區別
- netbeans+xdebug
- Socketlog
- PHP常見錯誤
- 小黃鴨調試法,每個程序員都要知道的
- 應用場景
- 第三方登錄
- 圖片處理
- 博客
- SAE
- REST實踐
- Cli
- ajax分頁
- barcode條形碼
- excel
- 發郵件
- 漢字轉全拼和首字母,支持帶聲調
- 中文分詞
- 瀏覽器useragent解析
- freelog項目實戰
- 需求分析
- 數據庫設計
- 編碼實踐
- 前端實現
- rest接口
- 文章發布
- 文件上傳
- 視頻播放
- 音樂播放
- 圖片幻燈片展示
- 注冊和登錄
- 個人資料更新
- 第三方登錄的使用
- 后臺
- 微信的開發
- 首頁及個人主頁
- 列表
- 歸檔
- 搜索
- 分頁
- 總結經驗
- 自我提升
- 進行小項目的鍛煉
- 對現有輪子的重構和移植
- 寫技術博客
- 制作視頻教程
- 學習PHP的知識和新特性
- 和同行直接溝通、交流
- 學好英語,走向國際
- 如何參與
- 瀏覽官網和極思維還有看云
- 回答ThinkPHP新手的問題
- 嘗試發現ThinkPHP的bug,告訴官方人員或者push request
- 開發能提高效率的ThinkPHP工具
- 嘗試翻譯官方文檔
- 幫新手入門
- 創造基于ThinkPHP的產品,進行連帶推廣
- 展望未來
- OneThink
- ThinkPHP4
- 附錄