Skip to content

Commit e98637d

Browse files
committed
新增:Transfer组件
1 parent 82c34e8 commit e98637d

File tree

10 files changed

+303
-62
lines changed

10 files changed

+303
-62
lines changed

asset/theme/default/style.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/asset/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"isutf8": "^3.0.0",
5252
"jquery": "^3.5.1",
5353
"js-cookie": "^2.2.1",
54-
"markdown": "^0.5.0",
54+
"showdown": "^2.1.0",
5555
"md5": "^2.3.0",
5656
"package-json-cleanup-loader": "^1.0.3",
5757
"query-string": "^6.14.0",
Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,58 @@
1-
const markdown = require("markdown").markdown
1+
// const markdown = require("markdown").markdown
2+
const showdown = require('showdown')
3+
4+
const converter = new showdown.Converter()
25

36
export const MarkdownUtil = {
4-
toHtml: (html) => {
7+
fixMarkdown: (md) => {
8+
let processed = md;
9+
10+
// 补全未闭合代码块
11+
const codeBlockCount = (processed.match(/```/g) || []).length;
12+
if (codeBlockCount % 2 !== 0) {
13+
processed += '\n```';
14+
}
15+
16+
// 处理表格未闭合
17+
const lines = processed.split('\n');
18+
let inTable = false;
19+
// 检查最后3行是否包含表格结构
20+
const checkTable = (lines) => {
21+
const tablePattern = /^(\|.*\|)\s*$/;
22+
const separatorPattern = /^(\|[-: ]+)+\|$/;
23+
return lines.some(line => tablePattern.test(line) || separatorPattern.test(line));
24+
};
25+
if (checkTable(lines.slice(-3))) {
26+
if (!/^\s*$/.test(lines[lines.length - 1])) {
27+
lines.push('');
28+
}
29+
}
30+
lines.forEach((line, index) => {
31+
if (line.startsWith('|') && !line.endsWith('|') && line.trim().length > 0) {
32+
lines[index] = line + '|';
33+
}
34+
});
35+
processed = lines.join('\n');
36+
37+
// 补全列表项换行(需在真实转换前处理)
38+
const lastLine = lines[lines.length - 1];
39+
if (lastLine.match(/^(\s*[-*+]|\d+\.)\s/)) {
40+
processed += '\n';
41+
}
42+
43+
return processed
44+
},
45+
toHtml: (md, autoFix) => {
46+
autoFix = autoFix || false
47+
if (autoFix) {
48+
md = MarkdownUtil.fixMarkdown(md)
49+
}
550
try {
6-
return markdown.toHTML(html)
51+
return converter.makeHtml(md)
52+
// return markdown.toHTML(md)
753
} catch (e) {
8-
return '解析错误' + +e
54+
return '解析错误' + e
955
}
1056
}
1157
}
58+

resources/asset/src/sui/adapt/element-ui.less

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@
108108
}
109109

110110
.el-dialog__title {
111-
font-size: var(--font-size);
111+
font-size: var(--font-size-medium);
112+
font-weight: bold;
112113
}
113114

114115
.el-dialog__headerbtn {
@@ -117,7 +118,7 @@
117118

118119
&:hover, &:focus {
119120
.el-dialog__close {
120-
color: var(--color-primary);
121+
color: var(--color-danger);
121122
}
122123
}
123124
}

resources/asset/src/svue/lib/api.js

Lines changed: 40 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,11 @@ let apiRequest = null, apiStore = null
66
let Dialog = {
77
tipError(msg) {
88
Message({
9-
message: msg,
10-
type: 'error',
11-
duration: 5 * 1000
9+
message: msg, type: 'error', duration: 5 * 1000
1210
})
13-
},
14-
tipSuccess(msg) {
11+
}, tipSuccess(msg) {
1512
Message({
16-
message: msg,
17-
type: 'success',
18-
duration: 5 * 1000
13+
message: msg, type: 'success', duration: 5 * 1000
1914
})
2015
}
2116
}
@@ -30,12 +25,7 @@ const isInited = () => {
3025
}
3126

3227
const isRemoteInited = () => {
33-
return apiStore && apiStore.state.api.baseUrl &&
34-
(
35-
apiStore.state.api.baseUrl.startsWith('http://')
36-
||
37-
apiStore.state.api.baseUrl.startsWith('https://')
38-
)
28+
return apiStore && apiStore.state.api.baseUrl && (apiStore.state.api.baseUrl.startsWith('http://') || apiStore.state.api.baseUrl.startsWith('https://'))
3929
}
4030

4131
const init = (store) => {
@@ -44,45 +34,38 @@ const init = (store) => {
4434
}
4535
apiStore = store
4636
apiRequest = axios.create({
47-
baseURL: apiStore ? apiStore.state.api.baseUrl : '',
48-
timeout: 60 * 1000
37+
baseURL: apiStore ? apiStore.state.api.baseUrl : '', timeout: 60 * 1000
4938
})
50-
apiRequest.interceptors.request.use(
51-
config => {
52-
if (apiStore) {
53-
let token = apiStore.state.api.token
54-
if (token) {
55-
config.headers[apiStore.state.api.tokenKey] = token
56-
}
57-
config.baseURL = apiStore.state.api.baseUrl
58-
// let additionalSendHeaders = Storage.getObject('ADDITIONAL_HEADERS', {})
59-
// for (let k in additionalSendHeaders) {
60-
// config.headers[k] = additionalSendHeaders[k]
61-
// }
39+
apiRequest.interceptors.request.use(config => {
40+
if (apiStore) {
41+
let token = apiStore.state.api.token
42+
if (token) {
43+
config.headers[apiStore.state.api.tokenKey] = token
6244
}
63-
config.headers['is-ajax'] = true
64-
return config
65-
},
66-
error => {
67-
Promise.reject(error)
45+
config.baseURL = apiStore.state.api.baseUrl
46+
// let additionalSendHeaders = Storage.getObject('ADDITIONAL_HEADERS', {})
47+
// for (let k in additionalSendHeaders) {
48+
// config.headers[k] = additionalSendHeaders[k]
49+
// }
6850
}
69-
)
70-
apiRequest.interceptors.response.use(
71-
response => {
72-
if (apiStore) {
73-
try {
74-
if (response.headers[apiStore.state.api.tokenKey]) {
75-
apiStore.commit('SET_API_TOKEN', response.headers[apiStore.state.api.tokenKey])
76-
}
77-
} catch (e) {
51+
config.headers['is-ajax'] = true
52+
return config
53+
}, error => {
54+
Promise.reject(error)
55+
})
56+
apiRequest.interceptors.response.use(response => {
57+
if (apiStore) {
58+
try {
59+
if (response.headers[apiStore.state.api.tokenKey]) {
60+
apiStore.commit('SET_API_TOKEN', response.headers[apiStore.state.api.tokenKey])
7861
}
62+
} catch (e) {
7963
}
80-
return response.data
81-
},
82-
error => {
83-
return Promise.reject(error)
8464
}
85-
)
65+
return response.data
66+
}, error => {
67+
return Promise.reject(error)
68+
})
8669
}
8770

8871
const processResponse = (res, failCB, successCB) => {
@@ -197,7 +180,7 @@ const eventSource = (url, param, successCallback, errorCallback, endCallback) =>
197180
}
198181
var es = new EventSource(url, {withCredentials: true});
199182
es.onerror = function (event) {
200-
errorCallback('ERROR:' + event)
183+
errorCallback('ERROR')
201184
es.close();
202185
}
203186
es.onmessage = function (event) {
@@ -221,15 +204,18 @@ const eventSource = (url, param, successCallback, errorCallback, endCallback) =>
221204
es.close()
222205
}
223206
}
207+
return {
208+
isRunning: function () {
209+
return es.readyState === EventSource.OPEN
210+
},
211+
close: function () {
212+
es.close()
213+
}
214+
}
224215
}
225216

226217
const Api = {
227-
isInited,
228-
isRemoteInited,
229-
init,
230-
post,
231-
eventSource,
232-
// postJson,
218+
isInited, isRemoteInited, init, post, eventSource, // postJson,
233219
// setApiBaseUrl,
234220
// getApiTokenKey,
235221
// setApiTokenKey

src/Core/Exception/BizException.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace ModStart\Core\Exception;
44

55

6+
use ModStart\Core\Input\Response;
7+
68
class BizException extends \Exception
79
{
810
public $param = [];
@@ -127,4 +129,9 @@ public static function throwsIfMessageMatch($error, $messagePatterns, $messagePr
127129
throw $error;
128130
}
129131

132+
public function toResponse()
133+
{
134+
return Response::generateError($this->getMessage());
135+
}
136+
130137
}

src/Field/Transfer.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
4+
namespace ModStart\Field;
5+
6+
7+
use ModStart\Core\Dao\ModelUtil;
8+
use ModStart\Core\Util\ConvertUtil;
9+
use ModStart\Core\Util\SerializeUtil;
10+
use ModStart\Core\Util\TagUtil;
11+
use ModStart\Field\Concern\CanCascadeFields;
12+
13+
class Transfer extends AbstractField
14+
{
15+
use CanCascadeFields;
16+
17+
/**
18+
* 使用JSON
19+
*/
20+
const SERIALIZE_TYPE_DEFAULT = null;
21+
/**
22+
* 使用冒号分割
23+
*/
24+
const SERIALIZE_TYPE_COLON_SEPARATED = 1;
25+
26+
protected $value = [];
27+
28+
protected function setup()
29+
{
30+
$this->addVariables([
31+
'options' => [],
32+
'serializeType' => null,
33+
]);
34+
}
35+
36+
public function options($options)
37+
{
38+
$this->addVariables(['options' => $options]);
39+
return $this;
40+
}
41+
42+
public function serializeType($value)
43+
{
44+
$this->addVariables(['serializeType' => $value]);
45+
return $this;
46+
}
47+
48+
public function optionModel($table, $keyName = 'id', $labelName = 'name')
49+
{
50+
return $this->options(ModelUtil::valueMap($table, $keyName, $labelName));
51+
}
52+
53+
public function optionType($typeCls)
54+
{
55+
return $this->options($typeCls::getList());
56+
}
57+
58+
public function optionItems($items, $idName = 'id', $titleName = 'title')
59+
{
60+
$options = [];
61+
foreach ($items as $i => $item) {
62+
$options[] = [
63+
'label' => $item->{$idName},
64+
'title' => $item->{$titleName},
65+
];
66+
}
67+
return $this->options($options);
68+
}
69+
70+
public function optionArray($items, $idName = 'id', $titleName = 'title')
71+
{
72+
$options = [];
73+
foreach ($items as $i => $item) {
74+
$options[] = [
75+
'label' => $item[$idName],
76+
'title' => $item[$titleName],
77+
];
78+
}
79+
return $this->options($options);
80+
}
81+
82+
public function unserializeValue($value, AbstractField $field)
83+
{
84+
if (null === $value) {
85+
return $value;
86+
}
87+
switch ($this->getVariable('serializeType')) {
88+
case self::SERIALIZE_TYPE_COLON_SEPARATED:
89+
return TagUtil::string2Array($value);
90+
default:
91+
return ConvertUtil::toArray($value);
92+
}
93+
}
94+
95+
public function serializeValue($value, $model)
96+
{
97+
switch ($this->getVariable('serializeType')) {
98+
case self::SERIALIZE_TYPE_COLON_SEPARATED:
99+
return TagUtil::array2String($value);
100+
default:
101+
if (is_array($value)) {
102+
foreach ($value as $k => $v) {
103+
if (is_numeric($v) && preg_match('/^\d+$/', $v)) {
104+
$value[$k] = intval($v);
105+
}
106+
}
107+
}
108+
return SerializeUtil::jsonEncode($value);
109+
}
110+
}
111+
112+
public function prepareInput($value, $model)
113+
{
114+
switch ($this->getVariable('serializeType')) {
115+
case self::SERIALIZE_TYPE_COLON_SEPARATED:
116+
return TagUtil::string2Array($value);
117+
default:
118+
return ConvertUtil::toArray($value, false);
119+
}
120+
}
121+
122+
public function render()
123+
{
124+
$this->addCascadeScript();
125+
return parent::render();
126+
}
127+
128+
}

src/Support/Concern/HasFields.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
* @method \ModStart\Field\Textarea textarea($column, $label = '')
6868
* @method \ModStart\Field\Text text($column, $label = '')
6969
* @method \ModStart\Field\Time time($column, $label = '')
70+
* @method \ModStart\Field\Transfer transfer($column, $label = '')
7071
* @method \ModStart\Field\Tree tree($column, $label = '')
7172
* @method \ModStart\Field\Type type($column, $label = '')
7273
* @method \ModStart\Field\Values values($column, $label = '')

src/Support/Manager/FieldManager.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public static function registerBuiltinFields()
7575
'richHtml' => \ModStart\Field\RichHtml::class,
7676
'select' => \ModStart\Field\Select::class,
7777
'selectRemote' => \ModStart\Field\SelectRemote::class,
78+
'transfer' => \ModStart\Field\Transfer::class,
7879
'switch' => \ModStart\Field\SwitchField::class,
7980
'tags' => \ModStart\Field\Tags::class,
8081
'text' => \ModStart\Field\Text::class,

0 commit comments

Comments
 (0)