切换导航条
此项目
正在载入...
登录
何书鹏
/
recruit
·
提交
转到一个项目
GitLab
转到仪表盘
项目
活动
文件
提交
管道
0
构建
0
图表
里程碑
问题
0
合并请求
0
成员
标记
维基
派生
网络
创建新的问题
下载为
邮件补丁
差异文件
浏览文件
作者
Karson
8 years ago
提交
1bcb46dbeeaf13f45d2c74f1616a3dd5c6f88de1
1 个父辈
e927a7b0
修复在安卓下后台无法加载的BUG
修复ios下Layer弹窗的错误 修复ios下iframe滚动的BUG 优化table兼容性
隐藏空白字符变更
内嵌
并排对比
正在显示
8 个修改的文件
包含
83 行增加
和
82 行删除
public/assets/css/backend.css
public/assets/css/backend.min.css
public/assets/js/backend/index.js
public/assets/js/fast.js
public/assets/js/require-backend.min.js
public/assets/js/require-frontend.min.js
public/assets/js/require-table.js
public/assets/less/backend.less
public/assets/css/backend.css
查看文件 @
1bcb46d
...
...
@@ -74,6 +74,11 @@ body.is-dialog {
-webkit-overflow-scrolling
:
touch
;
overflow
:
auto
;
}
.layui-layer-iframe
.layui-layer-content.ios-iframe-fix
{
-webkit-overflow-scrolling
:
touch
;
overflow
:
auto
;
overflow-y
:
auto
!important
;
}
@media
only
screen
and
(
min-width
:
481px
)
{
.row-flex
{
display
:
flex
;
...
...
public/assets/css/backend.min.css
查看文件 @
1bcb46d
此 diff 太大无法显示。
public/assets/js/backend/index.js
查看文件 @
1bcb46d
...
...
@@ -80,7 +80,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
});
//版本检测
var
checkupdate
=
function
(
ignoreversion
,
tips
=
false
)
{
var
checkupdate
=
function
(
ignoreversion
,
tips
)
{
$
.
ajax
({
url
:
Config
.
fastadmin
.
api_url
+
'/version/check'
,
type
:
'post'
,
...
...
@@ -114,12 +114,12 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
}
}
});
}
}
;
//读取版本检测信息
var
ignoreversion
=
localStorage
.
getItem
(
"ignoreversion"
);
if
(
ignoreversion
!==
"*"
)
{
checkupdate
(
ignoreversion
);
checkupdate
(
ignoreversion
,
false
);
}
//手动检测版本信息
$
(
"a[data-toggle='checkupdate']"
).
on
(
'click'
,
function
()
{
...
...
public/assets/js/fast.js
查看文件 @
1bcb46d
...
...
@@ -116,7 +116,7 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
url
=
Fast
.
api
.
fixurl
(
url
);
url
=
url
+
(
url
.
indexOf
(
"?"
)
>
-
1
?
"&"
:
"?"
)
+
"dialog=1"
;
var
area
=
[
$
(
window
).
width
()
>
800
?
'800px'
:
'95%'
,
$
(
window
).
height
()
>
600
?
'600px'
:
'95%'
];
Layer
.
open
(
$
.
extend
({
options
=
$
.
extend
({
type
:
2
,
title
:
title
,
shadeClose
:
true
,
...
...
@@ -158,7 +158,12 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
// observer.disconnect();
}
}
},
options
?
options
:
{}));
},
options
?
options
:
{});
if
(
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
&&
top
.
$
(
".tab-pane.active"
).
size
()
>
0
)
{
options
.
area
=
[
top
.
$
(
".tab-pane.active"
).
width
()
+
"px"
,
top
.
$
(
".tab-pane.active"
).
height
()
+
"px"
];
options
.
offset
=
"lt"
;
}
Layer
.
open
(
options
);
return
false
;
},
//关闭窗口并回传数据
...
...
@@ -183,33 +188,25 @@ define(['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, undefine
$
(
">"
,
footer
).
wrapAll
(
"<div class='row'></div>"
);
}
footer
.
insertAfter
(
layero
.
find
(
'.layui-layer-content'
));
}
var
heg
=
frame
.
outerHeight
();
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
var
oldheg
=
heg
+
titHeight
+
btnHeight
;
var
maxheg
=
$
(
window
).
height
()
<
600
?
$
(
window
).
height
()
:
600
;
if
(
frame
.
outerWidth
()
<
768
||
that
.
area
[
0
].
indexOf
(
"%"
)
>
-
1
)
{
maxheg
=
$
(
window
).
height
();
}
// 如果有.layer-footer或窗口小于600则重新排
if
(
layerfooter
.
size
()
>
0
||
oldheg
<
maxheg
||
that
.
area
[
0
].
indexOf
(
"%"
)
>
-
1
)
{
var
footerHeight
=
layero
.
find
(
'.layui-layer-footer'
).
outerHeight
()
||
0
;
footerHeight
=
0
;
if
(
oldheg
>=
maxheg
)
{
heg
=
Math
.
min
(
maxheg
,
oldheg
)
-
titHeight
-
btnHeight
-
footerHeight
;
}
layero
.
css
({
height
:
heg
+
titHeight
+
btnHeight
+
footerHeight
});
layero
.
find
(
"iframe"
).
css
({
height
:
heg
});
}
if
(
layerfooter
.
size
()
>
0
)
{
//绑定事件
footer
.
on
(
"click"
,
".btn"
,
function
()
{
if
(
$
(
this
).
hasClass
(
"disabled"
)
||
$
(
this
).
parent
().
hasClass
(
"disabled"
))
{
return
;
}
$
(
".btn:eq("
+
$
(
this
).
index
()
+
")"
,
layerfooter
).
trigger
(
"click"
);
});
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
//重设iframe高度
$
(
"iframe"
,
layero
).
height
(
layero
.
height
()
-
titHeight
-
btnHeight
);
}
//修复iOS下弹出窗口的高度和iOS下iframe无法滚动的BUG
if
(
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
)
{
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
$
(
"iframe"
,
layero
).
parent
().
addClass
(
"ios-iframe-fix"
).
css
(
"height"
,
layero
.
height
()
-
titHeight
-
btnHeight
);
$
(
"iframe"
,
layero
).
css
(
"height"
,
"100%"
);
}
},
success
:
function
(
options
,
callback
)
{
...
...
public/assets/js/require-backend.min.js
查看文件 @
1bcb46d
...
...
@@ -2041,7 +2041,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
url
=
Fast
.
api
.
fixurl
(
url
);
url
=
url
+
(
url
.
indexOf
(
"?"
)
>
-
1
?
"&"
:
"?"
)
+
"dialog=1"
;
var
area
=
[
$
(
window
).
width
()
>
800
?
'800px'
:
'95%'
,
$
(
window
).
height
()
>
600
?
'600px'
:
'95%'
];
Layer
.
open
(
$
.
extend
({
options
=
$
.
extend
({
type
:
2
,
title
:
title
,
shadeClose
:
true
,
...
...
@@ -2083,7 +2083,12 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
// observer.disconnect();
}
}
},
options
?
options
:
{}));
},
options
?
options
:
{});
if
(
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
&&
top
.
$
(
".tab-pane.active"
).
size
()
>
0
)
{
options
.
area
=
[
top
.
$
(
".tab-pane.active"
).
width
()
+
"px"
,
top
.
$
(
".tab-pane.active"
).
height
()
+
"px"
];
options
.
offset
=
"lt"
;
}
Layer
.
open
(
options
);
return
false
;
},
//关闭窗口并回传数据
...
...
@@ -2108,33 +2113,25 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
$
(
">"
,
footer
).
wrapAll
(
"<div class='row'></div>"
);
}
footer
.
insertAfter
(
layero
.
find
(
'.layui-layer-content'
));
}
var
heg
=
frame
.
outerHeight
();
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
var
oldheg
=
heg
+
titHeight
+
btnHeight
;
var
maxheg
=
$
(
window
).
height
()
<
600
?
$
(
window
).
height
()
:
600
;
if
(
frame
.
outerWidth
()
<
768
||
that
.
area
[
0
].
indexOf
(
"%"
)
>
-
1
)
{
maxheg
=
$
(
window
).
height
();
}
// 如果有.layer-footer或窗口小于600则重新排
if
(
layerfooter
.
size
()
>
0
||
oldheg
<
maxheg
||
that
.
area
[
0
].
indexOf
(
"%"
)
>
-
1
)
{
var
footerHeight
=
layero
.
find
(
'.layui-layer-footer'
).
outerHeight
()
||
0
;
footerHeight
=
0
;
if
(
oldheg
>=
maxheg
)
{
heg
=
Math
.
min
(
maxheg
,
oldheg
)
-
titHeight
-
btnHeight
-
footerHeight
;
}
layero
.
css
({
height
:
heg
+
titHeight
+
btnHeight
+
footerHeight
});
layero
.
find
(
"iframe"
).
css
({
height
:
heg
});
}
if
(
layerfooter
.
size
()
>
0
)
{
//绑定事件
footer
.
on
(
"click"
,
".btn"
,
function
()
{
if
(
$
(
this
).
hasClass
(
"disabled"
)
||
$
(
this
).
parent
().
hasClass
(
"disabled"
))
{
return
;
}
$
(
".btn:eq("
+
$
(
this
).
index
()
+
")"
,
layerfooter
).
trigger
(
"click"
);
});
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
//重设iframe高度
$
(
"iframe"
,
layero
).
height
(
layero
.
height
()
-
titHeight
-
btnHeight
);
}
//修复iOS下弹出窗口的高度和iOS下iframe无法滚动的BUG
if
(
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
)
{
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
$
(
"iframe"
,
layero
).
parent
().
addClass
(
"ios-iframe-fix"
).
css
(
"height"
,
layero
.
height
()
-
titHeight
-
btnHeight
);
$
(
"iframe"
,
layero
).
css
(
"height"
,
"100%"
);
}
},
success
:
function
(
options
,
callback
)
{
...
...
@@ -7855,18 +7852,18 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
$
(
toolbar
).
on
(
'click'
,
Table
.
config
.
refreshbtn
,
function
()
{
table
.
bootstrapTable
(
'refresh'
);
});
// 添加按钮事件
$
(
toolbar
).
on
(
'click'
,
Table
.
config
.
addbtn
,
function
()
{
var
ids
=
Table
.
api
.
selectedids
(
table
);
Fast
.
api
.
open
(
options
.
extend
.
add_url
+
"/ids"
+
(
ids
.
length
>
0
?
'/'
:
''
)
+
ids
.
join
(
","
),
__
(
'Add'
));
Fast
.
api
.
open
(
options
.
extend
.
add_url
+
(
ids
.
length
>
0
?
(
options
.
extend
.
add_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
ids
.
join
(
","
)
:
''
),
__
(
'Add'
));
});
// 批量编辑按钮事件
$
(
toolbar
).
on
(
'click'
,
Table
.
config
.
editbtn
,
function
()
{
var
ids
=
Table
.
api
.
selectedids
(
table
);
//循环弹出多个编辑框
$
.
each
(
ids
,
function
(
i
,
j
)
{
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
"/ids/"
+
j
,
__
(
'Edit'
));
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
(
options
.
extend
.
edit_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
j
,
__
(
'Edit'
));
});
});
// 批量操作按钮事件
...
...
@@ -7930,7 +7927,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
});
$
(
table
).
on
(
"click"
,
"[data-id].btn-edit"
,
function
(
e
)
{
e
.
preventDefault
();
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
"/ids/"
+
$
(
this
).
data
(
"id"
),
__
(
'Edit'
));
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
(
options
.
extend
.
edit_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
$
(
this
).
data
(
"id"
),
__
(
'Edit'
));
});
$
(
table
).
on
(
"click"
,
"[data-id].btn-del"
,
function
(
e
)
{
e
.
preventDefault
();
...
...
@@ -7969,7 +7966,7 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
'click .btn-editone'
:
function
(
e
,
value
,
row
,
index
)
{
e
.
stopPropagation
();
var
options
=
$
(
this
).
closest
(
'table'
).
bootstrapTable
(
'getOptions'
);
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
"/ids/"
+
row
[
options
.
pk
],
__
(
'Edit'
));
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
(
options
.
extend
.
edit_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
row
[
options
.
pk
],
__
(
'Edit'
));
},
'click .btn-delone'
:
function
(
e
,
value
,
row
,
index
)
{
e
.
stopPropagation
();
...
...
public/assets/js/require-frontend.min.js
查看文件 @
1bcb46d
...
...
@@ -2053,7 +2053,7 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
url
=
Fast
.
api
.
fixurl
(
url
);
url
=
url
+
(
url
.
indexOf
(
"?"
)
>
-
1
?
"&"
:
"?"
)
+
"dialog=1"
;
var
area
=
[
$
(
window
).
width
()
>
800
?
'800px'
:
'95%'
,
$
(
window
).
height
()
>
600
?
'600px'
:
'95%'
];
Layer
.
open
(
$
.
extend
({
options
=
$
.
extend
({
type
:
2
,
title
:
title
,
shadeClose
:
true
,
...
...
@@ -2095,7 +2095,12 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
// observer.disconnect();
}
}
},
options
?
options
:
{}));
},
options
?
options
:
{});
if
(
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
&&
top
.
$
(
".tab-pane.active"
).
size
()
>
0
)
{
options
.
area
=
[
top
.
$
(
".tab-pane.active"
).
width
()
+
"px"
,
top
.
$
(
".tab-pane.active"
).
height
()
+
"px"
];
options
.
offset
=
"lt"
;
}
Layer
.
open
(
options
);
return
false
;
},
//关闭窗口并回传数据
...
...
@@ -2120,33 +2125,25 @@ define('fast',['jquery', 'bootstrap', 'toastr', 'layer', 'lang'], function ($, u
$
(
">"
,
footer
).
wrapAll
(
"<div class='row'></div>"
);
}
footer
.
insertAfter
(
layero
.
find
(
'.layui-layer-content'
));
}
var
heg
=
frame
.
outerHeight
();
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
var
oldheg
=
heg
+
titHeight
+
btnHeight
;
var
maxheg
=
$
(
window
).
height
()
<
600
?
$
(
window
).
height
()
:
600
;
if
(
frame
.
outerWidth
()
<
768
||
that
.
area
[
0
].
indexOf
(
"%"
)
>
-
1
)
{
maxheg
=
$
(
window
).
height
();
}
// 如果有.layer-footer或窗口小于600则重新排
if
(
layerfooter
.
size
()
>
0
||
oldheg
<
maxheg
||
that
.
area
[
0
].
indexOf
(
"%"
)
>
-
1
)
{
var
footerHeight
=
layero
.
find
(
'.layui-layer-footer'
).
outerHeight
()
||
0
;
footerHeight
=
0
;
if
(
oldheg
>=
maxheg
)
{
heg
=
Math
.
min
(
maxheg
,
oldheg
)
-
titHeight
-
btnHeight
-
footerHeight
;
}
layero
.
css
({
height
:
heg
+
titHeight
+
btnHeight
+
footerHeight
});
layero
.
find
(
"iframe"
).
css
({
height
:
heg
});
}
if
(
layerfooter
.
size
()
>
0
)
{
//绑定事件
footer
.
on
(
"click"
,
".btn"
,
function
()
{
if
(
$
(
this
).
hasClass
(
"disabled"
)
||
$
(
this
).
parent
().
hasClass
(
"disabled"
))
{
return
;
}
$
(
".btn:eq("
+
$
(
this
).
index
()
+
")"
,
layerfooter
).
trigger
(
"click"
);
});
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
//重设iframe高度
$
(
"iframe"
,
layero
).
height
(
layero
.
height
()
-
titHeight
-
btnHeight
);
}
//修复iOS下弹出窗口的高度和iOS下iframe无法滚动的BUG
if
(
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
)
{
var
titHeight
=
layero
.
find
(
'.layui-layer-title'
).
outerHeight
()
||
0
;
var
btnHeight
=
layero
.
find
(
'.layui-layer-btn'
).
outerHeight
()
||
0
;
$
(
"iframe"
,
layero
).
parent
().
addClass
(
"ios-iframe-fix"
).
css
(
"height"
,
layero
.
height
()
-
titHeight
-
btnHeight
);
$
(
"iframe"
,
layero
).
css
(
"height"
,
"100%"
);
}
},
success
:
function
(
options
,
callback
)
{
...
...
public/assets/js/require-table.js
查看文件 @
1bcb46d
...
...
@@ -153,18 +153,18 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
$
(
toolbar
).
on
(
'click'
,
Table
.
config
.
refreshbtn
,
function
()
{
table
.
bootstrapTable
(
'refresh'
);
});
// 添加按钮事件
$
(
toolbar
).
on
(
'click'
,
Table
.
config
.
addbtn
,
function
()
{
var
ids
=
Table
.
api
.
selectedids
(
table
);
Fast
.
api
.
open
(
options
.
extend
.
add_url
+
"/ids"
+
(
ids
.
length
>
0
?
'/'
:
''
)
+
ids
.
join
(
","
),
__
(
'Add'
));
Fast
.
api
.
open
(
options
.
extend
.
add_url
+
(
ids
.
length
>
0
?
(
options
.
extend
.
add_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
ids
.
join
(
","
)
:
''
),
__
(
'Add'
));
});
// 批量编辑按钮事件
$
(
toolbar
).
on
(
'click'
,
Table
.
config
.
editbtn
,
function
()
{
var
ids
=
Table
.
api
.
selectedids
(
table
);
//循环弹出多个编辑框
$
.
each
(
ids
,
function
(
i
,
j
)
{
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
"/ids/"
+
j
,
__
(
'Edit'
));
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
(
options
.
extend
.
edit_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
j
,
__
(
'Edit'
));
});
});
// 批量操作按钮事件
...
...
@@ -228,7 +228,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
});
$
(
table
).
on
(
"click"
,
"[data-id].btn-edit"
,
function
(
e
)
{
e
.
preventDefault
();
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
"/ids/"
+
$
(
this
).
data
(
"id"
),
__
(
'Edit'
));
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
(
options
.
extend
.
edit_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
$
(
this
).
data
(
"id"
),
__
(
'Edit'
));
});
$
(
table
).
on
(
"click"
,
"[data-id].btn-del"
,
function
(
e
)
{
e
.
preventDefault
();
...
...
@@ -267,7 +267,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
'click .btn-editone'
:
function
(
e
,
value
,
row
,
index
)
{
e
.
stopPropagation
();
var
options
=
$
(
this
).
closest
(
'table'
).
bootstrapTable
(
'getOptions'
);
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
"/ids/"
+
row
[
options
.
pk
],
__
(
'Edit'
));
Fast
.
api
.
open
(
options
.
extend
.
edit_url
+
(
options
.
extend
.
edit_url
.
match
(
/
(\?
|&
)
+/
)
?
"&ids="
:
"/ids/"
)
+
row
[
options
.
pk
],
__
(
'Edit'
));
},
'click .btn-delone'
:
function
(
e
,
value
,
row
,
index
)
{
e
.
stopPropagation
();
...
...
public/assets/less/backend.less
查看文件 @
1bcb46d
...
...
@@ -97,6 +97,11 @@ body.is-dialog {
}
}
}
.layui-layer-iframe .layui-layer-content.ios-iframe-fix {
-webkit-overflow-scrolling: touch;
overflow: auto;
overflow-y: auto!important;
}
@media only screen and (min-width : 481px) {
.row-flex {
display: flex;
...
...
请
注册
或
登录
后发表评论