diff --git a/app/Models/AbstractModel.php b/app/Models/AbstractModel.php index 59981a3b1..e5691305c 100644 --- a/app/Models/AbstractModel.php +++ b/app/Models/AbstractModel.php @@ -217,6 +217,12 @@ class AbstractModel extends Model */ protected function performInsertOrIgnore(Builder $query, array|string|null $uniqueBy) { + // MySQL INSERT IGNORE 无法按指定列限制冲突范围,所有 unique 冲突一并吞掉。 + // 若调用方传了 $uniqueBy 期望精确 scope,这里直接抛错,避免与框架语义偷偷不一致。 + if ($uniqueBy !== null) { + throw new \InvalidArgumentException('saveOrIgnore $uniqueBy is not supported on MySQL driver; pass null.'); + } + if ($this->usesUniqueIds()) { $this->setUniqueIds(); } @@ -240,10 +246,12 @@ class AbstractModel extends Model } if ($this->getIncrementing()) { - $this->setAttribute( - $this->getKeyName(), - $query->getConnection()->getPdo()->lastInsertId() - ); + $lastId = $query->getConnection()->getPdo()->lastInsertId(); + // 无 auto_increment 列的表上 INSERT IGNORE 即使插入成功 lastInsertId 也返回 "0", + // 别用它去覆盖业务设置的主键。 + if ($lastId > 0) { + $this->setAttribute($this->getKeyName(), $lastId); + } } $this->exists = true; diff --git a/language/original-web.txt b/language/original-web.txt index 12f7a14d5..6820838c2 100644 --- a/language/original-web.txt +++ b/language/original-web.txt @@ -2467,3 +2467,5 @@ AI任务分析 页面已切换,引导已结束 步骤执行失败 操作引导启动失败 +最多只能添加(*)个 +该标签已存在 diff --git a/resources/assets/js/components/TagInput.vue b/resources/assets/js/components/TagInput.vue index ce5119e6c..633334140 100755 --- a/resources/assets/js/components/TagInput.vue +++ b/resources/assets/js/components/TagInput.vue @@ -6,7 +6,7 @@ tag="ul" draggable=".column-item" > -
+
{{text}}×
@@ -60,14 +60,6 @@ }, }, data() { - const disSource = []; - if( this.value ){ - this.value?.split(",").forEach(item => { - if (item) { - disSource.push(item) - } - }); - } return { minWidth: 80, @@ -78,7 +70,7 @@ content: '', - disSource, + disSource: this.parseValue(this.value), isFocus: false, @@ -103,25 +95,10 @@ this.wayMinWidth(); }, value(val) { - if( val && typeof val == 'string' ){ - let disSource = []; - val?.split(",").forEach(item => { - if (item) { - disSource.push(item) - } - }); - this.disSource = disSource; - } + this.disSource = this.parseValue(val); }, disSource(val) { - let temp = ''; - val.forEach(item => { - if (temp != '') { - temp += this.cut; - } - temp += item; - }); - this.$emit('input', temp); + this.$emit('input', val.join(this.joinChar())); this.$emit('on-change'); } }, @@ -134,6 +111,43 @@ } }, methods: { + normalizedCuts() { + const raw = Array.isArray(this.cut) ? this.cut : [this.cut]; + return raw.filter(c => typeof c === 'string' && c.length > 0); + }, + cutPattern() { + const cuts = this.normalizedCuts(); + if (cuts.length === 0) { + return null; + } + const escaped = cuts.map(c => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + return new RegExp(escaped.join('|'), 'g'); + }, + joinChar() { + return this.normalizedCuts()[0] || ','; + }, + parseValue(val) { + const list = []; + if (typeof val !== 'string' || val === '') { + return list; + } + val.split(this.joinChar()).forEach(item => { + const value = item.trim(); + if (value && list.indexOf(value) === -1) { + list.push(value); + } + }); + return list; + }, + splitByCuts(str) { + const pattern = this.cutPattern(); + return pattern ? str.split(pattern) : [str]; + }, + showTis(msg) { + this.tis = msg; + clearTimeout(this.tisTimeout); + this.tisTimeout = setTimeout(() => { this.tis = ''; }, 2000); + }, edit(disSource,index){ this.editData.disSource = disSource this.editData.index = index @@ -144,12 +158,17 @@ okText: "确定", value: disSource[index] + '', onOk: (desc) => { - if (!desc) { + const trimmed = (desc || '').trim() + if (!trimmed) { return `请输入名称` } - this.editData.name = desc - this.editData.disSource[this.editData.index] = desc - this.$set(this.disSource,this.editData.index,desc) + const exists = this.disSource.indexOf(trimmed) + if (exists !== -1 && exists !== this.editData.index) { + return `该标签已存在` + } + this.editData.name = trimmed + this.editData.disSource[this.editData.index] = trimmed + this.$set(this.disSource, this.editData.index, trimmed) return false }, }); @@ -192,7 +211,14 @@ pasteText(e) { e.preventDefault(); let content = (e.clipboardData || window.clipboardData).getData('text'); - this.addTag(false, content) + if (!content) { + return; + } + for (const item of this.splitByCuts(content)) { + const value = item.trim(); + if (!value) continue; + if (this.addTag(false, value) === false) break; + } }, downEnter(e) { if (e.isComposing || e.key === 'Process' || e.keyCode === 229) { @@ -220,32 +246,45 @@ this.$emit("on-blur", e) }, onKeyup(e) { + if (e.keyCode !== 13) { + this.addTag(e, this.content); + } this.$emit("on-keyup", e) }, addTag(e, content) { - if (e === false || e.keyCode === 13) { - if (content.trim() != '' && this.disSource.indexOf(content.trim()) === -1) { - this.disSource.push(content.trim()); + const isForce = e === false || e.keyCode === 13; + let value; + if (isForce) { + value = content.trim(); + } else { + if (content === '') return true; + let matchedCut = null; + for (const c of this.normalizedCuts()) { + if (c.length <= content.length && content.substring(content.length - c.length) === c) { + matchedCut = c; + break; + } } + if (matchedCut === null) return true; + value = content.substring(0, content.length - matchedCut.length).trim(); + } + if (value === '') { this.content = ''; - return; + return true; } if (this.max > 0 && this.disSource.length >= this.max) { this.content = ''; - this.tis = '最多只能添加' + this.max + '个'; - clearTimeout(this.tisTimeout); - this.tisTimeout = setTimeout(() => { this.tis = ''; }, 2000); - return; + this.showTis(this.$L('最多只能添加(*)个', this.max)); + return false; } - let temp = content.trim(); - let cutPos = temp.length - this.cut.length; - if (temp != '' && temp.substring(cutPos) === this.cut) { - temp = temp.substring(0, cutPos); - if (temp.trim() != '' && this.disSource.indexOf(temp.trim()) === -1) { - this.disSource.push(temp.trim()); - } + if (this.disSource.indexOf(value) !== -1) { this.content = ''; + this.showTis(this.$L('该标签已存在')); + return true; } + this.disSource.push(value); + this.content = ''; + return true; }, delTag(index) { if (index === false) { diff --git a/resources/assets/js/pages/login.vue b/resources/assets/js/pages/login.vue index fc0ff29c3..ff6cb07cd 100644 --- a/resources/assets/js/pages/login.vue +++ b/resources/assets/js/pages/login.vue @@ -502,6 +502,7 @@ export default { }, onLoginKeydown(e) { + if (e.isComposing || e.key === 'Process' || e.keyCode === 229) return; if (e.keyCode === 13) { this.onLogin(); } diff --git a/resources/assets/js/pages/manage.vue b/resources/assets/js/pages/manage.vue index a3f1c1729..2b91265b7 100644 --- a/resources/assets/js/pages/manage.vue +++ b/resources/assets/js/pages/manage.vue @@ -323,7 +323,7 @@
- +