Compare commits

...

12 Commits

Author SHA1 Message Date
Teo
d503e64098 违规记录 2025-07-23 11:47:11 +08:00
Teo
e7b0b54f01 部门管理修改功能分包单位bug 2025-07-18 19:33:04 +08:00
Teo
5d76471571 部门管理新增分包单位选项 2025-07-18 19:22:36 +08:00
Teo
d03efb8d6f 解决路由bug 2025-07-14 16:10:03 +08:00
Teo
748464b44a 系统管理/部门管理角色管理用户管理 2025-07-11 19:16:57 +08:00
Teo
4a0962b117 工作流路由重构 2025-07-11 11:18:01 +08:00
ff8dd472de 优化 2025-07-11 09:17:08 +08:00
b331d17032 审核 2025-07-10 15:16:44 +08:00
39e239a886 优化 2025-07-08 17:24:35 +08:00
76ae5832c0 优化 2025-07-08 17:23:32 +08:00
Teo
3c9ed824d3 解决404 2025-07-08 17:22:46 +08:00
Teo
8ef37c5a96 重构进度填报 2025-07-08 16:39:42 +08:00
93 changed files with 13283 additions and 1319 deletions

View File

@ -9,7 +9,7 @@ VITE_APP_BASE_API = 'http://192.168.110.119:8899'
# 无人机接口地址
VITE_APP_BASE_DRONE_API = 'http://192.168.110.8:9136'
VITE_APP_BASE_DRONE_API = 'http://58.17.134.85:9512'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'

View File

@ -14,7 +14,7 @@ VITE_APP_MONITOR_ADMIN = '/admin/applications'
VITE_APP_SNAILJOB_ADMIN = '/snail-job'
# 生产环境
VITE_APP_BASE_API = 'http://192.168.110.2:8899'
VITE_APP_BASE_API = 'http://58.17.134.85:8899'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

View File

@ -212,6 +212,9 @@
<script type="text/javascript"
src="http://58.17.134.85:7363/changxieoffice/web-apps/apps/api/documents/api.js"></script>
<script src="/js/html-docx.js"></script>
<script src="/webrtc/adapter-7.4.0.min.js"></script>
<script src="/webrtc/jquery-1.12.2.min.js"></script>
<script src="/webrtc/srs.sdk.js"></script>
<script src="./src/assets/sdk/YJEarth.min.js"></script>
<script src="./src/utils/reconnecting-websocket.js"></script>

Binary file not shown.

File diff suppressed because it is too large Load Diff

1
public/webrtc/adapter-7.4.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

6
public/webrtc/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

9
public/webrtc/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

19
public/webrtc/dash-v4.5.1.all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/webrtc/hls-0.14.17.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
public/webrtc/jquery-1.12.2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

486
public/webrtc/json2.js Normal file
View File

@ -0,0 +1,486 @@
/*
json2.js
2013-05-26
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, regexp: true */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (typeof JSON !== 'object') {
JSON = {};
}
(function () {
'use strict';
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function () {
return isFinite(this.valueOf())
? this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z'
: null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function () {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string'
? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0
? '[]'
: gap
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
: '[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0
? '{}'
: gap
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
: '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function'
? walk({'': j}, '')
: j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

9
public/webrtc/mpegts-1.7.2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

37
public/webrtc/srs.log.js Normal file
View File

@ -0,0 +1,37 @@
/**
* log specified, there must be a log element as:
<!-- for the log -->
<div class="alert alert-info fade in" id="txt_log">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong><span id="txt_log_title">标题:</span></strong>
<span id="txt_log_msg">日志内容</span>
</div>
*/
var srs_log_disabled = false;
function info(desc) {
if (srs_log_disabled) {
return;
}
$("#txt_log").addClass("alert-info").removeClass("alert-error").removeClass("alert-warn");
$("#txt_log_title").text("Info:");
$("#txt_log_msg").html(desc);
}
function warn(code, desc) {
if (srs_log_disabled) {
return;
}
$("#txt_log").removeClass("alert-info").removeClass("alert-error").addClass("alert-warn");
$("#txt_log_title").text("Warn:");
$("#txt_log_msg").html("code: " + code + ", " + desc);
}
function error(code, desc) {
if (srs_log_disabled) {
return;
}
$("#txt_log").removeClass("alert-info").addClass("alert-error").removeClass("alert-warn");
$("#txt_log_title").text("Error:");
$("#txt_log_msg").html("code: " + code + ", " + desc);
}

182
public/webrtc/srs.page.js Normal file
View File

@ -0,0 +1,182 @@
// to query the swf anti cache.
function srs_get_version_code() { return "1.33"; }
/**
* player specified size.
*/
function srs_get_player_modal() { return 740; }
function srs_get_player_width() { return srs_get_player_modal() - 30; }
function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
/**
* update the navigator, add same query string.
*/
function update_nav() {
$("#nav_srs_player").attr("href", "srs_player.html" + window.location.search);
$("#nav_rtc_player").attr("href", "rtc_player.html" + window.location.search);
$("#nav_rtc_publisher").attr("href", "rtc_publisher.html" + window.location.search);
$("#nav_whip").attr("href", "whip.html" + window.location.search);
$("#nav_whep").attr("href", "whep.html" + window.location.search);
$("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search);
$("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search);
$("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search);
$("#nav_vlc").attr("href", "vlc.html" + window.location.search);
}
// Special extra params, such as auth_key.
function user_extra_params(query, params, rtc) {
var queries = params || [];
for (var key in query.user_query) {
if (key === 'app' || key === 'autostart' || key === 'dir'
|| key === 'filename' || key === 'host' || key === 'hostname'
|| key === 'http_port' || key === 'pathname' || key === 'port'
|| key === 'server' || key === 'stream' || key === 'buffer'
|| key === 'schema' || key === 'vhost' || key === 'api'
|| key === 'path'
) {
continue;
}
if (query[key]) {
queries.push(key + '=' + query[key]);
}
}
return queries;
}
function is_default_port(schema, port) {
return (schema === 'http' && port === 80)
|| (schema === 'https' && port === 443)
|| (schema === 'webrtc' && port === 1985)
|| (schema === 'rtmp' && port === 1935);
}
/**
@param server the ip of server. default to window.location.hostname
@param vhost the vhost of HTTP-FLV. default to window.location.hostname
@param port the port of HTTP-FLV. default to 1935
@param app the app of HTTP-FLV. default to live.
@param stream the stream of HTTP-FLV. default to livestream.flv
*/
function build_default_flv_url() {
var query = parse_query_string();
var schema = (!query.schema)? "http":query.schema;
var server = (!query.server)? window.location.hostname:query.server;
var port = (!query.port)? (schema==="http"? 8080:1935) : Number(query.port);
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
var app = (!query.app)? "live":query.app;
var stream = (!query.stream)? "livestream.flv":query.stream;
var queries = [];
if (server !== vhost && vhost !== "__defaultVhost__") {
queries.push("vhost=" + vhost);
}
queries = user_extra_params(query, queries);
var uri = schema + "://" + server;
if (!is_default_port(schema, port)) {
uri += ":" + port;
}
uri += "/" + app + "/" + stream + "?" + queries.join('&');
while (uri.indexOf("?") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
return uri;
}
function build_default_rtc_url(query) {
// The format for query string to overwrite configs of server.
console.log('?eip=x.x.x.x to overwrite candidate. 覆盖服务器candidate(外网IP)配置');
console.log('?api=x to overwrite WebRTC API(1985).');
console.log('?schema=http|https to overwrite WebRTC API protocol.');
var server = (!query.server)? window.location.hostname:query.server;
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
var app = (!query.app)? "live":query.app;
var stream = (!query.stream)? "livestream":query.stream;
var api = query.api? ':'+query.api : '';
var queries = [];
if (server !== vhost && vhost !== "__defaultVhost__") {
queries.push("vhost=" + vhost);
}
if (query.schema && window.location.protocol !== query.schema + ':') {
queries.push('schema=' + query.schema);
}
queries = user_extra_params(query, queries, true);
var uri = "webrtc://" + server + api + "/" + app + "/" + stream + "?" + queries.join('&');
while (uri.lastIndexOf("?") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
return uri;
};
function build_default_whip_whep_url(query, apiPath) {
// The format for query string to overwrite configs of server.
console.log('?eip=x.x.x.x to overwrite candidate. 覆盖服务器candidate(外网IP)配置');
console.log('?api=x to overwrite WebRTC API(1985).');
console.log('?schema=http|https to overwrite WebRTC API protocol.');
console.log(`?path=xxx to overwrite default ${apiPath}`);
var server = (!query.server)? window.location.hostname:query.server;
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
var app = (!query.app)? "live":query.app;
var stream = (!query.stream)? "livestream":query.stream;
var api = ':' + (query.api || (window.location.protocol === 'http:' ? '1985' : '1990'));
const realApiPath = query.path || apiPath;
var queries = [];
if (server !== vhost && vhost !== "__defaultVhost__") {
queries.push("vhost=" + vhost);
}
if (query.schema && window.location.protocol !== query.schema + ':') {
queries.push('schema=' + query.schema);
}
queries = user_extra_params(query, queries, true);
var uri = window.location.protocol + "//" + server + api + realApiPath + "?app=" + app + "&stream=" + stream + "&" + queries.join('&');
while (uri.lastIndexOf("?") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
while (uri.lastIndexOf("&") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
return uri;
}
/**
* initialize the page.
* @param flv_url the div id contains the flv stream url to play
* @param hls_url the div id contains the hls stream url to play
* @param modal_player the div id contains the modal player
*/
function srs_init_flv(flv_url, modal_player) {
update_nav();
if (flv_url) {
$(flv_url).val(build_default_flv_url());
}
if (modal_player) {
$(modal_player).width(srs_get_player_modal() + "px");
$(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px");
}
}
function srs_init_rtc(id, query) {
update_nav();
$(id).val(build_default_rtc_url(query));
}
function srs_init_whip(id, query) {
update_nav();
$(id).val(build_default_whip_whep_url(query, '/rtc/v1/whip/'));
}
function srs_init_whep(id, query) {
update_nav();
$(id).val(build_default_whip_whep_url(query, '/rtc/v1/whep/'));
}

382
public/webrtc/srs.player.js Normal file
View File

@ -0,0 +1,382 @@
/**
* the SrsPlayer object.
* @param container the html container id.
* @param width a float value specifies the width of player.
* @param height a float value specifies the height of player.
* @param private_object [optional] an object that used as private object,
* for example, the logic chat object which owner this player.
* Usage:
<script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.player.js"></script>
<div id="player"></div>
var p = new SrsPlayer("player", 640, 480);
p.set_srs_player_url("srs_player.swf?v=1.0.0");
p.on_player_ready = function() {
p.set_bt(0.8);
p.set_mbt(1.2);
p.play("rtmp://ossrs.net/live/livestream");
};
p.on_player_metadata = function(metadata) {
console.log(metadata);
console.log(p.dump_log());
};
p.start();
*/
function SrsPlayer(container, width, height, private_object) {
if (!SrsPlayer.__id) {
SrsPlayer.__id = 100;
}
if (!SrsPlayer.__players) {
SrsPlayer.__players = [];
}
SrsPlayer.__players.push(this);
this.private_object = private_object;
this.container = container;
this.width = width;
this.height = height;
this.id = SrsPlayer.__id++;
this.stream_url = null;
this.buffer_time = 0.3; // default to 0.3
this.max_buffer_time = this.buffer_time * 3; // default to 3 x bufferTime.
this.volume = 1.0; // default to 100%
this.callbackObj = null;
this.srs_player_url = "srs_player/release/srs_player.swf?_version="+srs_get_version_code();
// callback set the following values.
this.meatadata = {}; // for on_player_metadata
this.time = 0; // current stream time.
this.buffer_length = 0; // current stream buffer length.
this.kbps = 0; // current stream bitrate(video+audio) in kbps.
this.fps = 0; // current stream video fps.
this.rtime = 0; // flash relative time in ms.
this.__fluency = {
total_empty_count: 0,
total_empty_time: 0,
current_empty_time: 0
};
this.__fluency.on_stream_empty = function(time) {
this.total_empty_count++;
this.current_empty_time = time;
};
this.__fluency.on_stream_full = function(time) {
if (this.current_empty_time > 0) {
this.total_empty_time += time - this.current_empty_time;
this.current_empty_time = 0;
}
};
this.__fluency.calc = function(time) {
var den = this.total_empty_count * 4 + this.total_empty_time * 2 + time;
if (den > 0) {
return time * 100 / den;
}
return 0;
};
}
/**
* user can set some callback, then start the player.
* @param url the default url.
* callbacks:
* on_player_ready():int, when srs player ready, user can play().
* on_player_metadata(metadata:Object):int, when srs player get metadata.
* methods:
* set_bt(t:Number):void, set the buffer time in seconds.
* set_mbt(t:Number):void, set the max buffer time in seconds.
* dump_log():String, get all logs of player.
*/
SrsPlayer.prototype.start = function(url) {
if (url) {
this.stream_url = url;
}
// embed the flash.
var flashvars = {};
flashvars.id = this.id;
flashvars.on_player_ready = "__srs_on_player_ready";
flashvars.on_player_metadata = "__srs_on_player_metadata";
flashvars.on_player_timer = "__srs_on_player_timer";
flashvars.on_player_empty = "__srs_on_player_empty";
flashvars.on_player_full = "__srs_on_player_full";
flashvars.on_player_status = "__srs_on_player_status";
var params = {};
params.wmode = "opaque";
params.allowFullScreen = "true";
params.allowScriptAccess = "always";
var attributes = {};
var self = this;
swfobject.embedSWF(
this.srs_player_url,
this.container,
this.width, this.height,
"11.1.0", "js/AdobeFlashPlayerInstall.swf",
flashvars, params, attributes,
function(callbackObj){
self.callbackObj = callbackObj;
if (!callbackObj.success) {
console.error('Initialize player failed:'); console.error(callbackObj);
}
}
);
return this;
}
/**
* play the stream.
* @param stream_url the url of stream, rtmp or http.
* @param volume the volume, 0 is mute, 1 is 100%, 2 is 200%.
*/
SrsPlayer.prototype.play = function(url, volume) {
this.stop();
SrsPlayer.__players.push(this);
if (url) {
this.stream_url = url;
}
// volume maybe 0, so never use if(volume) to check its value.
if (volume != null && volume != undefined) {
this.volume = volume;
}
this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time, this.max_buffer_time, this.volume);
}
/**
* stop play stream.
*/
SrsPlayer.prototype.stop = function() {
this.callbackObj.ref.__stop();
}
/**
* pause the play.
*/
SrsPlayer.prototype.pause = function() {
this.callbackObj.ref.__pause();
}
/**
* resume the play.
*/
SrsPlayer.prototype.resume = function() {
this.callbackObj.ref.__resume();
}
/**
* get the stream fluency, where 100 is 100%.
*/
SrsPlayer.prototype.fluency = function() {
return this.__fluency.calc(this.rtime);
}
/**
* get the stream empty count.
*/
SrsPlayer.prototype.empty_count = function() {
return this.__fluency.total_empty_count;
}
/**
* get all log data.
*/
SrsPlayer.prototype.dump_log = function() {
return this.callbackObj.ref.__dump_log();
}
/**
* to set the DAR, for example, DAR=16:9 where num=16,den=9.
* @param num, for example, 16.
* use metadata width if 0.
* use user specified width if -1.
* @param den, for example, 9.
* use metadata height if 0.
* use user specified height if -1.
*/
SrsPlayer.prototype.set_dar = function(num, den) {
this.callbackObj.ref.__set_dar(num, den);
}
/**
* set the fullscreen size data.
* @refer the refer fullscreen mode. it can be:
* video: use video orignal size.
* screen: use screen size to rescale video.
* @param percent, the rescale percent, where
* 100 means 100%.
*/
SrsPlayer.prototype.set_fs = function(refer, percent) {
this.callbackObj.ref.__set_fs(refer, percent);
}
/**
* set the stream buffer time in seconds.
* @buffer_time the buffer time in seconds.
*/
SrsPlayer.prototype.set_bt = function(buffer_time) {
if (this.buffer_time == buffer_time) {
return;
}
this.buffer_time = buffer_time;
this.callbackObj.ref.__set_bt(buffer_time);
// reset the max buffer time to 3 x buffer_time.
this.set_mbt(buffer_time * 3);
}
/**
* set the stream max buffer time in seconds.
* @param max_buffer_time the max buffer time in seconds.
* @remark this is the key feature for realtime communication by flash.
*/
SrsPlayer.prototype.set_mbt = function(max_buffer_time) {
// we must atleast set the max buffer time to 0.6s.
max_buffer_time = Math.max(0.6, max_buffer_time);
// max buffer time always greater than buffer time.
max_buffer_time = Math.max(this.buffer_time, max_buffer_time);
if (parseInt(this.max_buffer_time * 10) == parseInt(max_buffer_time * 10)) {
return;
}
this.max_buffer_time = max_buffer_time;
this.callbackObj.ref.__set_mbt(max_buffer_time);
}
/**
* set the srs_player.swf url
* @param url, srs_player.swf's url.
* @param params, object.
*/
SrsPlayer.prototype.set_srs_player_url = function(url, params) {
var query_array = [],
query_string = "",
p;
params = params || {};
params._version = srs_get_version_code();
for (p in params) {
if (params.hasOwnProperty(p)) {
query_array.push(p + "=" + encodeURIComponent(params[p]));
}
}
query_string = query_array.join("&");
this.srs_player_url = url + "?" + query_string;
}
/**
* the callback when player is ready.
*/
SrsPlayer.prototype.on_player_ready = function() {
}
/**
* the callback when player got metadata.
* @param metadata the metadata which player got.
*/
SrsPlayer.prototype.on_player_metadata = function(metadata) {
// ignore.
}
/**
* the callback when player timer event.
* @param time current stream time.
* @param buffer_length current buffer length.
* @param kbps current video plus audio bitrate in kbps.
* @param fps current video fps.
* @param rtime current relative time by flash.util.getTimer().
*/
SrsPlayer.prototype.on_player_timer = function(time, buffer_length, kbps, fps, rtime) {
// ignore.
}
/**
* the callback when player got NetStream.Buffer.Empty
* @param time current relative time by flash.util.getTimer().
*/
SrsPlayer.prototype.on_player_empty = function(time) {
// ignore.
}
/**
* the callback when player got NetStream.Buffer.Full
* @param time current relative time by flash.util.getTimer().
*/
SrsPlayer.prototype.on_player_full = function(time) {
// ignore.
}
/**
* the callback when player status change.
* @param code the status code, "init", "connected", "play", "closed", "rejected", "failed".
* init => connected/rejected/failed
* connected => play/rejected => closed
* @param desc the description for the status.
*/
SrsPlayer.prototype.on_player_status = function(code, desc) {
// ignore.
}
/**
* helpers.
*/
function __srs_find_player(id) {
for (var i = 0; i < SrsPlayer.__players.length; i++) {
var player = SrsPlayer.__players[i];
if (player.id != id) {
continue;
}
return player;
}
throw new Error("player not found. id=" + id);
}
function __srs_on_player_ready(id) {
var player = __srs_find_player(id);
player.on_player_ready();
}
function __srs_on_player_metadata(id, metadata) {
var player = __srs_find_player(id);
// user may override the on_player_metadata,
// so set the data before invoke it.
player.metadata = metadata;
player.on_player_metadata(metadata);
}
function __srs_on_player_timer(id, time, buffer_length, kbps, fps, rtime) {
var player = __srs_find_player(id);
buffer_length = Math.max(0, buffer_length);
buffer_length = Math.min(player.buffer_time, buffer_length);
time = Math.max(0, time);
// user may override the on_player_timer,
// so set the data before invoke it.
player.time = time;
player.buffer_length = buffer_length;
player.kbps = kbps;
player.fps = fps;
player.rtime = rtime;
player.on_player_timer(time, buffer_length, kbps, fps, rtime);
}
function __srs_on_player_empty(id, time) {
var player = __srs_find_player(id);
player.__fluency.on_stream_empty(time);
player.on_player_empty(time);
}
function __srs_on_player_full(id, time) {
var player = __srs_find_player(id);
player.__fluency.on_stream_full(time);
player.on_player_full(time);
}
function __srs_on_player_status(id, code, desc) {
var player = __srs_find_player(id);
player.on_player_status(code, desc);
if (code != "closed") {
return;
}
for (var i = 0; i < SrsPlayer.__players.length; i++) {
var player = SrsPlayer.__players[i];
if (player.id != this.id) {
continue;
}
SrsPlayer.__players.splice(i, 1);
break;
}
}

View File

@ -0,0 +1,173 @@
/**
* the SrsPublisher object.
* @param container the html container id.
* @param width a float value specifies the width of publisher.
* @param height a float value specifies the height of publisher.
* @param private_object [optional] an object that used as private object,
* for example, the logic chat object which owner this publisher.
*/
function SrsPublisher(container, width, height, private_object) {
if (!SrsPublisher.__id) {
SrsPublisher.__id = 100;
}
if (!SrsPublisher.__publishers) {
SrsPublisher.__publishers = [];
}
SrsPublisher.__publishers.push(this);
this.private_object = private_object;
this.container = container;
this.width = width;
this.height = height;
this.id = SrsPublisher.__id++;
this.callbackObj = null;
// set the values when publish.
this.url = null;
this.vcodec = {};
this.acodec = {};
// callback set the following values.
this.cameras = [];
this.microphones = [];
this.code = 0;
// error code defines.
this.errors = {
"100": "无法获取指定的摄像头。", //error_camera_get
"101": "无法获取指定的麦克风。", //error_microphone_get
"102": "摄像头为禁用状态推流时请允许flash访问摄像头。", //error_camera_muted
"103": "服务器关闭了连接。", //error_connection_closed
"104": "服务器连接失败。", //error_connection_failed
"199": "未知错误。"
};
}
/**
* user can set some callback, then start the publisher.
* callbacks:
* on_publisher_ready(cameras, microphones):int, when srs publisher ready, user can publish.
* on_publisher_error(code, desc):int, when srs publisher error, callback this method.
* on_publisher_warn(code, desc):int, when srs publisher warn, callback this method.
*/
SrsPublisher.prototype.start = function() {
// embed the flash.
var flashvars = {};
flashvars.id = this.id;
flashvars.width = this.width;
flashvars.height = this.height;
flashvars.on_publisher_ready = "__srs_on_publisher_ready";
flashvars.on_publisher_error = "__srs_on_publisher_error";
flashvars.on_publisher_warn = "__srs_on_publisher_warn";
var params = {};
params.wmode = "opaque";
params.allowFullScreen = "true";
params.allowScriptAccess = "always";
var attributes = {};
var self = this;
swfobject.embedSWF(
"srs_publisher/release/srs_publisher.swf?_version="+srs_get_version_code(),
this.container,
this.width, this.height,
"11.1.0", "js/AdobeFlashPlayerInstall.swf",
flashvars, params, attributes,
function(callbackObj){
self.callbackObj = callbackObj;
}
);
return this;
}
/**
* publish stream to server.
* @param url a string indicates the rtmp url to publish.
* @param vcodec an object contains the video codec info.
* @param acodec an object contains the audio codec info.
*/
SrsPublisher.prototype.publish = function(url, vcodec, acodec) {
this.stop();
SrsPublisher.__publishers.push(this);
if (url) {
this.url = url;
}
if (vcodec) {
this.vcodec = vcodec;
}
if (acodec) {
this.acodec = acodec;
}
this.callbackObj.ref.__publish(this.url, this.width, this.height, this.vcodec, this.acodec);
}
SrsPublisher.prototype.stop = function() {
for (var i = 0; i < SrsPublisher.__publishers.length; i++) {
var player = SrsPublisher.__publishers[i];
if (player.id != this.id) {
continue;
}
SrsPublisher.__publishers.splice(i, 1);
break;
}
this.callbackObj.ref.__stop();
}
/**
* when publisher ready.
* @param cameras a string array contains the names of cameras.
* @param microphones a string array contains the names of microphones.
*/
SrsPublisher.prototype.on_publisher_ready = function(cameras, microphones) {
}
/**
* when publisher error.
* @code the error code.
* @desc the error desc message.
*/
SrsPublisher.prototype.on_publisher_error = function(code, desc) {
throw new Error("publisher error. code=" + code + ", desc=" + desc);
}
SrsPublisher.prototype.on_publisher_warn = function(code, desc) {
throw new Error("publisher warn. code=" + code + ", desc=" + desc);
}
function __srs_find_publisher(id) {
for (var i = 0; i < SrsPublisher.__publishers.length; i++) {
var publisher = SrsPublisher.__publishers[i];
if (publisher.id != id) {
continue;
}
return publisher;
}
throw new Error("publisher not found. id=" + id);
}
function __srs_on_publisher_ready(id, cameras, microphones) {
var publisher = __srs_find_publisher(id);
publisher.cameras = cameras;
publisher.microphones = microphones;
publisher.on_publisher_ready(cameras, microphones);
}
function __srs_on_publisher_error(id, code) {
var publisher = __srs_find_publisher(id);
publisher.code = code;
publisher.on_publisher_error(code, publisher.errors[""+code]);
}
function __srs_on_publisher_warn(id, code) {
var publisher = __srs_find_publisher(id);
publisher.code = code;
publisher.on_publisher_warn(code, publisher.errors[""+code]);
}

681
public/webrtc/srs.sdk.js Normal file
View File

@ -0,0 +1,681 @@
//
// Copyright (c) 2013-2021 Winlin
//
// SPDX-License-Identifier: MIT
//
'use strict';
function SrsError(name, message) {
this.name = name;
this.message = message;
this.stack = (new Error()).stack;
}
SrsError.prototype = Object.create(Error.prototype);
SrsError.prototype.constructor = SrsError;
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher.
function SrsRtcPublisherAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: {ideal: 320, max: 576}
}
};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// or autostart the publish:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.publish = async function (url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", {direction: "sendonly"});
self.pc.addTransceiver("video", {direction: "sendonly"});
//self.pc.addTransceiver("video", {direction: "sendonly"});
//self.pc.addTransceiver("audio", {direction: "sendonly"});
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({track: track});
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function (resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({type: 'answer', sdp: session.sdp})
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/publish/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-await-promise based SRS RTC Player.
function SrsRtcPlayerAsync() {
var self = {};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// webrtc://r.ossrs.net:80/live/livestream
// or autostart the play:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.play = async function(url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", {direction: "recvonly"});
self.pc.addTransceiver("video", {direction: "recvonly"});
//self.pc.addTransceiver("video", {direction: "recvonly"});
//self.pc.addTransceiver("audio", {direction: "recvonly"});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function(resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({type: 'answer', sdp: session.sdp})
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the player.
self.close = function() {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got remote track.
// Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream
self.ontrack = function (event) {
// https://webrtc.org/getting-started/remote-streams
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/play/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
self.stream = new MediaStream();
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function(event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher by WHIP.
function SrsRtcWhipWhepAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: {ideal: 320, max: 576}
}
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to publish with, for example:
// http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream
self.publish = async function (url) {
if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);
self.pc.addTransceiver("audio", {direction: "sendonly"});
self.pc.addTransceiver("video", {direction: "sendonly"});
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({track: track});
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({type: 'answer', sdp: answer})
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to play with, for example:
// http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream
self.play = async function(url) {
if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
self.pc.addTransceiver("audio", {direction: "recvonly"});
self.pc.addTransceiver("video", {direction: "recvonly"});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function(resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({type: 'answer', sdp: answer})
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
// Internal APIs.
self.__internal = {
parseId: (url, offer, answer) => {
let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':';
sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n'));
const a = document.createElement("a");
a.href = url;
return {
sessionid: sessionid, // Should be ice-ufrag of answer:offer.
simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
};
},
};
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function(event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
function SrsRtcFormatSenders(senders, kind) {
var codecs = [];
senders.forEach(function (sender) {
var params = sender.getParameters();
params && params.codecs && params.codecs.forEach(function(c) {
if (kind && sender.track.kind !== kind) {
return;
}
if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
return;
}
var s = '';
s += c.mimeType.replace('audio/', '').replace('video/', '');
s += ', ' + c.clockRate + 'HZ';
if (sender.track.kind === "audio") {
s += ', channels: ' + c.channels;
}
s += ', pt: ' + c.payloadType;
codecs.push(s);
});
});
return codecs.join(", ");
}

View File

@ -0,0 +1,8 @@
/**
* parse the rtmp url,
* for example: rtmp://demo.srs.com:1935/live...vhost...players/livestream
* @return object {server, port, vhost, app, stream}
*/
function srs_parse_rtmp_url(rtmp_url) {
return parse_rtmp_url(rtmp_url);
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,686 @@
// winlin.utility.js
/**
* common utilities
* depends: jquery1.10
* https://gitee.com/winlinvip/codes/rpn0c2ewbomj81augzk4y59
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* v 1.0.23
*/
/**
* padding the output.
* padding(3, 5, '0') is 00003
* padding(3, 5, 'x') is xxxx3
* @see http://blog.csdn.net/win_lin/article/details/12065413
*/
function padding(number, length, prefix) {
if(String(number).length >= length){
return String(number);
}
return padding(prefix+number, length, prefix);
}
/**
* extends system array, to remove all specified elem.
* @param arr the array to remove elem from.
* @param elem the elem to remove.
* @remark all elem will be removed.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_remove(arr, 10) // arr=[15, 20, 30, 20, 40]
* system_array_remove(arr, 20) // arr=[15, 30, 40]
*/
function system_array_remove(arr, elem) {
if (!arr) {
return;
}
var removed = true;
var i = 0;
while (removed) {
removed = false;
for (; i < arr.length; i++) {
if (elem == arr[i]) {
arr.splice(i, 1);
removed = true;
break;
}
}
}
}
/**
* whether the array contains specified element.
* @param arr the array to find.
* @param elem_or_function the element value or compare function.
* @returns true contains elem; otherwise false.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_contains(arr, 10) // true
* system_array_contains(arr, 11) // false
* system_array_contains(arr, function(elem){return elem == 30;}); // true
* system_array_contains(arr, function(elem){return elem == 60;}); // false
*/
function system_array_contains(arr, elem_or_function) {
return system_array_get(arr, elem_or_function) != null;
}
/**
* get the specified element from array
* @param arr the array to find.
* @param elem_or_function the element value or compare function.
* @returns the matched elem; otherwise null.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_get(arr, 10) // 10
* system_array_get(arr, 11) // null
* system_array_get(arr, function(elem){return elem == 30;}); // 30
* system_array_get(arr, function(elem){return elem == 60;}); // null
*/
function system_array_get(arr, elem_or_function) {
for (var i = 0; i < arr.length; i++) {
if (typeof elem_or_function == "function") {
if (elem_or_function(arr[i])) {
return arr[i];
}
} else {
if (elem_or_function == arr[i]) {
return arr[i];
}
}
}
return null;
}
/**
* to iterate on array.
* @param arr the array to iterate on.
* @param pfn the function to apply on it. return false to break loop.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_foreach(arr, function(elem, index){
* console.log('index=' + index + ',elem=' + elem);
* });
* @return true when iterate all elems.
*/
function system_array_foreach(arr, pfn) {
if (!pfn) {
return false;
}
for (var i = 0; i < arr.length; i++) {
if (!pfn(arr[i], i)) {
return false;
}
}
return true;
}
/**
* whether the str starts with flag.
*/
function system_string_startswith(str, flag) {
if (typeof flag == "object" && flag.constructor == Array) {
for (var i = 0; i < flag.length; i++) {
if (system_string_startswith(str, flag[i])) {
return true;
}
}
}
return str && flag && str.length >= flag.length && str.indexOf(flag) == 0;
}
/**
* whether the str ends with flag.
*/
function system_string_endswith(str, flag) {
if (typeof flag == "object" && flag.constructor == Array) {
for (var i = 0; i < flag.length; i++) {
if (system_string_endswith(str, flag[i])) {
return true;
}
}
}
return str && flag && str.length >= flag.length && str.indexOf(flag) == str.length - flag.length;
}
/**
* trim the start and end of flag in str.
* @param flag a string to trim.
*/
function system_string_trim(str, flag) {
if (!flag || !flag.length || typeof flag != "string") {
return str;
}
while (system_string_startswith(str, flag)) {
str = str.slice(flag.length);
}
while (system_string_endswith(str, flag)) {
str = str.slice(0, str.length - flag.length);
}
return str;
}
/**
* array sort asc, for example:
* [a, b] in [10, 11, 9]
* then sort to: [9, 10, 11]
* Usage, for example:
obj.data.data.sort(function(a, b){
return array_sort_asc(a.metadata.meta_id, b.metadata.meta_id);
});
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @remark, if need desc, use -1*array_sort_asc(a,b)
*/
function array_sort_asc(elem_a, elem_b) {
if (elem_a > elem_b) {
return 1;
}
return (elem_a < elem_b)? -1 : 0;
}
function array_sort_desc(elem_a, elem_b) {
return -1 * array_sort_asc(elem_a, elem_b);
}
function system_array_sort_asc(elem_a, elem_b) {
return array_sort_asc(elem_a, elem_b);
}
function system_array_sort_desc(elem_a, elem_b) {
return -1 * array_sort_asc(elem_a, elem_b);
}
/**
* parse the query string to object.
* parse the url location object as: host(hostname:http_port), pathname(dir/filename)
* for example, url http://192.168.1.168:1980/ui/players.html?vhost=player.vhost.com&app=test&stream=livestream
* parsed to object:
{
host : "192.168.1.168:1980",
hostname : "192.168.1.168",
http_port : 1980,
pathname : "/ui/players.html",
dir : "/ui",
filename : "/players.html",
vhost : "player.vhost.com",
app : "test",
stream : "livestream"
}
* @see: http://blog.csdn.net/win_lin/article/details/17994347
*/
function parse_query_string(){
var obj = {};
// add the uri object.
// parse the host(hostname:http_port), pathname(dir/filename)
obj.host = window.location.host;
obj.hostname = window.location.hostname;
obj.http_port = (window.location.port == "")? 80:window.location.port;
obj.pathname = window.location.pathname;
if (obj.pathname.lastIndexOf("/") <= 0) {
obj.dir = "/";
obj.filename = "";
} else {
obj.dir = obj.pathname.slice(0, obj.pathname.lastIndexOf("/"));
obj.filename = obj.pathname.slice(obj.pathname.lastIndexOf("/"));
}
// pure user query object.
obj.user_query = {};
// parse the query string.
var query_string = String(window.location.search).replace(" ", "").split("?")[1];
if(query_string === undefined){
query_string = String(window.location.hash).replace(" ", "").split("#")[1];
if(query_string === undefined){
return obj;
}
}
__fill_query(query_string, obj);
return obj;
}
function __fill_query(query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
/**
* parse the rtmp url,
* for example: rtmp://demo.srs.com:1935/live...vhost...players/livestream
* @return object {server, port, vhost, app, stream}
* for exmaple, rtmp_url is rtmp://demo.srs.com:1935/live...vhost...players/livestream
* parsed to object:
{
server: "demo.srs.com",
port: 1935,
vhost: "players",
app: "live",
stream: "livestream"
}
*/
function parse_rtmp_url(rtmp_url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = rtmp_url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (rtmp_url.indexOf("://") > 0) {
schema = rtmp_url.slice(0, rtmp_url.indexOf("://"));
}
var port = a.port;
if (!port) {
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: rtmp_url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
__fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
}
/**
* get the agent.
* @return an object specifies some browser.
* for example, get_browser_agents().MSIE
* @see: http://blog.csdn.net/win_lin/article/details/17994347
*/
function get_browser_agents() {
var agent = navigator.userAgent;
/**
WindowsPC platform, Win7:
chrome 31.0.1650.63:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
firefox 23.0.1:
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101
Firefox/23.0
safari 5.1.7(7534.57.2):
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2
(KHTML, like Gecko) Version/5.1.7 Safari/534.57.2
opera 15.0.1147.153:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
OPR/15.0.1147.153
360 6.2.1.272:
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
.NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
.NET4.0E)
IE 10.0.9200.16750(update: 10.0.12):
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
.NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
.NET4.0E)
*/
return {
// platform
Android: agent.indexOf("Android") != -1,
Windows: agent.indexOf("Windows") != -1,
iPhone: agent.indexOf("iPhone") != -1,
// Windows Browsers
Chrome: agent.indexOf("Chrome") != -1,
Firefox: agent.indexOf("Firefox") != -1,
QQBrowser: agent.indexOf("QQBrowser") != -1,
MSIE: agent.indexOf("MSIE") != -1,
// Android Browsers
Opera: agent.indexOf("Presto") != -1,
MQQBrowser: agent.indexOf("MQQBrowser") != -1
};
}
/**
* format relative seconds to HH:MM:SS,
* for example, 210s formated to 00:03:30
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @usage relative_seconds_to_HHMMSS(210)
*/
function relative_seconds_to_HHMMSS(seconds){
var date = new Date();
date.setTime(Number(seconds) * 1000);
var ret = padding(date.getUTCHours(), 2, '0')
+ ":" + padding(date.getUTCMinutes(), 2, '0')
+ ":" + padding(date.getUTCSeconds(), 2, '0');
return ret;
}
/**
* format absolute seconds to HH:MM:SS,
* for example, 1389146480s (2014-01-08 10:01:20 GMT+0800) formated to 10:01:20
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @usage absolute_seconds_to_HHMMSS(new Date().getTime() / 1000)
*/
function absolute_seconds_to_HHMMSS(seconds){
var date = new Date();
date.setTime(Number(seconds) * 1000);
var ret = padding(date.getHours(), 2, '0')
+ ":" + padding(date.getMinutes(), 2, '0')
+ ":" + padding(date.getSeconds(), 2, '0');
return ret;
}
/**
* format absolute seconds to YYYY-mm-dd,
* for example, 1389146480s (2014-01-08 10:01:20 GMT+0800) formated to 2014-01-08
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @usage absolute_seconds_to_YYYYmmdd(new Date().getTime() / 1000)
*/
function absolute_seconds_to_YYYYmmdd(seconds) {
var date = new Date();
date.setTime(Number(seconds) * 1000);
var ret = date.getFullYear()
+ "-" + padding(date.getMonth() + 1, 2, '0')
+ "-" + padding(date.getDate(), 2, '0');
return ret;
}
/**
* parse the date in str to Date object.
* @param str the date in str, format as "YYYY-mm-dd", for example, 2014-12-11
* @returns a date object.
* @usage YYYYmmdd_parse("2014-12-11")
*/
function YYYYmmdd_parse(str) {
var date = new Date();
date.setTime(Date.parse(str));
return date;
}
/**
* async refresh function call. to avoid multiple call.
* @remark AsyncRefresh is for jquery to refresh the speicified pfn in a page;
* if angularjs, use AsyncRefresh2 to change pfn, cancel previous request for angularjs use singleton object.
* @param refresh_interval the default refresh interval ms.
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* the pfn can be implements as following:
var async_refresh = new AsyncRefresh(pfn, 3000);
function pfn() {
if (!async_refresh.refresh_is_enabled()) {
async_refresh.request(100);
return;
}
$.ajax({
type: 'GET', async: true, url: 'xxxxx',
complete: function(){
if (!async_refresh.refresh_is_enabled()) {
async_refresh.request(0);
} else {
async_refresh.request(async_refresh.refresh_interval);
}
},
success: function(res){
// if donot allow refresh, directly return.
if (!async_refresh.refresh_is_enabled()) {
return;
}
// render the res.
}
});
}
*/
function AsyncRefresh(pfn, refresh_interval) {
this.refresh_interval = refresh_interval;
this.__handler = null;
this.__pfn = pfn;
this.__enabled = true;
}
/**
* disable the refresher, the pfn must check the refresh state.
*/
AsyncRefresh.prototype.refresh_disable = function() {
this.__enabled = false;
}
AsyncRefresh.prototype.refresh_enable = function() {
this.__enabled = true;
}
AsyncRefresh.prototype.refresh_is_enabled = function() {
return this.__enabled;
}
/**
* start new async request
* @param timeout the timeout in ms.
* user can use the refresh_interval of the AsyncRefresh object,
* which initialized in constructor.
*/
AsyncRefresh.prototype.request = function(timeout) {
if (this.__handler) {
clearTimeout(this.__handler);
}
this.__handler = setTimeout(this.__pfn, timeout);
}
/**
* async refresh v2, support cancellable refresh, and change the refresh pfn.
* @remakr for angularjs. if user only need jquery, maybe AsyncRefresh is better.
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* Usage:
bsmControllers.controller('CServers', ['$scope', 'MServer', function($scope, MServer){
async_refresh2.refresh_change(function(){
// 获取服务器列表
MServer.servers_load({}, function(data){
$scope.servers = data.data.servers;
async_refresh2.request();
});
}, 3000);
async_refresh2.request(0);
}]);
bsmControllers.controller('CStreams', ['$scope', 'MStream', function($scope, MStream){
async_refresh2.refresh_change(function(){
// 获取流列表
MStream.streams_load({}, function(data){
$scope.streams = data.data.streams;
async_refresh2.request();
});
}, 3000);
async_refresh2.request(0);
}]);
*/
function AsyncRefresh2() {
/**
* the function callback before call the pfn.
* the protype is function():bool, which return true to invoke, false to abort the call.
* null to ignore this callback.
*
* for example, user can abort the refresh by find the class popover:
* async_refresh2.on_before_call_pfn = function() {
* if ($(".popover").length > 0) {
* async_refresh2.request();
* return false;
* }
* return true;
* };
*/
this.on_before_call_pfn = null;
// use a anonymous function to call, and check the enabled when actually invoke.
this.__call = {
pfn: null,
timeout: 0,
__enabled: false,
__handler: null
};
}
// singleton
var async_refresh2 = new AsyncRefresh2();
/**
* initialize or refresh change. cancel previous request, setup new request.
* @param pfn a function():void to request after timeout. null to disable refresher.
* @param timeout the timeout in ms, to call pfn. null to disable refresher.
*/
AsyncRefresh2.prototype.initialize = function(pfn, timeout) {
this.refresh_change(pfn, timeout);
}
/**
* stop refresh, the refresh pfn is set to null.
*/
AsyncRefresh2.prototype.stop = function() {
this.__call.__enabled = false;
}
/**
* restart refresh, use previous config.
*/
AsyncRefresh2.prototype.restart = function() {
this.__call.__enabled = true;
this.request(0);
}
/**
* change refresh pfn, the old pfn will set to disabled.
*/
AsyncRefresh2.prototype.refresh_change = function(pfn, timeout) {
// cancel the previous call.
if (this.__call.__handler) {
clearTimeout(this.__handler);
}
this.__call.__enabled = false;
// setup new call.
this.__call = {
pfn: pfn,
timeout: timeout,
__enabled: true,
__handler: null
};
}
/**
* start new request, we never auto start the request,
* user must start new request when previous completed.
* @param timeout [optional] if not specified, use the timeout in initialize or refresh_change.
*/
AsyncRefresh2.prototype.request = function(timeout) {
var self = this;
var this_call = this.__call;
// clear previous timeout.
if (this_call.__handler) {
clearTimeout(this_call.__handler);
}
// override the timeout
if (timeout == undefined) {
timeout = this_call.timeout;
}
// if user disabled refresher.
if (this_call.pfn == null || timeout == null) {
return;
}
this_call.__handler = setTimeout(function(){
// cancelled by refresh_change, ignore.
if (!this_call.__enabled) {
return;
}
// callback if the handler installled.
if (self.on_before_call_pfn) {
if (!self.on_before_call_pfn()) {
return;
}
}
// do the actual call.
this_call.pfn();
}, timeout);
}

View File

@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DroneConfigVO, DroneConfigForm, DroneConfigQuery } from '@/api/drone/droneConfig/types';
/**
* 查询无人机配置列表
* @param query
* @returns {*}
*/
export const listDroneConfig = (query?: DroneConfigQuery): AxiosPromise<DroneConfigVO[]> => {
return request({
url: '/drone/droneConfig/list',
method: 'get',
params: query
});
};
/**
* 查询无人机配置详细
* @param id
*/
export const getDroneConfig = (id: string | number): AxiosPromise<DroneConfigVO> => {
return request({
url: '/drone/droneConfig/' + id,
method: 'get'
});
};
/**
* 新增无人机配置
* @param data
*/
export const addDroneConfig = (data: DroneConfigForm) => {
return request({
url: '/drone/droneConfig',
method: 'post',
data: data
});
};
/**
* 修改无人机配置
* @param data
*/
export const updateDroneConfig = (data: DroneConfigForm) => {
return request({
url: '/drone/droneConfig',
method: 'put',
data: data
});
};
/**
* 删除无人机配置
* @param id
*/
export const delDroneConfig = (id: string | number | Array<string | number>) => {
return request({
url: '/drone/droneConfig/' + id,
method: 'delete'
});
};

View File

@ -0,0 +1,80 @@
export interface DroneConfigVO {
/**
* 主键id
*/
id: string | number;
/**
* 项目id
*/
projectId: string | number;
/**
* 配置名称
*/
configName: string;
/**
* 配置地址
*/
configUrl: string;
/**
* 备注
*/
remark: string;
}
export interface DroneConfigForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 项目id
*/
projectId?: string | number;
/**
* 配置名称
*/
configName?: string;
/**
* 配置地址
*/
configUrl?: string;
/**
* 备注
*/
remark?: string;
dockSocketUrl?: string;
aiUrl?: string;
srsUrl?: string;
rtmpPort?: string;
rtcPort?: string;
}
export interface DroneConfigQuery extends PageQuery {
/**
* 项目id
*/
projectId?: string | number;
/**
* 配置名称
*/
configName?: string;
/**
* 配置地址
*/
configUrl?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@ -23,7 +23,7 @@ import { AttendanceMonthVO } from '../attendance/types';
export const listConstructionMonth = (query?: ConstructionMonthQuery): AxiosPromise<AttendanceMonthVO[]> => {
return request({
url: '/project/constructionUser/list/attendance/month',
url: '/contractor/constructionUser/list/attendance/month',
method: 'get',
params: query
});
@ -36,7 +36,7 @@ export const listConstructionMonth = (query?: ConstructionMonthQuery): AxiosProm
export const listConstructionUser = (query?: ConstructionUserQuery): AxiosPromise<ConstructionUserVO[]> => {
return request({
url: '/project/constructionUser/list',
url: '/contractor/constructionUser/list',
method: 'get',
params: query
});
@ -48,7 +48,7 @@ export const listConstructionUser = (query?: ConstructionUserQuery): AxiosPromis
*/
export const getConstructionUser = (id: string | number): AxiosPromise<ConstructionUserVO> => {
return request({
url: '/project/constructionUser/' + id,
url: '/contractor/constructionUser/' + id,
method: 'get'
});
};
@ -59,7 +59,7 @@ export const getConstructionUser = (id: string | number): AxiosPromise<Construct
*/
export const transferConstructionUser = (data: skipType) => {
return request({
url: '/project/constructionUser/change/project',
url: '/contractor/constructionUser/change/project',
method: 'put',
data: data
});
@ -81,7 +81,7 @@ export const getProjectContractorList = () => {
*/
export const addConstructionUser = (data: ConstructionUserForm): AxiosPromise<string | number> => {
return request({
url: '/project/constructionUser',
url: '/contractor/constructionUser',
method: 'post',
data: data
});
@ -93,7 +93,7 @@ export const addConstructionUser = (data: ConstructionUserForm): AxiosPromise<st
*/
export const updateConstructionUser = (data: ConstructionUserForm) => {
return request({
url: '/project/constructionUser',
url: '/contractor/constructionUser',
method: 'put',
data: data
});
@ -105,7 +105,7 @@ export const updateConstructionUser = (data: ConstructionUserForm) => {
*/
export const delConstructionUser = (id: string | number | Array<string | number>) => {
return request({
url: '/project/constructionUser/' + id,
url: '/contractor/constructionUser/' + id,
method: 'delete'
});
};
@ -116,7 +116,7 @@ export const delConstructionUser = (id: string | number | Array<string | number>
*/
export const updateConstructionUserStatus = (data: ConstructionUserStatusForm) => {
return request({
url: '/project/constructionUser/batch/status',
url: '/contractor/constructionUser/batch/status',
method: 'put',
data: data
});
@ -128,7 +128,7 @@ export const updateConstructionUserStatus = (data: ConstructionUserStatusForm) =
*/
export const updateConstructionUserPlayCardStatus = (data: ConstructionUserPlayCardForm) => {
return request({
url: '/project/constructionUser/batch/clock',
url: '/contractor/constructionUser/batch/clock',
method: 'put',
data: data
});
@ -140,7 +140,7 @@ export const updateConstructionUserPlayCardStatus = (data: ConstructionUserPlayC
*/
export const updateConstructionUserPlayCardOneStatus = (data: ConstructionUserPlayCardForm) => {
return request({
url: '/project/constructionUser/clock',
url: '/contractor/constructionUser/clock',
method: 'put',
data: data
});
@ -152,7 +152,7 @@ export const updateConstructionUserPlayCardOneStatus = (data: ConstructionUserPl
*/
export const updateConstructionUserSalary = (data: ConstructionUserSalaryForm) => {
return request({
url: '/project/constructionUser/salary',
url: '/contractor/constructionUser/salary',
method: 'put',
data: data
});
@ -164,7 +164,7 @@ export const updateConstructionUserSalary = (data: ConstructionUserSalaryForm) =
*/
export const getConstructionUserExit = (query: ConstructionUserExitForm) => {
return request({
url: '/project/constructionUserExit/list',
url: '/contractor/constructionUserExit/list',
method: 'get',
params: query
});
@ -177,7 +177,7 @@ export const getConstructionUserExit = (query: ConstructionUserExitForm) => {
export const dowloadConstructionUserTemplate = (query: ConstructionUserTemplateForm) => {
let { projectId } = query;
const fileName = projectId + '_project.zip';
return download('/project/constructionUserFile/exportFileTemplate', query, fileName);
return download('/contractor/constructionUserFile/exportFileTemplate', query, fileName);
};
/**
@ -186,7 +186,7 @@ export const dowloadConstructionUserTemplate = (query: ConstructionUserTemplateF
*/
export const delConstructionUserMember = (data: ConstructionUserMembeForm) => {
return request({
url: '/project/projectTeamMember/',
url: '/contractor/projectTeamMember/',
method: 'delete',
data
});
@ -198,7 +198,7 @@ export const delConstructionUserMember = (data: ConstructionUserMembeForm) => {
*/
export const importConstructionUserInfo = (file: string) => {
return request({
url: '/project/constructionUserFile/upload/zip',
url: '/contractor/constructionUserFile/upload/zip',
method: 'post',
data: { file }
});

View File

@ -10,7 +10,7 @@ import { ContractorForm, ContractorQuery, ContractorVO } from '@/api/project/con
export const listContractor = (query?: ContractorQuery): AxiosPromise<ContractorVO[]> => {
return request({
url: '/project/contractor/list',
url: '/contractor/contractor/list',
method: 'get',
params: query
});
@ -22,7 +22,7 @@ export const listContractor = (query?: ContractorQuery): AxiosPromise<Contractor
*/
export const getContractor = (id: string | number): AxiosPromise<ContractorVO> => {
return request({
url: '/project/contractor/' + id,
url: '/contractor/contractor/' + id,
method: 'get'
});
};
@ -33,7 +33,7 @@ export const getContractor = (id: string | number): AxiosPromise<ContractorVO> =
*/
export const addContractor = (data: ContractorForm): AxiosPromise<string | number> => {
return request({
url: '/project/contractor',
url: '/contractor/contractor',
method: 'post',
data: data
});
@ -45,7 +45,7 @@ export const addContractor = (data: ContractorForm): AxiosPromise<string | numbe
*/
export const updateContractor = (data: ContractorForm) => {
return request({
url: '/project/contractor',
url: '/contractor/contractor',
method: 'put',
data: data
});
@ -57,7 +57,7 @@ export const updateContractor = (data: ContractorForm) => {
*/
export const delContractor = (id: string | number | Array<string | number>) => {
return request({
url: '/project/contractor/' + id,
url: '/contractor/contractor/' + id,
method: 'delete'
});
};

View File

@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ContractorMaterialVO, ContractorMaterialForm, ContractorMaterialQuery } from '@/api/contractor/contractorMaterial/types';
import { ContractorMaterialVO, ContractorMaterialForm, ContractorMaterialQuery } from '@/api/project/contractorMaterial/types';
/**
* 查询分包方物料列表

View File

@ -10,7 +10,7 @@ import { ContractorToolVO, ContractorToolForm, ContractorToolQuery } from '@/api
export const listContractorTool = (query?: ContractorToolQuery): AxiosPromise<ContractorToolVO[]> => {
return request({
url: '/project/contractorTool/list',
url: '/contractor/contractorTool/list',
method: 'get',
params: query
});
@ -22,7 +22,7 @@ export const listContractorTool = (query?: ContractorToolQuery): AxiosPromise<Co
*/
export const getContractorTool = (id: string | number): AxiosPromise<ContractorToolVO> => {
return request({
url: '/project/contractorTool/' + id,
url: '/contractor/contractorTool/' + id,
method: 'get'
});
};
@ -33,7 +33,7 @@ export const getContractorTool = (id: string | number): AxiosPromise<ContractorT
*/
export const addContractorTool = (data: ContractorToolForm) => {
return request({
url: '/project/contractorTool',
url: '/contractor/contractorTool',
method: 'post',
data: data
});
@ -45,7 +45,7 @@ export const addContractorTool = (data: ContractorToolForm) => {
*/
export const updateContractorTool = (data: ContractorToolForm) => {
return request({
url: '/project/contractorTool',
url: '/contractor/contractorTool',
method: 'put',
data: data
});
@ -57,7 +57,7 @@ export const updateContractorTool = (data: ContractorToolForm) => {
*/
export const delContractorTool = (id: string | number | Array<string | number>) => {
return request({
url: '/project/contractorTool/' + id,
url: '/contractor/contractorTool/' + id,
method: 'delete'
});
};

View File

@ -10,7 +10,7 @@ import { SubcontractVO, SubcontractForm, SubcontractQuery } from '@/api/project/
export const listSubcontract = (query?: SubcontractQuery): AxiosPromise<SubcontractVO[]> => {
return request({
url: '/project/subcontract/list',
url: '/contractor/subcontract/list',
method: 'get',
params: query
});
@ -22,7 +22,7 @@ export const listSubcontract = (query?: SubcontractQuery): AxiosPromise<Subcontr
*/
export const getSubcontract = (id: string | number): AxiosPromise<SubcontractVO> => {
return request({
url: '/project/subcontract/' + id,
url: '/contractor/subcontract/' + id,
method: 'get'
});
};
@ -33,7 +33,7 @@ export const getSubcontract = (id: string | number): AxiosPromise<SubcontractVO>
*/
export const addSubcontract = (data: SubcontractForm) => {
return request({
url: '/project/subcontract',
url: '/contractor/subcontract',
method: 'post',
data: data
});
@ -45,7 +45,7 @@ export const addSubcontract = (data: SubcontractForm) => {
*/
export const updateSubcontract = (data: SubcontractForm) => {
return request({
url: '/project/subcontract',
url: '/contractor/subcontract',
method: 'put',
data: data
});
@ -57,7 +57,7 @@ export const updateSubcontract = (data: SubcontractForm) => {
*/
export const delSubcontract = (id: string | number | Array<string | number>) => {
return request({
url: '/project/subcontract/' + id,
url: '/contractor/subcontract/' + id,
method: 'delete'
});
};

View File

@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ViolationRecordVO, ViolationRecordForm, ViolationRecordQuery } from '@/api/safety/violationRecord/types';
/**
* 查询违规记录列表
* @param query
* @returns {*}
*/
export const listViolationRecord = (query?: ViolationRecordQuery): AxiosPromise<ViolationRecordVO[]> => {
return request({
url: '/safety/violationRecord/list',
method: 'get',
params: query
});
};
/**
* 查询违规记录详细
* @param id
*/
export const getViolationRecord = (id: string | number): AxiosPromise<ViolationRecordVO> => {
return request({
url: '/safety/violationRecord/' + id,
method: 'get'
});
};
/**
* 新增违规处理人
* @param data
*/
export const addViolationRecord = (data: any) => {
return request({
url: '/safety/violationRecord/handler',
method: 'post',
data: data
});
};
/**
* 修改违规记录
* @param data
*/
export const updateViolationRecord = (data: ViolationRecordForm) => {
return request({
url: '/safety/violationRecord',
method: 'put',
data: data
});
};
/**
* 删除违规记录
* @param id
*/
export const delViolationRecord = (id: string | number | Array<string | number>) => {
return request({
url: '/safety/violationRecord/' + id,
method: 'delete'
});
};

View File

@ -0,0 +1,241 @@
export interface ViolationRecordVO {
/**
* 主键id
*/
id: string | number;
/**
* 项目id
*/
projectId: string | number;
/**
* 违章等级id
*/
levelId: string | number;
/**
* 违章类型
*/
violationType: string;
/**
* 违章时间
*/
violationTime: string;
/**
* 违章处理人id
*/
handlerId: string | number;
/**
* 整改措施
*/
measure: string;
/**
* 整改时间
*/
rectificationTime: string;
/**
* 复查情况
*/
review: string;
/**
* 复查状态1通过 2未通过
*/
reviewType: string;
/**
* 复查时间
*/
reviewTime: string;
/**
* 处理流程类型(0仅通知 1通知整改复查)
*/
processType: string;
/**
* 工单状态1通知 2整改 3复查
*/
status: string;
/**
* 备注
*/
remark: string;
}
export interface ViolationRecordForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 项目id
*/
projectId?: string | number;
/**
* 违章等级id
*/
levelId?: string | number;
/**
* 识别记录id
*/
recognizeId?: string | number;
/**
* 违章类型
*/
violationType?: string;
/**
* 违章时间
*/
violationTime?: string;
/**
* 违章处理人id
*/
handlerId?: string | number;
/**
* 处理期限
*/
disposeDeadline?: string;
/**
* 处理时间
*/
disposeTime?: string;
/**
* 整改措施
*/
measure?: string;
/**
* 整改时间
*/
rectificationTime?: string;
/**
* 复查情况
*/
review?: string;
/**
* 复查状态1通过 2未通过
*/
reviewType?: string;
/**
* 复查时间
*/
reviewTime?: string;
/**
* 处理流程类型(0仅通知 1通知整改复查)
*/
processType?: string;
/**
* 工单状态1通知 2整改 3复查
*/
status?: string;
/**
* 备注
*/
remark?: string;
}
export interface ViolationRecordQuery extends PageQuery {
/**
* 主键id
*/
id?: string | number;
/**
* 项目id
*/
projectId?: string | number;
/**
* 违章类型
*/
violationType?: string;
/**
* 违章时间
*/
violationTime?: string;
/**
* 违章处理人id
*/
handlerId?: string | number;
/**
* 处理期限
*/
disposeDeadline?: string;
/**
* 处理时间
*/
disposeTime?: string;
/**
* 整改措施
*/
measure?: string;
/**
* 整改时间
*/
rectificationTime?: string;
/**
* 复查情况
*/
review?: string;
/**
* 复查状态1通过 2未通过
*/
reviewType?: string;
/**
* 复查时间
*/
reviewTime?: string;
/**
* 处理流程类型(0仅通知 1通知整改复查)
*/
processType?: string;
/**
* 工单状态1通知 2整改 3复查
*/
status?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {DeptForm, DeptQuery, DeptTreeVO, DeptVO} from './types';
import { DeptForm, DeptQuery, DeptTreeVO, DeptVO } from './types';
// 查询部门列表
export const listDept = (query?: DeptQuery) => {
@ -11,6 +11,24 @@ export const listDept = (query?: DeptQuery) => {
});
};
export const getDeptList = () => {
return request({
url: '/project/project/listNoDept',
method: 'get'
});
};
/**
* 查询分包单位
* @param projectId
*/
export const optionProjectSelect = (projectId: number | string): any => {
return request({
url: '/contractor/contractor/listNoDept/' + projectId,
method: 'get'
});
};
/**
* 通过deptIds查询部门
* @param deptIds

View File

@ -5,6 +5,8 @@ export interface DeptQuery extends PageQuery {
deptName?: string;
deptCategory?: string;
status?: number;
deptType?: string;
isShow?: string;
}
/**
@ -16,6 +18,9 @@ export interface DeptVO extends BaseEntity {
parentId: number | string;
children: DeptVO[];
deptId: number | string;
contractorList: any[];
projectId: number | string;
projectList: any[];
deptName: string;
deptCategory: string;
orderNum: number;
@ -48,10 +53,15 @@ export interface DeptForm {
parentId?: number | string;
children?: DeptForm[];
deptId?: number | string;
projectId?: number | string;
contractorId?: number | string;
rowProjectId?: number | string;
deptName?: string;
deptCategory?: string;
orderNum?: number;
leader?: string;
deptType?: string;
isShow?: string;
phone?: string;
email?: string;
status?: string;

View File

@ -11,6 +11,14 @@ export function listPost(query: { pageNum: number; pageSize: number }): AxiosPro
});
}
// 查询岗位列表
export function listTreeByProject(projectId: string): AxiosPromise<PostVO[]> {
return request({
url: '/system/dept/list/treeByProjectId/' + projectId,
method: 'get'
});
}
// 查询岗位详细
export function getPost(postId: string | number): AxiosPromise<PostVO> {
return request({
@ -56,3 +64,14 @@ export function delPost(postId: string | number | (string | number)[]) {
method: 'delete'
});
}
// 获取岗位选择框列表
export function getRoleList(deptId?: number | string): AxiosPromise<any[]> {
return request({
url: '/system/role/listNoPage',
method: 'get',
params: {
deptId
}
});
}

View File

@ -6,7 +6,8 @@ export interface DeptTreeOption {
label: string;
parentId: string;
weight: number;
children?: DeptTreeOption[];
children: DeptTreeOption[];
disabled: boolean;
}
export interface RoleDeptTree {
@ -34,6 +35,7 @@ export interface RoleVO extends BaseEntity {
export interface RoleQuery extends PageQuery {
roleName: string;
roleKey: string;
deptId: string;
status: string;
}
@ -46,7 +48,9 @@ export interface RoleForm {
deptCheckStrictly: boolean;
remark: string;
dataScope?: string;
isSpecial: string;
roleId: string | undefined;
menuIds: Array<string | number>;
deptIds: Array<string | number>;
deptId?: string;
}

View File

@ -202,10 +202,11 @@ export const listUserByDeptId = (deptId: string | number): AxiosPromise<UserVO[]
/**
* 查询部门下拉树结构
*/
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
export const deptTreeSelect = (data?: { isShow: string }): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/user/deptTree',
method: 'get'
method: 'get',
params: data
});
};

View File

@ -3,6 +3,7 @@ export interface FlowDefinitionQuery extends PageQuery {
flowName?: string;
category: string | number;
isPublish?: number;
projectId: string | number;
}
export interface FlowDefinitionVo {
@ -23,6 +24,7 @@ export interface FlowDefinitionForm {
flowCode: string;
category: string;
formPath: string;
projectId: string;
}
export interface definitionXmlVO {

View File

@ -2,9 +2,11 @@ import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
export default {
routerJump(routerJumpVo: RouterJumpVo, proxy) {
console.log(routerJumpVo.formPath);
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: routerJumpVo.formPath,
path: routerJumpVo.formPath ? '/' + routerJumpVo.formPath : routerJumpVo.formPath,
query: {
id: routerJumpVo.businessId,
type: routerJumpVo.type,

View File

@ -49,12 +49,12 @@ VXETable.config({
//本地保存飞机配置
import { setLocal } from './utils';
setLocal('dockAir', 'http://192.168.110.24:9136');
setLocal('aiUrl', 'http://192.168.110.23:8000');
setLocal('host', '192.168.110.199');
setLocal('rtmpPort', '1935');
setLocal('rtcPort', '1985');
setLocal('dockSocketUrl', 'ws://192.168.110.8:9136/websocket');
setLocal('dockAir', 'http://58.17.134.85:9512');
setLocal('aiUrl', 'http://58.17.134.85:9512');
setLocal('host', '121.37.237.116');
setLocal('rtmpPort', '28451');
setLocal('rtcPort', '28453');
setLocal('dockSocketUrl', 'ws://58.17.134.85:9512/websocket');
// 修改 el-dialog 默认点击遮照为不关闭
/*import { ElDialog } from 'element-plus';

View File

@ -7,39 +7,23 @@ import { Style, Stroke, Fill } from 'ol/style';
import GeoJSON from 'ol/format/GeoJSON';
import { polygon as turfPolygon, booleanIntersects } from '@turf/turf';
import { toLonLat } from 'ol/proj';
import DragPan from 'ol/interaction/DragPan';
import MouseWheelZoom from 'ol/interaction/MouseWheelZoom';
export class LassoSelector {
private map: OLMap;
private drawLayer: VectorLayer<VectorSource>;
private drawSource: VectorSource;
private overlaySource: VectorSource;
private overlaySource: VectorSource; // 新增用于闭合多边形
private overlayLayer: VectorLayer<VectorSource>;
private drawing = false;
private coordinates: [number, number][] = [];
private targetSource: VectorSource;
private isShiftKeyDown = false;
private onSelectCallback: (selected: Feature[], isInvert?: boolean) => void;
private dragPanInteraction: DragPan | null = null;
private mouseWheelZoomInteraction: MouseWheelZoom | null = null;
private onSelectCallback: (selected: Feature[]) => void;
constructor(map: OLMap, targetSource: VectorSource, onSelect: (selected: Feature[], isInvert?: boolean) => void) {
constructor(map: OLMap, targetSource: VectorSource, onSelect: (selected: Feature[]) => void) {
this.map = map;
this.targetSource = targetSource;
this.onSelectCallback = onSelect;
// 找出拖动和滚轮缩放交互
this.dragPanInteraction = this.map
.getInteractions()
.getArray()
.find((interaction) => interaction instanceof DragPan) as DragPan;
this.mouseWheelZoomInteraction = this.map
.getInteractions()
.getArray()
.find((interaction) => interaction instanceof MouseWheelZoom) as MouseWheelZoom;
this.drawSource = new VectorSource();
this.drawLayer = new VectorLayer({
source: this.drawSource,
@ -52,6 +36,7 @@ export class LassoSelector {
});
this.map.addLayer(this.drawLayer);
// 新增:闭合多边形图层(半透明填充)
this.overlaySource = new VectorSource();
this.overlayLayer = new VectorLayer({
source: this.overlaySource,
@ -74,25 +59,17 @@ export class LassoSelector {
// 禁用默认右键菜单
this.map.getViewport().addEventListener('contextmenu', (e) => e.preventDefault());
// pointerdown 捕获左键按下
this.map.getViewport().addEventListener('pointerdown', (e) => {
if (e.button === 0 && !this.drawing) {
e.preventDefault();
e.stopPropagation();
this.isShiftKeyDown = e.shiftKey;
// 右键按下开始绘制
this.map.getViewport().addEventListener('mousedown', (e) => {
if (e.button === 2 && !this.drawing) {
this.drawing = true;
this.coordinates = [];
this.drawSource.clear();
this.overlaySource.clear();
// 禁用拖动和缩放
if (this.dragPanInteraction) this.dragPanInteraction.setActive(false);
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(false);
}
});
// pointermove 画线
// 鼠标移动实时绘制线和闭合多边形
this.map.on('pointermove', (evt) => {
if (!this.drawing) return;
const coord = evt.coordinate as [number, number];
@ -101,30 +78,11 @@ export class LassoSelector {
this.renderPolygon();
});
// pointerup 捕获左键抬起
this.map.getViewport().addEventListener('pointerup', (e) => {
if (e.button === 0 && this.drawing) {
e.preventDefault();
e.stopPropagation();
// 右键抬起结束绘制并选中
this.map.getViewport().addEventListener('mouseup', (e) => {
if (e.button === 2 && this.drawing) {
this.drawing = false;
this.handleDrawEnd();
// 恢复拖动和缩放
if (this.dragPanInteraction) this.dragPanInteraction.setActive(true);
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(true);
}
});
// 防止拖动导致意外事件
this.map.getViewport().addEventListener('pointercancel', (e) => {
if (this.drawing) {
this.drawing = false;
this.drawSource.clear();
this.overlaySource.clear();
if (this.dragPanInteraction) this.dragPanInteraction.setActive(true);
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(true);
}
});
}
@ -142,6 +100,7 @@ export class LassoSelector {
this.overlaySource.clear();
if (this.coordinates.length < 3) return;
// 闭合多边形坐标(首尾闭合)
const polygonCoords = [...this.coordinates, this.coordinates[0]];
const polygon = new Polygon([polygonCoords]);
const feature = new Feature({ geometry: polygon });
@ -181,10 +140,7 @@ export class LassoSelector {
}
});
if (selected.length) {
this.onSelectCallback(selected, this.isShiftKeyDown);
}
this.onSelectCallback(selected);
this.drawSource.clear();
this.overlaySource.clear();
}
@ -192,5 +148,6 @@ export class LassoSelector {
destroy() {
this.map.removeLayer(this.drawLayer);
this.map.removeLayer(this.overlayLayer);
// 如有需要,解绑事件
}
}

View File

@ -25,7 +25,7 @@ export const globalHeaders = () => {
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
axios.defaults.headers['projectId'] = cache.local.getJSON('selectedProject').id;
axios.defaults.headers['isEncrypt'] = true;
// 创建 axios 实例
const service = axios.create({
@ -85,6 +85,8 @@ service.interceptors.request.use(
// 生成一个 AES 密钥
const aesKey = generateAesKey();
config.headers[encryptHeader] = encrypt(encryptBase64(aesKey));
console.log(encrypt(encryptBase64(aesKey)));
config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey);
}
}

View File

@ -163,12 +163,11 @@ const handleSelectionChange = (selection: any) => {
};
const handleDetail = async (row) => {
const res = await listByIds(row.attachments);
const res = await listByIds(row.id);
tableDetail.value = {
...row,
...res.data[0]
};
console.log('🚀 ~ handleDetail ~ tableDetail.value:', tableDetail.value);
detailVisible.value = true;
};

View File

@ -12,65 +12,24 @@
</el-table-column>
<el-table-column align="center" prop="bookName" label="卷册名称" />
<el-table-column align="center" prop="bookNo" label="卷册号" />
<!-- <el-table-column align="center" label="附图">
<template #default="{ row }">
<ImagePreview :src="row.hasAttachment" disabled :width="80" />
</template>
</el-table-column> -->
<!-- <el-table-column align="center" prop="changeReasons" label="变更原因" width="100">
<template #default="{ row }">
{{ row.changeReasons?.join(', ') }}
</template>
</el-table-column> -->
<el-table-column align="center" prop="changeContent" label="变更内容" />
<el-table-column align="center" prop="costEstimate" label="变更费用估算" />
<!-- <el-table-column label="施工承包单位" align="center">
<el-table-column align="center" prop="contractorManager" label="项目经理" width="100" />
<el-table-column align="center" prop="contractorDate" label="日期" width="130">
<template #default="{ row }">{{ formatDate(row.contractorDate) }}</template>
</el-table-column>
</el-table-column>
<el-table-column label="总承包单位" align="center">
<el-table-column align="center" prop="generalTechLeader" label="项目技术负责人" width="150" />
<el-table-column align="center" prop="generalDate" label="日期" width="130">
<template #default="{ row }">{{ formatDate(row.generalDate) }}</template>
</el-table-column>
</el-table-column>
<el-table-column label="设计单位" align="center">
<el-table-column align="center" prop="designer" label="设计代表" width="100" />
<el-table-column align="center" prop="designerDate" label="日期" width="130">
<template #default="{ row }">{{ formatDate(row.designerDate) }}</template>
</el-table-column>
</el-table-column>
<el-table-column label="项目监理单位" align="center">
<el-table-column align="center" prop="supervisorEngineer" label="监理工程师" width="100" />
<el-table-column align="center" prop="chiefSupervisor" label="总监理工程师" width="110" />
<el-table-column align="center" prop="supervisorDate" label="日期" width="130">
<template #default="{ row }">{{ formatDate(row.supervisorDate) }}</template>
</el-table-column>
</el-table-column>
<el-table-column label="建设单位" align="center">
<el-table-column align="center" prop="ownerLeader" label="负责人" />
<el-table-column align="center" prop="ownerDate" label="日期" width="130">
<template #default="{ row }">{{ formatDate(row.ownerDate) }}</template>
</el-table-column>
</el-table-column> -->
<el-table-column align="center" prop="content" label="操作" width="160">
<el-table-column align="center" prop="costEstimate" label="变更费用估算1" />
<el-table-column label="流程状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column align="center" prop="content" label="操作" width="240">
<template #default="scope">
<el-button link type="warning" v-if="scope.row.status === 'draft'" icon="Edit" @click="handleUpdate(scope.row)" class="ml-3"
>审批
</el-button>
<el-button link type="primary" icon="View" @click="handleViewInfo(scope.row)" class="ml-3"> 查看流程 </el-button>
<el-button link type="success" icon="View" @click="handleDetail(scope.row)" class="ml-3"> 详情 </el-button>
<!-- <el-button link type="primary" icon="Download" @click="handleDownload()"> 下载 </el-button> -->
<!-- <el-button link type="warning" icon="Edit" @click="handleDelete(scope.row)"> 修改 </el-button> -->
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 详情 -->
<el-dialog title="变更单详情" v-model="detailVisible" width="1000">
<div class="w[850px] ma word-export-wrapper" ref="exportRef">
@ -189,36 +148,16 @@
import { listByIds } from '@/api/system/oss';
import { dayjs } from 'element-plus';
import { saveAs } from 'file-saver';
const form = reactive({
projectName: '',
unitName: '',
profession: '',
applyDate: '2025-6-23 13:03:56',
bookName: '',
bookNo: '',
hasAttachment:
'http://58.17.134.85:9000/xinnengyuan-dev/doc/safety/knowledge/1897160897167638529/知识库/指导手册/2025-06-27_2f56bca1c4bc46c6b226858a18713c48.jpg', // 附图链接或图片地址
changeReasons: [0, 3, 5],
changeContent: '',
costEstimate: '',
contractorManager: '',
contractorDate: '',
generalTechLeader: '',
generalDate: '',
designer: '',
designerDate: '',
supervisorEngineer: '',
chiefSupervisor: '',
supervisorDate: '',
ownerLeader: '',
ownerDate: ''
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const props = defineProps({
data: {
type: Array,
default: () => []
},
thumbnail: {
type: String,
default: ''
}
});
const tableDetail = ref<any>({});
@ -237,13 +176,11 @@ const detailVisible = ref(false);
const formatDate = (val: string | Date) => (val ? dayjs(val).format('YYYY-MM-DD') : '');
const handleDetail = async (row) => {
const res = await listByIds(row.hasAttachment);
const res = await listByIds(row.id);
tableDetail.value = {
...row,
hasAttachmentList: res.data
};
console.log(tableDetail.value);
detailVisible.value = true;
};
/** 多选框选中数据 */
@ -254,7 +191,32 @@ const handleSelectionChange = (selection: any) => {
const handleDelete = (row) => {
emit('delete', row.id);
};
const handleUpdate = (row) => {
// 添加审批
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/cory/template/indexEdit`,
query: {
thumbnailUrl: props.thumbnail,
row: JSON.stringify(row),
id: row.id,
type: 'update'
}
});
};
const handleViewInfo = (row) => {
// 添加审批
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/cory/template/indexEdit`,
query: {
thumbnailUrl: props.thumbnail,
row: JSON.stringify(row),
id: row.id,
type: 'view'
}
});
};
const handleDownload = () => {
const style = `
<style>

View File

@ -1,75 +1,59 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="模板类型" prop="projectType">
<el-select v-model="queryParams.projectType" placeholder="请选择模板类型" clearable filterable @change="selectType">
<el-option v-for="item in projectTypeOptions" :key="item.name" :label="item.name" :value="item.name"> </el-option>
</el-select>
</el-form-item>
<!-- <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> -->
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" :disabled="addSingle" @click="handleAdd" v-hasPermi="['quality:qualityInspection:add']"
>新增</el-button
>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['quality:qualityInspection:edit']"
>修改</el-button
>
</el-col> -->
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['quality:qualityInspection:remove']"
>删除</el-button
>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['quality:qualityInspection:export']">导出</el-button>
</el-col> -->
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<!-- card body -->
<Contactform
v-if="queryParams.projectType == '外部联系单'"
@selection-change="handleSelectionChange"
:data="tableData"
@delete="handleDelete"
></Contactform>
<EngineeringChangeApplicationForm
v-if="queryParams.projectType == '变更单'"
@selection-change="handleSelectionChange"
:data="tableData"
@delete="handleDelete"
></EngineeringChangeApplicationForm>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-tabs type="border-card" v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="变更单" name="1"
><el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" :disabled="addSingle" @click="handleAddApp" v-hasPermi="['quality:qualityInspection:add']"
>上传变更单模版</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<EngineeringChangeApplicationForm
@selection-change="handleSelectionChange"
:data="tableData"
:thumbnail="projectTypeOptions[1].thumbnail"
@delete="handleDelete"
></EngineeringChangeApplicationForm>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/> </el-card
></el-tab-pane>
<el-tab-pane label="外部联系单" name="0"
><el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" :disabled="addSingle" @click="handleAdd" v-hasPermi="['quality:qualityInspection:add']"
>上传外部联系单模版</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<Contactform @selection-change="handleSelectionChange" :data="tableData" @delete="handleDelete"></Contactform>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/> </el-card
></el-tab-pane>
</el-tabs>
<el-dialog title="新增模板" v-model="dialogVisible" width="800">
<el-form :model="form" :rules="rules" ref="formRef" label-width="110px">
<div class="flex">
<!-- <img :src="thumbnailUrl" alt="" style="width: 150px;" /> -->
<div><image-preview :src="thumbnailUrl" width="150px"></image-preview></div>
<div v-if="queryParams.projectType == '外部联系单'">
<div>
<el-form-item label="工程名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入工程名称" />
</el-form-item>
@ -111,94 +95,6 @@
<el-date-picker v-model="form.ownerDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
</div>
<div v-if="queryParams.projectType === '变更单'">
<el-form-item label="工程名称">
<el-input v-model="form.projectName" />
</el-form-item>
<el-form-item label="提出单位">
<el-input v-model="form.unitName" />
</el-form-item>
<el-form-item label="专业">
<el-input v-model="form.profession" />
</el-form-item>
<el-form-item label="提出日期">
<el-date-picker v-model="form.applyDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
<el-form-item label="卷册名称">
<el-input v-model="form.bookName" />
</el-form-item>
<el-form-item label="卷册号">
<el-input v-model="form.bookNo" />
</el-form-item>
<el-form-item label="附图">
<image-upload v-model="form.hasAttachment"></image-upload>
</el-form-item>
<el-form-item label="变更原因">
<el-checkbox-group v-model="form.changeReasons">
<el-checkbox label="设计漏项" class="w45%" />
<el-checkbox label="设计改进" />
<el-checkbox label="设计差错" class="w45%" />
<el-checkbox label="接口差错" />
<el-checkbox label="业主要求" class="w45%" />
<el-checkbox label="施工承包商要求" />
<el-checkbox label="外部资料不符" class="w45%" />
<el-checkbox label="材料代用或其他" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="变更内容">
<el-input v-model="form.changeContent" />
</el-form-item>
<el-form-item label="变更费用估算">
<el-input v-model="form.costEstimate" />
</el-form-item>
<el-divider class="mb-10! mt-10!">施工承包单位</el-divider>
<el-form-item label="项目经理" prop="asupervisorLeader">
<el-input v-model="form.asupervisorLeader" placeholder="请输入项目经理姓名" />
</el-form-item>
<el-form-item label="日期" prop="asupervisorDate">
<el-date-picker v-model="form.asupervisorDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
<el-divider class="mb-10! mt-10!">总承包单位</el-divider>
<el-form-item label="项目技术负责人" prop="bsupervisorLeader">
<el-input v-model="form.bsupervisorLeader" placeholder="请输入项目技术负责人姓名" />
</el-form-item>
<el-form-item label="日期" prop="bsupervisorDate">
<el-date-picker v-model="form.bsupervisorDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
<el-divider class="mb-10! mt-10!">设计单位</el-divider>
<el-form-item label="设计代表" prop="csupervisorLeader">
<el-input v-model="form.csupervisorLeader" placeholder="请输入设计代表姓名" />
</el-form-item>
<el-form-item label="日期" prop="csupervisorDate">
<el-date-picker v-model="form.csupervisorDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
<el-divider class="mb-10! mt-10!">项目监理单位</el-divider>
<el-form-item label="监理工程师" prop="dsupervisorLeader">
<el-input v-model="form.dsupervisorLeader" placeholder="请输入监理工程师姓名" />
</el-form-item>
<el-form-item label="总监理工程师" prop="dasupervisorLeader">
<el-input v-model="form.dasupervisorLeader" placeholder="请输入总监理工程师姓名" />
</el-form-item>
<el-form-item label="日期" prop="dsupervisorDate">
<el-date-picker v-model="form.dsupervisorDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
<el-divider class="mb-10! mt-10!">建设单位</el-divider>
<el-form-item label="负责人" prop="esupervisorLeader">
<el-input v-model="form.esupervisorLeader" placeholder="请输入负责人姓名" />
</el-form-item>
<el-form-item label="日期" prop="esupervisorDate">
<el-date-picker v-model="form.esupervisorDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
</div>
</div>
</el-form>
<template #footer>
@ -224,15 +120,10 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const { safety_inspection_violation_type, safety_inspection_check_type } = toRefs<any>(
proxy?.useDict('safety_inspection_violation_type', 'safety_inspection_check_type')
);
const teamOpt = ref([]);
const foremanOpt = ref([]);
const thumbnailUrl = ref('');
const tableData = ref([]);
const total = ref(0);
const activeName = ref('1');
const formRef = ref<FormInstance>();
const dialogVisible = ref<boolean>(false);
const showSearch = ref(true);
@ -338,17 +229,6 @@ const handleAdd = () => {
dialogVisible.value = true;
};
const changeForeman = (value: string | number) => {
// const team = teamList.value.filter((team) => team.id === value)[0];
// foremanOpt.value = team.foremanList?.map((foreman: foremanQuery) => ({
// label: foreman.foremanName,
// value: foreman.foremanId
// }));
// form.value.correctorId = '';
};
const handleQuery = () => {};
const resetQuery = () => {};
const getList = async () => {
if (!queryParams.value.projectType) {
const res = await listContactTypeformtemplate(queryParams.value);
@ -376,8 +256,6 @@ const handleDelete = async (id?: string) => {
getList();
}
};
const handleUpdate = () => {};
/** 多选框选中数据 */
const handleSelectionChange = (selection: any) => {
ids.value = selection.map((item) => item.id);
@ -392,10 +270,23 @@ const selectType = (value: string) => {
getList();
};
const resetForm = () => {
formRef.value?.resetFields();
const handleClick = (val) => {
console.log(val);
queryParams.value.projectType = val.props.name;
getList();
};
const handleAddApp = (row) => {
// 添加审批
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/relation-management/changeContact/indexEdit`,
query: {
thumbnailUrl: projectTypeOptions.value[1].thumbnail,
row,
type: 'add'
}
});
};
onMounted(() => {
getList();
});

View File

@ -0,0 +1,442 @@
<template>
<div class="p-4 bg-gray-50">
<div class="max-w-4xl mx-auto">
<!-- 顶部按钮区域 -->
<el-card class="mb-4 rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.id"
:status="form.status"
:pageType="routeParams.type"
/>
</el-card>
<!-- 表单区域 -->
<el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden">
<div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-800">变更联系单</h3>
</div>
<div class="p-6">
<el-form
ref="leaveFormRef"
v-loading="loading"
:disabled="routeParams.type === 'view'"
:model="form"
:rules="rules"
label-width="100px"
class="space-y-4"
>
<div class="grid grid-cols-1 gap-4">
<div class="flex">
<div><image-preview :src="thumbnailUrl" width="150px"></image-preview></div>
<div>
<el-form-item label="工程名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入工程名称" />
</el-form-item>
<el-form-item label="编号" prop="serialNumber">
<el-input v-model="form.serialNumber" placeholder="请输入编号" />
</el-form-item>
<el-form-item label="致" prop="to">
<el-input v-model="form.to" placeholder="致:" />
</el-form-item>
<el-form-item label="主题" prop="subject">
<el-input v-model="form.subject" placeholder="请输入主题" />
</el-form-item>
<el-form-item label="内容" prop="content">
<el-input v-model="form.content" type="textarea" :rows="6" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="附件" prop="attachments">
<file-upload v-model="form.attachments" :limit="1" :file-type="['pdf', 'png', 'jpg', 'jpeg', 'gif', 'bmp']"></file-upload>
</el-form-item>
<el-divider class="mb-10! mt-10!">施工项目部</el-divider>
<el-form-item label="项目负责人" prop="contractorLeader">
<el-input v-model="form.contractorLeader" placeholder="请输入负责人姓名" />
</el-form-item>
<el-form-item label="日期" prop="contractorDate">
<el-date-picker v-model="form.contractorDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
<el-divider class="mb-10! mt-10!">项目监理机构</el-divider>
<el-form-item label="总监理工程师" prop="supervisorLeader">
<el-input v-model="form.supervisorLeader" placeholder="请输入总监理工程师姓名" />
</el-form-item>
<el-form-item label="日期" prop="supervisorDate">
<el-date-picker v-model="form.supervisorDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
<el-divider class="mb-10! mt-10!">建设单位</el-divider>
<el-form-item label="业主代表" prop="ownerRep">
<el-input v-model="form.ownerRep" placeholder="请输入业主代表" />
</el-form-item>
<el-form-item label="日期" prop="ownerDate">
<el-date-picker v-model="form.ownerDate" type="date" placeholder="选择日期" style="width: 100%" />
</el-form-item>
</div>
</div>
</div>
</el-form>
</div>
</el-card>
<!-- 提交组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<approvalRecord ref="approvalRecordRef"></approvalRecord>
<!-- 流程选择对话框 -->
<el-dialog
draggable
v-model="dialogVisible.visible"
:title="dialogVisible.title"
:before-close="handleClose"
width="500"
class="rounded-lg shadow-lg"
>
<div class="p-4">
<p class="text-gray-600 mb-4">请选择要启动的流程</p>
<el-select v-model="flowCode" placeholder="请选择流程" style="width: 100%">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<template #footer>
<div class="dialog-footer p-4 border-t border-gray-100 flex justify-end space-x-3">
<el-button @click="handleClose" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors"
>取消</el-button
>
<el-button type="primary" @click="submitFlow()" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors"
>确认</el-button
>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup name="Leave" lang="ts">
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
import { startWorkFlow } from '@/api/workflow/task';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { useUserStoreHook } from '@/store/modules/user';
import { listByIds } from '@/api/system/oss';
import { addContactnotice,getContactnotice ,updateContactnotice} from '@/api/cory/contactnotice';
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const buttonLoading = ref(false);
const loading = ref(true);
const thumbnailUrl = ref('');
//路由参数
const routeParams = ref<Record<string, any>>({});
const flowCodeOptions = [
{
value: currentProject.value?.id + '_changecontact',
label: '变更联系单审批'
},
];
const flowCode = ref<string>('');
const status = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
const leaveFormRef = ref<ElFormInstance>();
const dialog = reactive({
visible: false,
title: '',
isEdit: false
});
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
});
const taskVariables = ref<Record<string, any>>({});
const initFormData = {
id: undefined,
projectId: currentProject.value?.id,
projectType: '',
projectName: '',
serialNumber: '',
to: '',
subject: '',
content: '',
attachments: '',
contractorLeader: '',
contractorDate: '',
supervisorLeader: '',
supervisorDate: '',
ownerRep: '',
ownerDate: '',
unitName: '',
profession: '',
applyDate: '',
bookName: '',
bookNo: '',
hasAttachment: '',
changeReasons: [],
changeContent: '',
costEstimate: '',
status: ''
};
const data = reactive({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
fileName: undefined,
fileType: undefined,
fileSuffix: undefined,
fileStatus: undefined,
originalName: undefined,
newest: undefined,
params: {}
},
rules: {
projectName: [{ required: true, message: '请输入工程名称', trigger: 'blur' }],
projectType: [{ required: true, message: '请选择模板类型', trigger: 'blur' }],
serialNumber: [{ required: true, message: '请输入编号', trigger: 'blur' }],
to: [{ required: true, message: '请输入接收方', trigger: 'blur' }],
subject: [{ required: true, message: '请输入主题', trigger: 'blur' }],
content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}
});
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
const { form, rules } = toRefs(data);
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
leaveFormRef.value?.resetFields();
};
/** 获取详情 */
const getInfo = () => {
loading.value = true;
buttonLoading.value = false;
nextTick(async () => {
const res = await getContactnotice(routeParams.value.id);
var data = {
...JSON.parse(routeParams.value.row),
...JSON.parse(res.data.detail)
};
console.log(routeParams.value);
Object.assign(form.value, data);
loading.value = false;
buttonLoading.value = false;
});
};
/** 提交按钮 */
const submitForm = (status1: string) => {
status.value = status1;
leaveFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// var res;
// if (form.value.id) {
// res = await updateContactnotice(form.value).finally(() => (buttonLoading.value = false));
// } else {
// res = await addContactnotice(form.value).finally(() => (buttonLoading.value = false));
// }
// if (res.code == 200) {
dialog.visible = false;
submit(status.value, form.value);
// }
}
});
};
const submitFlow = async () => {
handleStartWorkFlow(form.value);
dialogVisible.visible = false;
};
//提交申请
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = data.id;
//流程变量
taskVariables.value = {
// leave4/5 使用的流程变量
userList: ['1', '3', '4']
};
submitFormData.value.variables = taskVariables.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
buttonLoading.value = false;
submitVerifyRef.value.openDialog(resp.data.taskId);
}
} finally {
buttonLoading.value = false;
}
};
//审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(form.value.id);
};
//提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
//审批
const approvalVerifyOpen = async () => {
submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
// 图纸上传成功之后 开始提交
const submit = async (status, data) => {
form.value = data;
if (status === 'draft') {
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
}
//说明启动过先随意穿个参数
if (flowCode.value === '' || flowCode.value === null) {
flowCode.value = 'xx';
}
console.log(data);
await handleStartWorkFlow(data);
}
};
onMounted(() => {
nextTick(async () => {
routeParams.value = proxy.$route.query;
thumbnailUrl.value = proxy.$route.query.thumbnailUrl;
reset();
loading.value = false;
if (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval') {
getInfo();
}
});
});
</script>
<style scoped lang="scss">
/* 全局样式 */
:root {
--primary: #409eff;
--primary-light: #66b1ff;
--primary-dark: #3a8ee6;
--success: #67c23a;
--warning: #e6a23c;
--danger: #f56c6c;
--info: #909399;
}
/* 表单样式优化 */
.el-form-item {
.el-form-item__label {
color: #606266;
font-weight: 500;
}
.el-input__inner,
.el-select .el-input__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
.el-textarea__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
}
/* 按钮样式优化 */
.el-button {
border-radius: 4px;
transition: all 0.2s;
&.is-primary {
background-color: var(--primary);
border-color: var(--primary);
&:hover {
background-color: var(--primary-light);
border-color: var(--primary-light);
}
&:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
}
&.is-text {
color: var(--primary);
&:hover {
color: var(--primary-light);
background-color: rgba(64, 158, 255, 0.05);
}
}
}
/* 卡片样式优化 */
.el-card {
transition: all 0.3s ease;
&:hover {
/* transform: translateY(-2px); */
}
}
/* 对话框样式优化 */
.el-dialog {
.el-dialog__header {
background-color: #f5f7fa;
border-bottom: 1px solid #ebeef5;
padding: 15px 20px;
}
.el-dialog__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-dialog__footer {
padding: 15px 20px;
border-top: 1px solid #ebeef5;
}
}
</style>

View File

@ -306,7 +306,19 @@ const selectType = (value: string) => {
const resetForm = () => {
formRef.value?.resetFields();
};
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
onMounted(() => {
getList();
});

View File

@ -16,15 +16,16 @@
<el-form-item label="专业" prop="specialty">
<el-input v-model="queryParams.specialty" placeholder="请输入专业" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="提出日期" prop="submitDate">
<el-date-picker clearable v-model="queryParams.submitDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择提出日期" />
</el-form-item>
<el-form-item label="卷册名称" prop="volumeName">
<el-input v-model="queryParams.volumeName" placeholder="请输入卷册名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="卷册号" prop="volumeNo">
<el-input v-model="queryParams.volumeNo" placeholder="请输入卷册号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="提出日期" prop="submitDate">
<el-date-picker clearable v-model="queryParams.submitDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择提出日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
@ -38,12 +39,7 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['design:designChange:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['design:designChange:remove']"
>删除</el-button
>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['design:designChange:add']">上传设计变更</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -68,16 +64,26 @@
</el-table-column>
<el-table-column label="变更内容" align="center" prop="changeContent" />
<!-- <el-table-column label="变更费用估算" align="center" prop="costEstimation" /> -->
<el-table-column label="变更文件" align="center" prop="fileId">
<el-table-column label="变更文件" align="center">
<template #default="scope">
<span style="color: rgb(41 145 255);cursor: pointer" @click="onOpen(scope.row.file.url)"> {{ scope.row.file.originalName }}</span>
<span v-if="scope.row.file" style="color: rgb(41 145 255); cursor: pointer" @click="onOpen(scope.row.file.url)">
{{ scope.row.file.originalName }}</span
>
</template>
</el-table-column>
<el-table-column label="流程状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)"></el-button>
<el-tooltip content="查看流程" placement="top">
<el-button link type="primary" icon="View" @click="handleViewInfo(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="查看文档" placement="top">
<el-button link type="primary" icon="Document" @click="handleView(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['design:designChange:edit']"></el-button>
@ -90,91 +96,13 @@
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog
:title="dialog.title"
v-model="dialog.visible"
:close-on-click-modal="false"
:close-on-press-escape="false"
width="800px"
append-to-body
>
<el-form ref="designChangeFormRef" :model="form" :rules="rules" label-width="110px">
<el-row>
<el-col :span="12">
<el-form-item label="申请单编号" prop="formNo">
<el-input v-model="form.formNo" placeholder="请输入申请单编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工程名称" prop="projectName"> <el-input v-model="form.projectName" placeholder="请输入工程名称" /> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="提出单位" prop="submitUnit">
<el-input v-model="form.submitUnit" placeholder="请输入提出单位" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业" prop="specialty">
<el-input v-model="form.specialty" placeholder="请输入专业" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="提出日期" prop="submitDate">
<el-date-picker clearable v-model="form.submitDate" type="date" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择提出日期">
</el-date-picker> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="卷册名称" prop="volumeName"> <el-input v-model="form.volumeName" placeholder="请输入卷册名称" /> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="卷册号" prop="volumeNo"> <el-input v-model="form.volumeNo" placeholder="请输入卷册号" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="附图" prop="attachmentPic"> <image-upload v-model="form.attachmentPic" :fileSize="100" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="变更原因" prop="changeReason">
<el-checkbox-group v-model="form.changeReason">
<el-checkbox v-for="dict in design_change_reason_type" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item></el-col
>
<el-col :span="24">
<el-form-item label="变更内容" prop="changeContent">
<el-input v-model="form.changeContent" type="textarea" placeholder="请输入内容" /> </el-form-item
></el-col>
<!-- <el-col :span="12">
<el-form-item label="变更费用估算" prop="costEstimation">
<el-input v-model="form.costEstimation" type="textarea" placeholder="请输入内容" /> </el-form-item
></el-col> -->
<el-col :span="24">
<el-form-item label="变更费用估算表" label-width="110px" prop="costEstimationFile">
<file-upload v-model="form.costEstimationFile" :fileSize="100" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="变更文件" prop="fileId"> <file-upload v-model="form.fileId" :fileSize="100" /> </el-form-item
></el-col>
<el-col :span="24"
><el-form-item label="备注" prop="remark"> <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> </el-form-item
></el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<wordDetial ref="wordDetialRef"></wordDetial>
</div>
</template>
<script setup name="DesignChange" lang="ts">
import { listDesignChange, getDesignChange, delDesignChange, addDesignChange, updateDesignChange } from '@/api/design/designChange';
import { DesignChangeVO, DesignChangeQuery, DesignChangeForm } from '@/api/design/designChange/types';
import { listDesignChange, delDesignChange } from '@/api/design/designChange';
import { DesignChangeVO } from '@/api/design/designChange/types';
import { useUserStoreHook } from '@/store/modules/user';
import wordDetial from '@/components/wordDetial/index';
@ -185,7 +113,6 @@ const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const designChangeList = ref<DesignChangeVO[]>([]);
const buttonLoading = ref(false);
const wordDetialRef = ref<InstanceType<typeof wordDetial>>();
const loading = ref(true);
const showSearch = ref(true);
@ -193,36 +120,9 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const queryFormRef = ref<ElFormInstance>();
const designChangeFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DesignChangeForm = {
id: undefined,
projectId: currentProject.value?.id,
formNo: undefined,
projectName: undefined,
submitUnit: undefined,
specialty: undefined,
submitDate: undefined,
volumeName: undefined,
volumeNo: undefined,
attachmentPic: undefined,
changeReason: [],
changeContent: undefined,
costEstimation: undefined,
costEstimationFile: undefined,
fileId: undefined,
status: undefined,
remark: undefined
};
const data = reactive<PageData<DesignChangeForm, DesignChangeQuery>>({
form: { ...initFormData },
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
@ -237,14 +137,10 @@ const data = reactive<PageData<DesignChangeForm, DesignChangeQuery>>({
changeReason: undefined,
status: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
projectId: [{ required: true, message: '项目id不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const { queryParams } = toRefs(data);
/** 查询设计变更管理列表 */
const getList = async () => {
@ -255,18 +151,6 @@ const getList = async () => {
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
designChangeFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
@ -288,40 +172,37 @@ const handleSelectionChange = (selection: DesignChangeVO[]) => {
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加设计变更';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DesignChangeVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getDesignChange(_id);
Object.assign(form.value, res.data);
form.value.changeReason = form.value.changeReason.split(',');
dialog.visible = true;
dialog.title = '修改设计变更';
};
/** 提交按钮 */
const submitForm = () => {
designChangeFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
form.value.changeReason = form.value.changeReason.join(',');
if (form.value.id) {
await updateDesignChange({ ...form.value }).finally(() => (buttonLoading.value = false));
} else {
await addDesignChange(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/design-management/designChange/indexEdit`,
query: {
type: 'add'
}
});
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DesignChangeVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/design-management/designChange/indexEdit`,
query: {
id: row.id,
type: 'update'
}
});
};
/** 查看按钮操作 */
const handleViewInfo = (row) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/design-management/designChange/indexEdit`,
query: {
id: row.id,
type: 'view'
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DesignChangeVO) => {
const _ids = row?.id || ids.value;
@ -332,13 +213,25 @@ const handleDelete = async (row?: DesignChangeVO) => {
};
const handleView = (row) => {
// 查看详情
wordDetialRef.value?.openDialog(row,design_change_reason_type.value);
wordDetialRef.value?.openDialog(row, design_change_reason_type.value);
};
// 预览
const onOpen = (path: string) => {
window.open(path, '_blank');
window.open(path, '_blank');
};
onMounted(() => {
getList();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>

View File

@ -0,0 +1,442 @@
<template>
<div class="p-4 bg-gray-50">
<div class="max-w-4xl mx-auto">
<!-- 顶部按钮区域 -->
<el-card class="mb-4 rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.id"
:status="form.status"
:pageType="routeParams.type"
/>
</el-card>
<!-- 表单区域 -->
<el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden">
<div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-800">设计变更信息</h3>
</div>
<div class="p-6">
<el-form
ref="leaveFormRef"
v-loading="loading"
:disabled="routeParams.type === 'view'"
:model="form"
:rules="rules"
label-width="100px"
class="space-y-4"
>
<div class="grid grid-cols-1 gap-4">
<el-row>
<el-col :span="12">
<el-form-item label="申请单编号" prop="formNo">
<el-input v-model="form.formNo" placeholder="请输入申请单编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工程名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入工程名称" /> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="提出单位" prop="submitUnit">
<el-input v-model="form.submitUnit" placeholder="请输入提出单位" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业" prop="specialty">
<el-input v-model="form.specialty" placeholder="请输入专业" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="提出日期" prop="submitDate">
<el-date-picker clearable v-model="form.submitDate" type="date" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择提出日期">
</el-date-picker> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="卷册名称" prop="volumeName"> <el-input v-model="form.volumeName" placeholder="请输入卷册名称" /> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="卷册号" prop="volumeNo"> <el-input v-model="form.volumeNo" placeholder="请输入卷册号" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="附图" prop="attachmentPic"> <image-upload v-model="form.attachmentPic" :fileSize="100" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="变更原因" prop="changeReason">
<el-checkbox-group v-model="form.changeReason">
<el-checkbox v-for="dict in design_change_reason_type" :key="dict.value" :value="dict.value">
{{ dict.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item></el-col
>
<el-col :span="24">
<el-form-item label="变更内容" prop="changeContent">
<el-input v-model="form.changeContent" type="textarea" placeholder="请输入内容" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="变更费用估算表" label-width="110px" prop="costEstimationFile">
<file-upload v-model="form.costEstimationFile" :fileSize="100" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="变更文件" prop="fileId"> <file-upload v-model="form.fileId" :fileSize="100" /> </el-form-item
></el-col>
<el-col :span="24"
><el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> </el-form-item
></el-col>
</el-row>
</div>
</el-form>
</div>
</el-card>
<!-- 提交组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<approvalRecord ref="approvalRecordRef"></approvalRecord>
<!-- 流程选择对话框 -->
<el-dialog
draggable
v-model="dialogVisible.visible"
:title="dialogVisible.title"
:before-close="handleClose"
width="500"
class="rounded-lg shadow-lg"
>
<div class="p-4">
<p class="text-gray-600 mb-4">请选择要启动的流程</p>
<el-select v-model="flowCode" placeholder="请选择流程" style="width: 100%">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<template #footer>
<div class="dialog-footer p-4 border-t border-gray-100 flex justify-end space-x-3">
<el-button @click="handleClose" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors"
>取消</el-button
>
<el-button type="primary" @click="submitFlow()" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors"
>确认</el-button
>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup name="Leave" lang="ts">
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
import { startWorkFlow } from '@/api/workflow/task';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { useUserStoreHook } from '@/store/modules/user';
import { getDesignChange, addDesignChange, updateDesignChange } from '@/api/design/designChange';
const { design_change_reason_type } = toRefs<any>(proxy?.useDict('design_change_reason_type'));
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const buttonLoading = ref(false);
const loading = ref(true);
//路由参数
const routeParams = ref<Record<string, any>>({});
const flowCodeOptions = [
{
value: currentProject.value?.id + '_designchanged',
label: '设计变更审批'
},
];
const flowCode = ref<string>('');
const status = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
const leaveFormRef = ref<ElFormInstance>();
const dialog = reactive({
visible: false,
title: '',
isEdit: false
});
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
});
const taskVariables = ref<Record<string, any>>({});
const initFormData = {
id: undefined,
projectId: currentProject.value?.id,
formNo: undefined,
projectName: undefined,
submitUnit: undefined,
specialty: undefined,
submitDate: undefined,
volumeName: undefined,
volumeNo: undefined,
attachmentPic: undefined,
changeReason: [],
changeContent: undefined,
costEstimation: undefined,
costEstimationFile: undefined,
fileId: undefined,
status: undefined,
remark: undefined
};
const data = reactive({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
fileName: undefined,
fileType: undefined,
fileSuffix: undefined,
fileStatus: undefined,
originalName: undefined,
newest: undefined,
params: {}
},
rules: {}
});
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
const { form, rules } = toRefs(data);
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
leaveFormRef.value?.resetFields();
};
/** 获取详情 */
const getInfo = () => {
loading.value = true;
buttonLoading.value = false;
nextTick(async () => {
const res = await getDesignChange(routeParams.value.id);
Object.assign(form.value, res.data);
if(form.value.changeReason.length >0){
form.value.changeReason = form.value.changeReason.split(',');
}
loading.value = false;
buttonLoading.value = false;
});
};
/** 提交按钮 */
const submitForm = (status1: string) => {
status.value = status1;
var changeReason=''
if(form.value.changeReason.length >0){
changeReason = form.value.changeReason.join(',');
}
leaveFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
var res;
if (form.value.id) {
res = await updateDesignChange({...form.value,changeReason}).finally(() => (buttonLoading.value = false));
} else {
res = await addDesignChange({...form.value,changeReason}).finally(() => (buttonLoading.value = false));
}
if (res.code == 200) {
dialog.visible = false;
submit(status.value, res.data);
}
}
});
};
const submitFlow = async () => {
handleStartWorkFlow(form.value);
dialogVisible.visible = false;
};
//提交申请
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = data.id;
//流程变量
taskVariables.value = {
// leave4/5 使用的流程变量
userList: ['1', '3', '4']
};
submitFormData.value.variables = taskVariables.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
buttonLoading.value = false;
submitVerifyRef.value.openDialog(resp.data.taskId);
}
} finally {
buttonLoading.value = false;
}
};
//审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(form.value.id);
};
//提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
//审批
const approvalVerifyOpen = async () => {
submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
// 图纸上传成功之后 开始提交
const submit = async (status, data) => {
form.value = data;
if (status === 'draft') {
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
}
//说明启动过先随意穿个参数
if (flowCode.value === '' || flowCode.value === null) {
flowCode.value = 'xx';
}
await handleStartWorkFlow(data);
}
};
onMounted(() => {
nextTick(async () => {
routeParams.value = proxy.$route.query;
reset();
loading.value = false;
if (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval') {
getInfo();
}
});
});
</script>
<style scoped lang="scss">
/* 全局样式 */
:root {
--primary: #409eff;
--primary-light: #66b1ff;
--primary-dark: #3a8ee6;
--success: #67c23a;
--warning: #e6a23c;
--danger: #f56c6c;
--info: #909399;
}
/* 表单样式优化 */
.el-form-item {
.el-form-item__label {
color: #606266;
font-weight: 500;
}
.el-input__inner,
.el-select .el-input__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
.el-textarea__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
}
/* 按钮样式优化 */
.el-button {
border-radius: 4px;
transition: all 0.2s;
&.is-primary {
background-color: var(--primary);
border-color: var(--primary);
&:hover {
background-color: var(--primary-light);
border-color: var(--primary-light);
}
&:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
}
&.is-text {
color: var(--primary);
&:hover {
color: var(--primary-light);
background-color: rgba(64, 158, 255, 0.05);
}
}
}
/* 卡片样式优化 */
.el-card {
transition: all 0.3s ease;
&:hover {
/* transform: translateY(-2px); */
}
}
/* 对话框样式优化 */
.el-dialog {
.el-dialog__header {
background-color: #f5f7fa;
border-bottom: 1px solid #ebeef5;
padding: 15px 20px;
}
.el-dialog__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-dialog__footer {
padding: 15px 20px;
border-top: 1px solid #ebeef5;
}
}
</style>

View File

@ -88,8 +88,8 @@
</template>
<script setup name="Drawing" lang="ts">
import { listDrawing, getDrawing, delDrawing, addDrawing, updateDrawing } from '@/api/design/drawing';
import { DrawingVO, DrawingQuery, DrawingForm } from '@/api/design/drawing/types';
import { listDrawing, delDrawing } from '@/api/design/drawing';
import { DrawingVO } from '@/api/design/drawing/types';
import { useUserStoreHook } from '@/store/modules/user';
import { LeaveVO } from '@/api/workflow/leave/types';
import { cancelProcessApply } from '@/api/workflow/instance';
@ -110,65 +110,24 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const fileList = ref([]); // 存储上传的文件列表
const selectedFile = ref(null); // 当前选中的文件
const queryFormRef = ref<ElFormInstance>();
const drawingFormRef = ref<ElFormInstance>();
const activeName = ref('1');
const dialog = reactive({
visible: false,
title: '',
isEdit: false
});
const initFormData: DrawingForm = {
id: undefined,
projectId: currentProject.value?.id,
versionNumber: undefined,
fileName: undefined,
fileUrl: undefined,
fileType: undefined,
fileSuffix: undefined,
originalName: undefined,
remark: undefined
};
const data = reactive<PageData<DrawingForm, DrawingQuery>>({
form: { ...initFormData },
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
fileName: undefined,
fileType: undefined,
fileType: 1,
fileSuffix: undefined,
fileStatus: undefined,
originalName: undefined,
newest: undefined,
params: {}
},
rules: {
versionNumber: [{ required: true, message: '版本号不能为空', trigger: 'blur' }],
fileName: [{ required: true, message: '文件名称不能为空', trigger: 'blur' }],
fileType: [{ required: true, message: '文件类型不能为空', trigger: 'change' }],
file: [
{
validator: (rule, value, callback) => {
console.log(dialog.isEdit);
console.log(fileList.value);
// 新增时必须上传文件
if (!dialog.isEdit && !fileList.value.length) {
callback(new Error('请上传图纸文件'));
} else {
callback();
}
},
trigger: 'change'
}
]
}
});
const { queryParams, form, rules } = toRefs(data);
const { queryParams } = toRefs(data);
/** 查询图纸管理列表 */
const getList = async () => {
@ -179,20 +138,6 @@ const getList = async () => {
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
drawingFormRef.value?.resetFields();
fileList.value = [];
selectedFile.value = null;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
@ -216,7 +161,7 @@ const handleSelectionChange = (selection: DrawingVO[]) => {
const handleAdd = () => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/drawing/indexEdit`,
path: `/design-management/drawing/indexEdit`,
query: {
type: 'add'
}
@ -227,7 +172,7 @@ const handleAdd = () => {
const handleUpdate = async (row?: DrawingVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/drawing/indexEdit`,
path: `/design-management/drawing/indexEdit`,
query: {
id: row.id,
type: 'update'
@ -253,7 +198,7 @@ const handleView = (row) => {
const handleViewInfo = (row?: LeaveVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/drawing/indexEdit`,
path: `/design-management/drawing/indexEdit`,
query: {
id: row.id,
type: 'view'
@ -283,4 +228,16 @@ const handleClick = (val) => {
onMounted(() => {
getList();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>

View File

@ -176,7 +176,7 @@ const data = reactive<PageData<LeaveForm, LeaveQuery>>({
versionNumber: [{ required: true, message: '版本号不能为空', trigger: 'blur' }],
fileName: [{ required: true, message: '文件名称不能为空', trigger: 'blur' }],
fileType: [{ required: true, message: '文件类型不能为空', trigger: 'change' }],
file: [
fileUrl: [
{
validator: (rule, value, callback) => {
// 新增时必须上传文件

View File

@ -14,8 +14,8 @@
<el-input v-model="queryParams.originalName" placeholder="请输入原文件名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="审核状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择审核状态" clearable >
<el-option v-for="dict in wf_business_status" :key="dict.value" :label="dict.label" :value="dict.value"/>
<el-select v-model="queryParams.status" placeholder="请选择审核状态" clearable>
<el-option v-for="dict in wf_business_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
@ -46,12 +46,11 @@
<el-table-column label="原文件名" align="center" prop="originalName" />
<el-table-column label="流程状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.status"/>
<dict-tag :options="wf_business_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180"> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding">
<template #default="scope">
<el-row :gutter="10" class="mb8">
@ -68,7 +67,9 @@
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" size="small" icon="View" v-if="scope.row.status != 'draft'" @click="handleViewInfo(scope.row)">查看</el-button>
<el-button type="primary" size="small" icon="View" v-if="scope.row.status != 'draft'" @click="handleViewInfo(scope.row)"
>查看</el-button
>
</el-col>
<el-col :span="1.5" v-if="scope.row.status === 'waiting'">
<el-button size="small" type="primary" icon="Notification" @click="handleCancelProcessApply(scope.row.id)">撤销</el-button>
@ -77,15 +78,14 @@
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="SpecialScheme" lang="ts">
import { listSpecialScheme, getSpecialScheme, delSpecialScheme, addSpecialScheme, updateSpecialScheme } from '@/api/design/specialScheme';
import { SpecialSchemeVO, SpecialSchemeQuery, SpecialSchemeForm } from '@/api/design/specialScheme/types';
import { listSpecialScheme, delSpecialScheme } from '@/api/design/specialScheme';
import { SpecialSchemeVO } from '@/api/design/specialScheme/types';
import { useUserStoreHook } from '@/store/modules/user';
import { cancelProcessApply } from '@/api/workflow/instance';
@ -96,7 +96,6 @@ const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const specialSchemeList = ref<SpecialSchemeVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
@ -105,24 +104,8 @@ const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const specialSchemeFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: SpecialSchemeForm = {
id: undefined,
projectId: currentProject.value?.id,
versionNumber: undefined,
fileName: undefined,
fileUrl: undefined,
fileSuffix: undefined,
remark: undefined,
}
const data = reactive<PageData<SpecialSchemeForm, SpecialSchemeQuery>>({
form: {...initFormData},
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
@ -131,20 +114,11 @@ const data = reactive<PageData<SpecialSchemeForm, SpecialSchemeQuery>>({
fileName: undefined,
originalName: undefined,
status: undefined,
params: {
}
},
rules: {
versionNumber: [
{ required: true, message: "版本号不能为空", trigger: "blur" }
],
fileName: [
{ required: true, message: "文件名称不能为空", trigger: "blur" }
],
params: {}
}
});
const { queryParams, form, rules } = toRefs(data);
const { queryParams } = toRefs(data);
/** 查询专项方案管理列表 */
const getList = async () => {
@ -153,96 +127,66 @@ const getList = async () => {
specialSchemeList.value = res.rows;
total.value = res.total;
loading.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
specialSchemeFormRef.value?.resetFields();
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: SpecialSchemeVO[]) => {
ids.value = selection.map(item => item.id);
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
};
/** 新增按钮操作 */
const handleAdd = () => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/specialScheme/indexEdit`,
path: `/design-management/specialScheme/indexEdit`,
query: {
type: 'add'
}
});
}
};
/** 修改按钮操作 */
const handleUpdate = async (row?: SpecialSchemeVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/specialScheme/indexEdit`,
path: `/design-management/specialScheme/indexEdit`,
query: {
id: row.id,
id: row.id,
type: 'update'
}
});
}
/** 提交按钮 */
const submitForm = () => {
specialSchemeFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateSpecialScheme(form.value).finally(() => buttonLoading.value = false);
} else {
await addSpecialScheme(form.value).finally(() => buttonLoading.value = false);
}
proxy?.$modal.msgSuccess("操作成功");
dialog.visible = false;
await getList();
}
});
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: SpecialSchemeVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除专项方案管理编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
await proxy?.$modal.confirm('是否确认删除专项方案管理编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delSpecialScheme(_ids);
proxy?.$modal.msgSuccess("删除成功");
proxy?.$modal.msgSuccess('删除成功');
await getList();
}
};
const handleView = (row) => {
var url= row.file.url
var url = row.file.url;
window.open(url, '_blank');
};
/** 查看按钮操作 */
const handleViewInfo = (row) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/specialScheme/indexEdit`,
path: `/design-management/specialScheme/indexEdit`,
query: {
id: row.id,
type: 'view'
@ -264,4 +208,16 @@ const handleCancelProcessApply = async (id: string) => {
onMounted(() => {
getList();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>

View File

@ -1,143 +1,155 @@
<template>
<el-tabs v-model="activeName" class="demo-tabs p5" @tab-click="handleCheckMian">
<el-tab-pane label="资料" name="first">
<div class="profile_engin">
<div class="box_info">
<div class="tree_left1" id="tree_left1">
<div class="file_upload check_select">
<div class="box_btn">
<file-upload
v-model="state.paramsQuery.file"
:limit="100"
:uploadUrl="uploadUrl"
:params="uploadParams"
:on-upload-success="uploadFile"
:fileType="[]"
<div>
<el-tabs v-model="activeName" class="demo-tabs p5" @tab-click="handleCheckMian">
<el-tab-pane label="资料" name="first">
<div class="profile_engin">
<div class="box_info">
<div class="tree_left1" id="tree_left1">
<div class="file_upload check_select">
<div class="box_btn">
<file-upload
v-model="state.paramsQuery.file"
:limit="100"
:uploadUrl="uploadUrl"
:params="uploadParams"
:on-upload-success="uploadFile"
:fileType="[]"
>
<el-button type="primary" style="float: left" :disabled="!state.parentPid">
<el-icon size="small"><Plus /></el-icon>上传文件
</el-button>
</file-upload>
</div>
<el-button type="primary" :disabled="!state.parentPid" @click="onExport"
><el-icon><Download /></el-icon>下载</el-button
>
<el-button type="primary" @click="onBook"
><el-icon><View /></el-icon>查看全项目文件</el-button
>
<el-button type="primary" style="float: left" :disabled="!state.parentPid">
<el-icon size="small"><Plus /></el-icon>上传文件
</el-button>
</file-upload>
</div>
<el-button type="primary" :disabled="!state.parentPid" @click="onExport"
><el-icon><Download /></el-icon>下载</el-button
<div class="file_upload check_select">
<el-input class="input_left" v-model="filterText" size="small" placeholder="请输入文件名称" />
</div>
<el-tree
ref="treeRef"
highlight-current
:default-expand-all="state.checked"
:filter-node-method="filterFolder"
:data="state.treeList"
node-key="id"
accordion
:expand-on-click-node="false"
@node-click="handleNodeClick"
:current-node-key="state.selectedNodeId"
>
<el-button type="primary" @click="onBook"
><el-icon><View /></el-icon>查看全项目文件</el-button
>
</div>
<div class="file_upload check_select">
<el-input class="input_left" v-model="filterText" size="small" placeholder="请输入文件名称" />
</div>
<el-tree
ref="treeRef"
highlight-current
:default-expand-all="state.checked"
:filter-node-method="filterFolder"
:data="state.treeList"
node-key="id"
accordion
:expand-on-click-node="false"
@node-click="handleNodeClick"
:current-node-key="state.selectedNodeId"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<el-icon color="#f1a81a"><FolderOpened /></el-icon>
<span>{{ node.label }}</span>
</span>
</template>
</el-tree>
<div class="resize-handle resize-handle-right right"></div>
</div>
<div class="list_right" id="list_right1">
<div>
<el-form :model="state.paramsQuery" ref="queryRef" :inline="true" label-width="100px">
<el-row>
<el-col :span="7" class="colBlock">
<el-form-item label="文件名称" prop="fileName">
<el-input
v-model="state.paramsQuery.fileName"
placeholder="请输入文件名称"
clearable
@keyup.enter.native="getdocumentDataList"
/>
</el-form-item>
</el-col>
<el-col :span="6" class="m-l10">
<el-form-item>
<el-button type="primary" @click="searchInfo"
><el-icon><Search /></el-icon>搜索</el-button
>
<el-button @click="resetQuery"
><el-icon><Refresh /></el-icon>重置</el-button
>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-table v-loading="state.loading" :data="state.infoList" height="67vh" border>
<el-table-column label="序号" align="center" type="index" min-width="50px" />
<el-table-column label="文件名称" align="center" prop="fileName"></el-table-column>
<el-table-column label="文件类型" align="center" prop="fileSuffix" width="100px" />
<el-table-column label="上传时间" align="center" prop="createTime"> </el-table-column>
<el-table-column label="操作" align="center" width="300">
<template #default="scope">
<el-button type="primary" link @click="handleView(scope.row)" v-if="acceptType.includes(scope.row.fileSuffix)"
><el-icon><View /></el-icon>查看</el-button
>
<el-button type="primary" v-if="state.wordType.includes(scope.row.fileSuffix)" link @click="updataView(scope.row)"
><el-icon><EditPen /></el-icon>修改文件</el-button
>
<el-button type="primary" link @click="onExportView(scope.row)"
><el-icon><Download /></el-icon>下载</el-button
>
<el-button type="success" link @click="updateName(scope.row)"
><el-icon><EditPen /></el-icon>修改名称</el-button
>
<el-button type="danger" link @click="handleDelete(scope.row)"
><el-icon><DeleteFilled /></el-icon>删除</el-button
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<el-icon color="#f1a81a"><FolderOpened /></el-icon>
<span>{{ node.label }}</span>
</span>
</template>
</el-table-column>
</el-table>
<pagination
:total="state.total"
v-model:page="state.paramsQuery.pageNum"
v-model:limit="state.paramsQuery.pageSize"
@pagination="getdocumentDataList"
/>
</el-tree>
<div class="resize-handle resize-handle-right right"></div>
</div>
<div class="list_right" id="list_right1">
<div>
<el-form :model="state.paramsQuery" ref="queryRef" :inline="true" label-width="100px">
<el-row>
<el-col :span="7" class="colBlock">
<el-form-item label="文件名称" prop="fileName">
<el-input
v-model="state.paramsQuery.fileName"
placeholder="请输入文件名称"
clearable
@keyup.enter.native="getdocumentDataList"
/>
</el-form-item>
</el-col>
<el-col :span="6" class="m-l10">
<el-form-item>
<el-button type="primary" @click="searchInfo"
><el-icon><Search /></el-icon>搜索</el-button
>
<el-button @click="resetQuery"
><el-icon><Refresh /></el-icon>重置</el-button
>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-table v-loading="state.loading" :data="state.infoList" height="67vh" border>
<el-table-column label="序号" align="center" type="index" min-width="50px" />
<el-table-column label="文件名称" align="center" prop="fileName"></el-table-column>
<el-table-column label="文件类型" align="center" prop="fileSuffix" width="100px" />
<el-table-column label="流程状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime"> </el-table-column>
<el-table-column label="操作" align="center" width="300">
<template #default="scope">
<el-button type="primary" link @click="handleApproval(scope.row)"
><el-icon><Plus /></el-icon>审批</el-button
>
<el-button type="primary" link @click="handleViewApproval(scope.row)"
><el-icon><View /></el-icon>流程查看</el-button
>
<el-button type="primary" link @click="handleView(scope.row)" v-if="acceptType.includes(scope.row.fileSuffix)"
><el-icon><View /></el-icon>查看</el-button
>
<el-button type="primary" v-if="state.wordType.includes(scope.row.fileSuffix)" link @click="updataView(scope.row)"
><el-icon><EditPen /></el-icon>修改文件</el-button
>
<el-button type="primary" link @click="onExportView(scope.row)"
><el-icon><Download /></el-icon>下载</el-button
>
<el-button type="success" link @click="updateName(scope.row)"
><el-icon><EditPen /></el-icon>修改名称</el-button
>
<el-button type="danger" link @click="handleDelete(scope.row)"
><el-icon><DeleteFilled /></el-icon>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
:total="state.total"
v-model:page="state.paramsQuery.pageNum"
v-model:limit="state.paramsQuery.pageSize"
@pagination="getdocumentDataList"
/>
</div>
</div>
<documentsDeailsVue ref="documentDetailRef" v-if="state.showDocumentDetail" @onClose="onClose"></documentsDeailsVue>
<documentsEdit ref="documentDataEditRef" v-if="state.showdocumentDataEdit" @onClose="onCloseEdit"></documentsEdit>
<bookFile ref="bookFileRef" @onExportView="onExportView" @onBook="handleView" @onExport="onExport"></bookFile>
<el-dialog draggable title="上传文件" v-model="uploadFileder" width="30%">
<file-upload v-model="state.paramsQuery.file"></file-upload>
<template #footer>
<span>
<el-button @click="uploadFileder = false">取消</el-button>
<el-button type="primary" @click="subMitUpload">确定</el-button>
</span>
</template>
</el-dialog>
</div>
<documentsDeailsVue ref="documentDetailRef" v-if="state.showDocumentDetail" @onClose="onClose"></documentsDeailsVue>
<documentsEdit ref="documentDataEditRef" v-if="state.showdocumentDataEdit" @onClose="onCloseEdit"></documentsEdit>
<bookFile ref="bookFileRef" @onExportView="onExportView" @onBook="handleView" @onExport="onExport"></bookFile>
<el-dialog draggable title="上传文件" v-model="uploadFileder" width="30%">
<file-upload v-model="state.paramsQuery.file"></file-upload>
<template #footer>
<span>
<el-button @click="uploadFileder = false">取消</el-button>
<el-button type="primary" @click="subMitUpload">确定</el-button>
</span>
</template>
</el-dialog>
</div>
<el-image-viewer
ref="imageRef"
style="width: 100%; height: 100%"
:url-list="[imgUrl]"
v-if="imgUrl"
show-progress
fit="cover"
@close="imgUrl = ''"
/>
</el-tab-pane>
<el-tab-pane label="回收站" name="second">
<RecyclingStation ref="recylingRef"></RecyclingStation>
</el-tab-pane>
</el-tabs>
<el-image-viewer
ref="imageRef"
style="width: 100%; height: 100%"
:url-list="[imgUrl]"
v-if="imgUrl"
show-progress
fit="cover"
@close="imgUrl = ''"
/>
</el-tab-pane>
<el-tab-pane label="回收站" name="second">
<RecyclingStation ref="recylingRef"></RecyclingStation>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup name="KnowledgeDocument" lang="ts">
@ -153,7 +165,6 @@ import {
import documentsEdit from './component/documentsEdit.vue';
import documentsDeailsVue from './component/documentsDeails.vue';
import RecyclingStation from './component/recyclingStation.vue';
import { useUserStoreHook } from '@/store/modules/user';
import bookFile from './component/bookFile.vue';
const activeName = ref('first');
@ -162,6 +173,7 @@ const { proxy } = getCurrentInstance() as any;
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const uploadUrl = computed(() => {
return `/design/technicalStandard/file`;
});
@ -171,7 +183,6 @@ const uploadParams = computed(() => {
projectId: state.projectId
};
});
const imgUrl = ref<string>('');
const filterText = ref('');
const treeRef = ref();
@ -289,7 +300,7 @@ const setInfo = (arr) => {
const handleNodeClick = (row) => {
state.parentRow = row;
state.parentPid = row.parentId;
console.log('🚀 ~ handleNodeClick ~ state.parentPid:', state.parentPid);
console.log(row);
state.parentName = row.label;
state.paramsQuery.folderId = row.id;
getdocumentDataList();
@ -443,6 +454,44 @@ const onBook = () => {
onMounted(() => {
gettreeStructureData();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
state.projectId = nid;
state.paramsQuery.projectId = nid;
if (activeName.value === 'first') {
gettreeStructureData();
} else {
// 回收站
recylingRef.value.getDocumentDataList();
}
}
);
onUnmounted(() => {
listeningProject();
});
const handleApproval = (row) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/design-management/technicalStandard/indexEdit`,
query: {
id: row.id,
type: 'update'
}
});
};
const handleViewApproval = (row) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/design-management/technicalStandard/indexEdit`,
query: {
id: row.id,
type: 'view'
}
});
};
</script>
<style lang="scss" scoped>
@ -473,7 +522,7 @@ onMounted(() => {
width: 100%;
}
.tree_left1 {
width: 30%;
width: 20%;
background-color: #fff;
border: 1px solid #dddddd;
border-radius: 6px;
@ -536,7 +585,7 @@ onMounted(() => {
}
}
.list_right {
width: 69.5%;
width: 79.5%;
background: white;
border: 1px solid #ededed;
padding: 10px;

View File

@ -0,0 +1,356 @@
<template>
<div class="p-4 bg-gray-50">
<div class="max-w-4xl mx-auto">
<!-- 顶部按钮区域 -->
<el-card class="mb-4 rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.id"
:status="form.status"
:pageType="routeParams.type"
/>
</el-card>
<!-- 表单区域 -->
<el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden">
<div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-800">设计原则</h3>
</div>
<div class="p-6">
<el-form
ref="leaveFormRef"
v-loading="loading"
:disabled="routeParams.type === 'view'"
:model="form"
:rules="rules"
label-width="100px"
class="space-y-4"
>
<div class="grid grid-cols-1 gap-4">
<el-row>
<el-col :span="12">
<el-form-item label="文件名称" prop="formNo">
<el-input disabled v-model="form.fileName" placeholder="请输入文件名称" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="文件" prop="formNo">
<div style="display: flex;">
<span style="color: rgb(50, 142, 248);" >{{ form.originalName }}</span>
<!-- <el-button type="primary" link @click="handleView(scope.row)"
><el-icon><View /></el-icon>查看</el-button
> -->
</div>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</div>
</el-card>
<!-- 提交组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<approvalRecord ref="approvalRecordRef"></approvalRecord>
<!-- 流程选择对话框 -->
<el-dialog
draggable
v-model="dialogVisible.visible"
:title="dialogVisible.title"
:before-close="handleClose"
width="500"
class="rounded-lg shadow-lg"
>
<div class="p-4">
<p class="text-gray-600 mb-4">请选择要启动的流程</p>
<el-select v-model="flowCode" placeholder="请选择流程" style="width: 100%">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<template #footer>
<div class="dialog-footer p-4 border-t border-gray-100 flex justify-end space-x-3">
<el-button @click="handleClose" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors"
>取消</el-button
>
<el-button type="primary" @click="submitFlow()" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors"
>确认</el-button
>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup name="Leave" lang="ts">
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
import { startWorkFlow } from '@/api/workflow/task';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { useUserStoreHook } from '@/store/modules/user';
const { design_change_reason_type } = toRefs<any>(proxy?.useDict('design_change_reason_type'));
import { getKnowledgeDocument } from '@/api/design/technicalStandard';
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const buttonLoading = ref(false);
const loading = ref(true);
//路由参数
const routeParams = ref<Record<string, any>>({});
const flowCode = ref<string>('');
const status = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const flowCodeOptions = [
{
value: currentProject.value?.id + '_principletechnical',
label: '设计原则审批'
},
{
value: currentProject.value?.id + '_requirementstechnica',
label: '业主需求清单审批'
}
];
const leaveFormRef = ref<ElFormInstance>();
const dialog = reactive({
visible: false,
title: '',
isEdit: false
});
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
});
const taskVariables = ref<Record<string, any>>({});
const initFormData = {
id: undefined,
fileName:undefined,
fileUrl:undefined,
status:undefined,
originalName:undefined
};
const data = reactive({
form: { ...initFormData },
rules: {}
});
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
const { form, rules } = toRefs(data);
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
leaveFormRef.value?.resetFields();
};
/** 获取详情 */
const getInfo = () => {
loading.value = true;
buttonLoading.value = false;
nextTick(async () => {
const res = await getKnowledgeDocument(routeParams.value.id);
Object.assign(form.value, res.data);
loading.value = false;
buttonLoading.value = false;
});
};
/** 提交按钮 */
const submitForm = (status1: string) => {
status.value = status1;
submit(status.value,form.value);
};
const submitFlow = async () => {
handleStartWorkFlow(form.value);
dialogVisible.visible = false;
};
//提交申请
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = data.id;
//流程变量
taskVariables.value = {
// leave4/5 使用的流程变量
userList: ['1', '3', '4']
};
submitFormData.value.variables = taskVariables.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
buttonLoading.value = false;
submitVerifyRef.value.openDialog(resp.data.taskId);
}
} finally {
buttonLoading.value = false;
}
};
//审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(form.value.id);
};
//提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
//审批
const approvalVerifyOpen = async () => {
submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
// 图纸上传成功之后 开始提交
const submit = async (status, data) => {
form.value = data;
if (status === 'draft') {
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
}
//说明启动过先随意穿个参数
if (flowCode.value === '' || flowCode.value === null) {
flowCode.value = 'xx';
}
await handleStartWorkFlow(data);
}
};
onMounted(() => {
nextTick(async () => {
routeParams.value = proxy.$route.query;
reset();
loading.value = false;
if (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval') {
getInfo();
}
});
});
</script>
<style scoped lang="scss">
/* 全局样式 */
:root {
--primary: #409eff;
--primary-light: #66b1ff;
--primary-dark: #3a8ee6;
--success: #67c23a;
--warning: #e6a23c;
--danger: #f56c6c;
--info: #909399;
}
/* 表单样式优化 */
.el-form-item {
.el-form-item__label {
color: #606266;
font-weight: 500;
}
.el-input__inner,
.el-select .el-input__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
.el-textarea__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
}
/* 按钮样式优化 */
.el-button {
border-radius: 4px;
transition: all 0.2s;
&.is-primary {
background-color: var(--primary);
border-color: var(--primary);
&:hover {
background-color: var(--primary-light);
border-color: var(--primary-light);
}
&:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
}
&.is-text {
color: var(--primary);
&:hover {
color: var(--primary-light);
background-color: rgba(64, 158, 255, 0.05);
}
}
}
/* 卡片样式优化 */
.el-card {
transition: all 0.3s ease;
&:hover {
/* transform: translateY(-2px); */
}
}
/* 对话框样式优化 */
.el-dialog {
.el-dialog__header {
background-color: #f5f7fa;
border-bottom: 1px solid #ebeef5;
padding: 15px 20px;
}
.el-dialog__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-dialog__footer {
padding: 15px 20px;
border-top: 1px solid #ebeef5;
}
}
</style>

View File

@ -430,6 +430,8 @@ export default {
return new Promise((resovle, reject) => {
if (this.info) {
const { cameraIndex, gateway, sn, videoIndex } = this.info;
console.log(this.info);
liveStart({
cameraIndex,
definition: 0,

View File

@ -0,0 +1,266 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<!-- <el-form-item label="配置名称" prop="configName">
<el-input v-model="queryParams.configName" placeholder="请输入配置名称" clearable @keyup.enter="handleQuery" />
</el-form-item> -->
<el-form-item label="配置地址" prop="configUrl">
<el-input v-model="queryParams.configUrl" placeholder="请输入配置地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['drone:droneConfig:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['drone:droneConfig:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['drone:droneConfig:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['drone:droneConfig:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="droneConfigList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" width="50" />
<!-- <el-table-column label="配置名称" align="center" prop="configName" /> -->
<el-table-column label="rtmp端口" align="center" prop="rtmpPort" />
<el-table-column label="rtcp端口" align="center" prop="rtcPort" />
<el-table-column label="rtmp服务地址" align="center" prop="dockSocketUrl" />
<el-table-column label="ai识别服务地址" align="center" prop="aiUrl" />
<el-table-column label="srs服务地址" align="center" prop="srsUrl" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['drone:droneConfig:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['drone:droneConfig:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改无人机配置对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="droneConfigFormRef" :model="form" :rules="rules" label-width="150px">
<el-form-item label="无人机服务地址" prop="dockSocketUrl">
<el-input v-model="form.dockSocketUrl" placeholder="请输入无人机服务地址" />
</el-form-item>
<el-form-item label="ai识别服务地址" prop="aiUrl">
<el-input v-model="form.aiUrl" placeholder="请输入ai识别服务地址" />
</el-form-item>
<el-form-item label="srs服务器地址" prop="srsUrl">
<el-input v-model="form.srsUrl" placeholder="请输入srs服务器地址" />
</el-form-item>
<el-form-item label="srs(rtmp服务端口)" prop="rtmpPort">
<el-input v-model="form.rtmpPort" placeholder="请输入srs(rtmp服务端口)" />
</el-form-item>
<el-form-item label="srs(webrtc服务端口)" prop="rtcPort">
<el-input v-model="form.rtcPort" placeholder="请输入srs(webrtc服务端口)" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="DroneConfig" lang="ts">
import { listDroneConfig, getDroneConfig, delDroneConfig, addDroneConfig, updateDroneConfig } from '@/api/drone/droneConfig';
import { DroneConfigVO, DroneConfigQuery, DroneConfigForm } from '@/api/drone/droneConfig/types';
import { useUserStoreHook } from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const droneConfigList = ref<DroneConfigVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const droneConfigFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DroneConfigForm = {
id: undefined,
projectId: currentProject.value.id,
configName: undefined,
configUrl: undefined,
dockSocketUrl: undefined,
aiUrl: undefined,
srsUrl: undefined,
rtmpPort: undefined,
rtcPort: undefined,
remark: undefined
};
const data = reactive<PageData<DroneConfigForm, DroneConfigQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: currentProject.value.id,
configName: undefined,
configUrl: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
projectId: [{ required: true, message: '项目id不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询无人机配置列表 */
const getList = async () => {
loading.value = true;
const res = await listDroneConfig(queryParams.value);
droneConfigList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
droneConfigFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DroneConfigVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加无人机配置';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DroneConfigVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getDroneConfig(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改无人机配置';
};
/** 提交按钮 */
const submitForm = () => {
droneConfigFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateDroneConfig(form.value).finally(() => (buttonLoading.value = false));
} else {
await addDroneConfig(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DroneConfigVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除无人机配置编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delDroneConfig(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'drone/droneConfig/export',
{
...queryParams.value
},
`droneConfig_${new Date().getTime()}.xlsx`
);
};
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
onMounted(() => {
getList();
});
</script>

View File

@ -227,8 +227,10 @@
</el-form-item>
</el-col>
<el-col :span="24">
<span style="color:#ff0000ab;margin-bottom: 10px;display: block;">注意请上传doc/xls/ppt/txt/pdf/png/jpg/jpeg/zip格式文件</span>
</el-col><el-col :span="24">
<span style="color: #ff0000ab; margin-bottom: 10px; display: block"
>注意请上传doc/xls/ppt/txt/pdf/png/jpg/jpeg/zip格式文件</span
> </el-col
><el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
@ -446,6 +448,19 @@ const handleView = (row) => {
onMounted(() => {
getList();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>
<style scoped lang="scss">
.detail {

View File

@ -460,6 +460,19 @@ const handleView = (row) => {
onMounted(() => {
getList();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>
<style scoped lang="scss">
.detail {

View File

@ -447,6 +447,19 @@ const handleView = (row) => {
onMounted(() => {
getList();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>
<style scoped lang="scss">
.detail {

View File

@ -451,6 +451,19 @@ const handleView = (row) => {
onMounted(() => {
getList();
});
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>
<style scoped lang="scss">
.detail {

View File

@ -288,12 +288,16 @@ const getList = async () => {
}
}
loading.value = true;
console.log(queryParams.value);
loading.value = true;
const res = await listProgressCategory(queryParams.value);
const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'pid');
if (data) {
progressCategoryList.value = data;
try {
const res = await listProgressCategory(queryParams.value);
const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'pid');
if (data) {
progressCategoryList.value = data;
}
} finally {
// 不管成功或失败,最后都设置为 false
loading.value = false;
}
};

View File

@ -454,12 +454,6 @@ const initOLMap = () => {
})
});
map.on('click', (e: any) => {
var coordinate = e.coordinate;
// 将投影坐标转换为经纬度坐标
var lonLatCoordinate = toLonLat(coordinate);
// 输出转换后的经纬度坐标
console.log('经纬度坐标:', lonLatCoordinate);
const zoom = map.getView().getZoom();
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
map.forEachFeatureAtPixel(e.pixel, (feature: Feature) => {
@ -539,38 +533,6 @@ const initOLMap = () => {
});
};
// 你已有的 imageExtent 是 [minX, minY, maxX, maxY]
const createExtentBorderLayer = (extent: number[]) => {
// 构造矩形坐标,闭合成环,顺序可以是顺时针或逆时针
const coords = [
[
[extent[0], extent[1]],
[extent[0], extent[3]],
[extent[2], extent[3]],
[extent[2], extent[1]],
[extent[0], extent[1]]
]
];
const polygonFeature = new Feature(new Polygon(coords));
polygonFeature.setStyle(
new Style({
stroke: new Stroke({
color: 'red', // 你想要的边框颜色
width: 3 // 线宽
}),
fill: null // 不填充,纯边框
})
);
return new VectorLayer({
source: new VectorSource({
features: [polygonFeature]
})
});
};
const highlightStyle = (name, scale) => {
return new Style({
stroke: new Stroke({
@ -721,7 +683,7 @@ const addPointToMap = (features: Array<any>) => {
};
//选中几何图形
const toggleFeatureHighlight = (feature: Feature, addIfNotExist = true) => {
const toggleFeatureHighlight = (feature: Feature) => {
const zoom = map.getView().getZoom();
const scale = Math.max(zoom / 10, 1);
if (feature.get('status') === '2') return;
@ -730,41 +692,35 @@ const toggleFeatureHighlight = (feature: Feature, addIfNotExist = true) => {
const isHighlighted = feature.get('highlighted') === true;
if (isHighlighted) {
// 如果是反选或 toggle 模式,允许取消
feature.setStyle(defaultStyle(feature.get('name'), scale));
feature.set('highlighted', false);
const id = feature.get('id');
const idx = submitForm.value.finishedDetailIdList.indexOf(id);
if (idx > -1) submitForm.value.finishedDetailIdList.splice(idx, 1);
} else if (addIfNotExist) {
} else {
feature.setStyle(highlightStyle(feature.get('name'), scale));
feature.set('highlighted', true);
const id = feature.get('id');
if (!submitForm.value.finishedDetailIdList.includes(id)) submitForm.value.finishedDetailIdList.push(id);
if (!submitForm.value.finishedDetailIdList.includes(id)) {
submitForm.value.finishedDetailIdList.push(id);
}
}
};
onMounted(async () => {
// 地图初始化
initOLMap();
// geoTiffLoading.value = false;
map.addLayer(sharedLayer);
selector.value = new LassoSelector(map, sharedSource, (features, isInvert = false) => {
features.forEach((feature) => {
if (isInvert) {
// Shift + 左键 -> 只执行取消选中
if (feature.get('highlighted') === true) {
toggleFeatureHighlight(feature, false); // 取消选中addIfNotExist = false
}
} else {
// 普通左键 -> 只执行选中
if (feature.get('highlighted') !== true) {
toggleFeatureHighlight(feature, true); // 选中addIfNotExist = true
}
}
console.log(feature.get('status'));
toggleFeatureHighlight(feature);
});
});
enableMiddleMousePan(map);
// enableMiddleMousePan(map);
getList();
creatPoint(fromLonLat([107.13149145799198, 23.804125705140834]), 'Point', '1', '测试点1', '1');
});

View File

@ -1,156 +0,0 @@
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
import TileGrid from 'ol/tilegrid/TileGrid';
import { fromLonLat, transform, transformExtent } from 'ol/proj';
import { fromUrl } from 'geotiff';
import gcoord from 'gcoord';
import ImageLayer from 'ol/layer/Image';
import Static from 'ol/source/ImageStatic';
import { Feature } from 'ol';
import { Polygon } from 'ol/geom';
import { Stroke, Style } from 'ol/style';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
const mapContainer = ref<HTMLDivElement | null>(null);
// 您提供的经纬度范围EPSG:4326
const extent4326 = [107.13149481208748, 23.80411597354268, 107.13487254421389, 23.80801427852998];
// 计算中心点(经纬度)
const centerLon = (extent4326[0] + extent4326[2]) / 2;
const centerLat = (extent4326[1] + extent4326[3]) / 2;
// 转换 extent 到 EPSG:3857
const extent3857 = transformExtent(extent4326, 'EPSG:4326', 'EPSG:3857');
// 生成 resolutions256像素瓦片
const resolutions = Array.from({ length: 20 }, (_, z) => 156543.03392804097 / Math.pow(2, z));
onMounted(async () => {
// const tiff = await fromUrl('/image/clean_rgba_cleaned.tif');
// const image = await tiff.getImage();
// const width = image.getWidth();
// const height = image.getHeight();
// const bbox = image.getBoundingBox(); // [minX, minY, maxX, maxY]
// console.log('bbox', bbox);
// const rasters = await image.readRasters({ interleave: true });
// // 创建 Canvas
// const canvas = document.createElement('canvas');
// canvas.width = width;
// canvas.height = height;
// const ctx = canvas.getContext('2d')!;
// const imageData: any = ctx.createImageData(width, height);
// // 设置 RGBA 数据
// imageData.data.set(rasters); // ✅ 完整设置,不用手动循环
// ctx.putImageData(imageData, 0, 0);
// // 将 canvas 转成 Data URL 用作图层 source
// const imageUrl = canvas.toDataURL();
// // 转换为 WGS84 经纬度
// const minLonLat = transform([bbox[0], bbox[1]], 'EPSG:32648', 'EPSG:4326');
// const maxLonLat = transform([bbox[2], bbox[3]], 'EPSG:32648', 'EPSG:4326');
// console.log('minLonLat', minLonLat);
// console.log('maxLonLat', maxLonLat);
// // 转为 GCJ02高德地图坐标系
// const gcjMin = gcoord.transform(minLonLat as [number, number, number], gcoord.WGS84, gcoord.GCJ02);
// const gcjMax = gcoord.transform(maxLonLat as [number, number, number], gcoord.WGS84, gcoord.GCJ02);
// // 再转 EPSG:3857 供 OpenLayers 使用
// const minXY = fromLonLat(gcjMin);
// const maxXY = fromLonLat(gcjMax);
let imageExtent = [11926280.148303444, 2729254.667731837, 11926657.474014785, 2729714.281185133];
console.log('imageExtent', imageExtent);
console.log('extent3857', extent3857);
if (!mapContainer.value) return;
const tileGrid = new TileGrid({
extent: imageExtent,
origin: [-20037508.342789244, 20037508.342789244], // Web Mercator 左上角
resolutions,
tileSize: 256
});
// 高德卫星图图层 (EPSG:3857)
const gaodeLayer = new TileLayer({
source: new XYZ({
url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
maxZoom: 19,
wrapX: true
}),
opacity: 1
});
// 自定义瓦片图层EPSG:3857
const customLayer = new TileLayer({
source: new XYZ({
url: 'http://192.168.110.2:8000/api/projects/3/tasks/c2e3227f-343f-48b1-88c0-1432d6eab33f/orthophoto/tiles/{z}/{x}/{y}'
// extent: imageExtent
})
});
const layer = customLayer; // eg: TileLayer<XYZ>
const source = layer.getSource();
const projection = source?.getProjection();
console.log('图层坐标系:', projection?.getCode());
const borderLayer = createExtentBorderLayer(imageExtent);
const testLayer = createExtentBorderLayer([
...fromLonLat([107.13149481208748, 23.80411597354268]),
...fromLonLat([107.13487254421389, 23.80801427852998])
]);
// 创建地图
const map = new Map({
target: mapContainer.value,
layers: [gaodeLayer, customLayer, borderLayer, testLayer],
view: new View({
projection: 'EPSG:3857',
center: fromLonLat([centerLon, centerLat]),
zoom: 16,
minZoom: 10
// extent: extent3857 // 限制视图范围
})
});
});
const createExtentBorderLayer = (extent: number[]) => {
// 构造矩形坐标,闭合成环,顺序可以是顺时针或逆时针
const coords = [
[
[extent[0], extent[1]],
[extent[0], extent[3]],
[extent[2], extent[3]],
[extent[2], extent[1]],
[extent[0], extent[1]]
]
];
const polygonFeature = new Feature(new Polygon(coords));
polygonFeature.setStyle(
new Style({
stroke: new Stroke({
color: 'red', // 你想要的边框颜色
width: 3 // 线宽
}),
fill: null // 不填充,纯边框
})
);
return new VectorLayer({
source: new VectorSource({
features: [polygonFeature]
})
});
};
</script>
<style scoped>
.map-container {
width: 100%;
height: 100vh;
}
</style>

View File

@ -7,7 +7,7 @@
<el-form-item label="人员姓名" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入人员姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="分包公司" prop="contractorId">
<el-form-item label="分包公司" prop="contractorId" v-hasPermi="['contractor:contractor:list']">
<el-select v-model="queryParams.contractorId" clearable placeholder="全部">
<el-option v-for="item in contractorOpt" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
@ -40,13 +40,9 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['project:constructionUser:add']">新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['project:constructionUser:edit']">
修改
</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['contractor:constructionUser:add']">新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
@ -54,13 +50,15 @@
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['project:constructionUser:remove']"
v-hasPermi="['contractor:constructionUser:remove']"
>
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['project:constructionUser:export']">导出 </el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['contractor:constructionUser:export']"
>导出
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Edit" :disabled="multiple" @click="statusDialog = true">用户状态编辑 </el-button>
@ -82,18 +80,20 @@
<el-button type="success" plain>员工资料 </el-button>
</el-col>
<el-col :span="1.5" v-show="informationStatus">
<el-button type="primary" plain icon="Edit" @click="downloadTemplate">下载资料模板 </el-button>
<el-button type="primary" plain icon="Edit" @click="downloadTemplate" v-hasPermi="['contractor:constructionUserFile:download']"
>下载资料模板
</el-button>
</el-col>
<el-col :span="1.5" v-show="informationStatus">
<file-upload
v-model="filePath"
isImportInfo
:isShowTip="false"
uploadUrl="/project/constructionUserFile/upload/zip"
uploadUrl="/contractor/constructionUserFile/upload/zip"
:limit="1"
:file-size="50"
>
<el-button type="warning" plain icon="Edit">导入员工资料 </el-button>
<el-button type="warning" plain icon="Edit" v-hasPermi="['contractor:constructionUserFile:upload']">导入员工资料 </el-button>
</file-upload>
</el-col>
</el-row>
@ -165,20 +165,28 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" min-width="300">
<template #default="scope">
<el-space wrap>
<el-button link type="primary" icon="View" @click="handleShowDrawer(scope.row)" v-hasPermi="['project:constructionUser:query']">
<el-button link type="primary" icon="View" @click="handleShowDrawer(scope.row)" v-hasPermi="['contractor:constructionUser:query']">
详情
</el-button>
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:constructionUser:edit']">
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['contractor:constructionUser:edit']">
修改
</el-button>
<el-button link type="warning" icon="Female" @click="handlePlayCard(scope.row)"> 打卡 </el-button>
<el-button link type="danger" icon="Avatar" @click="handleJoinBlacklist(scope.row)" v-hasPermi="['project:constructionBlacklist:add']">
<el-button
link
type="danger"
icon="Avatar"
@click="handleJoinBlacklist(scope.row)"
v-hasPermi="['contractor:constructionBlacklist:add']"
>
黑名单
</el-button>
<!-- <el-button link type="primary" icon="Switch" @click="handleToggle(scope.row)"> 切换人脸 </el-button> -->
<el-button link type="primary" icon="Switch" @click="handleChange(scope.row)"> 人员迁移 </el-button>
<el-button link type="primary" icon="Switch" @click="handleChange(scope.row)" v-hasPermi="['contractor:constructionUser:migration']">
人员迁移
</el-button>
<el-button link type="primary" icon="ChatLineSquare" @click="handleExit(scope.row)"> 入退场记录 </el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['project:constructionUser:remove']">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['contractor:constructionUser:remove']">
删除
</el-button>
<el-tooltip content="红点:部分上传,绿点:已上传,无点:未上传" placement="right" effect="dark">
@ -194,7 +202,7 @@
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改施工人员对话框 -->
<el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="930px" append-to-body>
<el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="930px" append-to-body>
<el-form ref="constructionUserFormRef" :model="form" :rules="rules" label-width="130px" :inline="true">
<div class="block_box">
<div class="msg">用户信息</div>
@ -597,7 +605,7 @@ const initFormData: ConstructionUserForm = {
salary: undefined,
remark: undefined
};
const data = reactive<PageData<ConstructionUserForm, ConstructionUserQuery>>({
const data = reactive({
form: { ...initFormData },
queryParams: {
pageNum: 1,
@ -606,6 +614,7 @@ const data = reactive<PageData<ConstructionUserForm, ConstructionUserQuery>>({
nickName: undefined,
userName: undefined,
projectId: currentProject.value.id,
notUserRole: 1,
contractorId: undefined,
teamId: undefined,
status: undefined,
@ -780,6 +789,7 @@ const getContractorList = async () => {
label: contractor.name
}));
loading.value = false;
handleQuery();
};
const handleMonth = async (e: any) => {
@ -839,6 +849,7 @@ const reset = () => {
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
if (contractorOpt.value.length == 1) queryParams.value.contractorId = contractorOpt.value[0].value;
getList();
};
@ -1063,7 +1074,7 @@ const listeningProject = watch(
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
getContractorList();
}
);
@ -1072,7 +1083,6 @@ onUnmounted(() => {
});
onMounted(() => {
getList();
getContractorList();
});
</script>

View File

@ -25,20 +25,20 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['project:contractor:add']"> 新增 </el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['contractor:contractor:add']"> 新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['project:contractor:edit']"
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['contractor:contractor:edit']"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['project:contractor:remove']"
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['contractor:contractor:remove']"
>删除
</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['project:contractor:export']">导出 </el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['contractor:contractor:export']">导出 </el-button>
</el-col> -->
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -59,12 +59,14 @@
<el-table-column label="管理人联系电话" align="center" prop="custodianPhone" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-space wrap>
<el-button link type="primary" icon="View" @click="handleContractorFile(scope.row)">文件</el-button>
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:contractor:edit']">修改 </el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['project:contractor:remove']">
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['contractor:contractor:edit']"
>修改
</el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['contractor:contractor:remove']">
删除
</el-button>
</el-space>
@ -118,7 +120,7 @@ import { addContractor, delContractor, getContractor, listContractor, updateCont
import { ContractorForm, ContractorQuery, ContractorVO } from '@/api/project/contractor/types';
import ContractorFileDialog from '@/views/project/contractor/component/ContractorFileDialog.vue';
import { useUserStoreHook } from '@/store/modules/user';
import { listData } from '@/api/system/dict/data';
import { getDicts, listData } from '@/api/system/dict/data';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -179,25 +181,19 @@ const data = reactive<PageData<ContractorForm, ContractorQuery>>({
const { queryParams, form, rules } = toRefs(data);
/** 查询字典数据列表 */
const getDictList = async () => {
const res = await listData({
pageNum: 1,
pageSize: 10,
dictName: '',
dictType: 'contractor_type',
dictLabel: ''
});
dictList.value = res.rows;
const res = await getDicts('contractor_type');
dictList.value = res.data;
};
// 分包类型
const filterType=(val)=>{
let label='';
dictList.value.forEach(item=>{
if(item.dictValue==val){
label=item.dictLabel
const filterType = (val) => {
let label = '';
dictList.value.forEach((item) => {
if (item.dictValue == val) {
label = item.dictLabel;
}
})
});
return label;
}
};
/** 查询分包单位列表 */
const getList = async () => {
loading.value = true;

View File

@ -89,7 +89,7 @@
></el-button>
</el-tooltip>
<el-tooltip content="出入库" placement="top">
<el-button link type="primary" icon="view" @click="handleView(scope.row)" ></el-button>
<el-button link type="primary" icon="view" @click="handleView(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
@ -131,10 +131,10 @@
</div>
</template>
</el-dialog>
<el-dialog draggable :title="dialogRecord.title" v-model="dialogRecord.visible" width="1500px" append-to-body>
<div>
<contractorMaterialRecord ref="contractorMaterialRecordRef" ></contractorMaterialRecord>
</div>
<el-dialog draggable :title="dialogRecord.title" v-model="dialogRecord.visible" width="1500px" append-to-body>
<div>
<contractorMaterialRecord ref="contractorMaterialRecordRef"></contractorMaterialRecord>
</div>
</el-dialog>
</div>
</template>
@ -159,7 +159,7 @@ const currentProject = computed(() => userStore.selectedProject);
const contractorMaterialList = ref<ContractorMaterialVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const contractorMaterialRecordRef=ref(null)
const contractorMaterialRecordRef = ref(null);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
@ -218,6 +218,7 @@ const getSubList = async () => {
projectId: currentProject.value.id
});
contractorList.value = res.rows;
handleQuery();
};
/** 查询分包方物料列表 */
@ -244,6 +245,7 @@ const reset = () => {
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
if (contractorList.value.length == 1) queryParams.value.contractorId = contractorList.value[0].id;
getList();
};
@ -310,7 +312,6 @@ const listeningProject = watch(
queryParams.value.projectId = nid;
form.value.projectId = nid;
getSubList();
getList();
}
);
onUnmounted(() => {
@ -318,13 +319,12 @@ onUnmounted(() => {
});
const handleView = async (row: ContractorToolVO) => {
// 打开弹框
dialogRecord.visible=true;
dialogRecord.title=row.materialName+"-物料出入库";
dialogRecord.visible = true;
dialogRecord.title = row.materialName + '-物料出入库';
await nextTick();
contractorMaterialRecordRef.value.getAll(row);
};
onMounted(() => {
getSubList();
getList();
});
</script>

View File

@ -13,8 +13,8 @@
<el-input v-model="queryParams.toolName" placeholder="请输入工具名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="工具类型" prop="toolType">
<el-select v-model="queryParams.toolType" placeholder="请选择工具类型" clearable >
<el-option v-for="dict in contractor_tool_type" :key="dict.value" :label="dict.label" :value="dict.value"/>
<el-select v-model="queryParams.toolType" placeholder="请选择工具类型" clearable>
<el-option v-for="dict in contractor_tool_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="工具型号" prop="toolModel">
@ -35,13 +35,23 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['project:contractorTool:add']">新增</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['contractor:contractorTool:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['project:contractorTool:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['contractor:contractorTool:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['project:contractorTool:remove']">删除</el-button>
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['contractor:contractorTool:remove']"
>删除</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -52,7 +62,7 @@
<el-table-column label="工具名称" align="center" prop="toolName" />
<el-table-column label="工具类型" align="center" prop="toolType">
<template #default="scope">
<dict-tag :options="contractor_tool_type" :value="scope.row.toolType"/>
<dict-tag :options="contractor_tool_type" :value="scope.row.toolType" />
</template>
</el-table-column>
<el-table-column label="工具型号" align="center" prop="toolModel" />
@ -66,13 +76,19 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:contractorTool:edit']"></el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['contractor:contractorTool:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['project:contractorTool:remove']"></el-button>
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['contractor:contractorTool:remove']"
></el-button>
</el-tooltip>
<el-tooltip content="出入库" placement="top">
<el-button link type="primary" icon="view" @click="handleView(scope.row)" ></el-button>
<el-button link type="primary" icon="view" @click="handleView(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
@ -83,20 +99,15 @@
<el-form ref="contractorToolFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="分包方" prop="contractorId">
<el-select v-model="form.contractorId" filterable placeholder="请选择分包方">
<el-option v-for="(item, i) of contractorList" :key="i" :label="item.name" :value="item.id"> </el-option>
</el-select>
<el-option v-for="(item, i) of contractorList" :key="i" :label="item.name" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="工具名称" prop="toolName">
<el-input v-model="form.toolName" placeholder="请输入工具名称" />
</el-form-item>
<el-form-item label="工具类型" prop="toolType">
<el-select v-model="form.toolType" placeholder="请选择工具类型">
<el-option
v-for="dict in contractor_tool_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
<el-option v-for="dict in contractor_tool_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="工具型号" prop="toolModel">
@ -106,7 +117,7 @@
<file-upload v-model="form.file"/>
</el-form-item> -->
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
@ -116,10 +127,10 @@
</div>
</template>
</el-dialog>
<el-dialog draggable :title="dialogLevan.title" v-model="dialogLevan.visible" width="1500px" append-to-body>
<div>
<LevanAutbound ref="LevanAutboundRef" ></LevanAutbound>
</div>
<el-dialog draggable :title="dialogLevan.title" v-model="dialogLevan.visible" width="1500px" append-to-body>
<div>
<LevanAutbound ref="LevanAutboundRef"></LevanAutbound>
</div>
</el-dialog>
</div>
</template>
@ -127,7 +138,7 @@
<script setup name="ContractorTool" lang="ts">
import { listContractorTool, getContractorTool, delContractorTool, addContractorTool, updateContractorTool } from '@/api/project/contractorTool';
import { ContractorToolVO, ContractorToolQuery, ContractorToolForm } from '@/api/project/contractorTool/types';
import { listContractor, } from '@/api/project/contractor';
import { listContractor } from '@/api/project/contractor';
import { useUserStoreHook } from '@/store/modules/user';
import LevanAutbound from '@/views/project/contractorTool/component/LevanAutbound.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -139,13 +150,13 @@ const { contractor_tool_type } = toRefs<any>(proxy?.useDict('contractor_tool_typ
const contractorToolList = ref<ContractorToolVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const LevanAutboundRef=ref(null)
const LevanAutboundRef = ref(null);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const contractorList=ref([]);//分包列表
const contractorList = ref([]); //分包列表
const queryFormRef = ref<ElFormInstance>();
const contractorToolFormRef = ref<ElFormInstance>();
@ -168,10 +179,10 @@ const initFormData: ContractorToolForm = {
toolModel: undefined,
toolNumber: undefined,
file: undefined,
remark: undefined,
}
remark: undefined
};
const data = reactive<PageData<ContractorToolForm, ContractorToolQuery>>({
form: {...initFormData},
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
@ -181,30 +192,15 @@ const data = reactive<PageData<ContractorToolForm, ContractorToolQuery>>({
toolType: undefined,
toolModel: undefined,
toolNumber: undefined,
params: {
}
params: {}
},
rules: {
contractorId: [
{ required: true, message: "分包方不能为空", trigger: "blur" },
],
toolName: [
{ required: true, message: "工具名称不能为空", trigger: "blur" },
],
toolType: [
{ required: true, message: "工具类型不能为空", trigger: "blur" },
],
toolModel: [
{ required: true, message: "工具数量不能为空", trigger: "blur" },
],
toolNumber: [
{ required: true, message: "分包方不能为空", trigger: "blur" },
],
},
contractorId: [{ required: true, message: '分包方不能为空', trigger: 'blur' }],
toolName: [{ required: true, message: '工具名称不能为空', trigger: 'blur' }],
toolType: [{ required: true, message: '工具类型不能为空', trigger: 'blur' }],
toolModel: [{ required: true, message: '工具数量不能为空', trigger: 'blur' }],
toolNumber: [{ required: true, message: '分包方不能为空', trigger: 'blur' }]
}
});
const { contractor_tool_record_type } = toRefs<any>(proxy?.useDict('contractor_tool_record_type'));
@ -214,9 +210,10 @@ const getSubList = async () => {
const res = await listContractor({
pageNum: 1,
pageSize: 10000,
projectId: currentProject.value.id,
projectId: currentProject.value.id
});
contractorList.value = res.rows;
handleQuery();
};
/** 查询分包方工器具列表 */
@ -226,55 +223,56 @@ const getList = async () => {
contractorToolList.value = res.rows;
total.value = res.total;
loading.value = false;
}
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
};
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
form.value = { ...initFormData };
contractorToolFormRef.value?.resetFields();
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
if (contractorList.value.length == 1) queryParams.value.contractorId = contractorList.value[0].id;
getList();
}
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ContractorToolVO[]) => {
ids.value = selection.map(item => item.id);
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = "添加工器具";
}
dialog.title = '添加工器具';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ContractorToolVO) => {
reset();
const _id = row?.id || ids.value[0]
const _id = row?.id || ids.value[0];
const res = await getContractorTool(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = "修改工器具";
}
dialog.title = '修改工器具';
};
/** 提交按钮 */
const submitForm = () => {
@ -282,25 +280,25 @@ const submitForm = () => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateContractorTool(form.value).finally(() => buttonLoading.value = false);
await updateContractorTool(form.value).finally(() => (buttonLoading.value = false));
} else {
await addContractorTool(form.value).finally(() => buttonLoading.value = false);
await addContractorTool(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess("操作成功");
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: ContractorToolVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除分包方工器具编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
await proxy?.$modal.confirm('是否确认删除分包方工器具编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delContractorTool(_ids);
proxy?.$modal.msgSuccess("删除成功");
proxy?.$modal.msgSuccess('删除成功');
await getList();
}
};
//监听项目id刷新数据
const listeningProject = watch(
@ -308,8 +306,7 @@ const listeningProject = watch(
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getSubList()
getList();
getSubList();
}
);
onUnmounted(() => {
@ -317,13 +314,12 @@ onUnmounted(() => {
});
const handleView = async (row: ContractorToolVO) => {
// 打开弹框
dialogLevan.visible=true;
dialogLevan.title=row.toolName+"-工器具出入库";
dialogLevan.visible = true;
dialogLevan.title = row.toolName + '-工器具出入库';
await nextTick();
LevanAutboundRef.value.getAll(row);
};
onMounted(() => {
getSubList();
getList();
});
</script>

View File

@ -45,16 +45,36 @@
<el-table v-loading="loading" :data="projectList" @selection-change="handleSelectionChange">
<el-table-column type="expand" width="50">
<template #default="{ row }">
<div class="w187.25 ml-12.5">
<div class="w212.25 ml-12.5">
<el-button class="mb" type="primary" size="small" @click="handleOpenSetChild(row.id)" icon="plus">添加子项目</el-button>
<el-table :data="row.children" border stripe>
<el-table-column label="序号" type="index" width="55" align="center" />
<el-table-column label="名称" align="center" prop="projectName" width="296" />
<el-table-column label="名称" align="center" prop="projectName" width="296">
<template #default="scope">
<el-link
:type="scope.row.designId ? 'primary' : 'default'"
:disabled="!scope.row.designId"
@click="handleOpenLayer(scope.row)"
v-loading.fullscreen.lock="fullscreenLoading"
>{{ scope.row.projectName }}</el-link
>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="199" />
<el-table-column fixed="right" align="center" label="操作" class-name="small-padding fixed-width" width="199">
<el-table-column fixed="right" align="center" label="操作" class-name="small-padding fixed-width" width="299">
<template #default="scope">
<el-space>
<file-upload
:limit="1"
:fileSize="200"
:fileType="['dxf']"
v-model:model-value="dxfFile"
uploadUrl="/project/projectFile/upload/dxf"
:data="{ projectId: scope.row.id }"
>
<el-button link type="primary" icon="upload">上传DXF </el-button>
</file-upload>
<el-button
link
type="success"
@ -76,7 +96,7 @@
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="项目名称" align="center" prop="projectName">
<template #default="scope">
<!-- <template #default="scope">
<el-link
:type="scope.row.designId ? 'primary' : 'default'"
:disabled="!scope.row.designId"
@ -84,7 +104,7 @@
v-loading.fullscreen.lock="fullscreenLoading"
>{{ scope.row.projectName }}</el-link
>
</template>
</template> -->
</el-table-column>
<el-table-column label="项目简称" align="center" prop="shortName" />
<el-table-column label="状态" align="center" prop="status">
@ -126,16 +146,6 @@
<template #default="scope">
<el-space>
<el-button link type="primary" icon="FolderOpened" @click="handleShowUpload(scope.row)">导入安全协议书 </el-button>
<file-upload
:limit="1"
:fileSize="200"
:fileType="['dxf']"
v-model:model-value="dxfFile"
uploadUrl="/project/projectFile/upload/dxf"
:data="{ projectId: scope.row.id }"
>
<el-button link type="primary" icon="upload">上传DXF </el-button>
</file-upload>
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:project:edit']">修改 </el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['project:project:remove']">删除 </el-button>

View File

@ -0,0 +1,188 @@
<template>
<div>
<div class="block_box">
<span>用户信息</span>
<el-form label-width="130px">
<el-row :gutter="20" justify="space-around">
<el-col :span="12">
<el-form-item label="人脸照">
<el-image :src="userDetail?.facePicUrl" style="width: 150px; height: 150px" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="姓名">
{{ userDetail?.userName }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话">
{{ userDetail?.phone }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别">
<dict-tag :options="user_sex_type" :value="userDetail?.sex" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年龄">
{{ dayjs().diff(dayjs(userDetail?.sfzBirth), 'year') }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="民族">
{{ userDetail?.nation }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="籍贯">
{{ userDetail?.nativePlace }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证号码">
{{ userDetail?.sfzNumber }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证号码">
{{ userDetail?.sfzNumber }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证有效开始期">
{{ dayjs(userDetail?.sfzStart).format('YYYY 年 MM 月 DD 日') }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证有效结束期">
{{ dayjs(userDetail?.sfzEnd).format('YYYY 年 MM 月 DD 日') }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="身份证地址">
{{ userDetail?.sfzSite }}
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="block_box">
<span>银行卡</span>
<el-form label-width="130px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="银行卡号">
{{ userDetail?.yhkNumber }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="银行开户行">
{{ userDetail?.yhkOpeningBank }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="持卡人">
{{ userDetail?.yhkCardholder }}
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="block_box">
<span>单位信息</span>
<el-form label-width="130px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="施工单位">
{{ userDetail?.contractorVo?.name }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工种">
<dict-tag :options="type_of_work" :value="userDetail?.typeOfWork" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="block_box">
<span>其他信息</span>
<el-form label-width="130px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="班组">
{{ userDetail?.teamVo?.teamName }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="打卡状态">
<dict-tag :options="user_clock_type" :value="userDetail?.clock" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入场时间">
{{ userDetail?.entryDate ? dayjs(userDetail?.entryDate).format('YYYY 年 MM 月 DD 日 HH:mm:ss') : '' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="离场时间">
{{ userDetail?.leaveDate ? dayjs(userDetail?.leaveDate).format('YYYY 年 MM 月 DD 日 HH:mm:ss') : '' }}
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { getConstructionUser } from '@/api/project/constructionUser';
import { ConstructionUserVO } from '@/api/project/constructionUser/types';
import { dayjs } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { type_of_work, user_sex_type, user_clock_type } = toRefs<any>(proxy?.useDict('type_of_work', 'user_sex_type', 'user_clock_type'));
interface Props {
userId?: string | number;
}
const props = defineProps<Props>();
const loading = ref<boolean>(false);
const userDetail = ref<ConstructionUserVO>();
const getUserDetail = async () => {
loading.value = true;
const res = await getConstructionUser(props.userId);
if (res.data && res.code === 200) {
userDetail.value = res.data;
}
loading.value = false;
};
onMounted(() => {
getUserDetail();
});
watch(
() => props.userId,
(newId, oldId) => {
if (newId !== oldId) {
getUserDetail();
}
}
);
</script>
<style lang="scss" scoped>
.block_box {
border: 1px solid #9eccfa;
border-radius: 6px;
padding: 10px 20px 20px 10px;
margin: 15px;
> span {
color: #409eff;
font-weight: 700;
font-size: 14px;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -33,15 +33,15 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['project:subcontract:add']">新增</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['contractor:subcontract:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['project:subcontract:edit']"
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['contractor:subcontract:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['project:subcontract:remove']"
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['contractor:subcontract:remove']"
>删除</el-button
>
</el-col>
@ -69,10 +69,16 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:subcontract:edit']"></el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['contractor:subcontract:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['project:subcontract:remove']"></el-button>
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['contractor:subcontract:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
@ -80,15 +86,15 @@
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改分包合同对话框 -->
<el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="subcontractFormRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="分包方" prop="contractorId">
<el-select v-model="form.contractorId" filterable placeholder="请选择分包方">
<el-option v-for="(item, i) of contractorList" :key="i" :label="item.name" :value="item.id"> </el-option>
</el-select>
<el-option v-for="(item, i) of contractorList" :key="i" :label="item.name" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="合同文件" prop="contractFileId">
<file-upload fileSize="" :fileType="['doc', 'xls', 'ppt', 'txt', 'pdf', 'png', 'jpg', 'jpeg']" v-model="form.contractFileId" />
<file-upload :fileType="['doc', 'xls', 'ppt', 'txt', 'pdf', 'png', 'jpg', 'jpeg']" v-model="form.contractFileId" />
</el-form-item>
<el-form-item label="合同名称" prop="contractName">
<el-input v-model="form.contractName" placeholder="请输入合同名称" />
@ -105,8 +111,7 @@
<el-input v-model="form.contractAmount" type="number" placeholder="请输入合同金额" />
</el-form-item>
<el-form-item label="合同日期" prop="contractTime">
<el-date-picker clearable v-model="form.contractTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择合同日期">
</el-date-picker>
<el-date-picker clearable v-model="form.contractTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择合同日期"> </el-date-picker>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
@ -125,7 +130,7 @@
<script setup name="Subcontract" lang="ts">
import { listSubcontract, getSubcontract, delSubcontract, addSubcontract, updateSubcontract } from '@/api/project/subcontract';
import { SubcontractVO, SubcontractQuery, SubcontractForm } from '@/api/project/subcontract/types';
import { listContractor, } from '@/api/project/contractor';
import { listContractor } from '@/api/project/contractor';
import { useUserStoreHook } from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -141,7 +146,7 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const contractorList=ref([]);//分包列表
const contractorList = ref([]); //分包列表
const queryFormRef = ref<ElFormInstance>();
const subcontractFormRef = ref<ElFormInstance>();
@ -181,7 +186,7 @@ const data = reactive<PageData<SubcontractForm, SubcontractQuery>>({
contractNumber: [{ required: true, message: '合同编号不能为空', trigger: 'change' }],
contractName: [{ required: true, message: '合同名称不能为空', trigger: 'change' }],
contractAmount: [{ required: true, message: '合同金额不能为空', trigger: 'change' }],
contractTime: [{ required: true, message: '合同时间不能为空', trigger: 'change' }],
contractTime: [{ required: true, message: '合同时间不能为空', trigger: 'change' }]
}
});
@ -200,9 +205,11 @@ const getSubList = async () => {
const res = await listContractor({
pageNum: 1,
pageSize: 10000,
projectId: currentProject.value.id,
projectId: currentProject.value.id
});
contractorList.value = res.rows;
handleQuery();
};
/** 取消按钮 */
@ -220,6 +227,7 @@ const reset = () => {
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
if (contractorList.value.length == 1) queryParams.value.contractorId = contractorList.value[0].id;
getList();
};
@ -285,7 +293,7 @@ const listeningProject = watch(
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
getSubList();
}
);
@ -294,6 +302,5 @@ onUnmounted(() => {
});
onMounted(() => {
getSubList();
getList();
});
</script>

View File

@ -23,10 +23,30 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" :offset="0" prop="dept">
<el-form-item label="部门">
<el-cascader
:options="postListAll"
v-model="formData.dept"
clearable
filterable
:show-all-levels="false"
placeholder="请选择部门"
:props="{ expandTrigger: 'hover', checkStrictly: true, value: 'id', emitPath: false, multiple: true }"
@change="changeDept"
>
</el-cascader>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="岗位" prop="postIdList">
<el-select v-model="formData.postIdList" placeholder="请选择岗位" multiple>
<el-option v-for="dict in postListAll" :key="dict.postId" :label="dict.postName" :value="dict.postId" />
<el-select v-model="formData.postIdList" placeholder="请选择岗位" multiple :disabled="!formData.dept">
<el-option
v-for="dict in formData.postWorkList"
:key="dict.postId"
:label="dict.deptName + '/' + dict.postName"
:value="dict.postId"
/>
</el-select>
</el-form-item>
</el-col>
@ -60,7 +80,7 @@ const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
// Props
defineProps<{
const props = defineProps<{
tourTypeOptions: any[];
risxList: any[];
postListAll: any[];
@ -78,6 +98,7 @@ const loading = ref(false);
const formData = reactive({
id: undefined,
violationLevel: undefined,
dept: undefined,
color: undefined,
violationType: undefined as any,
wxOrPc: undefined,
@ -88,6 +109,7 @@ const formData = reactive({
deletedAt: undefined,
projectId: currentProject.value?.id,
riskType: '',
postWorkList: [] as any[],
postIdList: [] as any[]
});
@ -127,6 +149,7 @@ const resetForm = () => {
violationLevel: undefined,
color: undefined,
violationType: undefined,
dept: undefined,
wxOrPc: undefined,
createBy: undefined,
updateBy: undefined,
@ -135,6 +158,7 @@ const resetForm = () => {
deletedAt: undefined,
projectId: currentProject.value?.id,
riskType: '',
postWorkList: [] as any[],
postIdList: []
});
};
@ -165,6 +189,32 @@ const onSubmit = () => {
});
};
const changeDept = (val: any) => {
formData.postWorkList = getPostVoListByIds(props.postListAll, val);
formData.postIdList = [];
console.log(formData.postWorkList, val);
};
function getPostVoListByIds(tree: any[], idList: (string | number)[]): any[] {
const idSet = new Set(idList.map(String)); // 用于快速匹配 ID统一为字符串比较
const result: any[] = [];
function dfs(nodes: any[]) {
for (const node of nodes) {
if (idSet.has(String(node.id))) {
result.push(...(node.postVoList || []));
}
if (node.children && node.children.length > 0) {
dfs(node.children);
}
}
}
dfs(tree);
return result;
}
defineExpose({
openDialog,
closeDialog

View File

@ -25,10 +25,30 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" :offset="0" prop="dept">
<el-form-item label="部门">
<el-cascader
:options="postListAll"
v-model="formData.dept"
clearable
filterable
:show-all-levels="false"
placeholder="请选择部门"
:props="{ expandTrigger: 'hover', checkStrictly: true, value: 'id', emitPath: false, multiple: true }"
@change="changeDept"
>
</el-cascader>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="岗位" prop="postIdList">
<el-select v-model="formData.postIdList" placeholder="请选择岗位" multiple>
<el-option v-for="dict in postListAll" :key="dict.postId" :label="dict.postName" :value="dict.postId" />
<el-select v-model="formData.postIdList" placeholder="请选择岗位" multiple :disabled="!formData.dept">
<el-option
v-for="dict in formData.postWorkList"
:key="dict.postId"
:label="dict.deptName + '/' + dict.postName"
:value="dict.postId"
/>
</el-select>
</el-form-item>
</el-col>
@ -57,7 +77,7 @@ import { ElMessage, ElLoading } from 'element-plus';
import { getViolationLevel, addViolationLevel, updateViolationLevel } from '@/api/safety/violationLevel';
import { ViolationLevelForm, ViolationLevelVO } from '@/api/safety/violationLevel/types';
defineProps<{
const props = defineProps<{
tourTypeOptions: any[];
risxList: any[];
postListAll: any[];
@ -75,11 +95,16 @@ const formData = reactive<
risx?: string[] | string;
tourType?: string[] | string;
postIdList: number[];
dept: any;
postWorkList: any[];
}
>({
id: undefined,
violationLevel: undefined,
color: undefined,
dept: undefined,
postWorkList: [] as any[],
riskType: undefined,
createBy: undefined,
updateBy: undefined,
@ -115,6 +140,8 @@ const openDialog = (row?: ViolationLevelVO) => {
const data = res.data;
data.postIdList = (data.postList || []).map((item: any) => item.postId);
data.violationType = data.violationType ? (data.violationType as string).split(',') : [];
formData.dept = (data.postList || []).map((item: any) => item.deptId);
formData.postWorkList = getPostVoListByIds(props.postListAll, formData.dept);
Object.assign(formData, data);
});
});
@ -174,11 +201,41 @@ const resetForm = () => {
createdAt: undefined,
updatedAt: undefined,
deletedAt: undefined,
dept: undefined,
postWorkList: [],
postIdList: [],
risx: '',
posts: []
});
};
const changeDept = (val: any) => {
formData.postWorkList = getPostVoListByIds(props.postListAll, val);
formData.postIdList = [];
console.log(formData.postWorkList, val);
};
function getPostVoListByIds(tree: any[], idList: (string | number)[]): any[] {
const idSet = new Set(idList.map(String)); // 用于快速匹配 ID统一为字符串比较
const result: any[] = [];
function dfs(nodes: any[]) {
for (const node of nodes) {
if (idSet.has(String(node.id))) {
result.push(...(node.postVoList || []));
}
if (node.children && node.children.length > 0) {
dfs(node.children);
}
}
}
dfs(tree);
return result;
}
defineExpose({
openDialog,
closeDialog

View File

@ -106,7 +106,7 @@ import { ViolationLevelForm, ViolationLevelVO } from '@/api/safety/violationLeve
import apiV1SystemBusViolationLevelAdd from '@/views/safety/violationLevel/component/add.vue';
import apiV1SystemBusViolationLevelEdit from '@/views/safety/violationLevel/component/edit.vue';
import { useUserStoreHook } from '@/store/modules/user';
import { listPost, optionselect } from '@/api/system/post';
import { listPost, listTreeByProject, optionselect } from '@/api/system/post';
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
@ -145,8 +145,9 @@ const state = reactive<any>({
});
const postList = () => {
listPost({ pageNum: 1, pageSize: 100 }).then((res) => {
state.postListAll = res.rows ?? [];
listTreeByProject(currentProject.value?.id).then((res) => {
console.log('🚀 ~ listTreeByProject ~ res:', res);
state.postListAll = res.data ?? [];
});
};
@ -157,8 +158,6 @@ const initTableData = () => {
const resetQuery = (formEl?: FormInstance) => {
if (!formEl) return;
formEl.resetFields();
console.log(useUserStoreHook().selectedProject, currentProject.value?.id);
busViolationLevelList();
};

View File

@ -0,0 +1,214 @@
<template>
<!-- <el-card v-loading="loading" body-class="printMe"> -->
<div class="w75% m-a">
<div id="printMe" class="pos-relative">
<div class="resultIcon"><img :src="'../../../../../src/assets/icons/svg/' + inspectionType + '.png'" alt="" /></div>
<!-- <h2 style="text-align: center; margin-top: 5px; font-weight: bold">安全生产监督检查通知书</h2> -->
<el-row>
<el-col :span="12" style="text-align: left">填报人{{ safetyInspectionDetail?.createByName }}</el-col>
<el-col :span="12" style="text-align: right">填报时间{{ safetyInspectionDetail?.createTime }}</el-col>
</el-row>
<el-descriptions :column="2" border style="margin-top: 8px" label-width="160px" size="large">
<el-descriptions-item label-align="center" label="检查项目" :span="2" class-name="zebra">{{ currentProject?.name }} </el-descriptions-item>
<el-descriptions-item label-align="center" label="违章类型" label-class-name="white" width="300px">
<dict-tag :options="violation_level_type" :value="safetyInspectionDetail?.violationType" />
</el-descriptions-item>
<el-descriptions-item label-align="center" label="违章等级" label-class-name="white">
<div class="flex">
{{ safetyInspectionDetail?.levelVo.violationLevel }}<dict-tag
:options="risk_level_type"
:value="safetyInspectionDetail?.levelVo.riskType"
/>
</div>
</el-descriptions-item>
<el-descriptions-item label-align="center" label="检查时间" class-name="zebra"
>{{ safetyInspectionDetail?.violationTime }}
</el-descriptions-item>
<el-descriptions-item label-align="center" label="检查人" class-name="zebra"
>{{ safetyInspectionDetail?.createByName }}
</el-descriptions-item>
<el-descriptions-item label-align="center" label="整改人" label-class-name="white"
>{{ safetyInspectionDetail?.handlerName }}
</el-descriptions-item>
<el-descriptions-item label-align="center" label="要求整改期限" label-class-name="white">
{{ safetyInspectionDetail?.disposeDeadline ? dayjs(safetyInspectionDetail?.disposeDeadline).format('YYYY - MM - DD ') : '' }}
</el-descriptions-item>
</el-descriptions>
<el-descriptions border direction="vertical" size="large">
<el-descriptions-item label-align="center" label="巡检结果" class-name="none"></el-descriptions-item>
</el-descriptions>
<el-descriptions :column="2" border label-width="160px" size="large">
<el-descriptions-item label-align="center" label="内容" :span="2" label-class-name="white"
>{{ safetyInspectionDetail?.recognizeVo.description }}
</el-descriptions-item>
<el-descriptions-item label-align="center" label="检查附件" :span="2" label-class-name="white">
<el-space wrap>
<!-- <div v-for="item in checkFileList" :key="item.ossId">
<span v-if="['.png', '.jpg', '.jpeg'].includes(item.fileSuffix)">
<image-preview :src="item.url" width="200px" />
</span>
<span v-else>
<el-link :href="`${item.url}`" type="primary" :underline="false" target="_blank">
<span> {{ item.originalName }} </span>
</el-link>
</span>
</div> -->
<span>
<image-preview :src="safetyInspectionDetail?.recognizeVo.picture" width="200px" />
</span>
</el-space>
</el-descriptions-item>
<el-descriptions-item label-align="center" label="检查状态" :span="2" label-class-name="white">
<el-steps style="max-width: 200px" :active="Number(safetyInspectionDetail?.status)" finish-status="finish">
<el-step v-for="item in safety_inspection_type" :key="item.value" :title="item.label" />
</el-steps>
</el-descriptions-item>
</el-descriptions>
</div>
</div>
<!-- </el-card> -->
<!-- <div class="dialog-footer">
<div class="btn-item" @click="handleExport">
<img src="../../../../assets/icons/svg/derived.png" />
<span>导出</span>
</div>
<div class="btn-item" v-print="'#printMe'">
<img src="../../../../assets/icons/svg/print.png" />
<span>打印</span>
</div>
</div> -->
</template>
<script setup lang="ts">
import { useUserStoreHook } from '@/store/modules/user';
import { downLoadOss, listByIds } from '@/api/system/oss';
import { OssVO } from '@/api/system/oss/types';
import { dayjs } from 'element-plus';
import { getViolationRecord } from '@/api/safety/violationRecord';
import { ViolationRecordVO } from '@/api/safety/violationRecord/types';
interface Props {
violationRecordId?: string | number;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { violation_level_type, safety_inspection_type, risk_level_type } = toRefs<any>(
proxy?.useDict('violation_level_type', 'review_type', 'reply_type', 'safety_inspection_type', 'risk_level_type')
);
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const props = defineProps<Props>();
const loading = ref<boolean>(false);
const safetyInspectionDetail = ref<any>();
const checkFileList = ref<OssVO[]>();
const rectificationFileList = ref<OssVO[]>();
//检查状态图片
const inspectionType = computed(() => {
let imgName = 'successLogo';
if (safetyInspectionDetail.value?.status == '2') imgName = 'rectification';
if (safetyInspectionDetail.value?.reviewType == '1') imgName = 'successful';
if (safetyInspectionDetail.value?.reviewType == '2') imgName = 'failure';
console.log('🚀 ~ inspectionType ~ imgName:', imgName);
return imgName;
});
const get = async () => {
loading.value = true;
const res = await getViolationRecord(props.violationRecordId);
if (res.data && res.code === 200) {
safetyInspectionDetail.value = res.data;
if (res.data.checkFile) {
const checkFileRes = await listByIds(res.data.checkFile.split(','));
checkFileList.value = checkFileRes.data;
}
if (res.data.rectificationFile) {
const rectificationFileRes = await listByIds(res.data.rectificationFile.split(','));
rectificationFileList.value = rectificationFileRes.data;
}
}
loading.value = false;
};
const handleExport = async () => {
await downLoadOss({ id: safetyInspectionDetail.value.id }, '/safety/safetyInspection/export/word', '安全生产监督检查通知书.zip');
};
onMounted(() => {
console.log('🚀 ~ onMounted ~ props.safetyInspectionId:', props.violationRecordId);
get();
});
watch(
() => props.violationRecordId,
(newId, oldId) => {
if (newId !== oldId) {
checkFileList.value = undefined;
rectificationFileList.value = undefined;
get();
}
}
);
</script>
<style scoped lang="scss">
#printMe {
padding: 15px 20px 20px 20px !important;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
}
:deep(.white) {
background: #fff !important;
}
:deep(.none) {
display: none !important;
}
:deep(.zebra) {
background: #f5f7fa;
}
@page {
size: auto;
margin: 0mm;
}
.dialog-footer {
height: 200px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: absolute;
top: 14%;
right: 6%;
background: #fff;
box-shadow: 0 0 10px #ddd;
text-align: center;
padding: 20px 10px;
.btn-item {
display: flex;
flex-direction: column;
justify-content: center;
cursor: pointer;
}
}
.resultIcon {
position: absolute;
top: 100px;
right: 50px;
z-index: 10;
width: 105px;
height: 105px;
img {
width: 105px;
}
}
</style>

View File

@ -0,0 +1,307 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="工单号" prop="id">
<el-input v-model="queryParams.id" placeholder="请输入工单号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="违章时间" prop="violationTime">
<el-date-picker clearable v-model="queryParams.violationTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择违章时间" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['safety:violationRecord:remove']"
>删除</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="violationRecordList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="工单号" align="center" prop="id" v-if="true" />
<el-table-column label="违章等级" align="center" prop="levelVo" width="180">
<template #default="scope">
<div class="flex justify-center">
{{ scope.row.levelVo.violationLevel }}<dict-tag :options="risk_level_type" :value="scope.row.levelVo.riskType" />
</div>
</template>
</el-table-column>
<el-table-column label="违章类型" align="center" prop="violationType">
<template #default="scope">
<dict-tag :options="violation_level_type" :value="scope.row.violationType" />
</template>
</el-table-column>
<el-table-column label="违章时间" align="center" prop="violationTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.violationTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="处理人" align="center" prop="handlerName" />
<el-table-column label="整改措施" align="center" prop="measure" />
<el-table-column label="整改时间" align="center" prop="rectificationTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.rectificationTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="复查情况" align="center" prop="review" />
<el-table-column label="复查状态" align="center" prop="reviewType">
<template #default="scope">
{{ scope.row.reviewType ? (scope.row.reviewType == 1 ? '已通过' : '未通过') : '' }}
</template>
</el-table-column>
<el-table-column label="复查时间" align="center" prop="reviewTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.reviewTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="处理流程类型(0仅通知 1通知整改复查)" align="center" prop="processType" /> -->
<el-table-column label="工单状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="safety_inspection_type" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="详情" placement="top">
<el-button
link
type="primary"
icon="View"
@click="handleShowDialog(scope.row)"
v-hasPermi="['safety:violationRecord:view']"
></el-button>
</el-tooltip>
<el-tooltip content="违章处理人" placement="top">
<el-button link type="primary" icon="User" @click="handleUpdate(scope.row)" v-hasPermi="['safety:violationRecord:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['safety:violationRecord:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改违规记录对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="violationRecordFormRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="违章处理人" prop="handlerId">
<el-input v-model="form.handlerId" placeholder="请输入违章处理人" />
</el-form-item>
<el-form-item label="处理期限" prop="disposeDeadline">
<el-date-picker clearable v-model="form.disposeDeadline" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择处理期限">
</el-date-picker>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog title="违规记录详情" v-model="showDetailDialog" width="60vw">
<ViolationRecordDetailDialog :violation-record-id="currentViolationRecordId" />
</el-dialog>
</div>
</template>
<script setup name="ViolationRecord" lang="ts">
import { listViolationRecord, getViolationRecord, delViolationRecord, addViolationRecord, updateViolationRecord } from '@/api/safety/violationRecord';
import { ViolationRecordVO, ViolationRecordQuery, ViolationRecordForm } from '@/api/safety/violationRecord/types';
import ViolationRecordDetailDialog from './component/violationRecordDetailDialog.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { violation_level_type, risk_level_type, safety_inspection_type } = toRefs<any>(
proxy?.useDict('violation_level_type', 'risk_level_type', 'safety_inspection_type')
);
const violationRecordList = ref<ViolationRecordVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const currentViolationRecordId = ref<string | number>(undefined);
const showDetailDialog = ref(false);
const queryFormRef = ref<ElFormInstance>();
const violationRecordFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ViolationRecordForm = {
id: undefined,
projectId: undefined,
levelId: undefined,
recognizeId: undefined,
violationType: undefined,
violationTime: undefined,
handlerId: undefined,
disposeDeadline: undefined,
disposeTime: undefined,
measure: undefined,
rectificationTime: undefined,
review: undefined,
reviewType: undefined,
reviewTime: undefined,
processType: undefined,
status: undefined,
remark: undefined
};
const data = reactive<PageData<ViolationRecordForm, ViolationRecordQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
id: undefined,
projectId: undefined,
violationType: undefined,
violationTime: undefined,
handlerId: undefined,
disposeDeadline: undefined,
disposeTime: undefined,
measure: undefined,
rectificationTime: undefined,
review: undefined,
reviewType: undefined,
reviewTime: undefined,
processType: undefined,
status: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
projectId: [{ required: true, message: '项目id不能为空', trigger: 'blur' }],
levelId: [{ required: true, message: '违章等级id不能为空', trigger: 'blur' }],
recognizeId: [{ required: true, message: '识别记录id不能为空', trigger: 'blur' }],
processType: [{ required: true, message: '处理流程类型(0仅通知 1通知整改复查)不能为空', trigger: 'change' }],
status: [{ required: true, message: '工单状态不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询违规记录列表 */
const getList = async () => {
loading.value = true;
const res = await listViolationRecord(queryParams.value);
violationRecordList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
violationRecordFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ViolationRecordVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加违规记录';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ViolationRecordVO) => {
reset();
form.value.id = row.id;
dialog.visible = true;
dialog.title = '选择违章处理人';
};
const handleShowDialog = (row?: ViolationRecordVO) => {
currentViolationRecordId.value = row.id;
showDetailDialog.value = true;
};
/** 提交按钮 */
const submitForm = () => {
violationRecordFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
await addViolationRecord(form.value).finally(() => (buttonLoading.value = false));
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: ViolationRecordVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除违规记录编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delViolationRecord(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'safety/violationRecord/export',
{
...queryParams.value
},
`violationRecord_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@ -47,6 +47,11 @@
>
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
<el-table-column prop="deptCategory" align="center" label="类别编码" width="200"></el-table-column>
<el-table-column prop="deptType" align="center" label="部门类型" width="200">
<template #default="scope">
<dict-tag :options="sys_dept_type" :value="scope.row.deptType" />
</template>
</el-table-column>
<el-table-column prop="orderNum" align="center" label="排序" width="200"></el-table-column>
<el-table-column prop="status" align="center" label="状态" width="100">
<template #default="scope">
@ -63,7 +68,7 @@
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dept:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-tooltip content="新增" placement="top" v-if="scope.row.deptType != '2' && scope.row.deptType != '4'">
<el-button v-hasPermi="['system:dept:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
@ -75,7 +80,7 @@
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-body width="600px">
<el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
<el-form ref="deptFormRef" :model="form" :rules="rules" label-width="110px">
<el-row>
<el-col v-if="form.parentId !== 0" :span="24">
<el-form-item label="上级部门" prop="parentId">
@ -128,6 +133,27 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门类型">
<el-select v-model="form.deptType" placeholder="请选择部门类型" @change="changeProject">
<el-option v-for="dict in sys_dept_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.deptType == '3'">
<el-form-item label="所属项目" prop="projectId">
<el-select v-model="form.projectId" placeholder="请选择所属项目">
<el-option v-for="item in projectList" :key="item.id" :label="item.projectName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.deptType == '4'">
<el-form-item label="分包单位" prop="contractorId">
<el-select v-model="form.contractorId" placeholder="请选择分包单位">
<el-option v-for="item in contractorList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
@ -141,7 +167,7 @@
</template>
<script setup name="Dept" lang="ts">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from '@/api/system/dept';
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild, getDeptList, optionProjectSelect } from '@/api/system/dept';
import { DeptForm, DeptQuery, DeptVO } from '@/api/system/dept/types';
import { UserVO } from '@/api/system/user/types';
import { listUserByDeptId } from '@/api/system/user';
@ -153,7 +179,7 @@ interface DeptOptionsType {
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const { sys_normal_disable, sys_dept_type } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_dept_type'));
const deptList = ref<DeptVO[]>([]);
const loading = ref(true);
@ -166,6 +192,8 @@ const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const projectList = ref([]);
const contractorList = ref([]);
const deptTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
@ -180,6 +208,11 @@ const initFormData: DeptForm = {
leader: undefined,
phone: undefined,
email: undefined,
isShow: undefined,
deptType: undefined,
projectId: undefined,
contractorId: undefined,
rowProjectId: undefined,
status: '0'
};
const initData: PageData<DeptForm, DeptQuery> = {
@ -189,7 +222,9 @@ const initData: PageData<DeptForm, DeptQuery> = {
pageSize: 10,
deptName: undefined,
deptCategory: undefined,
status: undefined
status: undefined,
isShow: undefined,
deptType: undefined
},
rules: {
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
@ -230,6 +265,8 @@ const cancel = () => {
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
projectList.value = [];
contractorList.value = [];
deptFormRef.value?.resetFields();
};
@ -240,6 +277,7 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
projectList.value = [];
handleQuery();
};
@ -259,6 +297,7 @@ const toggleExpandAll = (data: DeptVO[], status: boolean) => {
/** 新增按钮操作 */
const handleAdd = async (row?: DeptVO) => {
reset();
form.value.rowProjectId = row?.projectId;
const res = await listDept();
const data = proxy?.handleTree<DeptOptionsType>(res.data, 'deptId');
if (data) {
@ -274,10 +313,14 @@ const handleAdd = async (row?: DeptVO) => {
/** 修改按钮操作 */
const handleUpdate = async (row: DeptVO) => {
reset();
form.value.rowProjectId = row?.projectId;
//查询当前部门所有用户
getDeptAllUser(row.deptId);
const res = await getDept(row.deptId);
form.value = res.data;
projectList.value = res.data.projectList;
contractorList.value = res.data.contractorList;
const response = await listDeptExcludeChild(row.deptId);
const data = proxy?.handleTree<DeptOptionsType>(response.data, 'deptId');
if (data) {
@ -294,6 +337,17 @@ const handleUpdate = async (row: DeptVO) => {
dialog.visible = true;
dialog.title = '修改部门';
};
const changeProject = async (val: any) => {
if (val == '3' && (!projectList.value || !projectList.value.length)) {
const res = await getDeptList();
projectList.value = res.data;
} else if (val == '4' && (!contractorList.value || !contractorList.value.length)) {
const res = await optionProjectSelect(form.value.rowProjectId);
contractorList.value = res;
}
};
/** 提交按钮 */
const submitForm = () => {
deptFormRef.value?.validate(async (valid: boolean) => {

View File

@ -35,72 +35,110 @@
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="200" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="180">
<template #default="scope">
<el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top">
<el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="200" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
<el-table-column fixed="right" label="操作" width="180">
<template #default="scope">
<el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top">
<el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</el-col>
</el-row>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="roleFormRef" :model="form" :rules="rules" label-width="100px">
<el-form ref="roleFormRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="所属部门" prop="deptId">
<el-cascader
:options="deptOptions"
v-model="form.deptId"
placeholder="请选择所属部门"
clearable
filterable
:show-all-levels="false"
:props="{ value: 'id', emitPath: false, checkStrictly: true }"
@change=""
>
</el-cascader>
</el-form-item>
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
</el-form-item>
@ -124,7 +162,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand(Boolean($event), 'menu')">展开/折叠</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
<el-tree
@ -138,6 +176,9 @@
:props="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
<el-form-item label="是否为特殊角色">
<el-switch v-model="form.isSpecial" active-value="1" inactive-value="0" active-text="是" inactive-text="否"> </el-switch>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
@ -165,7 +206,7 @@
</el-select>
</el-form-item>
<el-form-item v-show="form.dataScope === '2'" label="数据权限">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand(Boolean($event), 'dept')">展开/折叠</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
<el-tree
@ -196,6 +237,8 @@ import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updat
import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu/index';
import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types';
import { MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types';
import api, { uploadCertList } from '@/api/system/user';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -214,8 +257,11 @@ const menuExpand = ref(false);
const menuNodeAll = ref(false);
const deptExpand = ref(true);
const deptNodeAll = ref(false);
const deptOptions = ref<DeptTreeOption[]>([]);
const deptOptions = ref<DeptTreeVO[]>([]);
const enabledDeptOptions = ref<DeptTreeVO[]>([]);
const openDataScope = ref(false);
const deptName = ref('');
/** 数据范围选项*/
const dataScopeOptions = ref([
@ -232,6 +278,7 @@ const roleFormRef = ref<ElFormInstance>();
const dataScopeRef = ref<ElFormInstance>();
const menuRef = ref<ElTreeInstance>();
const deptRef = ref<ElTreeInstance>();
const deptTreeRef = ref<ElTreeInstance>();
const initForm: RoleForm = {
roleId: undefined,
@ -244,6 +291,8 @@ const initForm: RoleForm = {
remark: '',
dataScope: '1',
menuIds: [],
deptId: '',
isSpecial: null,
deptIds: []
};
@ -254,6 +303,7 @@ const data = reactive<PageData<RoleForm, RoleQuery>>({
pageSize: 10,
roleName: '',
roleKey: '',
deptId: '',
status: ''
},
rules: {
@ -269,6 +319,18 @@ const dialog = reactive<DialogOption>({
title: ''
});
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.deptId = data.id as string;
handleQuery();
};
/**
* 查询角色列表
*/
@ -293,6 +355,9 @@ const handleQuery = () => {
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.deptId = undefined;
deptTreeRef.value?.setCurrentKey(undefined);
handleQuery();
};
/**删除按钮操作 */
@ -496,8 +561,28 @@ const cancelDataScope = () => {
form.value = { ...initForm };
openDataScope.value = false;
};
/** 查询部门下拉树结构 */
const getDeptTree = async () => {
const res = await api.deptTreeSelect({ isShow: '1' });
deptOptions.value = res.data;
enabledDeptOptions.value = filterDisabledDept(res.data);
};
/** 过滤禁用的部门 */
const filterDisabledDept = (deptList: DeptTreeVO[]) => {
return deptList.filter((dept) => {
if (dept.disabled) {
return false;
}
if (dept.children && dept.children.length) {
dept.children = filterDisabledDept(dept.children);
}
return true;
});
};
onMounted(() => {
getDeptTree(); // 初始化部门数据
getList();
});
</script>

View File

@ -317,7 +317,7 @@ import { RoleVO } from '@/api/system/role/types';
import { PostVO } from '@/api/system/post/types';
import { globalHeaders } from '@/utils/request';
import { to } from 'await-to-js';
import { optionselect } from '@/api/system/post';
import { getRoleList, optionselect } from '@/api/system/post';
import ShuttleFrame from '../../project/projectRelevancy/component/ShuttleFrame.vue';
const router = useRouter();
@ -430,6 +430,7 @@ const initData: PageData<UserForm, UserQuery> = {
}
],
phonenumber: [
{ required: true, message: '请输入手机号码', trigger: 'blur' },
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: '请输入正确的手机号码',
@ -469,7 +470,7 @@ const getList = async () => {
/** 查询部门下拉树结构 */
const getDeptTree = async () => {
const res = await api.deptTreeSelect();
const res = await api.deptTreeSelect({ isShow: '1' });
deptOptions.value = res.data;
enabledDeptOptions.value = filterDisabledDept(res.data);
};
@ -623,7 +624,6 @@ const handleAdd = async () => {
dialog.visible = true;
dialog.title = '新增用户';
postOptions.value = data.posts;
roleOptions.value = data.roles;
form.value.password = initPassword.value.toString();
};
@ -638,7 +638,7 @@ const handleUpdate = async (row?: UserForm) => {
postOptions.value = data.posts;
roleOptions.value = data.roles;
form.value.postIds = data.postIds;
form.value.roleIds = data.roleIds;
form.value.roleIds = data.user.roleIds;
form.value.password = '';
};
@ -682,8 +682,11 @@ onMounted(() => {
async function handleDeptChange(value: number | string) {
const response = await optionselect(value);
const roleList = await getRoleList(value);
roleOptions.value = roleList.data;
postOptions.value = response.data;
form.value.postIds = [];
form.value.roleIds = [];
}
const shuttleVisible = ref(false);

View File

@ -18,7 +18,7 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['workflow:category:add']">新增</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['workflows:category:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
@ -41,13 +41,13 @@
<el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:category:edit']" />
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflows:category:edit']" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['workflow:category:add']" />
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['workflows:category:add']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:category:remove']" />
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflows:category:remove']" />
</el-tooltip>
</template>
</el-table-column>
@ -59,7 +59,7 @@
<el-tree-select
v-model="form.parentId"
:data="categoryOptions"
:props="{ value: 'categoryId', label: 'categoryName', children: 'children' } as any"
:props="{ value: 'categoryId', label: 'categoryName', children: 'children' }"
value-key="categoryId"
placeholder="请选择上级分类"
check-strictly

View File

@ -22,10 +22,10 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['workflow:leave:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
<el-button v-hasPermi="['workflows:leave:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['workflow:leave:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
<el-button v-hasPermi="['workflows:leave:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
@ -60,12 +60,12 @@
<template #default="scope">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
<el-button v-hasPermi="['workflow:leave:edit']" size="small" type="primary" icon="Edit" @click="handleUpdate(scope.row)"
<el-button v-hasPermi="['workflows:leave:edit']" size="small" type="primary" icon="Edit" @click="handleUpdate(scope.row)"
>修改</el-button
>
</el-col>
<el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
<el-button v-hasPermi="['workflow:leave:remove']" size="small" type="primary" icon="Delete" @click="handleDelete(scope.row)"
<el-button v-hasPermi="['workflows:leave:remove']" size="small" type="primary" icon="Delete" @click="handleDelete(scope.row)"
>删除</el-button
>
</el-col>
@ -167,7 +167,7 @@ const handleSelectionChange = (selection: LeaveVO[]) => {
const handleAdd = () => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/leaveEdit/index`,
path: `/test/leaveEdit/index`,
query: {
type: 'add'
}
@ -178,7 +178,7 @@ const handleAdd = () => {
const handleUpdate = (row?: LeaveVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/leaveEdit/index`,
path: `/test/leaveEdit/index`,
query: {
id: row.id,
type: 'update'
@ -190,7 +190,7 @@ const handleUpdate = (row?: LeaveVO) => {
const handleView = (row?: LeaveVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/leaveEdit/index`,
path: `/test/leaveEdit/index`,
query: {
id: row.id,
type: 'view'

View File

@ -30,7 +30,7 @@ const open = async (definitionId, disabled) => {
};
/** 关闭按钮 */
function close() {
const obj = { path: '/workflow/processDefinition', query: { activeName: proxy.$route.query.activeName } };
const obj = { path: '/test/test/processDefinition', query: { activeName: proxy.$route.query.activeName } };
proxy.$tab.closeOpenPage(obj);
}

View File

@ -10,13 +10,13 @@
class="mt-2"
node-key="id"
:data="categoryOptions"
:props="{ label: 'label', children: 'children' } as any"
:props="{ label: 'label', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
></el-tree>
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
@ -132,7 +132,7 @@
<el-tree-select
v-model="selectCategory"
:data="categoryOptions"
:props="{ value: 'id', label: 'label', children: 'children' } as any"
:props="{ value: 'id', label: 'label', children: 'children' }"
filterable
value-key="id"
:render-after-expand="false"
@ -164,7 +164,7 @@
<el-tree-select
v-model="form.category"
:data="categoryOptions"
:props="{ value: 'id', label: 'label', children: 'children' } as any"
:props="{ value: 'id', label: 'label', children: 'children' }"
filterable
value-key="id"
:render-after-expand="false"
@ -174,7 +174,7 @@
</el-form-item>
<el-form-item label="流程编码" prop="flowCode">
<el-input v-model="form.flowCode" placeholder="请输入流程编码" maxlength="20" show-word-limit>
<template #prepend >{{ currentProject.id }}</template>
<template #prepend>{{ currentProject.id }}</template>
</el-input>
</el-form-item>
<el-form-item label="流程名称" prop="flowName">
@ -442,7 +442,7 @@ const handlerImportDefinition = (data: UploadRequestOptions): XMLHttpRequest =>
*/
const design = async (row: FlowDefinitionVo) => {
proxy.$router.push({
path: `/workflow/design/index`,
path: `/test/design/index`,
query: {
definitionId: row.id,
disabled: false,
@ -456,13 +456,10 @@ const design = async (row: FlowDefinitionVo) => {
* @param row
*/
const designView = async (row: FlowDefinitionVo) => {
proxy.$router.push({
path: `/workflow/design/index`,
query: {
definitionId: row.id,
disabled: true,
activeName: activeName.value
}
proxy.$tab.openPage(`/test/design/index`, '', {
definitionId: row.id,
disabled: true,
activeName: activeName.value
});
};
/** 表单重置 */
@ -532,4 +529,17 @@ const handleCopyDef = async (row: FlowDefinitionVo) => {
const handleExportDef = () => {
proxy?.download(`/workflow/definition/exportDef/${ids.value[0]}`, {}, `${flowCodeList.value[0]}.json`);
};
//id
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
</script>

View File

@ -10,7 +10,7 @@
class="mt-2"
node-key="id"
:data="categoryOptions"
:props="{ label: 'label', children: 'children' } as any"
:props="{ label: 'label', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current

View File

@ -29,18 +29,20 @@ export default defineConfig(({ mode, command }: ConfigEnv): UserConfig => {
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
},"/warm-flow-ui": {
target: 'http://192.168.110.119:8899',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
},"/warm-flow": {
},
'/warm-flow-ui': {
target: 'http://192.168.110.119:8899',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
},
"/workflow": {
'/warm-flow': {
target: 'http://192.168.110.119:8899',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
},
'/workflow': {
target: 'http://192.168.110.119:8899',
changeOrigin: true,
ws: true,