Browse Source

新增多图自定义描述数据功能
新增选择附件页上传功能
新增Api跨域判断
优化API文档绑定域名时URL判断
优化后台左侧无权限菜单的显示逻辑
优化验证码失败时自动刷新验证码
修复Windows下离线安装成功后不删除压缩包的BUG
修复通用搜索需要重置2次的BUG
修复表格导出配置不生效的BUG
修复data-table-id不统一的BUG
修复多图片预览时错误的BUG

Karson 5 years ago
parent
commit
8c76ac77a4

+ 12 - 3
application/admin/command/Api/library/Builder.php

@@ -167,6 +167,11 @@ class Builder
             $sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
         }
         arsort($sectorArr);
+        $routes = include_once CONF_PATH . 'route.php';
+        $subdomain = false;
+        if (config('url_domain_deploy') && isset($routes['__domain__']) && isset($routes['__domain__']['api']) && $routes['__domain__']['api']) {
+            $subdomain = true;
+        }
         $counter = 0;
         $section = null;
         $weigh = 0;
@@ -181,12 +186,16 @@ class Builder
                 if (0 === count($docs)) {
                     continue;
                 }
+                $route = is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0];
+                if ($subdomain) {
+                    $route = substr($route, 4);
+                }
                 $docslist[$section][$name] = [
                     'id'                => $counter,
                     'method'            => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
                     'method_label'      => $this->generateBadgeForMethod($docs),
                     'section'           => $section,
-                    'route'             => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0],
+                    'route'             => $route,
                     'title'             => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
                     'summary'           => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
                     'body'              => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
@@ -200,7 +209,7 @@ class Builder
                 $counter++;
             }
         }
-        
+
         //重建排序
         foreach ($docslist as $index => &$methods) {
             $methodSectorArr = [];
@@ -223,7 +232,7 @@ class Builder
     /**
      * 渲染
      * @param string $template
-     * @param array $vars
+     * @param array  $vars
      * @return string
      */
     public function render($template, $vars = [])

+ 1 - 0
application/admin/command/Api/library/Extractor.php

@@ -245,6 +245,7 @@ class Extractor
                 return \think\Loader::parseName($item);
             }, $suffixArr));
             $urlArr[] = $method->getName();
+
             $methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
         }
         if (!isset($methodAnnotations['ApiSector'])) {

+ 2 - 2
application/admin/command/Api/template/index.html

@@ -428,8 +428,8 @@
             $(document).ready(function () {
 
                 if (storage) {
-                    $('#token').val(storage.getItem('token'));
-                    $('#apiUrl').val(storage.getItem('apiUrl'));
+                    storage.getItem('token') && $('#token').val(storage.getItem('token'));
+                    storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
                 }
 
                 $('[data-toggle="tooltip"]').tooltip({

+ 2 - 0
application/admin/controller/Addon.php

@@ -191,6 +191,7 @@ class Addon extends Backend
             $tmpFile = $addonTmpDir . $info->getSaveName();
             try {
                 Service::unzip($tmpName);
+                unset($info);
                 @unlink($tmpFile);
                 $infoFile = $tmpAddonDir . 'info.ini';
                 if (!is_file($infoFile)) {
@@ -235,6 +236,7 @@ class Addon extends Backend
                     throw new Exception(__($e->getMessage()));
                 }
             } catch (Exception $e) {
+                unset($info);
                 @unlink($tmpFile);
                 @rmdirs($tmpAddonDir);
                 $this->error(__($e->getMessage()));

+ 25 - 1
application/admin/library/Auth.php

@@ -374,12 +374,28 @@ class Auth extends \fast\Auth
         $refererUrl = Session::get('referer');
         $pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
         // 必须将结果集转换为数组
-        $ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
+        $ruleList = collection(\app\admin\model\AuthRule::where('status', 'normal')
+            ->where('ismenu', 1)
+            ->order('weigh', 'desc')
+            ->cache("__menu__")
+            ->select())->toArray();
+        $indexRuleList = \app\admin\model\AuthRule::where('status', 'normal')
+            ->where('ismenu', 0)
+            ->where('name', 'like', '%/index')
+            ->column('name,pid');
+        $pidArr = array_filter(array_unique(array_map(function ($item) {
+            return $item['pid'];
+        }, $ruleList)));
         foreach ($ruleList as $k => &$v) {
             if (!in_array($v['name'], $userRule)) {
                 unset($ruleList[$k]);
                 continue;
             }
+            $indexRuleName = $v['name'] . '/index';
+            if (isset($indexRuleList[$indexRuleName]) && !in_array($indexRuleName, $userRule)) {
+                unset($ruleList[$k]);
+                continue;
+            }
             $v['icon'] = $v['icon'] . ' fa-fw';
             $v['url'] = '/' . $module . '/' . $v['name'];
             $v['badge'] = isset($badgeList[$v['name']]) ? $badgeList[$v['name']] : '';
@@ -389,6 +405,14 @@ class Auth extends \fast\Auth
             $selected = $v['name'] == $fixedPage ? $v : $selected;
             $referer = url($v['url']) == $refererUrl ? $v : $referer;
         }
+        $lastArr = array_diff($pidArr, array_filter(array_unique(array_map(function ($item) {
+            return $item['pid'];
+        }, $ruleList))));
+        foreach ($ruleList as $index => $item) {
+            if (in_array($item['id'], $lastArr)) {
+                unset($ruleList[$index]);
+            }
+        }
         if ($selected == $referer) {
             $referer = [];
         }

+ 1 - 0
application/admin/view/general/attachment/select.html

@@ -7,6 +7,7 @@
                 <div class="widget-body no-padding">
                     <div id="toolbar" class="toolbar">
                         {:build_toolbar('refresh')}
+                        <span><button type="button" id="plupload-image" class="btn btn-success plupload" data-mimetype="{$Think.get.mimetype|default=''}" data-multiple="true"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
                         {if request()->get('multiple') == 'true'}
                         <a class="btn btn-danger btn-choose-multi"><i class="fa fa-check"></i> {:__('Choose')}</a>
                         {/if}

+ 20 - 0
application/common/controller/Api.php

@@ -11,6 +11,7 @@ use think\Lang;
 use think\Loader;
 use think\Request;
 use think\Response;
+use think\Route;
 
 /**
  * API控制器基类
@@ -90,6 +91,25 @@ class Api
      */
     protected function _initialize()
     {
+        if (Config::get('url_domain_deploy')) {
+            $domain = Route::rules('domain');
+            if (isset($domain['api'])) {
+                if (isset($_SERVER['HTTP_ORIGIN'])) {
+                    header("Access-Control-Allow-Origin: " . $this->request->server('HTTP_ORIGIN'));
+                    header('Access-Control-Allow-Credentials: true');
+                    header('Access-Control-Max-Age: 86400');
+                }
+                if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
+                    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
+                        header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
+                    }
+                    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
+                        header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
+                    }
+                }
+            }
+        }
+
         //移除HTML标签
         $this->request->filter('trim,strip_tags,htmlspecialchars');
 

+ 1 - 1
application/common/controller/Backend.php

@@ -254,7 +254,7 @@ class Backend extends Controller
         $search = $this->request->get("search", '');
         $filter = $this->request->get("filter", '');
         $op = $this->request->get("op", '', 'trim');
-        $sort = $this->request->get("sort", "id");
+        $sort = $this->request->get("sort", $this->model->getPk() ?: 'id');
         $order = $this->request->get("order", "DESC");
         $offset = $this->request->get("offset", 0);
         $limit = $this->request->get("limit", 0);

+ 1 - 1
application/config.php

@@ -272,7 +272,7 @@ return [
         //自动检测更新
         'checkupdate'         => false,
         //版本号
-        'version'             => '1.0.0.20190510_beta',
+        'version'             => '1.0.0.20190628_beta',
         //API接口地址
         'api_url'             => 'https://api.fastadmin.net',
     ],

+ 2 - 2
composer.json

@@ -15,14 +15,14 @@
         }
     ],
     "require": {
-        "php": ">=5.4.0",
+        "php": ">=5.6.0",
         "topthink/framework": "~5.0.24",
         "overtrue/wechat": "~3.1",
         "endroid/qr-code": "^1.9",
         "topthink/think-captcha": "^1.0",
         "mtdowling/cron-expression": "^1.2",
         "phpmailer/phpmailer": "^5.2",
-        "karsonzhang/fastadmin-addons": "~1.1.4",
+        "karsonzhang/fastadmin-addons": "~1.1.9",
         "overtrue/pinyin": "~3.0",
         "phpoffice/phpspreadsheet": "^1.2"
     },

+ 3 - 3
public/api.html

@@ -3730,7 +3730,7 @@
 
             <div class="row mt0 footer">
                 <div class="col-md-6" align="left">
-                    Generated on 2019-02-26 17:13:43                </div>
+                    Generated on 2019-06-28 12:14:48                </div>
                 <div class="col-md-6" align="right">
                     <a href="https://www.fastadmin.net" target="_blank">FastAdmin</a>
                 </div>
@@ -3810,8 +3810,8 @@
             $(document).ready(function () {
 
                 if (storage) {
-                    $('#token').val(storage.getItem('token'));
-                    $('#apiUrl').val(storage.getItem('apiUrl'));
+                    storage.getItem('token') && $('#token').val(storage.getItem('token'));
+                    storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
                 }
 
                 $('[data-toggle="tooltip"]').tooltip({

+ 1 - 1
public/assets/js/backend/category.js

@@ -32,7 +32,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'name', title: __('Name'), align: 'left'},
                         {field: 'nickname', title: __('Nickname')},
                         {field: 'flag', title: __('Flag'), formatter: Table.api.formatter.flag},
-                        {field: 'image', title: __('Image'), operate: false, formatter: Table.api.formatter.image},
+                        {field: 'image', title: __('Image'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},
                         {field: 'weigh', title: __('Weigh')},
                         {field: 'status', title: __('Status'), operate: false, formatter: Table.api.formatter.status},
                         {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}

+ 7 - 0
public/assets/js/backend/general/attachment.js

@@ -71,6 +71,8 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
             table.bootstrapTable({
                 url: $.fn.bootstrapTable.defaults.extend.index_url,
                 sortName: 'id',
+                showToggle: false,
+                showExport: false,
                 columns: [
                     [
                         {field: 'state', checkbox: true,},
@@ -115,6 +117,11 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
 
             // 为表格绑定事件
             Table.api.bindevent(table);
+            require(['upload'], function (Upload) {
+                Upload.api.plupload($("#toolbar .plupload"), function () {
+                    $(".btn-refresh").trigger("click");
+                });
+            });
         },
         add: function () {
             Controller.api.bindevent();

+ 2 - 0
public/assets/js/backend/index.js

@@ -459,6 +459,8 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
                     avatar: data.avatar
                 }));
                 location.href = Backend.api.fixurl(data.url);
+            }, function (data) {
+                $("input[name=captcha]").next(".input-group-addon").find("img").trigger("click");
             });
         }
     };

+ 1 - 1
public/assets/js/backend/user/user.js

@@ -30,7 +30,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {field: 'nickname', title: __('Nickname'), operate: 'LIKE'},
                         {field: 'email', title: __('Email'), operate: 'LIKE'},
                         {field: 'mobile', title: __('Mobile'), operate: 'LIKE'},
-                        {field: 'avatar', title: __('Avatar'), formatter: Table.api.formatter.image, operate: false},
+                        {field: 'avatar', title: __('Avatar'), events: Table.api.events.image, formatter: Table.api.formatter.image, operate: false},
                         {field: 'level', title: __('Level'), operate: 'BETWEEN', sortable: true},
                         {field: 'gender', title: __('Gender'), visible: false, searchList: {1: __('Male'), 0: __('Female')}},
                         {field: 'score', title: __('Score'), operate: 'BETWEEN', sortable: true},

+ 3 - 1
public/assets/js/bootstrap-table-commonsearch.js

@@ -40,7 +40,9 @@
         // 重置搜索
         form.on("click", "button[type=reset]", function (event) {
             form[0].reset();
-            that.onCommonSearch();
+            setTimeout(function () {
+                that.onCommonSearch();
+            }, 0);
         });
 
     };

+ 2 - 0
public/assets/js/frontend/user.js

@@ -57,6 +57,8 @@ define(['jquery', 'bootstrap', 'frontend', 'form', 'template'], function ($, und
                 setTimeout(function () {
                     location.href = ret.url ? ret.url : "/";
                 }, 1000);
+            }, function (data) {
+                $("input[name=captcha]").next(".input-group-addon").find("img").trigger("click");
             });
         },
         changepwd: function () {

+ 59 - 11
public/assets/js/require-backend.min.js

@@ -159,7 +159,13 @@ require(['jquery', 'bootstrap'], function ($, undefined) {
                 //加载相应模块
                 if (Config.jsname) {
                     require([Config.jsname], function (Controller) {
-                        Controller[Config.actionname] != undefined && Controller[Config.actionname]();
+                        if (Controller.hasOwnProperty(Config.actionname)) {
+                            Controller[Config.actionname]();
+                        } else {
+                            if (Controller.hasOwnProperty("_empty")) {
+                                Controller._empty();
+                            }
+                        }
                     }, function (e) {
                         console.error(e);
                         // 这里可捕获模块加载的错误
@@ -5708,9 +5714,9 @@ define('backend',['fast', 'template', 'moment'], function (Fast, Template, Momen
                 if (url.indexOf("{ids}") > -1) {
                     var ids = 0;
                     var tableId = $(elem).data("table-id");
-                    if (tableId && $(tableId).size() > 0 && $(tableId).data("bootstrap.table")) {
+                    if (tableId && $("#" + tableId).size() > 0 && $("#" + tableId).data("bootstrap.table")) {
                         var Table = require("table");
-                        ids = Table.api.selectedids($(tableId)).join(",");
+                        ids = Table.api.selectedids($("#" + tableId)).join(",");
                     }
                     url = url.replace(/\{ids\}/g, ids);
                 }
@@ -6451,17 +6457,49 @@ define('upload',['jquery', 'bootstrap', 'plupload', 'template'], function ($, un
                                 });
                             });
                         }
+                        //刷新隐藏textarea的值
+                        var refresh = function (name) {
+                            var data = {};
+                            var textarea = $("textarea[name='" + name + "']");
+                            var container = textarea.prev("ul");
+                            $.each($("input,select,textarea", container).serializeArray(), function (i, j) {
+                                var reg = /\[?(\w+)\]?\[(\w+)\]$/g;
+                                var match = reg.exec(j.name);
+                                if (!match)
+                                    return true;
+                                if (!isNaN(match[2])) {
+                                    data[i] = j.value;
+                                } else {
+                                    match[1] = "x" + parseInt(match[1]);
+                                    if (typeof data[match[1]] === 'undefined') {
+                                        data[match[1]] = {};
+                                    }
+                                    data[match[1]][match[2]] = j.value;
+                                }
+                            });
+                            var result = [];
+                            $.each(data, function (i, j) {
+                                result.push(j);
+                            });
+                            textarea.val(JSON.stringify(result));
+                        };
                         if (preview_id && input_id) {
-                            $(document.body).on("keyup change", "#" + input_id, function () {
+                            $(document.body).on("keyup change", "#" + input_id, function (e) {
                                 var inputStr = $("#" + input_id).val();
                                 var inputArr = inputStr.split(/\,/);
                                 $("#" + preview_id).empty();
                                 var tpl = $("#" + preview_id).data("template") ? $("#" + preview_id).data("template") : "";
+                                var extend = $("#" + preview_id).next().is("textarea") ? $("#" + preview_id).next("textarea").val() : "{}";
+                                var json = {};
+                                try {
+                                    json = JSON.parse(extend);
+                                } catch (e) {
+                                }
                                 $.each(inputArr, function (i, j) {
                                     if (!j) {
                                         return true;
                                     }
-                                    var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data()};
+                                    var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data(), key: i, index: i, value: (json && typeof json[i] !== 'undefined' ? json[i] : null)};
                                     var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data);
                                     $("#" + preview_id).append(html);
                                 });
@@ -6469,15 +6507,20 @@ define('upload',['jquery', 'bootstrap', 'plupload', 'template'], function ($, un
                             $("#" + input_id).trigger("change");
                         }
                         if (preview_id) {
+                            //监听文本框改变事件
+                            $("#" + preview_id).on('change keyup', "input,textarea,select", function () {
+                                refresh($(this).closest("ul").data("name"));
+                            });
                             // 监听事件
                             $(document.body).on("fa.preview.change", "#" + preview_id, function () {
-                                var urlArr = new Array();
+                                var urlArr = [];
                                 $("#" + preview_id + " [data-url]").each(function (i, j) {
                                     urlArr.push($(this).data("url"));
                                 });
                                 if (input_id) {
                                     $("#" + input_id).val(urlArr.join(","));
                                 }
+                                refresh($("#" + preview_id).data("name"));
                             });
                             // 移除按钮事件
                             $(document.body).on("click", "#" + preview_id + " .btn-trash", function () {
@@ -8901,7 +8944,7 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
                     },
                     dataFilter: function (data) {
                         if (data.code === 1) {
-                            return "";
+                            return data.msg ? { "ok": data.msg } : '';
                         } else {
                             return data.msg;
                         }
@@ -9153,7 +9196,7 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
                             var textarea = $("textarea[name='" + name + "']", form);
                             var container = textarea.closest("dl");
                             var template = container.data("template");
-                            $.each($("input,select", container).serializeArray(), function (i, j) {
+                            $.each($("input,select,textarea", container).serializeArray(), function (i, j) {
                                 var reg = /\[(\w+)\]\[(\w+)\]$/g;
                                 var match = reg.exec(j.name);
                                 if (!match)
@@ -9423,7 +9466,9 @@ define('form',['jquery', 'bootstrap', 'upload', 'validator'], function ($, undef
         // 重置搜索
         form.on("click", "button[type=reset]", function (event) {
             form[0].reset();
-            that.onCommonSearch();
+            setTimeout(function () {
+                that.onCommonSearch();
+            }, 0);
         });
 
     };
@@ -9967,6 +10012,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                         return __('Choose');
                     }
                 }, locales);
+                $.fn.bootstrapTable.defaults.exportTypes = defaults.exportTypes;
             },
             // 绑定事件
             bindevent: function (table) {
@@ -10035,7 +10081,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                     var field = $(this).closest("ul").data("field");
                     var value = $(this).data("value");
                     $("select[name='" + field + "'] option[value='" + value + "']", table.closest(".bootstrap-table").find(".commonsearch-table")).prop("selected", true);
-                    table.bootstrapTable('refresh', {});
+                    table.bootstrapTable('refresh', {pageNumber:1});
                     return false;
                 });
                 // 刷新按钮事件
@@ -10141,7 +10187,8 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                                     pid: pid,
                                     field: Table.config.dragsortfield,
                                     orderway: options.sortOrder,
-                                    table: options.extend.table
+                                    table: options.extend.table,
+                                    pk: options.pk
                                 }
                             };
                             Fast.api.ajax(params, function (data, ret) {
@@ -10273,6 +10320,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
                         });
                         Layer.photos({
                             photos: {
+                                "start": $(this).parent().index(),
                                 "data": data
                             },
                             anim: 5 //0-6的选择,指定弹出图片动画类型,默认随机(请注意,3.0之前的版本用shift参数)

+ 1 - 1
public/assets/js/require-form.js

@@ -272,7 +272,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
                             var textarea = $("textarea[name='" + name + "']", form);
                             var container = textarea.closest("dl");
                             var template = container.data("template");
-                            $.each($("input,select", container).serializeArray(), function (i, j) {
+                            $.each($("input,select,textarea", container).serializeArray(), function (i, j) {
                                 var reg = /\[(\w+)\]\[(\w+)\]$/g;
                                 var match = reg.exec(j.name);
                                 if (!match)

+ 1 - 0
public/assets/js/require-table.js

@@ -103,6 +103,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
                         return __('Choose');
                     }
                 }, locales);
+                $.fn.bootstrapTable.defaults.exportTypes = defaults.exportTypes;
             },
             // 绑定事件
             bindevent: function (table) {

+ 40 - 3
public/assets/js/require-upload.js

@@ -260,17 +260,49 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
                                 });
                             });
                         }
+                        //刷新隐藏textarea的值
+                        var refresh = function (name) {
+                            var data = {};
+                            var textarea = $("textarea[name='" + name + "']");
+                            var container = textarea.prev("ul");
+                            $.each($("input,select,textarea", container).serializeArray(), function (i, j) {
+                                var reg = /\[?(\w+)\]?\[(\w+)\]$/g;
+                                var match = reg.exec(j.name);
+                                if (!match)
+                                    return true;
+                                if (!isNaN(match[2])) {
+                                    data[i] = j.value;
+                                } else {
+                                    match[1] = "x" + parseInt(match[1]);
+                                    if (typeof data[match[1]] === 'undefined') {
+                                        data[match[1]] = {};
+                                    }
+                                    data[match[1]][match[2]] = j.value;
+                                }
+                            });
+                            var result = [];
+                            $.each(data, function (i, j) {
+                                result.push(j);
+                            });
+                            textarea.val(JSON.stringify(result));
+                        };
                         if (preview_id && input_id) {
-                            $(document.body).on("keyup change", "#" + input_id, function () {
+                            $(document.body).on("keyup change", "#" + input_id, function (e) {
                                 var inputStr = $("#" + input_id).val();
                                 var inputArr = inputStr.split(/\,/);
                                 $("#" + preview_id).empty();
                                 var tpl = $("#" + preview_id).data("template") ? $("#" + preview_id).data("template") : "";
+                                var extend = $("#" + preview_id).next().is("textarea") ? $("#" + preview_id).next("textarea").val() : "{}";
+                                var json = {};
+                                try {
+                                    json = JSON.parse(extend);
+                                } catch (e) {
+                                }
                                 $.each(inputArr, function (i, j) {
                                     if (!j) {
                                         return true;
                                     }
-                                    var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data()};
+                                    var data = {url: j, fullurl: Fast.api.cdnurl(j), data: $(that).data(), key: i, index: i, value: (json && typeof json[i] !== 'undefined' ? json[i] : null)};
                                     var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data);
                                     $("#" + preview_id).append(html);
                                 });
@@ -278,15 +310,20 @@ define(['jquery', 'bootstrap', 'plupload', 'template'], function ($, undefined,
                             $("#" + input_id).trigger("change");
                         }
                         if (preview_id) {
+                            //监听文本框改变事件
+                            $("#" + preview_id).on('change keyup', "input,textarea,select", function () {
+                                refresh($(this).closest("ul").data("name"));
+                            });
                             // 监听事件
                             $(document.body).on("fa.preview.change", "#" + preview_id, function () {
-                                var urlArr = new Array();
+                                var urlArr = [];
                                 $("#" + preview_id + " [data-url]").each(function (i, j) {
                                     urlArr.push($(this).data("url"));
                                 });
                                 if (input_id) {
                                     $("#" + input_id).val(urlArr.join(","));
                                 }
+                                refresh($("#" + preview_id).data("name"));
                             });
                             // 移除按钮事件
                             $(document.body).on("click", "#" + preview_id + " .btn-trash", function () {