refactor: 四端完成 Options API → script setup + vuex → pinia 迁移

- buyer(95) / seller(169) / im(53) / manager(280) 共 597 个组件全部从 Options API 转换为 <script setup>
- 四端 vuex 全部移除,状态管理统一迁移至 pinia 4(stores/ 目录)
- util.js 等基础设施去除 vm 首参依赖;全局属性($Message/$filters 等)在 script 内改为具名导入
- 被外部 ref 访问的公共组件均已补齐 defineExpose(清单见各端 MIGRATION-VUE3.md)
- 各端 eslint 启用 vue/component-api-style 防止新代码回退 Options API
- 四端 yarn build 全部通过;迁移约定与注意事项已更新至各端 MIGRATION-VUE3.md
This commit is contained in:
lifenlong
2026-10-13 07:06:59 +08:00
parent 870bacdd8c
commit 88fc1b0b2a
674 changed files with 71220 additions and 69943 deletions

View File

@@ -6,13 +6,10 @@
</div>
</template>
<script>
<script setup>
import { Loading } from "@element-plus/icons-vue";
export default {
name: "circleLoading",
components: { Loading },
};
defineOptions({ name: "circleLoading" });
</script>
<style lang="scss" scoped>

View File

@@ -6,23 +6,22 @@
</div>
</template>
<script>
export default {
name: "configDrawer",
data() {
return {
drawer: false,
};
},
methods: {
open() {
this.drawer = true;
},
close() {
this.drawer = false;
},
},
};
<script setup>
import { ref } from "vue";
defineOptions({ name: "configDrawer" });
const drawer = ref(false);
function open() {
drawer.value = true;
}
function close() {
drawer.value = false;
}
defineExpose({ open, close });
</script>
<style scoped>

View File

@@ -13,56 +13,55 @@
</div>
</template>
<script>
<script setup>
import { ref } from "vue";
import { initDepartment } from "@/api/index";
export default {
name: "departmentChoose",
data() {
return {
selectDep: [],
department: [],
cascaderProps: {
value: "value",
label: "label",
children: "children",
checkStrictly: true,
emitPath: true,
},
};
},
methods: {
initDepartmentData() {
initDepartment().then((res) => {
if (res.success) {
const arr = res.result;
this.filterData(arr);
this.department = arr;
}
});
},
handleChangeDep(value) {
let departmentId = "";
if (value && value.length > 0) {
departmentId = value[value.length - 1];
}
this.$emit("on-change", departmentId);
},
clearSelect() {
this.selectDep = [];
},
filterData(data) {
data.forEach((e) => {
e.value = e.id;
e.label = e.title;
if (e.children) {
this.filterData(e.children);
}
});
},
},
created() {
this.initDepartmentData();
},
};
defineOptions({ name: "departmentChoose" });
const emit = defineEmits(["on-change"]);
const selectDep = ref([]);
const department = ref([]);
const cascaderProps = ref({
value: "value",
label: "label",
children: "children",
checkStrictly: true,
emitPath: true,
});
function initDepartmentData() {
initDepartment().then((res) => {
if (res.success) {
const arr = res.result;
filterData(arr);
department.value = arr;
}
});
}
function handleChangeDep(value) {
let departmentId = "";
if (value && value.length > 0) {
departmentId = value[value.length - 1];
}
emit("on-change", departmentId);
}
function clearSelect() {
selectDep.value = [];
}
function filterData(data) {
data.forEach((e) => {
e.value = e.id;
e.label = e.title;
if (e.children) {
filterData(e.children);
}
});
}
initDepartmentData();
// userManage.vue 通过 depRef 引用本组件(当前未实际调用方法),作为公共组件 API 保留
defineExpose({ clearSelect, selectDep });
</script>

View File

@@ -35,101 +35,100 @@
</div>
</template>
<script>
<script setup>
import { ref } from "vue";
import { initDepartment } from "@/api/index";
export default {
name: "departmentTreeChoose",
props: {
multiple: {
type: Boolean,
default: false,
},
clearable: {
type: Boolean,
default: true,
},
placeholder: {
type: String,
default: "点击选择部门",
},
defineOptions({ name: "departmentTreeChoose" });
const props = defineProps({
multiple: {
type: Boolean,
default: false,
},
data() {
return {
depLoading: false,
departmentTitle: "",
searchKey: "",
dataDep: [],
cloneDep: [],
departmentId: [],
treeProps: {
label: "title",
children: "children",
},
};
clearable: {
type: Boolean,
default: true,
},
methods: {
initDepartmentData() {
this.depLoading = true;
initDepartment()
.then((res) => {
if (res.success) {
this.dataDep = res.result;
this.cloneDep = JSON.parse(JSON.stringify(this.dataDep));
}
})
.finally(() => {
this.depLoading = false;
});
},
searchDep() {
if (this.searchKey) {
this.dataDep = this.cloneDep.filter(
(item) => item.title.indexOf(this.searchKey) > -1
);
} else {
this.dataDep = JSON.parse(JSON.stringify(this.cloneDep));
}
},
selectTree(node) {
if (!node) {
this.$emit("on-change", null);
this.departmentId = "";
this.departmentTitle = "";
return;
}
this.departmentId = node.id;
this.departmentTitle = node.title;
this.$emit("on-change", {
departmentId: this.departmentId,
departmentTitle: this.departmentTitle,
});
},
clearSelect() {
this.departmentId = [];
this.departmentTitle = "";
this.initDepartmentData();
if (this.multiple) {
this.$emit("on-change", []);
} else {
this.$emit("on-change", "");
}
this.$emit("on-clear");
},
setData(ids, title) {
this.departmentTitle = title;
if (this.multiple) {
this.departmentId = ids;
} else {
this.departmentId = [];
this.departmentId.push(ids);
}
},
},
created() {
this.initDepartmentData();
placeholder: {
type: String,
default: "点击选择部门",
},
});
const emit = defineEmits(["on-change", "on-clear"]);
const depLoading = ref(false);
const departmentTitle = ref("");
const searchKey = ref("");
const dataDep = ref([]);
const cloneDep = ref([]);
const departmentId = ref([]);
const treeProps = {
label: "title",
children: "children",
};
function initDepartmentData() {
depLoading.value = true;
initDepartment()
.then((res) => {
if (res.success) {
dataDep.value = res.result;
cloneDep.value = JSON.parse(JSON.stringify(dataDep.value));
}
})
.finally(() => {
depLoading.value = false;
});
}
function searchDep() {
if (searchKey.value) {
dataDep.value = cloneDep.value.filter(
(item) => item.title.indexOf(searchKey.value) > -1
);
} else {
dataDep.value = JSON.parse(JSON.stringify(cloneDep.value));
}
}
function selectTree(node) {
if (!node) {
emit("on-change", null);
departmentId.value = "";
departmentTitle.value = "";
return;
}
departmentId.value = node.id;
departmentTitle.value = node.title;
emit("on-change", {
departmentId: departmentId.value,
departmentTitle: departmentTitle.value,
});
}
function clearSelect() {
departmentId.value = [];
departmentTitle.value = "";
initDepartmentData();
if (props.multiple) {
emit("on-change", []);
} else {
emit("on-change", "");
}
emit("on-clear");
}
function setData(ids, title) {
departmentTitle.value = title;
if (props.multiple) {
departmentId.value = ids;
} else {
departmentId.value = [];
departmentId.value.push(ids);
}
}
initDepartmentData();
// 父组件(userManage)通过 ref 调用 setData / clearSelect
defineExpose({ setData, clearSelect });
</script>
<style lang="scss" scoped>

View File

@@ -29,99 +29,105 @@
</div>
</template>
<script>
export default {
name: "setPassword",
props: {
modelValue: String,
value: String,
size: String,
placeholder: {
type: String,
default: "请输入密码,长度为6-20个字符",
},
disabled: {
type: Boolean,
default: false,
},
readonly: {
type: Boolean,
default: false,
},
maxlength: {
type: Number,
default: 20,
},
<script setup>
import { ref, watch } from "vue";
defineOptions({ name: "setPassword" });
const props = defineProps({
modelValue: String,
value: String,
size: String,
placeholder: {
type: String,
default: "请输入密码,长度为6-20个字符",
},
emits: ["update:modelValue", "input", "on-change"],
data() {
return {
currentValue: this.modelValue ?? this.value ?? "",
tipStyle: "password-tip-none",
strengthValue: 0,
progressStatus: "",
strength: "无",
grade: 0,
};
disabled: {
type: Boolean,
default: false,
},
watch: {
modelValue(val) {
this.setCurrentValue(val);
},
value(val) {
this.setCurrentValue(val);
},
readonly: {
type: Boolean,
default: false,
},
methods: {
checkStrengthValue(v) {
let grade = 0;
if (/\d/.test(v)) grade++;
if (/[a-z]/.test(v)) grade++;
if (/[A-Z]/.test(v)) grade++;
if (/\W/.test(v)) grade++;
if (v.length >= 10) grade++;
this.grade = grade;
return grade;
},
strengthChange() {
if (!this.currentValue) {
this.tipStyle = "password-tip-none";
this.strength = "无";
this.strengthValue = 0;
return;
}
const grade = this.checkStrengthValue(this.currentValue);
if (grade <= 1) {
this.progressStatus = "exception";
this.tipStyle = "password-tip-weak";
this.strength = "弱";
this.strengthValue = 33;
} else if (grade >= 2 && grade <= 4) {
this.progressStatus = "";
this.tipStyle = "password-tip-middle";
this.strength = "中";
this.strengthValue = 66;
} else {
this.progressStatus = "success";
this.tipStyle = "password-tip-strong";
this.strength = "强";
this.strengthValue = 100;
}
},
handleChange() {
this.strengthChange();
this.$emit("update:modelValue", this.currentValue);
this.$emit("input", this.currentValue);
this.$emit("on-change", this.currentValue, this.grade, this.strength);
},
setCurrentValue(value) {
if (value === this.currentValue) return;
this.currentValue = value ?? "";
this.strengthChange();
this.$emit("on-change", this.currentValue, this.grade, this.strength);
},
maxlength: {
type: Number,
default: 20,
},
};
});
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
const currentValue = ref(props.modelValue ?? props.value ?? "");
const tipStyle = ref("password-tip-none");
const strengthValue = ref(0);
const progressStatus = ref("");
const strength = ref("无");
const grade = ref(0);
watch(
() => props.modelValue,
(val) => {
setCurrentValue(val);
}
);
watch(
() => props.value,
(val) => {
setCurrentValue(val);
}
);
function checkStrengthValue(v) {
// 局部变量原名 grade 与顶层 ref 同名,改名避免遮蔽
let g = 0;
if (/\d/.test(v)) g++;
if (/[a-z]/.test(v)) g++;
if (/[A-Z]/.test(v)) g++;
if (/\W/.test(v)) g++;
if (v.length >= 10) g++;
grade.value = g;
return g;
}
function strengthChange() {
if (!currentValue.value) {
tipStyle.value = "password-tip-none";
strength.value = "无";
strengthValue.value = 0;
return;
}
const grade = checkStrengthValue(currentValue.value);
if (grade <= 1) {
progressStatus.value = "exception";
tipStyle.value = "password-tip-weak";
strength.value = "弱";
strengthValue.value = 33;
} else if (grade >= 2 && grade <= 4) {
progressStatus.value = "";
tipStyle.value = "password-tip-middle";
strength.value = "中";
strengthValue.value = 66;
} else {
progressStatus.value = "success";
tipStyle.value = "password-tip-strong";
strength.value = "强";
strengthValue.value = 100;
}
}
function handleChange() {
strengthChange();
emit("update:modelValue", currentValue.value);
emit("input", currentValue.value);
emit("on-change", currentValue.value, grade.value, strength.value);
}
function setCurrentValue(value) {
if (value === currentValue.value) return;
currentValue.value = value ?? "";
strengthChange();
emit("on-change", currentValue.value, grade.value, strength.value);
}
</script>
<style lang="scss" scoped>

View File

@@ -60,7 +60,7 @@
@closed="resetPicModal"
>
<ossManage
ref="ossManage"
ref="ossManageRef"
:is-component="true"
:initialize="picModalFlag"
:max-select="1"
@@ -70,110 +70,112 @@
</div>
</template>
<script>
<script setup>
import { nextTick, ref, useAttrs, watch } from "vue";
import { View } from "@element-plus/icons-vue";
import { uploadFile } from "@/api/index";
import { getStore } from "@/libs/storage";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
name: "uploadPicInput",
components: {
ossManage,
View,
defineOptions({ name: "uploadPicInput" });
const props = defineProps({
modelValue: String,
value: String,
size: {
default: "default",
type: String,
},
props: {
modelValue: String,
value: String,
size: {
default: "default",
type: String,
},
placeholder: {
type: String,
default: "图片链接",
},
showInput: {
type: Boolean,
default: true,
},
disabled: {
type: Boolean,
default: false,
},
readonly: {
type: Boolean,
default: false,
},
maxlength: Number,
placeholder: {
type: String,
default: "图片链接",
},
emits: ["update:modelValue", "input", "on-change"],
data() {
return {
accessToken: {},
currentValue: this.modelValue ?? this.value ?? "",
viewImage: false,
uploadFileUrl: uploadFile,
picModalFlag: false,
selectedFormBtnName: "",
picIndex: "",
};
showInput: {
type: Boolean,
default: true,
},
methods: {
handleCLickImg(val, index) {
this.picModalFlag = true;
this.selectedFormBtnName = val;
this.picIndex = index;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
handleOssCallback(item) {
this.picModalFlag = false;
if (!item?.url) return;
this.currentValue = item.url;
this.picIndex = "";
this.emitValue(this.currentValue);
},
resetPicModal() {
this.selectedFormBtnName = "";
this.picIndex = "";
},
init() {
this.accessToken = {
accessToken: this.getStore("accessToken"),
};
},
emitValue(val) {
this.$emit("update:modelValue", val);
this.$emit("input", val);
this.$emit("on-change", val);
},
handleChange() {
this.emitValue(this.currentValue);
this.$attrs.rollback && this.$attrs.rollback();
},
setCurrentValue(value) {
if (value === this.currentValue) {
return;
}
this.currentValue = value ?? "";
this.emitValue(this.currentValue);
},
disabled: {
type: Boolean,
default: false,
},
watch: {
modelValue(val) {
this.setCurrentValue(val);
},
value(val) {
this.setCurrentValue(val);
},
readonly: {
type: Boolean,
default: false,
},
created() {
this.init();
},
};
maxlength: Number,
});
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
const attrs = useAttrs();
const accessToken = ref({});
const currentValue = ref(props.modelValue ?? props.value ?? "");
const viewImage = ref(false);
const uploadFileUrl = ref(uploadFile);
const picModalFlag = ref(false);
const selectedFormBtnName = ref("");
const picIndex = ref("");
// 模板 ref:ossManage 与 import 组件同名,改名避免互相覆盖
const ossManageRef = ref(null);
function handleCLickImg(val, index) {
picModalFlag.value = true;
selectedFormBtnName.value = val;
picIndex.value = index;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
function handleOssCallback(item) {
picModalFlag.value = false;
if (!item?.url) return;
currentValue.value = item.url;
picIndex.value = "";
emitValue(currentValue.value);
}
function resetPicModal() {
selectedFormBtnName.value = "";
picIndex.value = "";
}
function init() {
accessToken.value = {
accessToken: getStore("accessToken"),
};
}
function emitValue(val) {
emit("update:modelValue", val);
emit("input", val);
emit("on-change", val);
}
function handleChange() {
emitValue(currentValue.value);
attrs.rollback && attrs.rollback();
}
function setCurrentValue(value) {
if (value === currentValue.value) {
return;
}
currentValue.value = value ?? "";
emitValue(currentValue.value);
}
watch(
() => props.modelValue,
(val) => {
setCurrentValue(val);
}
);
watch(
() => props.value,
(val) => {
setCurrentValue(val);
}
);
// 原 created 钩子
init();
</script>
<style lang="scss" scoped>

View File

@@ -75,167 +75,167 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, ref, watch } from "vue";
import { Camera, Delete, View } from "@element-plus/icons-vue";
import { uploadFile } from "@/api/index";
import { getStore } from "@/libs/storage";
import { Message } from "@/utils/message";
import vuedraggable from "vuedraggable";
export default {
name: "uploadPicThumb",
components: {
vuedraggable,
Camera,
Delete,
View,
defineOptions({ name: "uploadPicThumb" });
const props = defineProps({
modelValue: { type: null },
value: { type: null },
draggable: {
type: Boolean,
default: true,
},
props: {
modelValue: { type: null },
value: { type: null },
draggable: {
type: Boolean,
default: true,
},
multiple: {
type: Boolean,
default: true,
},
limit: {
type: Number,
default: 10,
},
multiple: {
type: Boolean,
default: true,
},
emits: ["update:modelValue", "input", "on-change", "uploadchange"],
data() {
return {
accessToken: {},
uploadFileUrl: uploadFile,
uploadList: [],
viewImage: false,
imgUrl: "",
};
limit: {
type: Number,
default: 10,
},
computed: {
bindValue() {
return this.modelValue !== undefined ? this.modelValue : this.value;
},
},
methods: {
draggableItemKey(item) {
return item.url || item.uid || item.name;
},
onEnd() {
this.returnValue();
},
init() {
this.setData(this.bindValue, true);
this.accessToken = {
accessToken: this.getStore("accessToken"),
};
},
handleView(imgUrl) {
this.imgUrl = imgUrl;
this.viewImage = true;
},
handleRemove(file) {
this.uploadList = this.uploadList.filter((i) => i.url !== file.url);
this.returnValue();
},
handleSuccess(res, file) {
if (res.success) {
file.url = res.result;
if (!this.multiple && this.uploadList.length > 0) {
this.uploadList.splice(0, 1);
}
this.uploadList.push(file);
this.returnValue();
} else {
this.$Message.error(res.message);
}
},
handleError(error) {
this.$Message.error(error.toString());
},
handleBeforeUpload(file) {
const okType = ["image/jpeg", "image/png", "image/gif", "image/jpg"].includes(file.type);
if (!okType) {
this.$Message.warning("请选择 .jpg .jpeg .png .gif 格式图片");
return false;
}
if (file.size / 1024 > 1024) {
this.$Message.warning("所选文件大小过大,不能超过 1M");
return false;
}
if (this.multiple && this.uploadList.length >= this.limit) {
this.$Message.warning("最多只能上传" + this.limit + "张图片");
return false;
}
return true;
},
emitValue(val) {
this.$emit("update:modelValue", val);
this.$emit("input", val);
this.$emit("on-change", val);
},
returnValue() {
if (!this.uploadList || this.uploadList.length < 1) {
const empty = this.multiple ? [] : "";
this.emitValue(empty);
return;
}
if (!this.multiple) {
this.emitValue(this.uploadList[0].url);
} else {
this.emitValue(this.uploadList.map((e) => e.url));
}
},
setData(v, init) {
if (typeof v == "string") {
if (this.multiple) {
this.$Message.warning("多张上传仅支持数组数据类型");
return;
}
if (!v) {
return;
}
this.uploadList = [{ url: v, status: "finished" }];
this.$emit("uploadchange", v);
this.emitValue(v);
} else if (typeof v == "object" && v) {
if (!this.multiple) {
this.$Message.warning("单张上传仅支持字符串数据类型");
return;
}
this.uploadList = [];
const list = v.length > this.limit ? v.slice(0, this.limit) : v;
if (v.length > this.limit) {
this.$Message.warning("最多只能上传" + this.limit + "张图片");
}
list.forEach((e) => {
this.uploadList.push({
status: "finished",
...(typeof e === "string" ? { url: e } : e),
});
});
if (init) {
this.emitValue(list);
} else {
this.$emit("on-change", list);
}
}
},
},
watch: {
modelValue(val) {
this.setData(val);
},
value(val) {
this.setData(val);
},
},
mounted() {
this.init();
},
};
});
const emit = defineEmits(["update:modelValue", "input", "on-change", "uploadchange"]);
const accessToken = ref({});
const uploadFileUrl = uploadFile;
const uploadList = ref([]);
const viewImage = ref(false);
const imgUrl = ref("");
// el-upload 模板 ref
const upload = ref(null);
const bindValue = computed(() => {
return props.modelValue !== undefined ? props.modelValue : props.value;
});
watch(
() => props.modelValue,
(val) => {
setData(val);
}
);
watch(
() => props.value,
(val) => {
setData(val);
}
);
function draggableItemKey(item) {
return item.url || item.uid || item.name;
}
function onEnd() {
returnValue();
}
function init() {
setData(bindValue.value, true);
accessToken.value = {
accessToken: getStore("accessToken"),
};
}
function handleView(url) {
imgUrl.value = url;
viewImage.value = true;
}
function handleRemove(file) {
uploadList.value = uploadList.value.filter((i) => i.url !== file.url);
returnValue();
}
function handleSuccess(res, file) {
if (res.success) {
file.url = res.result;
if (!props.multiple && uploadList.value.length > 0) {
uploadList.value.splice(0, 1);
}
uploadList.value.push(file);
returnValue();
} else {
Message.error(res.message);
}
}
function handleError(error) {
Message.error(error.toString());
}
function handleBeforeUpload(file) {
const okType = ["image/jpeg", "image/png", "image/gif", "image/jpg"].includes(file.type);
if (!okType) {
Message.warning("请选择 .jpg .jpeg .png .gif 格式图片");
return false;
}
if (file.size / 1024 > 1024) {
Message.warning("所选文件大小过大,不能超过 1M");
return false;
}
if (props.multiple && uploadList.value.length >= props.limit) {
Message.warning("最多只能上传" + props.limit + "张图片");
return false;
}
return true;
}
function emitValue(val) {
emit("update:modelValue", val);
emit("input", val);
emit("on-change", val);
}
function returnValue() {
if (!uploadList.value || uploadList.value.length < 1) {
const empty = props.multiple ? [] : "";
emitValue(empty);
return;
}
if (!props.multiple) {
emitValue(uploadList.value[0].url);
} else {
emitValue(uploadList.value.map((e) => e.url));
}
}
function setData(v, init) {
if (typeof v == "string") {
if (props.multiple) {
Message.warning("多张上传仅支持数组数据类型");
return;
}
if (!v) {
return;
}
uploadList.value = [{ url: v, status: "finished" }];
emit("uploadchange", v);
emitValue(v);
} else if (typeof v == "object" && v) {
if (!props.multiple) {
Message.warning("单张上传仅支持字符串数据类型");
return;
}
uploadList.value = [];
const list = v.length > props.limit ? v.slice(0, props.limit) : v;
if (v.length > props.limit) {
Message.warning("最多只能上传" + props.limit + "张图片");
}
list.forEach((e) => {
uploadList.value.push({
status: "finished",
...(typeof e === "string" ? { url: e } : e),
});
});
if (init) {
emitValue(list);
} else {
emit("on-change", list);
}
}
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>