Commit 5f2970ea authored by zhanglongbao's avatar zhanglongbao

feat

parent ac1d469b
......@@ -7,8 +7,10 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Address: typeof import('./src/components/widget/field/address/index.vue')['default']
Avatar: typeof import('./src/components/avatar/index.vue')['default']
copy: typeof import('./src/components/widget/field/input copy/index.vue')['default']
Create_form_data: typeof import('./src/components/create_form_data/index.vue')['default']
Date_time: typeof import('./src/components/widget/field/date_time/index.vue')['default']
Date_time_range: typeof import('./src/components/widget/field/date_time_range/index.vue')['default']
Dd: typeof import('./src/components/name/dd.vue')['default']
......@@ -19,22 +21,31 @@ declare module 'vue' {
Multiline: typeof import('./src/components/widget/field/multiline/index.vue')['default']
Name: typeof import('./src/components/name/index.vue')['default']
Number: typeof import('./src/components/widget/field/number/index.vue')['default']
Order_client: typeof import('./src/components/widget/field/order_client/index.vue')['default']
Order_product: typeof import('./src/components/widget/field/order_product/index.vue')['default']
Parting_line: typeof import('./src/components/widget/field/parting_line/index.vue')['default']
Popup: typeof import('./src/components/popup/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Select: typeof import('./src/components/select/index.vue')['default']
Select_address: typeof import('./src/components/select_address/index.vue')['default']
Select_client: typeof import('./src/components/select_client/index.vue')['default']
Select_customer: typeof import('./src/components/widget/field/select_customer/index.vue')['default']
Select_time: typeof import('./src/components/select_time/index.vue')['default']
Select_user_dept: typeof import('./src/components/select_user_dept/index.vue')['default']
Signature: typeof import('./src/components/widget/field/signature/index.vue')['default']
Single_choice: typeof import('./src/components/widget/field/single_choice/index.vue')['default']
Toast: typeof import('./src/components/toast/index.vue')['default']
User: typeof import('./src/components/widget/field/user/index.vue')['default']
User_dept_list: typeof import('./src/components/user_dept_list/index.vue')['default']
User_list: typeof import('./src/components/user_list/index.vue')['default']
VanButton: typeof import('vant/es')['Button']
VanCascader: typeof import('vant/es')['Cascader']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanDatePicker: typeof import('vant/es')['DatePicker']
VanPicker: typeof import('vant/es')['Picker']
VanPopup: typeof import('vant/es')['Popup']
VanRadio: typeof import('vant/es')['Radio']
VanStepper: typeof import('vant/es')['Stepper']
VanTimePicker: typeof import('vant/es')['TimePicker']
Widget: typeof import('./src/components/widget/index.vue')['default']
......
......@@ -2,9 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
<title>极速工单</title>
</head>
<body>
<div id="app"></div>
......
import axios from "axios";
import { getToken, getUrlKey } from "@/utils/public";
import { login } from "@/utils/container/index";
import { showToast } from "vant";
import { $toast } from "@/components/toast";
const external_token = getUrlKey("external_token") || "cr2pc8jsibf027753ppg";
const http = axios.create({
baseURL: "/api",
......@@ -14,6 +16,7 @@ http.interceptors.request.use((config) => {
arg: {
...(config.data || {}),
token,
external_token,
},
meta: {
app_id: getUrlKey("corp_id") || undefined,
......@@ -25,12 +28,12 @@ http.interceptors.request.use((config) => {
http.interceptors.response.use((response) => {
if (response.status == 200) {
if (response.data.code == 1000) {
showToast({ message: "登陆过期", position: "top" });
$toast({ type: "warning", message: "登陆过期" });
login();
}
if (![0, 1000].includes(response.data.code)) {
showToast({ message: response.data.msg, position: "top" });
$toast({ type: "warning", message: response.data.msg });
}
return response.data;
......
......@@ -8,6 +8,7 @@ import oss from "./methods/oss";
import order from "./methods/order";
import log from "./methods/log";
import power from "./methods/power";
import external from "./methods/external";
export default {
platform,
......@@ -20,4 +21,5 @@ export default {
order,
log,
power,
external,
};
import http from "../axios";
export default {
// 获取产品信息
getInfoByQrCode() {
return http.post("/order.external/getInfoByQrCode");
},
// 获取详情界面配置
getQrCodeWindow() {
return http.post("/order.external/getQrCodeWindow");
},
};
import http from "../axios";
import { container } from "@/utils/container/index";
export default {
// 获取表单类型列表
TemplateLists(params: { template_type?: number[]; form_ids?: string[] }) {
return http.post("/order.form/TemplateLists", params);
return http.post(
`/order.${container.qr_code ? "external" : "form"}/TemplateLists`,
params
);
},
// 获取工单类类型详情
FormDetail(params: { id: string }) {
return http.post("/order.form/FromDetail", params);
return http.post(
`/order.${container.qr_code ? "external" : "form"}/FromDetail`,
params
);
},
// 更新表单
FormUpdate(params: { form_ids: string[] }) {
......@@ -31,7 +38,9 @@ export default {
},
// 获取省市区地址
GetAddress() {
return http.post("/order.form/GetAddress");
return http.post(
`/order.${container.qr_code ? "external" : "form"}/GetAddress`
);
},
// 获取表单工作流
listFlowNode(params: { form_id: string }) {
......
import http from "../axios";
import { container } from "@/utils/container/index";
export default {
// 获取oss配置
getOssConfig() {
return http.post("/order.file/getOssConfig");
return http.post(
`/order.${container.qr_code ? "external" : "file"}/getOssConfig`
);
},
// 文件签名
getSignByMap(params: any) {
return http.post("/order.file/getSignByMap", params);
return http.post(
`/order.${container.qr_code ? "external" : "file"}/getSignByMap`,
params
);
},
};
......@@ -106,7 +106,7 @@ body {
.button {
--btn_bg_color: var(--blue);
--btn_color: white;
--btn_height: 29px;
--btn_height: 28px;
color: var(--btn_color);
background-color: var(--btn_bg_color);
......@@ -114,12 +114,12 @@ body {
border-radius: 4px;
cursor: pointer;
user-select: none;
font-size: 12px;
box-sizing: border-box;
height: var(--btn_height);
line-height: var(--btn_height);
display: inline-block;
text-align: center;
flex-shrink: 0;
&:hover {
opacity: 0.8;
......@@ -162,6 +162,15 @@ body {
background-color: transparent;
}
}
.bottom_button {
display: flex;
align-items: center;
.button {
--btn_height: 38px;
flex: 1;
}
}
// 标签
.tags {
......
import { createApp } from "vue";
import comp from "./index.vue";
import type { Props } from "./index.vue";
import { initApp } from "@/utils/init";
// 修改表单信息
export const $createFormData = (options: Props) => {
const container = document.createElement("div");
options.onHide = () => {
vm.unmount();
document.body.removeChild(container);
container.remove();
};
const vm = createApp(comp, options as any);
initApp(vm);
vm.mount(container);
document.body.appendChild(container);
};
<template>
<popupComp
ref="popup_comp"
:show_header="false"
:round="false"
height="100vh"
@closed="onHide">
<div class="formContent">
<formContentComp
:form_detail="form_detail"
v-model="data"
@editData="editData" />
<div class="bottom bottom_button">
<span class="button plain" @click="close">取消</span>
<span class="button" @click="confirm">创建</span>
</div>
</div>
</popupComp>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import popupComp from "@/components/popup/index.vue";
import api from "@/api";
import formContentComp from "@/components/widget/index.vue";
export interface Props {
form_id: string;
data?: object;
onHide?: () => void;
onConfirm?: (data: object) => void;
onEditConfirm?: (data: object) => void;
}
const props = defineProps<Props>();
const show = ref(false);
onMounted(() => {
show.value = true;
});
const confirm = () => {
props.onConfirm?.(data.value);
props.onEditConfirm?.(edit.value);
close();
};
const popup_comp = ref();
const close = () => {
popup_comp.value.show = false;
};
// 表单数据
const data = ref<object>({});
const edit = ref<object>({});
const editData = (data: object) => {
edit.value = { ...edit.value, ...data };
console.log("edit", edit.value);
};
// 表单信息
const form_detail = ref<FormDetail>({} as FormDetail);
const getFormDetail = async () => {
const msg = await api.form.FormDetail({
id: props.form_id,
});
if (msg.code == 0) {
form_detail.value = msg.data[0] || {};
}
};
// 初始化
const init = () => {
getFormDetail();
if (props.data) {
data.value = JSON.parse(JSON.stringify(props.data));
}
};
init();
</script>
<style lang="less" scoped>
.formContent {
height: 100vh;
display: flex;
flex-direction: column;
.bottom {
padding: 8px 20px;
flex-shrink: 0;
}
}
</style>
......@@ -34,7 +34,6 @@ interface PopupProps {
}
withDefaults(defineProps<PopupProps>(), {
position: "bottom",
height: "30%",
round: true,
show_header: true,
show_cancel: true,
......
import { createApp } from "vue";
import Comp from "./index.vue";
import type { Props } from "./index.vue";
export const $selectAddress = (options: Props) => {
const container = document.createElement("div");
options.onHide = () => {
vm.unmount();
document.body.removeChild(container);
container.remove();
};
const vm = createApp(Comp, options as any);
vm.mount(container);
document.body.appendChild(container);
};
<template>
<popupComp
ref="popup_comp"
title="选择地址"
@closed="onHide"
@cancel="close"
@confirm="confirm">
<van-cascader
:options="addressList"
:show-header="false"
:field-names="{
text: 'name',
value: 'name',
children: 'districts',
}"
@change="change" />
</popupComp>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useAddress } from "@/utils/hook";
import popupComp from "@/components/popup/index.vue";
export interface Props {
value?: string;
onHide?(): void;
onConfirm?(value: string): void;
}
const props = withDefaults(defineProps<Props>(), {});
const popup_comp = ref();
const select_address = ref<string>("");
const { addressList } = useAddress();
const change = (value: any) => {
select_address.value = value.selectedOptions
.map((it: any) => it.name)
.join("/");
};
const close = () => {
popup_comp.value.show = false;
};
const confirm = () => {
props.onConfirm?.(select_address.value);
close();
};
</script>
<style lang="less" scoped></style>
import { createApp } from "vue";
import Comp from "./index.vue";
import type { Props } from "./index.vue";
export const $selectClient = (options: Props) => {
const container = document.createElement("div");
options.onHide = () => {
vm.unmount();
document.body.removeChild(container);
container.remove();
};
const vm = createApp(Comp, options as any);
vm.mount(container);
document.body.appendChild(container);
};
<template>
<popupComp
ref="popup_comp"
title="选择客户"
@closed="onHide"
@cancel="close"
@confirm="confirm">
<div class="list">
<div
v-for="it in list"
class="customer"
:class="{ active: selected_client.id == it.id }"
@click="selected_client = it">
<div class="name">
<span>{{ it.customer_name }}</span>
<i class="icon-24" v-if="selected_client.id == it.id"></i>
<i class="icon-14" v-else></i>
</div>
<div class="info">
<span>联系人:{{ it.link_user_name }}</span>
<span>联系电话:{{ it.link_user_phone }}</span>
</div>
</div>
</div>
</popupComp>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import api from "@/api";
import { useDataList } from "@/utils/hook";
import popupComp from "@/components/popup/index.vue";
export interface Props {
value?: any;
onHide?(): void;
onConfirm?(value: any): void;
}
const props = withDefaults(defineProps<Props>(), {});
const popup_comp = ref();
const close = () => {
popup_comp.value.show = false;
};
const confirm = () => {
props.onConfirm?.(selected_client.value);
close();
};
// 选中客户
const selected_client = ref<any>(props.value || {});
// 产品表单
const client_form_list = ref<FormInfo[]>([]);
const getClientFormList = async () => {
const msg = await api.form.TemplateLists({ template_type: [1] });
if (msg.code == 0) {
client_form_list.value = msg.data.lists;
}
};
// 数据列表
const params = computed(() => ({
api: api.customer.listCustomInfo,
params: {
form_id: client_form_list.value[0].form_id,
size: 1000,
},
}));
const { list, getList } = useDataList(params);
const init = async () => {
await getClientFormList();
getList();
};
init();
</script>
<style lang="less" scoped>
.list {
display: flex;
flex-direction: column;
padding: 10px;
height: 400px;
overflow: auto;
.customer {
display: flex;
flex-direction: column;
border: 1px solid var(--border_color);
border-radius: 2px;
cursor: pointer;
> div {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
}
.name {
background-color: var(--bg_gray);
height: 34px;
}
.info {
height: 38px;
}
&.active {
border-color: var(--blue);
color: var(--blue);
.name {
background-color: var(--hover_blue);
}
}
}
.customer:not(:nth-last-of-type(1)) {
margin-bottom: 10px;
}
}
</style>
import { computed, ref } from "vue";
import api from "@/api";
import { showToast } from "vant";
import { $toast } from "@/components/toast";
import { Team } from "@/utils/hook";
/**
......@@ -203,10 +203,11 @@ export const useUserList = (props: Props) => {
depts.value[dept_id].length >
select_item.value.size!
) {
showToast({
$toast({
type: "warning",
message: "全选超出数量限制,请手动选择",
position: "top",
});
return;
}
......@@ -223,10 +224,11 @@ export const useUserList = (props: Props) => {
users.value[dept_id].length >
select_item.value.size!
) {
showToast({
$toast({
type: "warning",
message: "全选超出数量限制,请手动选择",
position: "top",
});
return;
}
users.value[dept_id].forEach((it) => {
......
import { createApp } from "vue";
import comp from "./index.vue";
import type { Props } from "./index.vue";
import { initApp } from "@/utils/init";
// toast
export const $toast = (options: Props) => {
const container = document.createElement("div");
options.onHide = () => {
vm.unmount();
document.body.removeChild(container);
container.remove();
};
const vm = createApp(comp, options as any);
initApp(vm);
vm.mount(container);
document.body.appendChild(container);
};
<template>
<Transition>
<div
v-if="show"
class="toast"
:style="{
color: config.color,
backgroundColor: config.bg_color,
border: `1px solid ${config.border}`,
}">
<i :class="config.icon"></i>
<span>{{ message }}</span>
</div>
</Transition>
</template>
<script setup lang="ts">
import { computed, ref, onMounted } from "vue";
export interface Props {
type?: "warning" | "success" | "error";
message: string;
onHide?: () => void;
}
const props = defineProps<Props>();
const show = ref(false);
onMounted(() => {
show.value = true;
});
setTimeout(() => {
show.value = false;
setTimeout(() => {
props.onHide?.();
}, 1000);
}, 1000);
const config = computed(() => {
if (props.type == "warning") {
return {
icon: "icon-131",
color: "#E6A23D",
bg_color: "#FDF6EC",
border: "#FAECD8",
};
} else if (props.type == "success") {
return {
icon: "icon-134",
color: "#67C23A",
bg_color: "#F0F9EB",
border: "#E1F3D8",
};
} else if (props.type == "error") {
return {
icon: "icon-69",
color: "#F56C6C",
bg_color: "#FEF0F0",
border: "#FDE2E2",
};
}
return {
icon: "icon-131",
color: "#909399",
bg_color: "#f4f4f5",
border: "#ececee",
};
});
</script>
<style lang="less" scoped>
.toast {
position: fixed;
z-index: 20000;
top: 10px;
left: 50%;
transform: translateX(-50%);
padding: 8px 10px;
border-radius: 4px;
font-size: 14px;
span {
margin-left: 4px;
}
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
<template>
<div class="box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">{{ config.key_name }}</span>
</div>
<div class="content">
<span class="border" @click="selectAddress">
{{ address.address }}
</span>
</div>
</div>
<div class="box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">详细地址</span>
</div>
<div class="content">
<input
type="text"
class="border"
v-model="address.detailed_address"
@input="change" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { FieldProps } from "../config";
import { $selectAddress } from "@/components/select_address/index";
const props = defineProps<FieldProps>();
const emit = defineEmits(["edit"]);
const address = ref<{
address: string;
detailed_address: string;
type: "1" | "2";
}>((props.data[props.config.key_id] || {}) as any);
const change = () => {
emit("edit", { [props.config.key_id]: address.value });
};
// 选择地址
const selectAddress = () => {
$selectAddress({
onConfirm: (data: string) => {
address.value.address = data;
change();
},
});
};
</script>
<style lang="less" scoped>
@import "../global.less";
</style>
......@@ -108,7 +108,33 @@ export const baseFieldConfig: FieldConfig[] = [
},
];
// 进阶字段
export const upgradeFieldConfig: FieldConfig[] = [];
export const upgradeFieldConfig: FieldConfig[] = [
{
title: "地址选择",
icon: "icon-301",
key_type: 21,
component: markRaw(
defineAsyncComponent(() => import("./address/index.vue"))
),
},
{
title: "手写签名",
icon: "icon-301",
key_type: 31,
component: markRaw(
defineAsyncComponent(() => import("./signature/index.vue"))
),
},
];
// 自定义widget_key_name
export const widgetKeyName: { [key: string]: any } = {};
export const widgetKeyName: { [key: string]: any } = {
// 工单客户(展示联系人联系电话
order_client: markRaw(
defineAsyncComponent(() => import("./order_client/index.vue"))
),
// 工单产品(展示编号型号
order_product: markRaw(
defineAsyncComponent(() => import("./order_product/index.vue"))
),
};
export const allFieldConfig = [...baseFieldConfig, ...upgradeFieldConfig];
......@@ -3,12 +3,6 @@
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">{{ config.key_name }}</span>
<el-tooltip
:content="config.key_text"
placement="top"
v-if="config.key_text">
<i class="icon-22"></i>
</el-tooltip>
</div>
<div class="content">
......
<template>
<div class="box client_box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">{{ config.key_name }}</span>
<div class="right">
<span>新增客户</span>
<span>
<i class="icon-141"></i>
刷新
</span>
</div>
</div>
<div class="content">
<div class="border" @click="selectClient">
<span>{{ client.customer_name }}</span>
<i class="icon-321"></i>
</div>
</div>
</div>
<div class="box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">联系人</span>
</div>
<div class="content">
<span class="border">{{ client.link_user_name }}</span>
</div>
</div>
<div class="box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">联系电话</span>
</div>
<div class="content">
<span class="border">{{ client.link_user_phone }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { FieldProps } from "../config";
import { $selectClient } from "@/components/select_client/index";
const props = defineProps<FieldProps>();
const emit = defineEmits(["edit"]);
const change = () => {
emit("edit", {
[props.config.key_id]: client.value.id,
customer_name: client.value.link_user_name,
customer_phone: client.value.link_user_phone,
});
};
const client = ref<any>({});
const init = () => {
client.value = props.data[props.config.key_id] || {};
};
init();
const selectClient = () => {
$selectClient({
value: client.value,
onConfirm: (data) => {
client.value = data;
console.log("client", client.value);
change();
},
});
};
</script>
<style lang="less" scoped>
@import "../global.less";
.client_box {
.label {
width: 100% !important;
.title {
flex: 1;
}
.right {
display: flex;
align-items: center;
color: var(--blue);
font-size: 13px;
span {
display: flex;
align-items: center;
padding-left: 14px;
i {
margin-right: 4px;
}
}
span:nth-of-type(1) {
border-right: 1px solid var(--blue);
padding-right: 14px;
}
}
}
.content {
.border {
display: flex;
justify-content: space-between;
position: relative;
i {
position: absolute;
right: 0;
top: 0;
height: 100%;
box-sizing: border-box;
width: 40px;
border-left: 1px solid var(--border_color);
display: flex;
align-items: center;
justify-content: center;
color: var(--orange);
font-size: 16px;
}
}
}
}
</style>
<template>
<div class="box product_box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">{{ config.key_name }}</span>
<div class="right">
<span>新增客户</span>
<span>
<i class="icon-141"></i>
刷新
</span>
</div>
</div>
<div class="content">
<div class="border" @click="selectClient">
<span>{{ client.customer_name }}</span>
<i class="icon-321"></i>
</div>
</div>
</div>
<div class="box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">联系人</span>
</div>
<div class="content">
<span class="border">{{ client.link_user_name }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { FieldProps } from "../config";
import { $selectClient } from "@/components/select_client/index";
const props = defineProps<FieldProps>();
const emit = defineEmits(["edit"]);
const change = () => {
emit("edit", {
[props.config.key_id]: client.value.id,
customer_name: client.value.link_user_name,
customer_phone: client.value.link_user_phone,
});
};
const client = ref<any>({});
const init = () => {
client.value = props.data[props.config.key_id] || {};
};
init();
const selectClient = () => {
$selectClient({
value: client.value,
onConfirm: (data) => {
client.value = data;
console.log("client", client.value);
change();
},
});
};
</script>
<style lang="less" scoped>
@import "../global.less";
.product_box {
.label {
width: 100% !important;
.title {
flex: 1;
}
.right {
display: flex;
align-items: center;
color: var(--blue);
font-size: 13px;
span {
display: flex;
align-items: center;
padding-left: 14px;
i {
margin-right: 4px;
}
}
span:nth-of-type(1) {
border-right: 1px solid var(--blue);
padding-right: 14px;
}
}
}
.content {
.border {
display: flex;
justify-content: space-between;
position: relative;
i {
position: absolute;
right: 0;
top: 0;
height: 100%;
box-sizing: border-box;
width: 40px;
border-left: 1px solid var(--border_color);
display: flex;
align-items: center;
justify-content: center;
color: var(--orange);
font-size: 16px;
}
}
}
}
</style>
<template>
<div class="box">
<div class="label">
<span class="required" v-if="required">*</span>
<span class="title">{{ config.key_name }}</span>
</div>
<div class="content">
<canvas ref="canvas_comp"></canvas>
<div class="btn_group">
<span class="button plain" @click="signature.clear">清空</span>
<span class="button" @click="save">确定</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { FieldProps } from "../config";
import SmoothSignature from "smooth-signature";
import { useOssStore } from "@/store/oss";
import axios from "axios";
import { ossSignature } from "@/utils/ossSignature";
import { $toast } from "@/components/toast/index";
const props = defineProps<FieldProps>();
const emit = defineEmits(["edit"]);
const Oss = useOssStore();
const change = (id: string) => {
emit("edit", { [props.config.key_id]: id });
};
const canvas_comp = ref();
const signature = ref();
onMounted(() => {
signature.value = new SmoothSignature(canvas_comp.value, {
height: 200,
minWidth: 3,
maxWidth: 8,
});
init();
});
const save = async () => {
if (signature.value.isEmpty()) {
$toast({ type: "warning", message: "无签名内容" });
return;
}
const base64 = signature.value.getPNG();
const buffer = base64ToBinary(base64);
// 上传文件
const data: any = Oss.getBaseData({
name: new Date().getTime() + ".png",
} as any);
const params = new FormData();
Object.keys(data).forEach((key) => {
params.set(key, data[key]);
});
params.append("file", buffer as any);
const msg = await axios.post(Oss.base_oss.host, params);
if (msg.data.code == 0) {
change(msg.data.data.data.id);
}
};
// 将base64图片转换成二进制
function base64ToBinary(base64Data: string) {
let binaryString = window.atob(base64Data.split(",")[1]);
let arrayBuffer = new ArrayBuffer(binaryString.length);
let intArray = new Uint8Array(arrayBuffer);
for (let i = 0, j = binaryString.length; i < j; i++) {
intArray[i] = binaryString.charCodeAt(i);
}
let data = [intArray];
const blob = new Blob(data);
return blob;
}
const init = async () => {
const file_url = props.data[props.config.key_id]?.file_url || "";
if (!file_url) return;
const ctx = canvas_comp.value.getContext("2d");
// 创建一个新的 Image 对象
const img = new Image();
// 设置图片的 src 属性
const url = await ossSignature(file_url);
img.src = url;
// 等待图片加载完成
img.onload = function () {
// 将图片绘制到 Canvas 上
ctx.drawImage(
img,
0,
0,
canvas_comp.value.width,
canvas_comp.value.height
);
};
};
</script>
<style lang="less" scoped>
@import "../global.less";
.content {
display: flex;
flex-direction: column;
canvas {
border: 1px solid var(--border_color);
border-radius: 4px;
margin: 10px 0;
}
.btn_group {
display: flex;
justify-content: flex-end;
}
}
</style>
......@@ -8,7 +8,12 @@ const routers: RouteRecordRaw[] = [
{
path: "/",
name: "home",
component: () => import("@/view/ceshi.vue"),
component: () => import("@/view/home/index.vue"),
},
{
path: "/code_product_detail",
name: "code_product_detail",
component: () => import("@/view/code_product_detail/index.vue"),
},
];
......@@ -18,12 +23,16 @@ const router = createRouter({
});
// 白名单列表
const whitelist: string[] = [];
const whitelist: string[] = ["/code_product_detail"];
let first = true; // 首次进入
router.beforeEach(async (to, from, next) => {
console.log("router.beforeEach", to, from);
// 获取oss
const OSS = useOssStore();
OSS.getOss();
// 是否是白名单
if (whitelist.includes(to.path)) {
next();
......@@ -31,9 +40,6 @@ router.beforeEach(async (to, from, next) => {
}
if (first) {
const USER = useUserStore();
const OSS = useOssStore();
// 获取token 登陆
let token = getUrlKey("token") || getToken();
if (!token) {
......@@ -42,8 +48,8 @@ router.beforeEach(async (to, from, next) => {
}
setToken(token);
const USER = useUserStore();
await USER.getUserInfo(token); // 获取用户信息
OSS.getOss(); // 获取oss
first = false;
}
......
import { getUrlKey } from "../public";
import * as dd_util from "./dd";
import * as wx_util from "./wx";
......@@ -14,6 +15,7 @@ export const container = {
browser:
!openContainer.includes("dingtalk") &&
!openContainer.includes("wxwork"),
qr_code: !!getUrlKey("external_token"),
};
// 登陆
......
......@@ -12,7 +12,7 @@ export const signature = (el: any, binding: DirectiveBinding<any>) => {
image_info = { src: binding.value };
}
if (image_info.src) {
if (image_info?.src) {
// 要操作的属性 src图片url bg背景图
const type = binding.arg || "src";
......
import { ref, ComputedRef } from "vue";
import { showToast } from "vant";
import { throttle } from "./public";
import api from "@/api";
import { UserInfo } from "@/components/select_user_dept/hook";
import { $toast } from "@/components/toast";
// 数据列表
interface UseDataList {
......@@ -31,7 +31,10 @@ export const useDataList = (props: ComputedRef<UseDataList>) => {
const deleteData = async (params: any) => {
const msg = await props.value?.deleteApi?.(params);
if (msg?.code == 0) {
showToast({ message: "删除成功", position: "top" });
$toast({
type: "warning",
message: "删除成功",
});
getList();
}
};
......
import { useOssStore } from "@/store/oss";
import axios from "axios";
import file_icon from "@/assets/file_icon/index";
import { showToast } from "vant";
import { $toast } from "@/components/toast";
/**
* 格式化文件大小
......@@ -140,15 +140,18 @@ export const uploadFile = (props: UploadFile) => {
// 验证文件格式
const ext = file.name.split(".").pop().toLowerCase();
if (props.ext && !props.ext.includes(ext)) {
showToast({ message: "请上传正确格式的文件", position: "top" });
$toast({
type: "warning",
message: "请上传正确格式的文件",
});
return;
}
// 验证文件大小
if (file.size > (props?.size || 5) * 1024 * 1024) {
showToast({
$toast({
type: "warning",
message: `文件大小不能超过 ${props.size}MB`,
position: "top",
});
return;
}
......
This diff is collapsed.
<template>
<div class="code_product_detail">
<img
class="background_img"
v-signature:src="detail_config?.back_ground?.file_url" />
<div class="detail">
<div
class="order"
v-if="product_detail.have_dealing_order"
:style="{ background: hexToRgba('#f5a034', 0.15) }">
<i class="icon-131"></i>
<span>
当前产品已有{{
product_detail.have_dealing_order
}}个工单正在处理中!
</span>
<i class="icon-46"></i>
</div>
<span
class="title"
v-if="detail_config?.show_customer_fields?.length">
客户信息
</span>
<div
class="detail_item"
v-if="getShowCusmtomerFields('customer_name')">
<span class="label">客户名称</span>
<span>{{ product_detail?.customer_info?.customer_name }}</span>
</div>
<div
class="detail_item"
v-if="getShowCusmtomerFields('link_user_name')">
<span class="label">联系人</span>
<span>{{ product_detail?.customer_info?.link_user_name }}</span>
</div>
<div
class="detail_item"
v-if="getShowCusmtomerFields('link_user_phone')">
<span class="label">联系电话</span>
<span>{{
product_detail?.customer_info?.link_user_phone
}}</span>
</div>
<div class="detail_item" v-if="getShowCusmtomerFields('link_addr')">
<span class="label">联系地址</span>
<span>
{{
product_detail?.customer_info?.link_addr?.address ||
"--"
}}
</span>
</div>
<div class="detail_item" v-if="getShowCusmtomerFields('link_addr')">
<span class="label">详细地址</span>
<span>
{{
product_detail?.customer_info?.link_addr
?.detailed_address || "--"
}}
</span>
</div>
<span
class="title"
v-if="detail_config?.show_product_fields?.length">
产品信息
</span>
<div
class="detail_item"
v-if="getShowProductFields('product_name')">
<span class="label">产品名称</span>
<span>{{ product_detail?.product_info?.product_name }}</span>
</div>
<div
class="detail_item"
v-if="getShowProductFields('product_number')">
<span class="label">产品编号</span>
<span>{{ product_detail?.product_info?.product_number }}</span>
</div>
<div
class="detail_item"
v-if="getShowProductFields('product_type')">
<span class="label">规格型号</span>
<span>{{ product_detail?.product_info?.product_type }}</span>
</div>
<div
class="detail_item"
v-if="getShowProductFields('product_images')">
<span class="label">产品照片</span>
<div class="images">
<img
v-for="it in product_detail?.product_info
?.product_images || []"
v-signature:src="it.file_url" />
</div>
</div>
</div>
<div class="operate bottom_button">
<span
class="button plain"
v-for="it in detail_config.buttons || []"
@click="createFormData(it)">
{{ it.button_name }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import api from "@/api";
import { File, hexToRgba } from "@/utils/public";
import { $createFormData } from "@/components/create_form_data/index";
// 产品详情
const product_detail = ref<{
customer_info: any;
have_dealing_order: number;
product_info: any;
}>({} as any);
const getProductDetail = async () => {
const msg = await api.external.getInfoByQrCode();
if (msg.code == 0) {
product_detail.value = msg.data;
console.log("product_detail", product_detail.value);
}
};
// 显示配置
interface QrCodeConfig {
form_id: string;
back_ground: File | null;
show_customer_fields: string[];
show_product_fields: string[];
buttons: {
button_name: string;
order_form_id: string;
button_order_type: string;
}[];
[k: string]: any;
}
const detail_config = ref<QrCodeConfig>({} as QrCodeConfig);
const getDetailConfig = async () => {
const msg = await api.external.getQrCodeWindow();
if (msg.code == 0) {
detail_config.value = msg.data;
console.log("detail_config", detail_config.value);
}
};
const getShowCusmtomerFields = (id: string) => {
return (detail_config.value.show_customer_fields || []).includes(id);
};
const getShowProductFields = (id: string) => {
return (detail_config.value.show_product_fields || []).includes(id);
};
// 创建
const createFormData = (button: any) => {
$createFormData({
form_id: button.order_form_id,
});
};
const init = () => {
getProductDetail();
getDetailConfig();
};
init();
</script>
<style lang="less" scoped>
.code_product_detail {
width: 100vw;
height: 100vh;
overflow: auto;
box-sizing: border-box;
position: relative;
.background_img {
width: 100%;
position: fixed;
}
.detail {
width: calc(100% - 40px);
box-sizing: border-box;
margin: 0 20px;
margin-top: 140px;
background: white;
position: relative;
border-top-left-radius: 14px;
border-top-right-radius: 14px;
padding: 14px;
min-height: calc(100vh - 140px);
box-sizing: border-box;
display: flex;
flex-direction: column;
box-shadow: 0 -2px 4px 1px #f3f3f3;
.order {
display: flex;
align-items: center;
color: var(--orange);
background-color: red;
padding: 10px;
border-radius: 6px;
span {
flex: 1;
margin: 0 6px;
}
}
.title {
position: relative;
font-weight: bold;
margin-left: 10px;
margin-top: 20px;
&::after {
content: "";
position: absolute;
left: -10px;
top: 0;
bottom: 0;
width: 4px;
background-color: var(--blue);
}
}
.detail_item {
margin-top: 20px;
margin-left: 10px;
display: flex;
.label {
width: 90px;
flex-shrink: 0;
}
span {
word-break: break-all;
}
.images {
display: flex;
flex-wrap: wrap;
img {
width: 70px;
height: 70px;
border-radius: 10px;
border: 1px dotted var(--icon_gray);
margin: 0 8px 8px 0;
}
}
}
}
.operate {
position: fixed;
bottom: 0;
width: 100%;
padding: 10px 30px;
box-sizing: border-box;
z-index: 1;
display: flex;
justify-content: center;
.button {
max-width: 50%;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>
<template>
<div> 123123231 </div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment